[libbyte-buddy-java] 01/01: New upstream version 1.7.1
Felix Natter
fnatter-guest at moszumanska.debian.org
Sat Jul 15 18:30:05 UTC 2017
This is an automated email from the git hooks/post-receive script.
fnatter-guest pushed a commit to branch master
in repository libbyte-buddy-java.
commit e33e1ab8e2203dd5607801e4e6f761c765a31fb3
Author: Felix Natter <fnatter at gmx.net>
Date: Sat Jul 15 20:17:07 2017 +0200
New upstream version 1.7.1
---
.gitattributes | 3 +
.gitignore | 27 +
.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 47624 bytes
.mvn/wrapper/maven-wrapper.properties | 1 +
.travis.yml | 28 +
CONTRIBUTING.md | 5 +
LICENSE | 202 +
README.md | 269 +
appveyor.yml | 22 +
byte-buddy-agent/pom.xml | 110 +
.../main/java/net/bytebuddy/agent/Attacher.java | 96 +
.../java/net/bytebuddy/agent/ByteBuddyAgent.java | 1189 +++
.../main/java/net/bytebuddy/agent/Installer.java | 71 +
.../java/net/bytebuddy/agent/VirtualMachine.java | 338 +
.../java/net/bytebuddy/agent/package-info.java | 4 +
.../java/net/bytebuddy/agent/AttacherTest.java | 93 +
.../agent/ByteBuddyAgentAgentProviderTest.java | 36 +
.../ByteBuddyAgentAttachmentProviderTest.java | 79 +
.../ByteBuddyAgentAttachmentTypeEvaluator.java | 31 +
.../agent/ByteBuddyAgentInstallationTest.java | 23 +
.../agent/ByteBuddyAgentProcessProviderTest.java | 25 +
.../net/bytebuddy/agent/ByteBuddyAgentTest.java | 67 +
.../java/net/bytebuddy/agent/InstallerTest.java | 92 +
.../agent/VirtualMachineForHotSpotTest.java | 120 +
.../test/utility/AgentAttachmentRule.java | 71 +
.../net/bytebuddy/test/utility/MockitoRule.java | 30 +
.../test/utility/ObjectPropertyAssertion.java | 329 +
.../net/bytebuddy/test/utility/UnixSocketRule.java | 50 +
byte-buddy-android-test/AndroidManifest.xml | 19 +
byte-buddy-android-test/pom.xml | 104 +
byte-buddy-android-test/res/drawable/icon.png | Bin 0 -> 110203 bytes
byte-buddy-android-test/res/layout/main.xml | 32 +
byte-buddy-android-test/res/values/strings.xml | 12 +
.../net/bytebuddy/android/test/TestActivity.java | 192 +
.../net/bytebuddy/android/test/package-info.java | 4 +
.../src/main/resources/maven.properties | 1 +
byte-buddy-android/pom.xml | 59 +
.../android/AndroidClassLoadingStrategy.java | 414 +
.../java/net/bytebuddy/android/package-info.java | 4 +
.../android/AndroidClassLoadingStrategyTest.java | 153 +
.../net/bytebuddy/test/utility/MockitoRule.java | 30 +
.../test/utility/ObjectPropertyAssertion.java | 324 +
byte-buddy-benchmark/pom.xml | 124 +
byte-buddy-benchmark/result.txt | 34 +
.../benchmark/ClassByExtensionBenchmark.java | 572 +
.../benchmark/ClassByImplementationBenchmark.java | 444 +
.../benchmark/StubInvocationBenchmark.java | 260 +
.../benchmark/SuperClassInvocationBenchmark.java | 340 +
.../benchmark/TrivialClassCreationBenchmark.java | 136 +
.../java/net/bytebuddy/benchmark/package-info.java | 4 +
.../bytebuddy/benchmark/runner/QuickRunner.java | 43 +
.../bytebuddy/benchmark/runner/package-info.java | 4 +
.../bytebuddy/benchmark/specimen/ExampleClass.java | 205 +
.../benchmark/specimen/ExampleInterface.java | 169 +
.../bytebuddy/benchmark/specimen/package-info.java | 4 +
.../bytebuddy/benchmark/AbstractBlackHoleTest.java | 19 +
...ExtensionBenchmarkByteBuddyInterceptorTest.java | 35 +
.../benchmark/ClassByExtensionBenchmarkTest.java | 211 +
.../ClassByImplementationBenchmarkTest.java | 125 +
.../benchmark/StubInvocationBenchmarkTest.java | 40 +
.../SuperClassInvocationBenchmarkTest.java | 50 +
.../TrivialClassCreationBenchmarkTest.java | 58 +
byte-buddy-dep/pom.xml | 73 +
.../src/main/java/net/bytebuddy/ByteBuddy.java | 1231 +++
.../main/java/net/bytebuddy/ClassFileVersion.java | 412 +
.../main/java/net/bytebuddy/NamingStrategy.java | 299 +
.../src/main/java/net/bytebuddy/TypeCache.java | 405 +
.../net/bytebuddy/agent/builder/AgentBuilder.java | 10342 +++++++++++++++++++
.../net/bytebuddy/agent/builder/LambdaFactory.java | 215 +
.../builder/ResettableClassFileTransformer.java | 268 +
.../net/bytebuddy/agent/builder/package-info.java | 7 +
.../src/main/java/net/bytebuddy/asm/Advice.java | 8706 ++++++++++++++++
.../java/net/bytebuddy/asm/AsmVisitorWrapper.java | 657 ++
.../main/java/net/bytebuddy/asm/MemberRemoval.java | 203 +
.../java/net/bytebuddy/asm/MemberSubstitution.java | 1217 +++
.../java/net/bytebuddy/asm/ModifierAdjustment.java | 481 +
.../net/bytebuddy/asm/TypeConstantAdjustment.java | 135 +
.../main/java/net/bytebuddy/asm/package-info.java | 4 +
.../main/java/net/bytebuddy/build/EntryPoint.java | 105 +
.../src/main/java/net/bytebuddy/build/Plugin.java | 22 +
.../java/net/bytebuddy/build/package-info.java | 4 +
.../net/bytebuddy/description/ByteCodeElement.java | 182 +
.../net/bytebuddy/description/DeclaredByType.java | 16 +
.../bytebuddy/description/ModifierReviewable.java | 542 +
.../net/bytebuddy/description/NamedElement.java | 71 +
.../bytebuddy/description/TypeVariableSource.java | 124 +
.../annotation/AnnotationDescription.java | 1255 +++
.../description/annotation/AnnotationList.java | 321 +
.../description/annotation/AnnotationSource.java | 70 +
.../description/annotation/AnnotationValue.java | 1928 ++++
.../description/annotation/package-info.java | 4 +
.../enumeration/EnumerationDescription.java | 162 +
.../description/enumeration/package-info.java | 4 +
.../description/field/FieldDescription.java | 585 ++
.../net/bytebuddy/description/field/FieldList.java | 259 +
.../bytebuddy/description/field/package-info.java | 4 +
.../description/method/MethodDescription.java | 1830 ++++
.../bytebuddy/description/method/MethodList.java | 283 +
.../description/method/ParameterDescription.java | 1068 ++
.../description/method/ParameterList.java | 614 ++
.../bytebuddy/description/method/package-info.java | 4 +
.../description/modifier/EnumerationState.java | 57 +
.../description/modifier/FieldManifestation.java | 80 +
.../description/modifier/FieldPersistence.java | 57 +
.../description/modifier/MethodArguments.java | 57 +
.../description/modifier/MethodManifestation.java | 111 +
.../description/modifier/MethodStrictness.java | 57 +
.../description/modifier/ModifierContributor.java | 193 +
.../bytebuddy/description/modifier/Ownership.java | 58 +
.../modifier/ParameterManifestation.java | 57 +
.../description/modifier/ProvisioningState.java | 57 +
.../description/modifier/SynchronizationState.java | 57 +
.../description/modifier/SyntheticState.java | 60 +
.../description/modifier/TypeManifestation.java | 99 +
.../bytebuddy/description/modifier/Visibility.java | 119 +
.../description/modifier/package-info.java | 4 +
.../net/bytebuddy/description/package-info.java | 6 +
.../description/type/PackageDescription.java | 134 +
.../bytebuddy/description/type/TypeDefinition.java | 286 +
.../description/type/TypeDescription.java | 8055 +++++++++++++++
.../net/bytebuddy/description/type/TypeList.java | 945 ++
.../description/type/TypeVariableToken.java | 121 +
.../bytebuddy/description/type/package-info.java | 4 +
.../net/bytebuddy/dynamic/ClassFileLocator.java | 1492 +++
.../java/net/bytebuddy/dynamic/DynamicType.java | 4464 ++++++++
.../src/main/java/net/bytebuddy/dynamic/Nexus.java | 195 +
.../java/net/bytebuddy/dynamic/NexusAccessor.java | 323 +
.../java/net/bytebuddy/dynamic/TargetType.java | 42 +
.../java/net/bytebuddy/dynamic/Transformer.java | 583 ++
.../bytebuddy/dynamic/TypeResolutionStrategy.java | 227 +
.../dynamic/loading/ByteArrayClassLoader.java | 1020 ++
.../bytebuddy/dynamic/loading/ClassInjector.java | 1493 +++
.../dynamic/loading/ClassLoadingStrategy.java | 461 +
.../dynamic/loading/ClassReloadingStrategy.java | 514 +
.../dynamic/loading/InjectionClassLoader.java | 59 +
.../dynamic/loading/MultipleParentClassLoader.java | 316 +
.../dynamic/loading/NoOpClassFileTransformer.java | 32 +
.../dynamic/loading/PackageDefinitionStrategy.java | 648 ++
.../bytebuddy/dynamic/loading/package-info.java | 5 +
.../java/net/bytebuddy/dynamic/package-info.java | 5 +
.../bytebuddy/dynamic/scaffold/FieldLocator.java | 349 +
.../bytebuddy/dynamic/scaffold/FieldRegistry.java | 298 +
.../dynamic/scaffold/InstrumentedType.java | 1188 +++
.../bytebuddy/dynamic/scaffold/MethodGraph.java | 1681 +++
.../bytebuddy/dynamic/scaffold/MethodRegistry.java | 1001 ++
.../dynamic/scaffold/TypeInitializer.java | 171 +
.../bytebuddy/dynamic/scaffold/TypeValidation.java | 62 +
.../net/bytebuddy/dynamic/scaffold/TypeWriter.java | 4220 ++++++++
.../inline/AbstractInliningDynamicTypeBuilder.java | 95 +
.../inline/InliningImplementationMatcher.java | 68 +
.../scaffold/inline/MethodNameTransformer.java | 100 +
.../scaffold/inline/MethodRebaseResolver.java | 488 +
.../scaffold/inline/RebaseDynamicTypeBuilder.java | 239 +
.../inline/RebaseImplementationTarget.java | 196 +
.../inline/RedefinitionDynamicTypeBuilder.java | 173 +
.../dynamic/scaffold/inline/package-info.java | 5 +
.../bytebuddy/dynamic/scaffold/package-info.java | 6 +
.../scaffold/subclass/ConstructorStrategy.java | 281 +
.../subclass/SubclassDynamicTypeBuilder.java | 221 +
.../subclass/SubclassImplementationTarget.java | 152 +
.../dynamic/scaffold/subclass/package-info.java | 5 +
.../implementation/DefaultMethodCall.java | 232 +
.../bytebuddy/implementation/ExceptionMethod.java | 216 +
.../bytebuddy/implementation/FieldAccessor.java | 734 ++
.../net/bytebuddy/implementation/FixedValue.java | 678 ++
.../bytebuddy/implementation/Implementation.java | 1582 +++
.../implementation/InvocationHandlerAdapter.java | 426 +
.../bytebuddy/implementation/InvokeDynamic.java | 2984 ++++++
.../implementation/LoadedTypeInitializer.java | 176 +
.../implementation/MethodAccessorFactory.java | 114 +
.../net/bytebuddy/implementation/MethodCall.java | 2037 ++++
.../bytebuddy/implementation/MethodDelegation.java | 1408 +++
.../net/bytebuddy/implementation/StubMethod.java | 49 +
.../bytebuddy/implementation/SuperMethodCall.java | 142 +
.../attribute/AnnotationAppender.java | 645 ++
.../attribute/AnnotationRetention.java | 55 +
.../attribute/AnnotationValueFilter.java | 94 +
.../attribute/FieldAttributeAppender.java | 215 +
.../attribute/MethodAttributeAppender.java | 429 +
.../attribute/TypeAttributeAppender.java | 218 +
.../implementation/attribute/package-info.java | 6 +
.../implementation/auxiliary/AuxiliaryType.java | 99 +
.../implementation/auxiliary/MethodCallProxy.java | 435 +
.../implementation/auxiliary/TrivialType.java | 57 +
.../implementation/auxiliary/TypeProxy.java | 736 ++
.../implementation/auxiliary/package-info.java | 6 +
.../implementation/bind/ArgumentTypeResolver.java | 253 +
.../implementation/bind/DeclaringTypeResolver.java | 33 +
.../bind/MethodDelegationBinder.java | 892 ++
.../bind/MethodNameEqualityResolver.java | 33 +
.../bind/ParameterLengthResolver.java | 30 +
.../bind/annotation/AllArguments.java | 158 +
.../implementation/bind/annotation/Argument.java | 164 +
.../bind/annotation/BindingPriority.java | 76 +
.../implementation/bind/annotation/Default.java | 180 +
.../bind/annotation/DefaultCall.java | 202 +
.../bind/annotation/DefaultMethod.java | 230 +
.../implementation/bind/annotation/Empty.java | 50 +
.../implementation/bind/annotation/FieldProxy.java | 1040 ++
.../implementation/bind/annotation/FieldValue.java | 155 +
.../bind/annotation/IgnoreForBinding.java | 40 +
.../implementation/bind/annotation/Morph.java | 591 ++
.../implementation/bind/annotation/Origin.java | 127 +
.../implementation/bind/annotation/Pipe.java | 479 +
.../bind/annotation/RuntimeType.java | 49 +
.../implementation/bind/annotation/StubValue.java | 63 +
.../implementation/bind/annotation/Super.java | 338 +
.../implementation/bind/annotation/SuperCall.java | 104 +
.../bind/annotation/SuperMethod.java | 140 +
.../TargetMethodAnnotationDrivenBinder.java | 712 ++
.../implementation/bind/annotation/This.java | 75 +
.../bind/annotation/package-info.java | 5 +
.../implementation/bind/package-info.java | 4 +
.../implementation/bytecode/Addition.java | 62 +
.../implementation/bytecode/ByteCodeAppender.java | 177 +
.../implementation/bytecode/Duplication.java | 178 +
.../bytebuddy/implementation/bytecode/Removal.java | 84 +
.../implementation/bytecode/StackManipulation.java | 203 +
.../implementation/bytecode/StackSize.java | 143 +
.../bytebuddy/implementation/bytecode/Throw.java | 27 +
.../implementation/bytecode/TypeCreation.java | 52 +
.../implementation/bytecode/assign/Assigner.java | 132 +
.../bytecode/assign/TypeCasting.java | 56 +
.../bytecode/assign/package-info.java | 6 +
.../assign/primitive/PrimitiveBoxingDelegate.java | 171 +
.../primitive/PrimitiveTypeAwareAssigner.java | 54 +
.../primitive/PrimitiveUnboxingDelegate.java | 309 +
.../primitive/PrimitiveWideningDelegate.java | 285 +
.../assign/primitive/VoidAwareAssigner.java | 53 +
.../bytecode/assign/primitive/package-info.java | 6 +
.../reference/ReferenceTypeAwareAssigner.java | 34 +
.../bytecode/assign/reference/package-info.java | 5 +
.../bytecode/collection/ArrayAccess.java | 227 +
.../bytecode/collection/ArrayFactory.java | 315 +
.../bytecode/collection/CollectionFactory.java | 30 +
.../bytecode/collection/package-info.java | 5 +
.../bytecode/constant/ClassConstant.java | 170 +
.../bytecode/constant/DefaultValue.java | 92 +
.../bytecode/constant/DoubleConstant.java | 104 +
.../bytecode/constant/FieldConstant.java | 94 +
.../bytecode/constant/FloatConstant.java | 111 +
.../bytecode/constant/IntegerConstant.java | 225 +
.../bytecode/constant/JavaConstantValue.java | 40 +
.../bytecode/constant/LongConstant.java | 104 +
.../bytecode/constant/MethodConstant.java | 296 +
.../bytecode/constant/NullConstant.java | 43 +
.../bytecode/constant/SerializedConstant.java | 88 +
.../bytecode/constant/TextConstant.java | 39 +
.../bytecode/constant/package-info.java | 5 +
.../bytecode/member/FieldAccess.java | 330 +
.../bytecode/member/HandleInvocation.java | 51 +
.../bytecode/member/MethodInvocation.java | 574 +
.../bytecode/member/MethodReturn.java | 100 +
.../bytecode/member/MethodVariableAccess.java | 455 +
.../bytecode/member/package-info.java | 6 +
.../implementation/bytecode/package-info.java | 5 +
.../net/bytebuddy/implementation/package-info.java | 4 +
.../bytebuddy/matcher/AccessibilityMatcher.java | 38 +
.../bytebuddy/matcher/AnnotationTypeMatcher.java | 38 +
.../java/net/bytebuddy/matcher/BooleanMatcher.java | 36 +
.../java/net/bytebuddy/matcher/CachingMatcher.java | 113 +
.../matcher/ClassLoaderHierarchyMatcher.java | 44 +
.../matcher/ClassLoaderParentMatcher.java | 43 +
.../matcher/CollectionElementMatcher.java | 54 +
.../matcher/CollectionErasureMatcher.java | 45 +
.../bytebuddy/matcher/CollectionItemMatcher.java | 42 +
.../matcher/CollectionOneToOneMatcher.java | 63 +
.../bytebuddy/matcher/CollectionSizeMatcher.java | 48 +
.../matcher/DeclaringAnnotationMatcher.java | 38 +
.../bytebuddy/matcher/DeclaringFieldMatcher.java | 39 +
.../bytebuddy/matcher/DeclaringMethodMatcher.java | 39 +
.../bytebuddy/matcher/DeclaringTypeMatcher.java | 41 +
.../net/bytebuddy/matcher/DefinedShapeMatcher.java | 39 +
.../net/bytebuddy/matcher/DescriptorMatcher.java | 37 +
.../java/net/bytebuddy/matcher/ElementMatcher.java | 146 +
.../net/bytebuddy/matcher/ElementMatchers.java | 2199 ++++
.../net/bytebuddy/matcher/EqualityMatcher.java | 36 +
.../java/net/bytebuddy/matcher/ErasureMatcher.java | 40 +
.../net/bytebuddy/matcher/FailSafeMatcher.java | 47 +
.../net/bytebuddy/matcher/FieldTypeMatcher.java | 38 +
.../java/net/bytebuddy/matcher/FilterableList.java | 126 +
.../net/bytebuddy/matcher/HasSuperTypeMatcher.java | 63 +
.../matcher/InheritedAnnotationMatcher.java | 38 +
.../net/bytebuddy/matcher/InstanceTypeMatcher.java | 37 +
.../java/net/bytebuddy/matcher/IsNamedMatcher.java | 23 +
.../java/net/bytebuddy/matcher/LatentMatcher.java | 282 +
.../matcher/MethodExceptionTypeMatcher.java | 38 +
.../bytebuddy/matcher/MethodOverrideMatcher.java | 88 +
.../matcher/MethodParameterTypeMatcher.java | 38 +
.../matcher/MethodParameterTypesMatcher.java | 40 +
.../bytebuddy/matcher/MethodParametersMatcher.java | 39 +
.../bytebuddy/matcher/MethodReturnTypeMatcher.java | 38 +
.../net/bytebuddy/matcher/MethodSortMatcher.java | 131 +
.../net/bytebuddy/matcher/ModifierMatcher.java | 178 +
.../java/net/bytebuddy/matcher/NameMatcher.java | 44 +
.../net/bytebuddy/matcher/NegatingMatcher.java | 36 +
.../java/net/bytebuddy/matcher/NullMatcher.java | 22 +
.../bytebuddy/matcher/SignatureTokenMatcher.java | 37 +
.../java/net/bytebuddy/matcher/StringMatcher.java | 176 +
.../java/net/bytebuddy/matcher/SubTypeMatcher.java | 38 +
.../net/bytebuddy/matcher/SuperTypeMatcher.java | 37 +
.../net/bytebuddy/matcher/TypeSortMatcher.java | 37 +
.../net/bytebuddy/matcher/VisibilityMatcher.java | 38 +
.../java/net/bytebuddy/matcher/package-info.java | 4 +
.../src/main/java/net/bytebuddy/package-info.java | 16 +
.../src/main/java/net/bytebuddy/pool/TypePool.java | 7785 ++++++++++++++
.../main/java/net/bytebuddy/pool/package-info.java | 5 +
.../java/net/bytebuddy/utility/CompoundList.java | 57 +
.../java/net/bytebuddy/utility/JavaConstant.java | 1310 +++
.../java/net/bytebuddy/utility/JavaModule.java | 479 +
.../main/java/net/bytebuddy/utility/JavaType.java | 98 +
.../java/net/bytebuddy/utility/RandomString.java | 121 +
.../java/net/bytebuddy/utility/StreamDrainer.java | 86 +
.../java/net/bytebuddy/utility/package-info.java | 4 +
.../utility/privilege/GetSystemPropertyAction.java | 31 +
.../utility/privilege/SetAccessibleAction.java | 35 +
.../bytebuddy/utility/privilege/package-info.java | 4 +
.../ExceptionTableSensitiveMethodVisitor.java | 299 +
.../visitor/LineNumberPrependingMethodVisitor.java | 46 +
.../utility/visitor/StackAwareMethodVisitor.java | 362 +
.../bytebuddy/utility/visitor/package-info.java | 4 +
.../src/test/java/net/bytebuddy/ByteBuddyTest.java | 128 +
.../bytebuddy/ByteBuddyTutorialExamplesTest.java | 826 ++
.../ClassFileVersionKnownVersionsTest.java | 120 +
.../java/net/bytebuddy/ClassFileVersionTest.java | 63 +
.../java/net/bytebuddy/NamingStrategyTest.java | 140 +
.../src/test/java/net/bytebuddy/TypeCacheTest.java | 164 +
.../builder/AgentBuilderCircularityLockTest.java | 54 +
...AgentBuilderDefaultApplicationRedefineTest.java | 184 +
...aultApplicationRedefinitionReiterationTest.java | 152 +
...tBuilderDefaultApplicationResubmissionTest.java | 126 +
...lderDefaultApplicationSuperTypeLoadingTest.java | 126 +
.../AgentBuilderDefaultApplicationTest.java | 1020 ++
...gentBuilderDefaultNativeMethodStrategyTest.java | 82 +
.../agent/builder/AgentBuilderDefaultTest.java | 2275 ++++
.../AgentBuilderDescriptionStrategyTest.java | 101 +
...BuilderFallbackStrategyByThrowableTypeTest.java | 42 +
.../AgentBuilderFallbackStrategySimpleTest.java | 25 +
...izationStrategySelfInjectionDispatcherTest.java | 220 +
.../AgentBuilderInitializationStrategyTest.java | 120 +
.../AgentBuilderInstallationListenerTest.java | 183 +
...ntBuilderLambdaInstrumentationStrategyTest.java | 81 +
.../agent/builder/AgentBuilderListenerTest.java | 327 +
.../AgentBuilderLocationStrategyCompoundTest.java | 57 +
...tBuilderLocationStrategyForClassLoaderTest.java | 64 +
.../AgentBuilderLocationStrategyNoOpTest.java | 24 +
.../AgentBuilderLocationStrategySimpleTest.java | 24 +
.../builder/AgentBuilderPoolStrategyTest.java | 62 +
.../AgentBuilderRawMatcherConjunctionTest.java | 92 +
.../AgentBuilderRawMatcherDisjunctionTest.java | 93 +
...entBuilderRawMatcherForElementMatchersTest.java | 145 +
.../AgentBuilderRawMatcherForLoadStateTest.java | 56 +
.../AgentBuilderRawMatcherInversionTest.java | 54 +
.../builder/AgentBuilderRawMatcherTrivialTest.java | 32 +
...lderRedefinitionStrategyBatchAllocatorTest.java | 288 +
...rRedefinitionStrategyDiscoveryStrategyTest.java | 66 +
...entBuilderRedefinitionStrategyListenerTest.java | 168 +
...definitionStrategyResubmissionStrategyTest.java | 893 ++
.../AgentBuilderRedefinitionStrategyTest.java | 79 +
.../AgentBuilderTransformerForBuildPluginTest.java | 51 +
.../agent/builder/AgentBuilderTransformerTest.java | 71 +
...lderTypeLocatorWithTypePoolCacheSimpleTest.java | 51 +
...tBuilderTypeStrategyForBuildEntryPointTest.java | 57 +
.../builder/AgentBuilderTypeStrategyTest.java | 82 +
.../bytebuddy/agent/builder/LambdaFactoryTest.java | 127 +
.../builder/RawMatcherForResolvableTypesTest.java | 51 +
.../net/bytebuddy/asm/AdviceAnnotationTest.java | 59 +
.../asm/AdviceBoxedParameterAssignmentTest.java | 209 +
.../asm/AdviceBoxedReturnAssignmentTest.java | 189 +
.../asm/AdviceCustomAnnotationOnFieldTest.java | 214 +
.../asm/AdviceCustomAnnotationOnParameterTest.java | 199 +
.../java/net/bytebuddy/asm/AdviceDeadCodeTest.java | 184 +
.../asm/AdviceExchangedDuplicationTest.java | 104 +
.../java/net/bytebuddy/asm/AdviceFrameTest.java | 501 +
.../net/bytebuddy/asm/AdviceIllegalTypeTest.java | 158 +
.../bytebuddy/asm/AdviceImplementationTest.java | 122 +
.../bytebuddy/asm/AdviceInconsistentFrameTest.java | 154 +
.../asm/AdviceInconsistentStackSizeTest.java | 153 +
.../java/net/bytebuddy/asm/AdviceJsrRetTest.java | 78 +
.../bytebuddy/asm/AdviceNoRegularReturnTest.java | 186 +
.../asm/AdviceNoRegularReturnWithinAdviceTest.java | 327 +
.../bytebuddy/asm/AdviceSizeConversionTest.java | 152 +
.../asm/AdviceSkipOnDefaultValueTest.java | 1438 +++
.../asm/AdviceSkipOnNonDefaultValueTest.java | 1438 +++
.../net/bytebuddy/asm/AdviceSuppressionTest.java | 731 ++
.../test/java/net/bytebuddy/asm/AdviceTest.java | 3673 +++++++
.../java/net/bytebuddy/asm/AdviceTypeTest.java | 1962 ++++
.../bytebuddy/asm/AdviceVariableAccessTest.java | 181 +
.../asm/AsmVisitorWrapperCompoundTest.java | 111 +
.../AsmVisitorWrapperForDeclaredFieldsTest.java | 128 +
.../AsmVisitorWrapperForDeclaredMethodsTest.java | 141 +
.../bytebuddy/asm/AsmVisitorWrapperNoOpTest.java | 51 +
.../java/net/bytebuddy/asm/MemberRemovalTest.java | 96 +
.../net/bytebuddy/asm/MemberSubstitutionTest.java | 828 ++
.../net/bytebuddy/asm/ModifierAdjustmentTest.java | 180 +
.../bytebuddy/asm/TypeConstantAdjustmentTest.java | 154 +
.../net/bytebuddy/build/EntryPointDefaultTest.java | 71 +
.../description/ByteCodeElementTokenListTest.java | 35 +
.../TypeVariableSourceVisitorNoOpTest.java | 31 +
.../AbstractAnnotationDescriptionTest.java | 746 ++
.../annotation/AbstractAnnotationListTest.java | 109 +
...onDescriptionAbstractPreparedExceptionTest.java | 38 +
...DescriptionAnnotationInvocationHandlerTest.java | 305 +
...nDescriptionAnnotationValueLoadedStateTest.java | 29 +
.../AnnotationDescriptionBuilderTest.java | 107 +
...orLoadedAnnotationDifferentClassLoaderTest.java | 34 +
...notationDescriptionForLoadedAnnotationTest.java | 68 +
.../AnnotationDescriptionLatentTest.java | 76 +
.../annotation/AnnotationListEmptyTest.java | 45 +
.../annotation/AnnotationListExplicitTest.java | 26 +
.../AnnotationListForLoadedAnnotationsTest.java | 27 +
.../annotation/AnnotationSourceTest.java | 22 +
.../annotation/AnnotationValueForConstantTest.java | 18 +
.../AnnotationValueRenderingDispatcherTest.java | 96 +
.../AbstractEnumerationDescriptionTest.java | 132 +
...erationDescriptionForLoadedEnumerationTest.java | 13 +
.../EnumerationDescriptionLatentTest.java | 14 +
.../field/AbstractFieldDescriptionTest.java | 275 +
.../description/field/AbstractFieldListTest.java | 33 +
.../field/FieldDescriptionForLoadedFieldsTest.java | 11 +
.../field/FieldDescriptionLatentTest.java | 19 +
.../field/FieldDescriptionSignatureTokenTest.java | 35 +
.../field/FieldDescriptionTokenTest.java | 74 +
.../description/field/FieldListEmptyTest.java | 20 +
.../description/field/FieldListExplicitTest.java | 26 +
.../field/FieldListForLoadedFieldsTest.java | 27 +
.../method/AbstractMethodDescriptionTest.java | 995 ++
.../description/method/AbstractMethodListTest.java | 33 +
.../method/AbstractParameterListTest.java | 43 +
.../method/MethodDescriptionForLoadedTest.java | 33 +
.../method/MethodDescriptionLatentTest.java | 46 +
...MethodDescriptionLatentTypeInitializerTest.java | 63 +
.../MethodDescriptionSignatureTokenTest.java | 44 +
.../method/MethodDescriptionTokenTest.java | 144 +
.../method/MethodDescriptionTypeTokenTest.java | 37 +
.../description/method/MethodListEmptyTest.java | 20 +
.../description/method/MethodListExplicitTest.java | 26 +
.../method/MethodListForLoadedTypesTest.java | 28 +
...escriptionForLoadedParameterDispatcherTest.java | 54 +
.../method/ParameterDescriptionTokenTest.java | 70 +
.../description/method/ParameterListEmptyTest.java | 30 +
.../method/ParameterListExplicitTest.java | 26 +
...eterListForLoadedExectutableDispatcherTest.java | 47 +
.../ParameterListForLoadedParameterTest.java | 32 +
.../modifier/AbstractModifierContributorTest.java | 31 +
.../description/modifier/EnumerationStateTest.java | 33 +
.../modifier/FieldManifestationTest.java | 46 +
.../description/modifier/FieldPersistenceTest.java | 39 +
.../description/modifier/MethodArgumentsTest.java | 33 +
.../modifier/MethodManifestationTest.java | 40 +
.../description/modifier/MethodStrictnessTest.java | 33 +
.../ModifierConstributorObjectPropertiesTest.java | 23 +
.../modifier/ModifierContributorResolverTest.java | 56 +
.../modifier/ModifierContributorTest.java | 57 +
.../description/modifier/OwnershipTest.java | 33 +
.../modifier/ParameterManifestationTest.java | 33 +
.../modifier/ProvisioningStateTest.java | 33 +
.../modifier/SynchronizationStateTest.java | 33 +
.../description/modifier/SyntheticStateTest.java | 33 +
.../modifier/TypeManifestationTest.java | 40 +
.../modifier/VisibilityExpansionTest.java | 41 +
.../description/modifier/VisibilityTest.java | 50 +
.../type/AbstractPackageDescriptionTest.java | 63 +
.../type/AbstractTypeDescriptionGenericTest.java | 2006 ++++
...TypeDescriptionGenericVariableDefiningTest.java | 455 +
.../type/AbstractTypeDescriptionTest.java | 791 ++
.../type/AbstractTypeListGenericTest.java | 54 +
.../description/type/AbstractTypeListTest.java | 47 +
.../type/GenericSignatureResolutionTest.java | 260 +
.../PackageDescriptionForLoadedPackageTest.java | 9 +
.../type/PackageDescriptionSimpleTest.java | 22 +
.../type/TypeDefinitionSortOtherTest.java | 21 +
.../description/type/TypeDefinitionSortTest.java | 70 +
.../type/TypeDefinitionSuperClassIteratorTest.java | 56 +
.../type/TypeDescriptionArrayProjectionTest.java | 131 +
.../type/TypeDescriptionForLoadedTypeTest.java | 152 +
.../TypeDescriptionForPackageDescriptionTest.java | 80 +
...TypeDescriptionGenericAnnotationReaderTest.java | 149 +
.../type/TypeDescriptionGenericBuilderTest.java | 231 +
...enericLazyProjectionWithLazyNavigationTest.java | 80 +
...nericOfNonGenericTypeForReifiedErasureTest.java | 154 +
...fParameterizedTypeForGenerifiedErasureTest.java | 28 +
...nericOfParameterizedTypeForReifiedTypeTest.java | 150 +
...icOfParameterizedTypeRenderingDelegateTest.java | 69 +
...scriptionGenericOfTypeVariableSymbolicTest.java | 162 +
...ricOfTypeVariableWithAnnotationOverlayTest.java | 116 +
...iptionGenericVisitorAnnotationStripperTest.java | 106 +
.../TypeDescriptionGenericVisitorAssignerTest.java | 573 +
...ptionGenericVisitorForSignatureVisitorTest.java | 21 +
.../TypeDescriptionGenericVisitorNoOpTest.java | 50 +
.../TypeDescriptionGenericVisitorReducingTest.java | 114 +
.../TypeDescriptionGenericVisitorReifyingTest.java | 93 +
...GenericVisitorSubstitutorForAttachmentTest.java | 79 +
...GenericVisitorSubstitutorForDetachmentTest.java | 67 +
...isitorSubstitutorForTokenNormalizationTest.java | 75 +
...sitorSubstitutorForTypeVariableBindingTest.java | 89 +
...peDescriptionGenericVisitorTypeErasingTest.java | 57 +
...nGenericVisitorValidatorForTypeAnnotations.java | 221 +
...TypeDescriptionGenericVisitorValidatorTest.java | 115 +
.../type/TypeDescriptionLatentTest.java | 103 +
.../description/type/TypeInitializerTest.java | 90 +
.../description/type/TypeListEmptyTest.java | 20 +
.../description/type/TypeListExplicitTest.java | 26 +
.../description/type/TypeListForLoadedTest.java | 26 +
.../description/type/TypeListGenericEmptyTest.java | 24 +
.../type/TypeListGenericExplicitTest.java | 26 +
.../type/TypeListGenericForLoadedTypesTest.java | 27 +
.../description/type/TypeVariableTokenTest.java | 67 +
.../dynamic/AbstractDynamicTypeBuilderTest.java | 1213 +++
.../dynamic/ClassFileLocatorAgentBasedTest.java | 144 +
.../dynamic/ClassFileLocatorCompoundTest.java | 79 +
.../ClassFileLocatorForClassLoaderTest.java | 132 +
...eLocatorForClassLoaderWeaklyReferencedTest.java | 91 +
.../dynamic/ClassFileLocatorForFolderTest.java | 73 +
.../dynamic/ClassFileLocatorForJarFileTest.java | 132 +
.../dynamic/ClassFileLocatorForModuleFileTest.java | 121 +
.../dynamic/ClassFileLocatorForModuleTest.java | 96 +
...ssFileLocatorForModuleWeaklyReferencedTest.java | 115 +
.../dynamic/ClassFileLocatorNoOpTest.java | 27 +
.../ClassFileLocatorPackageDiscriminatingTest.java | 72 +
.../dynamic/ClassFileLocatorResolutionTest.java | 41 +
.../dynamic/ClassFileLocatorSimpleTest.java | 54 +
.../DynamicTypeBuilderObjectPropertiesTest.java | 22 +
.../dynamic/DynamicTypeDefaultLoadedTest.java | 66 +
.../bytebuddy/dynamic/DynamicTypeDefaultTest.java | 296 +
.../dynamic/DynamicTypeDefaultUnloadedTest.java | 107 +
.../test/java/net/bytebuddy/dynamic/NexusTest.java | 231 +
.../java/net/bytebuddy/dynamic/TargetTypeTest.java | 76 +
.../bytebuddy/dynamic/TransformerCompoundTest.java | 55 +
.../bytebuddy/dynamic/TransformerForFieldTest.java | 132 +
.../dynamic/TransformerForMethodTest.java | 188 +
.../net/bytebuddy/dynamic/TransformerNoOpTest.java | 23 +
.../dynamic/TypeResolutionStrategyTest.java | 133 +
...sLoaderChildFirstPrependingEnumerationTest.java | 59 +
.../ByteArrayClassLoaderChildFirstTest.java | 237 +
.../ByteArrayClassLoaderEmptyEnumerationTest.java | 27 +
.../ByteArrayClassLoaderObjectPropertiesTest.java | 24 +
.../ByteArrayClassLoaderPackageLookupStrategy.java | 40 +
...teArrayClassLoaderSingletonEnumerationTest.java | 39 +
...rrayClassLoaderSynchronizationStrategyTest.java | 50 +
.../dynamic/loading/ByteArrayClassLoaderTest.java | 258 +
.../ClassInjectorUsingInstrumentationTest.java | 81 +
.../loading/ClassInjectorUsingReflectionTest.java | 206 +
.../loading/ClassInjectorUsingUnsafeTest.java | 78 +
.../loading/ClassLoadingStrategyDefaultTest.java | 265 +
...ssLoadingStrategyForBootstrapInjectionTest.java | 71 +
...ClassLoadingStrategyForUnsafeInjectionTest.java | 74 +
.../loading/ClassReloadingStrategyTest.java | 315 +
.../dynamic/loading/InjectionClassLoaderTest.java | 50 +
.../loading/MultipleParentClassLoaderTest.java | 226 +
.../loading/NoOpClassFileTransformerTest.java | 20 +
.../PackageDefinitionStrategyTypeSimpleTest.java | 136 +
.../PackageDefinitionStrategyTypeTrivialTest.java | 63 +
...PackageDefinitionStrategyTypeUndefinedTest.java | 60 +
.../loading/PackageDefinitionTrivialTest.java | 34 +
.../PackageTypeStrategyManifestReadingTest.java | 271 +
.../loading/PackageTypeStrategyNoOpTest.java | 22 +
.../FieldLocatorForClassHierarchyTest.java | 103 +
.../scaffold/FieldLocatorForExactTypeTest.java | 83 +
.../scaffold/FieldLocatorForTopLevelTypeTest.java | 78 +
.../dynamic/scaffold/FieldLocatorNoOpTest.java | 33 +
.../scaffold/FieldLocatorResolutionTest.java | 47 +
.../scaffold/FieldRegistryCompiledNoOpTest.java | 51 +
.../dynamic/scaffold/FieldRegistryDefaultTest.java | 106 +
.../scaffold/InstrumentedTypeDefaultTest.java | 1301 +++
.../InstrumentedTypeFactoryDefaultTest.java | 30 +
.../scaffold/InstrumentedTypeFrozenTest.java | 86 +
...hCompilerDefaultHarmonizerForJVMMethodTest.java | 83 +
...CompilerDefaultHarmonizerForJavaMethodTest.java | 87 +
.../MethodGraphCompilerDefaultKeyTest.java | 130 +
...dGraphCompilerDefaultMergerDirectionalTest.java | 36 +
.../scaffold/MethodGraphCompilerDefaultTest.java | 1665 +++
.../MethodGraphCompilerForDeclaredMethodsTest.java | 73 +
.../dynamic/scaffold/MethodGraphEmptyTest.java | 38 +
.../scaffold/MethodGraphLinkedDelegationTest.java | 78 +
.../dynamic/scaffold/MethodGraphNodeListTest.java | 52 +
.../scaffold/MethodGraphNodeSimpleTest.java | 41 +
.../dynamic/scaffold/MethodGraphNodeSortTest.java | 43 +
.../scaffold/MethodGraphNodeUnresolvedTest.java | 30 +
.../dynamic/scaffold/MethodGraphSimpleTest.java | 50 +
.../scaffold/MethodRegistryDefaultTest.java | 370 +
.../scaffold/MethodRegistryHandlerTest.java | 100 +
.../scaffold/TypeInitializerDrainDefaultTest.java | 56 +
.../dynamic/scaffold/TypeValidationTest.java | 42 +
...ationHandlerAppendingFrameWriterActiveTest.java | 80 +
...onHandlerAppendingFrameWriterExpandingTest.java | 25 +
...izationHandlerAppendingFrameWriterNoOpTest.java | 24 +
...WriterDefaultFrameComputingClassWriterTest.java | 78 +
.../dynamic/scaffold/TypeWriterDefaultTest.java | 623 ++
.../scaffold/TypeWriterFieldPoolRecordTest.java | 149 +
.../TypeWriterInitializerRemapperTest.java | 113 +
.../scaffold/TypeWriterMethodPoolRecordTest.java | 550 +
.../TypeWriterModifierPreservationTest.java | 145 +
.../AbstractDynamicTypeBuilderForInliningTest.java | 613 ++
.../inline/InlineImplementationMatcherTest.java | 122 +
.../inline/MethodNameTransformerPrefixingTest.java | 38 +
.../inline/MethodNameTransformerSuffixingTest.java | 36 +
.../inline/MethodRebaseResolverDefaultTest.java | 136 +
.../inline/MethodRebaseResolverDisabledTest.java | 43 +
...esolverResolutionForRebasedConstructorTest.java | 118 +
...baseResolverResolutionForRebasedMethodTest.java | 137 +
...ethodRebaseResolverResolutionPreservedTest.java | 38 +
...aseDynamicTypeBuilderRebaseableMatcherTest.java | 38 +
.../inline/RebaseDynamicTypeBuilderTest.java | 245 +
.../RebaseImplementationTargetFactoryTest.java | 60 +
...mentationTargetSpecialMethodInvocationTest.java | 20 +
.../inline/RebaseImplementationTargetTest.java | 184 +
.../inline/RedefinitionDynamicTypeBuilderTest.java | 194 +
.../subclass/ConstructorStrategyDefaultTest.java | 318 +
...ynamicTypeBuilderInstrumentableMatcherTest.java | 150 +
.../subclass/SubclassDynamicTypeBuilderTest.java | 668 ++
.../SubclassImplementationTargetFactoryTest.java | 66 +
.../subclass/SubclassImplementationTargetTest.java | 125 +
.../AbstractImplementationTargetTest.java | 128 +
.../AbstractSpecialMethodInvocationTest.java | 77 +
.../implementation/DefaultMethodCallTest.java | 204 +
.../implementation/ExceptionMethodTest.java | 118 +
...essorFieldNameExtractorForBeanPropertyTest.java | 71 +
...ccessorFieldNameExtractorForFixedValueTest.java | 33 +
.../implementation/FieldAccessorOtherTest.java | 264 +
.../implementation/FieldAccessorTest.java | 590 ++
.../FixedValueConstantPoolTypesTest.java | 249 +
.../bytebuddy/implementation/FixedValueTest.java | 329 +
.../implementation/ImplementationCompoundTest.java | 77 +
.../ImplementationContextDefaultOtherTest.java | 113 +
.../ImplementationContextDefaultTest.java | 596 ++
.../ImplementationContextDisabledTest.java | 118 +
.../implementation/ImplementationSimpleTest.java | 12 +
...entationSpecialMethodInvocationIllegalTest.java | 57 +
...mentationSpecialMethodInvocationSimpleTest.java | 52 +
...getAbstractBaseDefaultMethodInvocationTest.java | 28 +
.../InvocationHandlerAdapterTest.java | 234 +
.../implementation/InvokeDynamicTest.java | 559 +
.../LoadedTypeInitializerCompoundTest.java | 78 +
.../LoadedTypeInitializerForStaticFieldTest.java | 79 +
.../implementation/LoadedTypeInitializerNoOp.java | 20 +
.../MethodAccessorFactoryAccessTypeTest.java | 22 +
.../MethodAccessorFactoryIllegalTest.java | 43 +
.../bytebuddy/implementation/MethodCallTest.java | 1245 +++
.../implementation/MethodCallTypeTest.java | 160 +
.../MethodDelegationAllArgumentsTest.java | 128 +
.../MethodDelegationArgumentTest.java | 78 +
.../MethodDelegationBindingPriorityTest.java | 49 +
.../MethodDelegationChainedTest.java | 75 +
.../MethodDelegationCheckedExceptionTest.java | 35 +
.../MethodDelegationConstructionTest.java | 333 +
.../MethodDelegationDefaultCallTest.java | 159 +
.../MethodDelegationDefaultMethodTest.java | 114 +
.../MethodDelegationDefaultTest.java | 93 +
.../MethodDelegationExceptionTest.java | 65 +
.../MethodDelegationFieldProxyTest.java | 584 ++
.../MethodDelegationFieldValueTest.java | 218 +
.../MethodDelegationIgnoreForBindingTest.java | 47 +
.../implementation/MethodDelegationMorphTest.java | 263 +
.../implementation/MethodDelegationOriginTest.java | 304 +
.../implementation/MethodDelegationOtherTest.java | 83 +
.../implementation/MethodDelegationPipeTest.java | 276 +
.../MethodDelegationRuntimeTypeTest.java | 43 +
.../MethodDelegationStubValueTest.java | 111 +
.../MethodDelegationSuperCallTest.java | 239 +
.../MethodDelegationSuperMethodTest.java | 172 +
.../implementation/MethodDelegationSuperTest.java | 243 +
.../implementation/MethodDelegationTest.java | 351 +
.../implementation/MethodDelegationThisTest.java | 44 +
.../implementation/ModifierReviewableTest.java | 110 +
.../implementation/StubMethodOtherTest.java | 33 +
.../bytebuddy/implementation/StubMethodTest.java | 193 +
.../implementation/SuperMethodCallOtherTest.java | 202 +
.../implementation/SuperMethodCallTest.java | 175 +
.../attribute/AbstractAttributeAppenderTest.java | 92 +
.../AbstractFieldAttributeAppenderTest.java | 16 +
.../AbstractMethodAttributeAppenderTest.java | 15 +
.../AbstractTypeAttributeAppenderTest.java | 25 +
.../attribute/AnnotationAppenderDefaultTest.java | 380 +
.../AnnotationAppenderForTypeAnnotationsTest.java | 152 +
.../attribute/AnnotationAppenderTargetTest.java | 97 +
.../attribute/AnnotationRetentionTest.java | 42 +
.../AnnotationValueFilterDefaultTest.java | 92 +
.../FieldAttributeAppenderCompoundTest.java | 38 +
.../FieldAttributeAppenderFactoryCompoundTest.java | 50 +
.../FieldAttributeAppenderForAnnotationsTest.java | 65 +
...dAttributeAppenderForInstrumentedFieldTest.java | 99 +
.../attribute/FieldAttributeAppenderNoOpTest.java | 29 +
.../MethodAttributeAppenderCompoundTest.java | 37 +
.../MethodAttributeAppenderExplicitTest.java | 160 +
...MethodAttributeAppenderFactoryCompoundTest.java | 50 +
...buteAppenderForInstrumentedMethodOtherTest.java | 128 +
...AttributeAppenderForInstrumentedMethodTest.java | 380 +
...MethodAttributeAppenderForReceiverTypeTest.java | 53 +
.../attribute/MethodAttributeAppenderNoOpTest.java | 29 +
.../TypeAttributeAppenderCompoundTest.java | 37 +
.../TypeAttributeAppenderExplicitTest.java | 68 +
...nderForInstrumentedTypeDifferentiatingTest.java | 189 +
...peAttributeAppenderForInstrumentedTypeTest.java | 223 +
.../attribute/TypeAttributeAppenderNoOpTest.java | 21 +
.../auxiliary/AbstractMethodCallProxyTest.java | 67 +
.../AuxiliaryTypeSignatureRelevantTest.java | 30 +
.../MethodCallProxyObjectPropertiesTest.java | 17 +
.../MethodCallProxySingleArgumentTest.java | 175 +
.../auxiliary/MethodCallProxyTest.java | 121 +
.../implementation/auxiliary/TrivialTypeTest.java | 58 +
.../auxiliary/TypeProxyCreationTest.java | 361 +
.../TypeProxyInvocationFactoryDefaultTest.java | 59 +
.../auxiliary/TypeProxyObjectPropertiesTest.java | 48 +
.../bind/AbstractAmbiguityResolverTest.java | 31 +
.../bind/AbstractArgumentTypeResolverTest.java | 80 +
.../bind/ArgumentTypeResolverPrimitiveTest.java | 143 +
.../bind/ArgumentTypeResolverReferenceTest.java | 244 +
.../bind/DeclaringTypeResolverTest.java | 74 +
.../bind/MethodBindingAmbiguityResolutionTest.java | 46 +
.../bind/MethodBindingBuilderTest.java | 189 +
...DelegationBinderAmbiguityResolverChainTest.java | 58 +
...tionBinderAmbiguityResolverDirectionalTest.java | 50 +
...dDelegationBinderAmbiguityResolverNoOpTest.java | 19 +
.../bind/MethodDelegationBinderProcessorTest.java | 146 +
...thodDelegationBinderTerminationHandlerTest.java | 70 +
.../bind/MethodDelegationBinderTest.java | 76 +
...ethodDelegationBindingParameterBindingTest.java | 82 +
.../bind/MethodNameEqualityResolverTest.java | 56 +
.../bind/ParameterLengthResolverTest.java | 51 +
.../bind/ParameterMethodBindingTest.java | 71 +
.../annotation/AbstractAnnotationBinderTest.java | 86 +
.../bind/annotation/AbstractAnnotationTest.java | 25 +
.../bind/annotation/AllArgumentsBinderTest.java | 169 +
.../bind/annotation/ArgumentBinderTest.java | 143 +
.../annotation/BindingPriorityResolverTest.java | 78 +
.../bind/annotation/DefaultBinderTest.java | 99 +
.../bind/annotation/DefaultCallBinderTest.java | 141 +
.../bind/annotation/DefaultMethodBinderTest.java | 184 +
.../EmptyBinderObjectPropertiesTest.java | 12 +
.../bind/annotation/EmptyBinderTest.java | 78 +
.../bind/annotation/FieldProxyBinderTest.java | 345 +
.../bind/annotation/FieldValueBinderTest.java | 351 +
.../annotation/IgnoreForBindingVerifierTest.java | 72 +
.../bind/annotation/MorphBinderTest.java | 186 +
.../bind/annotation/OriginBinderTest.java | 165 +
.../bind/annotation/PipeBinderTest.java | 94 +
.../bind/annotation/RuntimeTypeVerifierTest.java | 70 +
.../bind/annotation/StubValueBinderTest.java | 86 +
.../bind/annotation/SuperBinderTest.java | 120 +
.../bind/annotation/SuperCallBinderTest.java | 114 +
.../bind/annotation/SuperMethodBinderTest.java | 151 +
.../TargetMethodAnnotationDrivenBinderTest.java | 413 +
...eterBinderForFixedValueOfConstantOtherTest.java | 182 +
...ParameterBinderForFixedValueOfConstantTest.java | 79 +
.../bind/annotation/ThisBinderTest.java | 124 +
.../bytecode/AdditionObjectPropertiesTest.java | 12 +
.../implementation/bytecode/AdditionTest.java | 64 +
.../bytecode/ByteCodeAppenderCompoundTest.java | 67 +
.../bytecode/ByteCodeAppenderSimpleTest.java | 69 +
.../bytecode/ByteCodeAppenderSizeTest.java | 28 +
.../bytecode/DuplicationOtherTest.java | 36 +
.../implementation/bytecode/DuplicationTest.java | 80 +
.../bytecode/DuplicationWithFlipTest.java | 75 +
.../bytecode/RemovalObjectPropertiesTest.java | 12 +
.../implementation/bytecode/RemovalTest.java | 80 +
.../bytecode/StackManipulationCompoundTest.java | 75 +
.../bytecode/StackManipulationSizeTest.java | 42 +
.../bytecode/StackManipulationTest.java | 61 +
.../bytecode/StackSizeMaximumTest.java | 43 +
.../bytecode/StackSizeObjectPropertiesTest.java | 12 +
.../implementation/bytecode/StackSizeTest.java | 58 +
.../implementation/bytecode/ThrowTest.java | 47 +
.../implementation/bytecode/TypeCreationTest.java | 74 +
...AssignerEqualTypesOnlyObjectPropertiesTest.java | 12 +
.../assign/AssignerEqualTypesOnlyTest.java | 103 +
.../AssignerRefusingObjectPropertiesTest.java | 12 +
.../bytecode/assign/AssignerRefusingTest.java | 66 +
.../bytecode/assign/AssignerTypingTest.java | 27 +
.../bytecode/assign/TypeCastingTest.java | 63 +
...rimitiveBoxingDelegateObjectPropertiesTest.java | 12 +
.../primitive/PrimitiveBoxingDelegateTest.java | 125 +
...imitiveBoxingDelegateTestWithReferenceTest.java | 12 +
.../PrimitiveTypeAwareAssignerBoxingTest.java | 94 +
...itiveTypeAwareAssignerImplicitUnboxingTest.java | 100 +
...itiveTypeAwareAssignerObjectPropertiesTest.java | 12 +
.../PrimitiveTypeAwareAssignerPrimitiveTest.java | 150 +
.../PrimitiveTypeAwareAssignerUnboxingTest.java | 87 +
.../PrimitiveUnboxingDelegateDirectTest.java | 148 +
.../PrimitiveUnboxingDelegateOtherTest.java | 25 +
.../PrimitiveUnboxingDelegateWideningTest.java | 119 +
.../PrimitiveWideningDelegateIllegalTest.java | 102 +
.../PrimitiveWideningDelegateNontrivialTest.java | 100 +
.../PrimitiveWideningDelegateOtherTest.java | 24 +
.../PrimitiveWideningDelegateTrivialTest.java | 88 +
.../VoidAwareAssignerNonVoidToVoidTest.java | 111 +
.../assign/primitive/VoidAwareAssignerTest.java | 77 +
.../VoidAwareAssignerVoidToNonVoidTest.java | 103 +
.../reference/ReferenceTypeAwareAssignerTest.java | 130 +
.../collection/AbstractArrayFactoryTest.java | 82 +
.../bytecode/collection/ArrayAccessOtherTest.java | 39 +
.../bytecode/collection/ArrayAccessTest.java | 83 +
.../ArrayFactoryObjectPropertiesTest.java | 56 +
.../collection/ArrayFactoryPrimitiveTest.java | 52 +
.../collection/ArrayFactoryReferenceTest.java | 45 +
.../constant/ClassConstantPrimitiveTest.java | 68 +
.../constant/ClassConstantReferenceTest.java | 115 +
.../constant/DefaultValueObjectPropertiesTest.java | 12 +
.../bytecode/constant/DefaultValueTest.java | 91 +
.../DoubleConstantObjectPropertiesTest.java | 13 +
.../constant/DoubleConstantOpcodeTest.java | 66 +
.../bytecode/constant/DoubleConstantTest.java | 72 +
.../bytecode/constant/FieldConstantTest.java | 158 +
.../FloatConstantObjectPropertiesTest.java | 13 +
.../bytecode/constant/FloatConstantOpcodeTest.java | 66 +
.../bytecode/constant/FloatConstantTest.java | 70 +
.../IntegerConstantObjectPropertiesTest.java | 15 +
.../constant/IntegerConstantOpcodeTest.java | 76 +
.../bytecode/constant/IntegerConstantTest.java | 106 +
.../bytecode/constant/JavaConstantValueTest.java | 52 +
.../constant/LongConstantObjectPropertiesTest.java | 13 +
.../bytecode/constant/LongConstantOpcodeTest.java | 65 +
.../bytecode/constant/LongConstantTest.java | 71 +
.../bytecode/constant/MethodConstantTest.java | 160 +
.../bytecode/constant/NullConstantTest.java | 43 +
.../bytecode/constant/SerializedConstantTest.java | 35 +
.../bytecode/constant/TextConstantTest.java | 44 +
.../bytecode/member/FieldAccessOtherTest.java | 153 +
.../bytecode/member/FieldAccessTest.java | 125 +
.../bytecode/member/HandleInvocationTest.java | 60 +
.../member/MethodInvocationDynamicTest.java | 97 +
.../member/MethodInvocationGenericTest.java | 150 +
.../member/MethodInvocationHandleTest.java | 99 +
.../bytecode/member/MethodInvocationOtherTest.java | 56 +
.../bytecode/member/MethodInvocationTest.java | 213 +
.../member/MethodReturnObjectPropertiesTest.java | 12 +
.../bytecode/member/MethodReturnTest.java | 90 +
.../MethodVariableAccessOfMethodArgumentsTest.java | 151 +
.../member/MethodVariableAccessOtherTest.java | 100 +
.../bytecode/member/MethodVariableAccessTest.java | 92 +
.../matcher/AbstractElementMatcherTest.java | 40 +
.../matcher/AbstractFilterableListTest.java | 64 +
.../matcher/AccessibilityMatcherTest.java | 42 +
.../matcher/AnnotationTypeMatcherTest.java | 49 +
.../net/bytebuddy/matcher/BooleanMatcherTest.java | 41 +
.../net/bytebuddy/matcher/CachingMatcherTest.java | 84 +
.../matcher/ClassLoaderHierarchyMatcherTest.java | 47 +
.../matcher/ClassLoaderParentMatcherTest.java | 42 +
.../matcher/CollectionElementMatcherTest.java | 60 +
.../matcher/CollectionErasureMatcherTest.java | 60 +
.../matcher/CollectionItemMatcherTest.java | 61 +
.../matcher/CollectionOneToOneMatcherTest.java | 89 +
.../matcher/CollectionSizeMatcherTest.java | 47 +
.../matcher/DeclaringAnnotationMatcherTest.java | 49 +
.../matcher/DeclaringFieldMatcherTest.java | 50 +
.../matcher/DeclaringMethodMatcherTest.java | 51 +
.../matcher/DeclaringTypeMatcherTest.java | 63 +
.../bytebuddy/matcher/DefinedShapeMatcherTest.java | 47 +
.../bytebuddy/matcher/DescriptorMatcherTest.java | 47 +
.../ElementMatcherJunctionConjunctionTest.java | 58 +
.../ElementMatcherJunctionDisjunctionTest.java | 58 +
.../net/bytebuddy/matcher/ElementMatchersTest.java | 1516 +++
.../net/bytebuddy/matcher/EqualityMatcherTest.java | 25 +
.../net/bytebuddy/matcher/ErasureMatcherTest.java | 57 +
.../net/bytebuddy/matcher/FailSafeMatcherTest.java | 60 +
.../bytebuddy/matcher/FieldTypeMatcherTest.java | 49 +
.../bytebuddy/matcher/FilterableListEmptyTest.java | 52 +
.../bytebuddy/matcher/HasSuperTypeMatcherTest.java | 69 +
.../matcher/InheritedAnnotationMatcherTest.java | 49 +
.../bytebuddy/matcher/InstanceTypeMatcherTest.java | 40 +
.../net/bytebuddy/matcher/IsNamedMatcherTest.java | 32 +
.../matcher/LatentMatcherAccessorTest.java | 39 +
.../matcher/LatentMatcherConjunctionTest.java | 49 +
.../matcher/LatentMatcherDisjunctionTest.java | 49 +
.../matcher/LatentMatcherForFieldTokenTest.java | 60 +
.../matcher/LatentMatcherForMethodTokenTest.java | 53 +
.../LatentMatcherForSelfDeclaredMethodTest.java | 40 +
.../matcher/MethodExceptionTypeMatcherTest.java | 52 +
.../matcher/MethodOverrideMatcherTest.java | 116 +
.../matcher/MethodParameterMatcherTest.java | 50 +
.../matcher/MethodParameterTypeMatcherTest.java | 49 +
.../matcher/MethodParameterTypesMatcherTest.java | 52 +
.../matcher/MethodReturnTypeMatcherTest.java | 49 +
.../MethodSortMatcherObjectPropertiesTest.java | 12 +
.../bytebuddy/matcher/MethodSortMatcherTest.java | 106 +
.../ModifierMatcherObjectPropertiesTest.java | 12 +
.../net/bytebuddy/matcher/ModifierMatcherTest.java | 81 +
.../net/bytebuddy/matcher/NameMatcherTest.java | 47 +
.../net/bytebuddy/matcher/NegatingMatcherTest.java | 37 +
.../net/bytebuddy/matcher/NullMatcherTest.java | 24 +
.../matcher/SignatureTokenMatcherTest.java | 44 +
.../matcher/StringMatcherObjectPropertiesTest.java | 12 +
.../net/bytebuddy/matcher/StringMatcherTest.java | 69 +
.../net/bytebuddy/matcher/SubTypeMatcherTest.java | 38 +
.../bytebuddy/matcher/SuperTypeMatcherTest.java | 38 +
.../net/bytebuddy/matcher/TypeSortMatcherTest.java | 52 +
.../bytebuddy/matcher/VisibilityMatcherTest.java | 42 +
.../bytebuddy/pool/TypePoolCacheProviderTest.java | 52 +
.../bytebuddy/pool/TypePoolClassLoadingTest.java | 69 +
.../TypePoolDefaultAnnotationDescriptionTest.java | 22 +
.../bytebuddy/pool/TypePoolDefaultCacheTest.java | 20 +
.../TypePoolDefaultComponentPoolStrategyTest.java | 59 +
.../TypePoolDefaultEnumerationDescriptionTest.java | 38 +
.../pool/TypePoolDefaultFieldDescriptionTest.java | 33 +
.../pool/TypePoolDefaultGenericTypeListTest.java | 48 +
.../pool/TypePoolDefaultHierarchyTest.java | 90 +
.../TypePoolDefaultLazyAnnotationListTest.java | 47 +
.../pool/TypePoolDefaultLazyFieldListTest.java | 47 +
.../pool/TypePoolDefaultLazyMethodListTest.java | 47 +
.../TypePoolDefaultLazyObjectPropertiesTest.java | 140 +
.../pool/TypePoolDefaultLazyParameterListTest.java | 57 +
.../TypePoolDefaultLazyTypeContainmentTest.java | 109 +
.../pool/TypePoolDefaultLazyTypeListTest.java | 46 +
.../pool/TypePoolDefaultMethodDescriptionTest.java | 52 +
.../TypePoolDefaultPackageDescriptionTest.java | 26 +
.../pool/TypePoolDefaultParameterBagTest.java | 91 +
.../pool/TypePoolDefaultPrimitiveTypeTest.java | 51 +
.../pool/TypePoolDefaultReaderModeTest.java | 28 +
.../net/bytebuddy/pool/TypePoolDefaultTest.java | 107 +
...efaultTypeDescriptionSuperClassLoadingTest.java | 62 +
.../pool/TypePoolDefaultTypeDescriptionTest.java | 55 +
...faultWithLazyResolutionTypeDescriptionTest.java | 274 +
.../java/net/bytebuddy/pool/TypePoolEmptyTest.java | 26 +
.../net/bytebuddy/pool/TypePoolExplicitTest.java | 71 +
.../net/bytebuddy/pool/TypePoolLazyFacadeTest.java | 76 +
.../TypePoolLazyFacadeTypeDescriptionTest.java | 55 +
.../net/bytebuddy/pool/TypePoolResolutionTest.java | 54 +
.../test/packaging/PackagePrivateConstructor.java | 9 +
.../test/packaging/PackagePrivateField.java | 19 +
.../test/packaging/PackagePrivateMethod.java | 23 +
.../test/packaging/PackagePrivateType.java | 23 +
.../test/packaging/SimpleOptionalType.java | 12 +
.../net/bytebuddy/test/packaging/SimpleType.java | 10 +
.../test/packaging/VisibilityFieldTestHelper.java | 7 +
.../test/packaging/VisibilityMethodTestHelper.java | 7 +
.../net/bytebuddy/test/scope/EnclosingType.java | 136 +
.../java/net/bytebuddy/test/scope/GenericType.java | 21 +
.../test/utility/AgentAttachmentRule.java | 70 +
.../net/bytebuddy/test/utility/CallTraceable.java | 47 +
.../test/utility/ClassFileExtraction.java | 107 +
.../test/utility/CustomHamcrestMatchers.java | 31 +
.../bytebuddy/test/utility/DebuggingWrapper.java | 72 +
.../bytebuddy/test/utility/IntegrationRule.java | 40 +
.../bytebuddy/test/utility/JavaVersionRule.java | 100 +
.../net/bytebuddy/test/utility/MockitoRule.java | 30 +
.../test/utility/ObjectPropertyAssertion.java | 327 +
.../test/visibility/PackageAnnotation.java | 9 +
.../java/net/bytebuddy/test/visibility/Sample.java | 5 +
.../net/bytebuddy/test/visibility/child/Child.java | 5 +
.../bytebuddy/test/visibility/package-info.java | 1 +
.../net/bytebuddy/utility/CompoundListTest.java | 67 +
.../JavaConstantMethodHandleDispatcherTest.java | 112 +
.../utility/JavaConstantMethodHandleTest.java | 246 +
.../JavaConstantMethodTypeDispatcherTest.java | 35 +
.../utility/JavaConstantMethodTypeTest.java | 150 +
.../java/net/bytebuddy/utility/JavaModuleTest.java | 87 +
.../java/net/bytebuddy/utility/JavaTypeTest.java | 107 +
.../net/bytebuddy/utility/RandomStringTest.java | 47 +
.../net/bytebuddy/utility/StreamDrainerTest.java | 23 +
.../privilege/GetSystemPropertyActionTest.java | 27 +
.../utility/privilege/SetAccessibleActionTest.java | 57 +
.../ExceptionTableSensitiveMethodVisitorTest.java | 100 +
.../LineNumberPrependingMethodVisitorTest.java | 25 +
.../visitor/StackAwareMethodVisitorTest.java | 153 +
.../precompiled/ArgumentBootstrap$SampleEnum.class | Bin 0 -> 1234 bytes
.../test/precompiled/ArgumentBootstrap.class | Bin 0 -> 4465 bytes
.../test/precompiled/ArgumentBootstrap.java | 61 +
.../precompiled/DelegationDefaultInterface.class | Bin 0 -> 429 bytes
.../precompiled/DelegationDefaultInterface.java | 10 +
.../test/precompiled/DelegationDefaultTarget.class | Bin 0 -> 1306 bytes
.../test/precompiled/DelegationDefaultTarget.java | 19 +
.../DelegationDefaultTargetExplicit.class | Bin 0 -> 1688 bytes
.../DelegationDefaultTargetExplicit.java | 19 +
.../DelegationDefaultTargetSerializable.class | Bin 0 -> 1304 bytes
.../DelegationDefaultTargetSerializable.java | 18 +
.../test/precompiled/LambdaSampleFactory.class | Bin 0 -> 4891 bytes
.../test/precompiled/LambdaSampleFactory.java | 40 +
.../test/precompiled/LegacyInterface.class | Bin 0 -> 142 bytes
.../test/precompiled/LegacyInterface.java | 5 +
.../MorphDefaultDelegationTargetExplicit.class | Bin 0 -> 1412 bytes
.../MorphDefaultDelegationTargetExplicit.java | 14 +
.../MorphDefaultDelegationTargetImplicit.class | Bin 0 -> 1360 bytes
.../MorphDefaultDelegationTargetImplicit.java | 14 +
.../test/precompiled/MorphDefaultInterface.class | Bin 0 -> 632 bytes
.../test/precompiled/MorphDefaultInterface.java | 10 +
.../test/precompiled/OriginExecutable.class | Bin 0 -> 677 bytes
.../test/precompiled/OriginExecutable.java | 15 +
.../precompiled/OriginExecutableWithCache.class | Bin 0 -> 704 bytes
.../precompiled/OriginExecutableWithCache.java | 15 +
.../test/precompiled/OriginMethodHandle.class | Bin 0 -> 793 bytes
.../test/precompiled/OriginMethodHandle.java | 14 +
.../test/precompiled/OriginMethodType.class | Bin 0 -> 779 bytes
.../test/precompiled/OriginMethodType.java | 14 +
.../test/precompiled/OtherTypeAnnotation.class | Bin 0 -> 460 bytes
.../test/precompiled/OtherTypeAnnotation.java | 13 +
.../test/precompiled/ParameterBootstrap.class | Bin 0 -> 2494 bytes
.../test/precompiled/ParameterBootstrap.java | 39 +
.../test/precompiled/ParameterNames.class | Bin 0 -> 444 bytes
.../bytebuddy/test/precompiled/ParameterNames.java | 16 +
.../ReceiverTypeSample$Generic$Inner.class | Bin 0 -> 1127 bytes
.../ReceiverTypeSample$Generic$Nested.class | Bin 0 -> 877 bytes
.../precompiled/ReceiverTypeSample$Generic.class | Bin 0 -> 948 bytes
.../precompiled/ReceiverTypeSample$Inner.class | Bin 0 -> 758 bytes
.../precompiled/ReceiverTypeSample$Nested.class | Bin 0 -> 582 bytes
.../test/precompiled/ReceiverTypeSample.class | Bin 0 -> 733 bytes
.../test/precompiled/ReceiverTypeSample.java | 55 +
.../precompiled/ReturnTypeInterfaceBridge.class | Bin 0 -> 596 bytes
.../precompiled/ReturnTypeInterfaceBridge.java | 11 +
.../ReturnTypeInterfaceBridgeBase.class | Bin 0 -> 438 bytes
.../precompiled/ReturnTypeInterfaceBridgeBase.java | 10 +
.../test/precompiled/SimpleTypeAnnotatedType.class | Bin 0 -> 709 bytes
.../test/precompiled/SimpleTypeAnnotatedType.java | 5 +
.../precompiled/SingleDefaultMethodClass.class | Bin 0 -> 426 bytes
.../test/precompiled/SingleDefaultMethodClass.java | 5 +
.../SingleDefaultMethodConflictingInterface.class | Bin 0 -> 474 bytes
.../SingleDefaultMethodConflictingInterface.java | 10 +
...ultMethodConflictingPreferringInterceptor.class | Bin 0 -> 1092 bytes
...aultMethodConflictingPreferringInterceptor.java | 12 +
.../precompiled/SingleDefaultMethodInterface.class | Bin 0 -> 435 bytes
.../precompiled/SingleDefaultMethodInterface.java | 10 +
...SingleDefaultMethodNonOverridingInterface.class | Bin 0 -> 261 bytes
.../SingleDefaultMethodNonOverridingInterface.java | 5 +
.../SingleDefaultMethodPreferringInterceptor.class | Bin 0 -> 1048 bytes
.../SingleDefaultMethodPreferringInterceptor.java | 12 +
.../precompiled/StandardArgumentBootstrap.class | Bin 0 -> 2522 bytes
.../precompiled/StandardArgumentBootstrap.java | 55 +
.../test/precompiled/TypeAnnotation.class | Bin 0 -> 450 bytes
.../bytebuddy/test/precompiled/TypeAnnotation.java | 13 +
.../TypeAnnotationOtherSamples$Bar.class | Bin 0 -> 824 bytes
.../TypeAnnotationOtherSamples$Qux$Baz.class | Bin 0 -> 739 bytes
.../TypeAnnotationOtherSamples$Qux.class | Bin 0 -> 562 bytes
.../precompiled/TypeAnnotationOtherSamples.class | Bin 0 -> 1616 bytes
.../precompiled/TypeAnnotationOtherSamples.java | 26 +
.../test/precompiled/TypeAnnotationSamples.class | Bin 0 -> 2273 bytes
.../test/precompiled/TypeAnnotationSamples.java | 25 +
.../test/precompiled/TypeConstantSample.class | Bin 0 -> 817 bytes
.../test/precompiled/TypeConstantSample.java | 9 +
.../precompiled/TypeVariableInterfaceBridge.class | Bin 0 -> 799 bytes
.../precompiled/TypeVariableInterfaceBridge.java | 11 +
.../TypeVariableInterfaceBridgeBase.class | Bin 0 -> 647 bytes
.../TypeVariableInterfaceBridgeBase.java | 8 +
byte-buddy-gradle-plugin/build.gradle | 53 +
.../net.bytebuddy.byte-buddy.properties | 1 +
.../net.bytebuddy.test/Sample.java.raw | 12 +
.../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 53324 bytes
.../gradle/wrapper/gradle-wrapper.properties | 6 +
byte-buddy-gradle-plugin/gradlew | 164 +
byte-buddy-gradle-plugin/gradlew.bat | 90 +
byte-buddy-gradle-plugin/pom.xml | 177 +
.../build/gradle/AbstractUserConfiguration.java | 140 +
.../bytebuddy/build/gradle/ByteBuddyExtension.java | 166 +
.../bytebuddy/build/gradle/ByteBuddyPlugin.java | 16 +
.../build/gradle/ClassLoaderResolver.java | 95 +
.../net/bytebuddy/build/gradle/Initialization.java | 63 +
.../build/gradle/PostCompilationAction.java | 51 +
.../net/bytebuddy/build/gradle/Transformation.java | 44 +
.../build/gradle/TransformationAction.java | 248 +
.../net/bytebuddy/build/gradle/package-info.java | 4 +
.../net.bytebuddy.byte-buddy.properties | 1 +
...bstractUserConfigurationPrefixIterableTest.java | 39 +
.../build/gradle/ByteBuddyExtensionTest.java | 147 +
.../build/gradle/ByteBuddyPluginTest.java | 102 +
.../build/gradle/ClassLoaderResolverTest.java | 44 +
.../bytebuddy/build/gradle/InitializationTest.java | 129 +
.../build/gradle/PostCompilationActionTest.java | 53 +
.../build/gradle/TransformationActionTest.java | 297 +
.../bytebuddy/build/gradle/TransformationTest.java | 72 +
.../java/net/bytebuddy/test/IllegalEntryPoint.java | 24 +
.../java/net/bytebuddy/test/IllegalPlugin.java | 18 +
.../bytebuddy/test/IllegalTransformEntryPoint.java | 24 +
.../net/bytebuddy/test/IllegalTransformPlugin.java | 22 +
.../net/bytebuddy/test/LiveInitializerPlugin.java | 25 +
.../java/net/bytebuddy/test/SimpleEntryPoint.java | 24 +
.../test/java/net/bytebuddy/test/SimplePlugin.java | 21 +
.../bytebuddy/test/utility/IntegrationRule.java | 40 +
.../net/bytebuddy/test/utility/MockitoRule.java | 30 +
.../test/utility/ObjectPropertyAssertion.java | 372 +
byte-buddy-maven-plugin/pom.xml | 85 +
.../build/maven/AbstractUserConfiguration.java | 74 +
.../net/bytebuddy/build/maven/ByteBuddyMojo.java | 441 +
.../bytebuddy/build/maven/ClassLoaderResolver.java | 131 +
.../net/bytebuddy/build/maven/Initialization.java | 57 +
.../net/bytebuddy/build/maven/MavenCoordinate.java | 74 +
.../net/bytebuddy/build/maven/Transformation.java | 38 +
.../net/bytebuddy/build/maven/package-info.java | 4 +
.../bytebuddy/build/maven/ByteBuddyMojoTest.java | 297 +
.../build/maven/ClassLoaderResolverTest.java | 103 +
.../bytebuddy/build/maven/InitializationTest.java | 133 +
.../bytebuddy/build/maven/MavenCoordinateTest.java | 27 +
.../bytebuddy/build/maven/TransformationTest.java | 65 +
.../java/net/bytebuddy/test/IllegalEntryPoint.java | 24 +
.../java/net/bytebuddy/test/IllegalPlugin.java | 18 +
.../bytebuddy/test/IllegalTransformEntryPoint.java | 24 +
.../net/bytebuddy/test/IllegalTransformPlugin.java | 22 +
.../net/bytebuddy/test/LiveInitializerPlugin.java | 25 +
.../java/net/bytebuddy/test/SimpleEntryPoint.java | 24 +
.../test/java/net/bytebuddy/test/SimplePlugin.java | 21 +
.../net/bytebuddy/test/utility/MockitoRule.java | 30 +
.../test/utility/ObjectPropertyAssertion.java | 331 +
.../resources/net/bytebuddy/test/empty.pom.xml | 15 +
.../net/bytebuddy/test/entry.illegal.pom.xml | 24 +
.../bytebuddy/test/entry.illegal.transform.pom.xml | 24 +
.../resources/net/bytebuddy/test/entry.pom.xml | 24 +
.../net/bytebuddy/test/illegal.apply.pom.xml | 21 +
.../resources/net/bytebuddy/test/illegal.pom.xml | 21 +
.../net/bytebuddy/test/live.allowed.pom.xml | 22 +
.../test/resources/net/bytebuddy/test/live.pom.xml | 22 +
.../resources/net/bytebuddy/test/simple.pom.xml | 21 +
.../resources/net/bytebuddy/test/suffix.pom.xml | 22 +
.../test/resources/net/bytebuddy/test/test.pom.xml | 21 +
byte-buddy/pom.xml | 178 +
checkstyle.xml | 76 +
mvnw | 214 +
mvnw.cmd | 146 +
pom.xml | 579 ++
release-notes.md | 860 ++
1106 files changed, 210151 insertions(+)
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..8311f95
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,3 @@
+mvnw linguist-vendored
+mvnw.cmd linguist-vendored
+.mvn/* linguist-vendored
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..15fd5d5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,27 @@
+# Eclipse
+.classpath
+.project
+.settings/
+
+# Intellij
+.idea/
+*.iml
+*.iws
+
+# Mac
+.DS_Store
+
+# Maven
+log/
+target/
+site/
+
+# Gradle
+byte-buddy-gradle-plugin/build/
+.gradle/
+
+# Shade plugin
+dependency-reduced-pom.xml
+
+# Android
+*/gen/*
diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 0000000..cd16a00
Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..7c3a925
--- /dev/null
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1 @@
+distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.2.5/apache-maven-3.2.5-bin.zip
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..3ee6a0d
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,28 @@
+language: java
+
+sudo: false
+
+# Triggers an update of the OracleJDK distribution. Travis is currently running v8u31 which
+# exposes problems with the instrumentation of invokedynamic call sites.
+addons:
+ apt:
+ packages:
+ - oracle-java8-installer
+
+branches:
+ only:
+ - master
+
+matrix:
+ include:
+ - jdk: openjdk6
+ - jdk: oraclejdk7
+ env: TARGET=-Pjava7
+ - jdk: oraclejdk8
+ env: TARGET=-Pjava8
+
+install: ./mvnw install -DskipTests=true -Dmaven.javadoc.skip=true -B -V
+script: ./mvnw jacoco:prepare-agent verify jacoco:report $TARGET -Pintegration -Dnet.bytebuddy.test.travis=true -B -V
+
+after_success:
+ - ./mvnw coveralls:report
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..e25899a
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,5 @@
+When you have fixed a bug, simply create a [pull request](https://github.com/raphw/byte-buddy/pulls) on GitHub. We will look into the matter as quickly as possible, once we receive the notification. Make however sure that you have accurately described your changes and the fixed issue and please provide a test case that reproduces the problem and proves that your fix is working. This makes our work much easier and we will be able to apply your patch much quicker. If you add new methods, f [...]
+
+If you are contributing a feature, please [get in touch](https://groups.google.com/forum/#!forum/byte-buddy) before spending a lot of time with it such that we can discuss how your changes are meaningful at Byte Buddy's current development state. Byte Buddy is meant to steadily provide more functionality but we do not grow its feature set on the expense of its stability and code consistency. However, do not be discouraged by this announcement. If you got deep enough into Byte Buddy's sou [...]
+
+If you feel like contributing to Byte Buddy's documentation, to its description on this web page or even to the structure and design of this web page, you are absolutely welcome to do so! We deeply believe that a thorough and up-to-date documentation is the key to a successful project and we will do our best to live up to this conviction. Even minor changes are welcome as long as they improve Byte Buddy's accessibility or appearance, because in the end, this project was made for its user [...]
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..e06d208
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ 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.
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..7726420
--- /dev/null
+++ b/README.md
@@ -0,0 +1,269 @@
+Byte Buddy
+==========
+
+<a href="http://bytebuddy.net">
+<img src="https://raw.githubusercontent.com/raphw/byte-buddy/gh-pages/images/logo-bg.png" alt="Byte Buddy logo" height="180px" align="right" />
+</a>
+
+runtime code generation for the Java virtual machine
+
+[![Build Status (Travis CI)](https://travis-ci.org/raphw/byte-buddy.svg?branch=master)](https://travis-ci.org/raphw/byte-buddy)
+[![Build Status (AppVeyor CI)](https://ci.appveyor.com/api/projects/status/github/raphw/byte-buddy?branch=master&svg=true)](https://ci.appveyor.com/project/raphw/byte-buddy)
+[![Coverage Status](http://img.shields.io/coveralls/raphw/byte-buddy/master.svg)](https://coveralls.io/r/raphw/byte-buddy?branch=master)
+[![Maven Central](https://maven-badges.herokuapp.com/maven-central/net.bytebuddy/byte-buddy-parent/badge.svg)](https://maven-badges.herokuapp.com/maven-central/net.bytebuddy/byte-buddy-parent)
+[![Download from Bintray](https://api.bintray.com/packages/raphw/maven/ByteBuddy/images/download.svg) ](https://bintray.com/raphw/maven/ByteBuddy/_latestVersion)
+
+Byte Buddy is a code generation and manipulation library for creating and modifying Java classes during the
+runtime of a Java application and without the help of a compiler. Other than the code generation utilities
+that [ship with the Java Class Library](http://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Proxy.html),
+Byte Buddy allows the creation of arbitrary classes and is not limited to implementing interfaces for the
+creation of runtime proxies. Furthermore, Byte Buddy offers a convenient API for changing classes either
+manually, using a Java agent or during a build.
+
+In order to use Byte Buddy, one does not require an understanding of Java byte code or the [class file format](http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html). In contrast, Byte Buddy’s API aims for code
+that is concise and easy to understand for everybody. Nevertheless, Byte Buddy remains fully customizable down
+to the possibility of defining custom byte code. Furthermore, the API was designed to be as non-intrusive as
+possible and as a result, Byte Buddy does not leave any trace in the classes that were created by it. For this
+reason, the generated classes can exist without requiring Byte Buddy on the class path. Because of this feature,
+Byte Buddy’s mascot was chosen to be a ghost.
+
+Byte Buddy is written in Java 6 but supports the generation of classes for any Java version. Byte Buddy is a
+light-weight library and only depends on the visitor API of the Java byte code parser library
+[ASM](http://asm.ow2.org/) which does itself
+[not require any further dependencies](https://repo1.maven.org/maven2/org/ow2/asm/asm/5.0.4/asm-5.0.4.pom).
+
+At first sight, runtime code generation can appear to be some sort of black magic that should be avoided and only
+few developers write applications that explicitly generate code during their runtime. However, this picture changes when
+creating libraries that need to interact with arbitrary code and types that are unknown at compile time. In this
+context, a library implementer must often choose between either requiring a user to implement library-proprietary
+interfaces or to generate code at runtime when the user’s types becomes first known to the library. Many known libraries
+such as for example *Spring* or *Hibernate* choose the latter approach which is popular among their users under the term
+of using [*Plain Old Java Objects*](http://en.wikipedia.org/wiki/Plain_Old_Java_Object). As a result, code generation
+has become an ubiquitous concept in the Java space. Byte Buddy is an attempt to innovate the runtime creation of Java
+types in order to provide a better tool set to those relying on code generation.
+
+___
+
+<a href="http://bytebuddy.net">
+<img src="https://raw.githubusercontent.com/raphw/byte-buddy/gh-pages/images/dukeschoice.jpg" alt="Duke's Choice award" height="110px" align="left" />
+</a>
+
+In October 2015, Byte Buddy was distinguished with a
+[*Duke's Choice award*](https://www.oracle.com/corporate/pressrelease/dukes-award-102815.html)
+by Oracle. The award appreciates Byte Buddy for its "*tremendous amount of innovation in Java Technology*".
+We feel very honored for having received this award and want to thank all users and everybody else who helped
+making Byte Buddy the success it has become. We really appreciate it!
+
+___
+
+Byte Buddy offers excellent performance at production quality. It is stable and in use by distiguished frameworks and tools such as [Mockito](http://mockito.org), [Hibernate](http://hibernate.org), [Google's Bazel build system](http://bazel.io) and [many others](https://github.com/raphw/byte-buddy/wiki/Projects-using-Byte-Buddy). Byte Buddy is also used by a large number of commercial products to great result. It is currently downloaded over a million times a year.
+
+Hello World
+-----------
+
+Saying *Hello World* with Byte Buddy is as easy as it can get. Any creation of a Java class starts with an instance
+of the `ByteBuddy` class which represents a configuration for creating new types:
+
+```java
+Class<?> dynamicType = new ByteBuddy()
+ .subclass(Object.class)
+ .method(ElementMatchers.named("toString"))
+ .intercept(FixedValue.value("Hello World!"))
+ .make()
+ .load(getClass().getClassLoader())
+ .getLoaded();
+assertThat(dynamicType.newInstance().toString(), is("Hello World!"));
+```
+
+The default `ByteBuddy` configuration which is used in the above example creates a Java class in the newest version of
+the class file format that is understood by the processing Java virtual machine. As hopefully obvious from
+the example code, the created type will extend the `Object` class and overrides its `toString` method which should
+return a fixed value of `Hello World!`. The method to be overridden is identified by a so-called `ElementMatcher`. In
+the above example, a predefined element matcher `named(String)` is used which identifies methods by their exact names.
+Byte Buddy comes with numerous predefined and well-tested matchers which are collected in the `ElementMatchers`
+class and which can be easily composed. The creation of custom matchers is however as simple as implementing the
+([functional](http://docs.oracle.com/javase/8/docs/api/java/lang/FunctionalInterface.html)) `ElementMatcher` interface.
+
+For implementing the `toString` method, the `FixedValue` class defines a constant return value for the overridden
+method. Defining a constant value is only one example of many method interceptors that ship with Byte Buddy. By
+implementing the `Implementation` interface, a method could however even be defined by custom byte code.
+
+Finally, the described Java class is created and then loaded into the Java virtual machine. For this purpose, a target
+class loader is required. Eventually, we can convince ourselves of the result by calling the `toString` method on an
+instance of the created class and finding the return value to represent the constant value we expected.
+
+A more complex example
+----------------------
+
+Of course, a *Hello World example* is a too simple use case for evaluating the quality of a code generation library.
+In reality, a user of such a library wants to perform more complex manipulations, for example by introducing hooks
+into the execution path of a Java program. Using Byte Buddy, doing so is however equally simple. The following example
+gives a taste of how method calls can be intercepted.
+
+Byte Buddy expresses dynamically defined method implementations by instances of the `Implementation` interface. In the
+previous example, `FixedValue` that implements this interface was already demonstrated. By implementing this interface,
+a user of Byte Buddy can go to the length of defining custom byte code for a method. Normally, it is however easier to
+use Byte Buddy's predefined implementations such as `MethodDelegation` which allows for implementing any method in
+plain Java. Using this implementation is straight forward as it operates by delegating the control flow to any POJO. As
+an example of such a POJO, Byte Buddy can for example redirect a call to the only method of the following class:
+
+```java
+public class GreetingInterceptor {
+ public Object greet(Object argument) {
+ return "Hello from " + argument;
+ }
+}
+```
+
+Note that the above `GreetingInterceptor` does not depend on any Byte Buddy type. This is good news because none of the classes
+that by Byte Buddy generates require Byte Buddy on the class path! Given the above `GreetingInterceptor`, we can use Byte Buddy
+to implement the Java 8 `java.util.function.Function` interface and its abstract `apply` method:
+
+```java
+Class<? extends java.util.function.Function> dynamicType = new ByteBuddy()
+ .subclass(java.util.function.Function.class)
+ .method(ElementMatchers.named("apply"))
+ .intercept(MethodDelegation.to(new GreetingInterceptor()))
+ .make()
+ .load(getClass().getClassLoader())
+ .getLoaded();
+assertThat((String) dynamicType.newInstance().apply("Byte Buddy"), is("Hello from Byte Buddy"));
+```
+
+Executing the above code, Byte Buddy implements Java's `Function` interface and implements the `apply` method
+as a delegation to an instance of the `GreetingInterceptor` POJO that we defined before. Now, every time that the
+`Function::apply` method is called, the control flow is dispatched to `GreetingInterceptor::greet` and the latter
+method's return value is returned from the interface's method.
+
+Interceptors can be defined to take with more generic inputs and outputs by annotating the interceptor's parameters.
+When Byte Buddy discovers an annotation, the library injects the dependency that the interceptor parameter requires.
+An example for a more general interceptor is the following class:
+
+```java
+public class GeneralInterceptor {
+ @RuntimeType
+ public Object intercept(@AllArguments Object[] allArguments,
+ @Origin Method method) {
+ // intercept any method of any signature
+ }
+}
+```
+
+With the above interceptor, any intercepted method could be matched and processed. For example, when matching
+`Function::apply`, the method's arguments would be passed as the single element of an array. Also, a `Method`
+reference to `Fuction::apply` would be passed as the interceptor's second argument due to the `@Origin`
+annotation. By declaring the `@RuntimeType` annotation on the method, Byte Buddy finally casts the returned
+value to the return value of the intercepted method if this is necessary. In doing so, Byte Buddy also applies
+automatic boxing and unboxing.
+
+Besides the annotations that were already mentioned there exist plenty of other predefined annotations. For
+example, when using the `@SuperCall` annotation on a `Runnable` or `Callable` type, Byte Buddy injects proxy
+instances that allow for an invocation of a non-abstract super method if such a method exists. And even if
+Byte Buddy does not cover au use case, Byte Buddy offers an extension mechanism for defining custom annotations.
+
+You might expect that using these annotations ties your code to Byte Buddy. However, Java ignores annotations in case
+that they are not visible to a class loader. This way, generated code can still exist without Byte Buddy! You can
+find more information on the `MethodDelegation` and on all of its predefined annotations in its *javadoc* and in
+Byte Buddy's tutorial.
+
+Changing existing classes
+----------------------
+
+Byte Buddy is not limited to creating subclasses but is also capable of redefining existing code. To do so, Byte Buddy offers a convenient API for defining so-called [Java agents](https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/package-summary.html). Java agents are plain old Java programs that can be used to alter the code of an existing Java application during its runtime. As an example, we can use Byte Buddy to change methods to print their execution time. For this, we [...]
+
+```java
+public class TimingInterceptor {
+ @RuntimeType
+ public static Object intercept(@Origin Method method,
+ @SuperCall Callable<?> callable) {
+ long start = System.currentTimeMillis();
+ try {
+ return callable.call();
+ } finally {
+ System.out.println(method + " took " + (System.currentTimeMillis() - start));
+ }
+ }
+}
+```
+
+Using a Java agent, we can now apply this interceptor to all types that match an `ElementMatcher` for a `TypeDescription`. For the example, we choose to add the above interceptor to all types with a name that ends in `Timed`. This is done for the sake of similicity whereas an annotation would probably be a more appropriate alternative to mark such classes for a production agent. Using Byte Buddy's `AgentBuilder` API, creating a Java agent is as easy as defining the following agent class:
+
+```java
+public class TimerAgent {
+ public static void premain(String arguments,
+ Instrumentation instrumentation) {
+ new AgentBuilder.Default()
+ .type(ElementMatchers.nameEndsWith("Timed"))
+ .transform((builder, type, classLoader, module) ->
+ builder.method(ElementMatchers.any())
+ .intercept(MethodDelegation.to(TimingInterceptor.class))
+ ).installOn(instrumentation);
+ }
+ }
+}
+```
+
+Similar to Java's `main` method, the `premain` method is the entry point to any Java agent from which we apply the redefinition. As one argument, a Java agent receives an instace of the `Instrumentation` interface which allows Byte Buddy to hook into the JVM's standard API for runtime class redefinition.
+
+This program is packaged together with a manifest file with the [`Premain-Class` attribute](https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/package-summary.html) pointing to the `TimerAgent`. The resulting *jar* file can now be added to any Java application by setting `-javaagent:timingagent.jar` similar to adding a jar to the class path. With the agent active, all classes ending in `Timed` do now print their execution time to the console.
+
+Byte Buddy is also capable of applying so-called runtime attachments by disabling class file format changes and using the `Advice` instrumentation. Please refer to the *javadoc* of the `Advice` and the `AgentBuilder` class for further information. Byte Buddy also offers the explicit change of Java classes via a `ByteBuddy` instance or by using the Byte Buddy *Maven* and *Gradle* plugins.
+
+Where to go from here?
+----------------------
+
+Byte Buddy is a comprehensive library and we only scratched the surface of Byte Buddy's capabilities. However, Byte
+Buddy aims for being easy to use by providing a domain-specific language for creating classes. Most runtime code
+generation can be done by writing readable code and without any knowledge of Java's class file format. If you want
+to learn more about Byte Buddy, you can find such a [tutorial on Byte Buddy's web page](http://bytebuddy.net/#/tutorial).
+Furthermore, Byte Buddy comes with a [detailed in-code documentation](http://bytebuddy.net/#/javadoc) and extensive
+test case coverage which can also serve as example code. Finally, you can find an up-to-date list of articles and
+presentations on Byte Buddy [in the wiki](https://github.com/raphw/byte-buddy/wiki/Web-Resources). When using Byte
+Buddy, make also sure to read the following information on maintaining a project dependency.
+
+Getting support
+----------------------------
+
+#### Commercial ####
+
+The use of Byte Buddy is free and does not require the purchase of a license. To get the most out of the library or to secure an easy start, it is however possible to purchase training, development hours or support plans. Rates are dependent on the scope and duration of an engagement. Please get in touch with <rafael.wth at gmail.com> for further information.
+
+#### Free ####
+
+General questions can be asked on [Stack Overflow](http://stackoverflow.com/questions/tagged/byte-buddy) or on the [Byte Buddy mailing list](https://groups.google.com/forum/#!forum/byte-buddy) which also serve as an archive for questions. Of course, bug reports will be considered also outside of a commercial plan. For open source projects, it is sometimes possible to receive extended help for taking Byte Buddy into use.
+
+Dependency and API evolution
+----------------------------
+
+Byte Buddy is written on top of [ASM](http://asm.ow2.org/), a mature and well-tested library for reading and writing
+compiled Java classes. In order to allow for advanced type manipulations, Byte Buddy is intentionally exposing the
+ASM API to its users. Of course, the direct use of ASM remains fully optional and most users will most likely never
+require it. This choice was made such that a user of Byte Buddy is not restrained to its higher-level functionality
+but can implement custom implementations without a fuzz when it is necessary.
+
+ASM has previously changed its public API but added a mechanism for API compatibility starting with version 4 of the library. In order to avoid version conflicts with such older versions, Byte Buddy repackages the ASM dependency into its own namespace. If you want to use ASM directly, use the `byte-buddy-dep` artifact offers a version of Byte Buddy with an explicit dependency to ASM. When doing so, you should then repackage *both* Byte Buddy and ASM into your namespace to avoid version c [...]
+
+License and development
+-----------------------
+
+Byte Buddy is licensed under the liberal and business-friendly
+[*Apache Licence, Version 2.0*](http://www.apache.org/licenses/LICENSE-2.0.html) and is freely available on
+GitHub. Byte Buddy is further released to the repositories of Maven Central and on JCenter. The project is built
+using <a href="http://maven.apache.org/">Maven</a>. From your shell, cloning and building the project would go
+something like this:
+
+```shell
+git clone https://github.com/raphw/byte-buddy.git
+cd byte-buddy
+mvn package
+```
+
+On these commands, Byte Buddy is cloned from GitHub and built on your machine. Byte Buddy is currently tested for the
+[*OpenJDK*](http://openjdk.java.net/) versions 6 and 7 and the *Oracle JDK* versions 7 and 8 using Travis CI.
+
+Please use GitHub's [issue tracker](https://github.com/raphw/byte-buddy/issues) for reporting bugs. When committing
+code, please provide test cases that prove the functionality of your features or that demonstrate a bug fix.
+Furthermore, make sure you are not breaking any existing test cases. If possible, please take the time to write
+some documentation. For feature requests or general feedback, you can also use the
+[issue tracker](https://github.com/raphw/byte-buddy/issues) or contact us on
+[our mailing list](https://groups.google.com/forum/#!forum/byte-buddy).
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..446d771
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,22 @@
+version: '{build}'
+
+branches:
+ only:
+ - master
+
+environment:
+ matrix:
+ - JAVA_HOME: C:\Program Files\Java\jdk1.6.0
+ PATH: C:\Program Files\Java\jdk1.6.0\bin;%PATH%
+ - JAVA_HOME: C:\Program Files\Java\jdk1.7.0
+ PATH: C:\Program Files\Java\jdk1.7.0\bin;%PATH%
+ TARGET: -Pjava7
+ - JAVA_HOME: C:\Program Files\Java\jdk1.8.0
+ PATH: C:\Program Files\Java\jdk1.8.0\bin;%PATH%
+ TARGET: -Pjava8
+
+build_script:
+ - java -Xmx32m -version
+ - javac -J-Xmx32m -version
+ - .\mvnw install -DskipTests=true -Dmaven.javadoc.skip=true -B -V
+ - .\mvnw jacoco:prepare-agent verify jacoco:report %TARGET% -Pintegration -Dnet.bytebuddy.test.travis=true -B -V
diff --git a/byte-buddy-agent/pom.xml b/byte-buddy-agent/pom.xml
new file mode 100644
index 0000000..cc58a31
--- /dev/null
+++ b/byte-buddy-agent/pom.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <artifactId>byte-buddy-parent</artifactId>
+ <groupId>net.bytebuddy</groupId>
+ <version>1.7.1</version>
+ </parent>
+
+ <artifactId>byte-buddy-agent</artifactId>
+ <packaging>jar</packaging>
+
+ <properties>
+ <bytebuddy.agent>net.bytebuddy.agent.Installer</bytebuddy.agent>
+ <attach.package.sun>com.sun.tools.attach</attach.package.sun>
+ <attach.package.ibm>com.ibm.tools.attach</attach.package.ibm>
+ <version.unixsocket>2.0.4</version.unixsocket>
+ </properties>
+
+ <name>Byte Buddy Java agent</name>
+ <description>The Byte Buddy Java agent allows to access the JVM's HotSwap feature.</description>
+
+ <!--
+ The Unix socket dependency can be excluded safely. Byte Buddy will safely discover the
+ non-availability and not use the corresponding virtual machine implementation. The
+ implementation requires Java 7+ and is deactivated on Java 6 VMs.
+ -->
+
+ <dependencies>
+ <dependency>
+ <groupId>com.kohlschutter.junixsocket</groupId>
+ <artifactId>junixsocket-native-common</artifactId>
+ <version>${version.unixsocket}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <scope>test</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy-agent</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ </dependencies>
+
+ <profiles>
+ <profile>
+ <id>extras</id>
+ <activation>
+ <activeByDefault>false</activeByDefault>
+ </activation>
+ <build>
+ <plugins>
+ <!-- Create manifest file which is required for creating an OSGi bundle. -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>${version.plugin.jar}</version>
+ <configuration>
+ <archive>
+ <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+ </archive>
+ </configuration>
+ </plugin>
+ <!-- Specify OSGi packaging and agent manifest headers. -->
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <version>${version.plugin.bundle}</version>
+ <executions>
+ <execution>
+ <phase>process-classes</phase>
+ <goals>
+ <goal>manifest</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <instructions>
+ <Premain-Class>${bytebuddy.agent}</Premain-Class>
+ <Agent-Class>${bytebuddy.agent}</Agent-Class>
+ <Can-Redefine-Classes>true</Can-Redefine-Classes>
+ <Can-Retransform-Classes>true</Can-Retransform-Classes>
+ <Can-Set-Native-Method-Prefix>true</Can-Set-Native-Method-Prefix>
+ <Import-Package>
+ ${attach.package.sun};resolution:="optional",
+ ${attach.package.ibm};resolution:="optional"
+ </Import-Package>
+ <Export-Package>
+ net.bytebuddy.agent
+ </Export-Package>
+ <Automatic-Module-Name>${project.groupId}.agent</Automatic-Module-Name>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/byte-buddy-agent/src/main/java/net/bytebuddy/agent/Attacher.java b/byte-buddy-agent/src/main/java/net/bytebuddy/agent/Attacher.java
new file mode 100644
index 0000000..d498467
--- /dev/null
+++ b/byte-buddy-agent/src/main/java/net/bytebuddy/agent/Attacher.java
@@ -0,0 +1,96 @@
+package net.bytebuddy.agent;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+import java.io.File;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * A Java program that attaches a Java agent to an external process.
+ */
+public class Attacher {
+
+ /**
+ * Base for access to a reflective member to make the code more readable.
+ */
+ private static final Object STATIC_MEMBER = null;
+
+ /**
+ * The name of the {@code attach} method of the {@code VirtualMachine} class.
+ */
+ private static final String ATTACH_METHOD_NAME = "attach";
+
+ /**
+ * The name of the {@code loadAgent} method of the {@code VirtualMachine} class.
+ */
+ private static final String LOAD_AGENT_METHOD_NAME = "loadAgent";
+
+ /**
+ * The name of the {@code detach} method of the {@code VirtualMachine} class.
+ */
+ private static final String DETACH_METHOD_NAME = "detach";
+
+ /**
+ * Runs the attacher as a Java application.
+ *
+ * @param args A list containing the fully qualified name of the virtual machine type,
+ * the process id, the fully qualified name of the Java agent jar followed by
+ * an empty string if the argument to the agent is {@code null} or any number
+ * of strings where the first argument is proceeded by any single character
+ * which is stripped off.
+ */
+ @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback")
+ public static void main(String[] args) {
+ try {
+ String argument;
+ if (args[3].isEmpty()) {
+ argument = null;
+ } else {
+ StringBuilder stringBuilder = new StringBuilder(args[3].substring(1));
+ for (int index = 4; index < args.length; index++) {
+ stringBuilder.append(" ").append(args[index]);
+ }
+ argument = stringBuilder.toString();
+ }
+ install(Class.forName(args[0]), args[1], new File(args[2]), argument);
+ } catch (Exception ignored) {
+ System.exit(1);
+ }
+ }
+
+ /**
+ * Installs a Java agent on a target VM.
+ *
+ * @param virtualMachineType The virtual machine type to use for the external attachment.
+ * @param processId The id of the process being target of the external attachment.
+ * @param agent The Java agent to attach.
+ * @param argument The argument to provide or {@code null} if no argument is provided.
+ * @throws NoSuchMethodException If the virtual machine type does not define an expected method.
+ * @throws InvocationTargetException If the virtual machine type raises an error.
+ * @throws IllegalAccessException If a method of the virtual machine type cannot be accessed.
+ */
+ protected static void install(Class<?> virtualMachineType,
+ String processId,
+ File agent,
+ String argument) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+ Object virtualMachineInstance = virtualMachineType
+ .getMethod(ATTACH_METHOD_NAME, String.class)
+ .invoke(STATIC_MEMBER, processId);
+ try {
+ virtualMachineType
+ .getMethod(LOAD_AGENT_METHOD_NAME, String.class, String.class)
+ .invoke(virtualMachineInstance, agent.getAbsolutePath(), argument);
+ } finally {
+ virtualMachineType
+ .getMethod(DETACH_METHOD_NAME)
+ .invoke(virtualMachineInstance);
+ }
+ }
+
+ /**
+ * The attacher provides only {@code static} utility methods and should not be instantiated.
+ */
+ private Attacher() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/byte-buddy-agent/src/main/java/net/bytebuddy/agent/ByteBuddyAgent.java b/byte-buddy-agent/src/main/java/net/bytebuddy/agent/ByteBuddyAgent.java
new file mode 100644
index 0000000..cc66d5e
--- /dev/null
+++ b/byte-buddy-agent/src/main/java/net/bytebuddy/agent/ByteBuddyAgent.java
@@ -0,0 +1,1189 @@
+package net.bytebuddy.agent;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.instrument.Instrumentation;
+import java.lang.management.ManagementFactory;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.*;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+
+/**
+ * <p>
+ * The Byte Buddy agent provides a JVM {@link java.lang.instrument.Instrumentation} in order to allow Byte Buddy the
+ * redefinition of already loaded classes. An agent must normally be specified via the command line via the
+ * {@code javaagent} parameter. As an argument to this parameter, one must specify the location of this agent's jar
+ * file such as for example in
+ * </p>
+ * <p>
+ * <code>
+ * java -javaagent:byte-buddy-agent.jar -jar app.jar
+ * </code>
+ * </p>
+ * <p>
+ * <b>Note</b>: The runtime installation of a Java agent is not possible on all JVMs. See the documentation for
+ * {@link ByteBuddyAgent#install()} for details on JVMs that are supported out of the box.
+ * </p>
+ * <p>
+ * <b>Important</b>: This class's name is known to the Byte Buddy main application and must not be altered.
+ * </p>
+ * <p>
+ * <b>Note</b>: Byte Buddy does not execute code using an {@link java.security.AccessController}. If a security manager
+ * is present, the user of this class is responsible for assuring any required privileges.
+ * </p>
+ */
+public class ByteBuddyAgent {
+
+ /**
+ * The manifest property specifying the agent class.
+ */
+ private static final String AGENT_CLASS_PROPERTY = "Agent-Class";
+
+ /**
+ * The manifest property specifying the <i>can redefine</i> property.
+ */
+ private static final String CAN_REDEFINE_CLASSES_PROPERTY = "Can-Redefine-Classes";
+
+ /**
+ * The manifest property specifying the <i>can retransform</i> property.
+ */
+ private static final String CAN_RETRANSFORM_CLASSES_PROPERTY = "Can-Retransform-Classes";
+
+ /**
+ * The manifest property specifying the <i>can set native method prefix</i> property.
+ */
+ private static final String CAN_SET_NATIVE_METHOD_PREFIX = "Can-Set-Native-Method-Prefix";
+
+ /**
+ * The manifest property value for the manifest version.
+ */
+ private static final String MANIFEST_VERSION_VALUE = "1.0";
+
+ /**
+ * The size of the buffer for copying the agent installer file into another jar.
+ */
+ private static final int BUFFER_SIZE = 1024;
+
+ /**
+ * Convenience indices for reading and writing to the buffer to make the code more readable.
+ */
+ private static final int START_INDEX = 0, END_OF_FILE = -1;
+
+ /**
+ * The status code expected as a result of a successful attachment.
+ */
+ private static final int SUCCESSFUL_ATTACH = 0;
+
+ /**
+ * Base for access to a reflective member to make the code more readable.
+ */
+ private static final Object STATIC_MEMBER = null;
+
+ /**
+ * Representation of the bootstrap {@link java.lang.ClassLoader}.
+ */
+ private static final ClassLoader BOOTSTRAP_CLASS_LOADER = null;
+
+ /**
+ * Represents a no-op argument for a dynamic agent attachment.
+ */
+ private static final String WITHOUT_ARGUMENT = null;
+
+ /**
+ * The naming prefix of all artifacts for an attacher jar.
+ */
+ private static final String ATTACHER_FILE_NAME = "byteBuddyAttacher";
+
+ /**
+ * The file extension for a class file.
+ */
+ private static final String CLASS_FILE_EXTENSION = ".class";
+
+ /**
+ * The file extension for a jar file.
+ */
+ private static final String JAR_FILE_EXTENSION = ".jar";
+
+ /**
+ * The class path argument to specify the class path elements.
+ */
+ private static final String CLASS_PATH_ARGUMENT = "-cp";
+
+ /**
+ * The Java property denoting the Java home directory.
+ */
+ private static final String JAVA_HOME = "java.home";
+
+ /**
+ * The Java property denoting the operating system name.
+ */
+ private static final String OS_NAME = "os.name";
+
+ /**
+ * The name of the method for reading the installer's instrumentation.
+ */
+ private static final String INSTRUMENTATION_METHOD = "getInstrumentation";
+
+ /**
+ * An indicator variable to express that no instrumentation is available.
+ */
+ private static final Instrumentation UNAVAILABLE = null;
+
+ /**
+ * The attachment type evaluator to be used for determining if an attachment requires an external process.
+ */
+ private static final AttachmentTypeEvaluator ATTACHMENT_TYPE_EVALUATOR = AccessController.doPrivileged(AttachmentTypeEvaluator.InstallationAction.INSTANCE);
+
+ /**
+ * The agent provides only {@code static} utility methods and should not be instantiated.
+ */
+ private ByteBuddyAgent() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * <p>
+ * Looks up the {@link java.lang.instrument.Instrumentation} instance of an installed Byte Buddy agent. Note that
+ * this method implies reflective lookup and reflective invocation such that the returned value should be cached
+ * rather than calling this method several times.
+ * </p>
+ * <p>
+ * <b>Note</b>: This method throws an {@link java.lang.IllegalStateException} If the Byte Buddy agent is not
+ * properly installed.
+ * </p>
+ *
+ * @return The {@link java.lang.instrument.Instrumentation} instance which is provided by an installed
+ * Byte Buddy agent.
+ */
+ public static Instrumentation getInstrumentation() {
+ Instrumentation instrumentation = doGetInstrumentation();
+ if (instrumentation == null) {
+ throw new IllegalStateException("The Byte Buddy agent is not initialized");
+ }
+ return instrumentation;
+ }
+
+ /**
+ * Attaches the given agent Jar on the target process which must be a virtual machine process. The default attachment provider
+ * is used for applying the attachment. This operation blocks until the attachment is complete. If the current VM does not supply
+ * any known form of attachment to a remote VM, an {@link IllegalStateException} is thrown. The agent is not provided an argument.
+ *
+ * @param agentJar The agent jar file.
+ * @param processId The target process id.
+ */
+ public static void attach(File agentJar, String processId) {
+ attach(agentJar, processId, WITHOUT_ARGUMENT);
+ }
+
+ /**
+ * Attaches the given agent Jar on the target process which must be a virtual machine process. The default attachment provider
+ * is used for applying the attachment. This operation blocks until the attachment is complete. If the current VM does not supply
+ * any known form of attachment to a remote VM, an {@link IllegalStateException} is thrown.
+ *
+ * @param agentJar The agent jar file.
+ * @param processId The target process id.
+ * @param argument The argument to provide to the agent.
+ */
+ public static void attach(File agentJar, String processId, String argument) {
+ attach(agentJar, processId, argument, AttachmentProvider.DEFAULT);
+ }
+
+ /**
+ * Attaches the given agent Jar on the target process which must be a virtual machine process. This operation blocks until the
+ * attachment is complete. The agent is not provided an argument.
+ *
+ * @param agentJar The agent jar file.
+ * @param processId The target process id.
+ * @param attachmentProvider The attachment provider to use.
+ */
+ public static void attach(File agentJar, String processId, AttachmentProvider attachmentProvider) {
+ attach(agentJar, processId, WITHOUT_ARGUMENT, attachmentProvider);
+ }
+
+ /**
+ * Attaches the given agent Jar on the target process which must be a virtual machine process. This operation blocks until the
+ * attachment is complete.
+ *
+ * @param agentJar The agent jar file.
+ * @param processId The target process id.
+ * @param argument The argument to provide to the agent.
+ * @param attachmentProvider The attachment provider to use.
+ */
+ public static void attach(File agentJar, String processId, String argument, AttachmentProvider attachmentProvider) {
+ install(attachmentProvider, processId, argument, new AgentProvider.ForExistingAgent(agentJar));
+ }
+
+ /**
+ * Attaches the given agent Jar on the target process which must be a virtual machine process. The default attachment provider
+ * is used for applying the attachment. This operation blocks until the attachment is complete. If the current VM does not supply
+ * any known form of attachment to a remote VM, an {@link IllegalStateException} is thrown. The agent is not provided an argument.
+ *
+ * @param agentJar The agent jar file.
+ * @param processProvider A provider of the target process id.
+ */
+ public static void attach(File agentJar, ProcessProvider processProvider) {
+ attach(agentJar, processProvider, WITHOUT_ARGUMENT);
+ }
+
+ /**
+ * Attaches the given agent Jar on the target process which must be a virtual machine process. The default attachment provider
+ * is used for applying the attachment. This operation blocks until the attachment is complete. If the current VM does not supply
+ * any known form of attachment to a remote VM, an {@link IllegalStateException} is thrown.
+ *
+ * @param agentJar The agent jar file.
+ * @param processProvider A provider of the target process id.
+ * @param argument The argument to provide to the agent.
+ */
+ public static void attach(File agentJar, ProcessProvider processProvider, String argument) {
+ attach(agentJar, processProvider, argument, AttachmentProvider.DEFAULT);
+ }
+
+ /**
+ * Attaches the given agent Jar on the target process which must be a virtual machine process. This operation blocks until the
+ * attachment is complete. The agent is not provided an argument.
+ *
+ * @param agentJar The agent jar file.
+ * @param processProvider A provider of the target process id.
+ * @param attachmentProvider The attachment provider to use.
+ */
+ public static void attach(File agentJar, ProcessProvider processProvider, AttachmentProvider attachmentProvider) {
+ attach(agentJar, processProvider, WITHOUT_ARGUMENT, attachmentProvider);
+ }
+
+ /**
+ * Attaches the given agent Jar on the target process which must be a virtual machine process. This operation blocks until the
+ * attachment is complete.
+ *
+ * @param agentJar The agent jar file.
+ * @param processProvider A provider of the target process id.
+ * @param argument The argument to provide to the agent.
+ * @param attachmentProvider The attachment provider to use.
+ */
+ public static void attach(File agentJar, ProcessProvider processProvider, String argument, AttachmentProvider attachmentProvider) {
+ install(attachmentProvider, processProvider.resolve(), argument, new AgentProvider.ForExistingAgent(agentJar));
+ }
+
+ /**
+ * <p>
+ * Installs an agent on the currently running Java virtual machine. Unfortunately, this does
+ * not always work. The runtime installation of a Java agent is supported for:
+ * </p>
+ * <ul>
+ * <li><b>JVM version 9+</b>: For Java VM of at least version 9, the attachment API was merged
+ * into a Jigsaw module and the runtime installation is always possible.</li>
+ * <li><b>OpenJDK / Oracle JDK / IBM J9 versions 8-</b>: The installation for HotSpot is only
+ * possible when bundled with a JDK up until Java version 8. It is not possible for runtime-only
+ * installations of HotSpot or J9 for these versions.</li>
+ * </ul>
+ * <p>
+ * If an agent cannot be installed, an {@link IllegalStateException} is thrown.
+ * </p>
+ * <p>
+ * <b>Important</b>: This is a rather computation-heavy operation. Therefore, this operation is
+ * not repeated after an agent was successfully installed for the first time. Instead, the previous
+ * instrumentation instance is returned. However, invoking this method requires synchronization
+ * such that subsequently to an installation, {@link ByteBuddyAgent#getInstrumentation()} should
+ * be invoked instead.
+ * </p>
+ *
+ * @return An instrumentation instance representing the currently running JVM.
+ */
+ public static Instrumentation install() {
+ return install(AttachmentProvider.DEFAULT);
+ }
+
+ /**
+ * Installs a Java agent using the Java attach API. This API is available under different
+ * access routes for different JVMs and JVM versions or it might not be available at all.
+ * If a Java agent cannot be installed by using the supplied attachment provider, an
+ * {@link IllegalStateException} is thrown. The same happens if the default process provider
+ * cannot resolve a process id for the current VM.
+ *
+ * @param attachmentProvider The attachment provider to use for the installation.
+ * @return An instrumentation instance representing the currently running JVM.
+ */
+ public static Instrumentation install(AttachmentProvider attachmentProvider) {
+ return install(attachmentProvider, ProcessProvider.ForCurrentVm.INSTANCE);
+ }
+
+ /**
+ * Installs a Java agent using the Java attach API. This API is available under different
+ * access routes for different JVMs and JVM versions or it might not be available at all.
+ * If a Java agent cannot be installed by using the supplied process provider, an
+ * {@link IllegalStateException} is thrown. The same happens if the default attachment
+ * provider cannot be used.
+ *
+ * @param processProvider The provider for the current JVM's process id.
+ * @return An instrumentation instance representing the currently running JVM.
+ */
+ public static Instrumentation install(ProcessProvider processProvider) {
+ return install(AttachmentProvider.DEFAULT, processProvider);
+ }
+
+ /**
+ * Installs a Java agent using the Java attach API. This API is available under different
+ * access routes for different JVMs and JVM versions or it might not be available at all.
+ * If a Java agent cannot be installed by using the supplied attachment provider and process
+ * provider, an {@link IllegalStateException} is thrown.
+ *
+ * @param attachmentProvider The attachment provider to use for the installation.
+ * @param processProvider The provider for the current JVM's process id.
+ * @return An instrumentation instance representing the currently running JVM.
+ */
+ public static synchronized Instrumentation install(AttachmentProvider attachmentProvider, ProcessProvider processProvider) {
+ Instrumentation instrumentation = doGetInstrumentation();
+ if (instrumentation != null) {
+ return instrumentation;
+ }
+ install(attachmentProvider, processProvider.resolve(), WITHOUT_ARGUMENT, AgentProvider.ForByteBuddyAgent.INSTANCE);
+ return doGetInstrumentation();
+ }
+
+ /**
+ * Installs a Java agent on a target VM.
+ *
+ * @param attachmentProvider The attachment provider to use.
+ * @param processId The process id of the target JVM process.
+ * @param argument The argument to provide to the agent.
+ * @param agentProvider The agent provider for the agent jar.
+ */
+ private static void install(AttachmentProvider attachmentProvider, String processId, String argument, AgentProvider agentProvider) {
+ AttachmentProvider.Accessor attachmentAccessor = attachmentProvider.attempt();
+ if (!attachmentAccessor.isAvailable()) {
+ throw new IllegalStateException("No compatible attachment provider is not available");
+ }
+ try {
+ if (ATTACHMENT_TYPE_EVALUATOR.requiresExternalAttachment(processId)) {
+ installExternal(attachmentAccessor.getExternalAttachment(), processId, agentProvider.resolve(), argument);
+ } else {
+ Attacher.install(attachmentAccessor.getVirtualMachineType(), processId, agentProvider.resolve(), argument);
+ }
+ } catch (RuntimeException exception) {
+ throw exception;
+ } catch (Exception exception) {
+ throw new IllegalStateException("Error during attachment using: " + attachmentProvider, exception);
+ }
+ }
+
+ /**
+ * Installs a Java agent to the current VM via an external process. This is typically required starting with OpenJDK 9
+ * when the {@code jdk.attach.allowAttachSelf} property is set to {@code false} what is the default setting.
+ *
+ * @param externalAttachment A description of the external attachment.
+ * @param processId The process id of the current process.
+ * @param agent The Java agent to install.
+ * @param argument The argument to provide to the agent or {@code null} if no argument should be supplied.
+ * @throws Exception If an exception occurs during the attachment or the external process fails the attachment.
+ */
+ private static void installExternal(AttachmentProvider.Accessor.ExternalAttachment externalAttachment,
+ String processId,
+ File agent,
+ String argument) throws Exception {
+ InputStream inputStream = Attacher.class.getResourceAsStream('/' + Attacher.class.getName().replace('.', '/') + CLASS_FILE_EXTENSION);
+ if (inputStream == null) {
+ throw new IllegalStateException("Cannot locate class file for Byte Buddy installation process");
+ }
+ File attachmentJar = null;
+ try {
+ try {
+ attachmentJar = File.createTempFile(ATTACHER_FILE_NAME, JAR_FILE_EXTENSION);
+ JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(attachmentJar));
+ try {
+ jarOutputStream.putNextEntry(new JarEntry(Attacher.class.getName().replace('.', '/') + CLASS_FILE_EXTENSION));
+ byte[] buffer = new byte[BUFFER_SIZE];
+ int index;
+ while ((index = inputStream.read(buffer)) != END_OF_FILE) {
+ jarOutputStream.write(buffer, START_INDEX, index);
+ }
+ jarOutputStream.closeEntry();
+ } finally {
+ jarOutputStream.close();
+ }
+ } finally {
+ inputStream.close();
+ }
+ StringBuilder classPath = new StringBuilder().append('"').append(attachmentJar.getAbsolutePath()).append('"');
+ for (File jar : externalAttachment.getClassPath()) {
+ classPath.append(File.pathSeparatorChar).append('"').append(jar.getAbsolutePath()).append('"');
+ }
+ if (new ProcessBuilder(System.getProperty(JAVA_HOME)
+ + File.separatorChar + "bin"
+ + File.separatorChar + (System.getProperty(OS_NAME, "").toLowerCase(Locale.US).contains("windows") ? "java.exe" : "java"),
+ CLASS_PATH_ARGUMENT,
+ classPath.toString(),
+ Attacher.class.getName(),
+ externalAttachment.getVirtualMachineType(),
+ processId,
+ "\"" + agent.getAbsolutePath() + "\"",
+ argument == null ? "" : ("=" + argument)).start().waitFor() != SUCCESSFUL_ATTACH) {
+ throw new IllegalStateException("Could not self-attach to current VM using external process");
+ }
+ } finally {
+ if (attachmentJar != null) {
+ if (!attachmentJar.delete()) {
+ attachmentJar.deleteOnExit();
+ }
+ }
+ }
+ }
+
+ /**
+ * Performs the actual lookup of the {@link java.lang.instrument.Instrumentation} from an installed
+ * Byte Buddy agent.
+ *
+ * @return The Byte Buddy agent's {@link java.lang.instrument.Instrumentation} instance.
+ */
+ @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Legal outcome where reflection communicates errors by throwing an exception")
+ private static Instrumentation doGetInstrumentation() {
+ try {
+ return (Instrumentation) ClassLoader.getSystemClassLoader()
+ .loadClass(Installer.class.getName())
+ .getMethod(INSTRUMENTATION_METHOD)
+ .invoke(STATIC_MEMBER);
+ } catch (Exception ignored) {
+ return UNAVAILABLE;
+ }
+ }
+
+ /**
+ * An attachment provider is responsible for making the Java attachment API available.
+ */
+ @SuppressFBWarnings(value = "IC_SUPERCLASS_USES_SUBCLASS_DURING_INITIALIZATION", justification = "Safe initialization is implied")
+ public interface AttachmentProvider {
+
+ /**
+ * The default attachment provider to be used.
+ */
+ AttachmentProvider DEFAULT = new Compound(ForJigsawVm.INSTANCE,
+ ForJ9Vm.INSTANCE,
+ ForToolsJarVm.JVM_ROOT,
+ ForToolsJarVm.JDK_ROOT,
+ ForToolsJarVm.MACINTOSH,
+ ForUnixHotSpotVm.INSTANCE);
+
+ /**
+ * Attempts the creation of an accessor for a specific JVM's attachment API.
+ *
+ * @return The accessor this attachment provider can supply for the currently running JVM.
+ */
+ Accessor attempt();
+
+ /**
+ * An accessor for a JVM's attachment API.
+ */
+ interface Accessor {
+
+ /**
+ * The name of the {@code VirtualMachine} class on any OpenJDK or Oracle JDK implementation.
+ */
+ String VIRTUAL_MACHINE_TYPE_NAME = "com.sun.tools.attach.VirtualMachine";
+
+ /**
+ * The name of the {@code VirtualMachine} class on IBM J9 VMs.
+ */
+ String VIRTUAL_MACHINE_TYPE_NAME_J9 = "com.ibm.tools.attach.VirtualMachine";
+
+ /**
+ * Determines if this accessor is applicable for the currently running JVM.
+ *
+ * @return {@code true} if this accessor is available.
+ */
+ boolean isAvailable();
+
+ /**
+ * Returns a {@code VirtualMachine} class. This method must only be called for available accessors.
+ *
+ * @return The virtual machine type.
+ */
+ Class<?> getVirtualMachineType();
+
+ /**
+ * Returns a description of a virtual machine class for an external attachment.
+ *
+ * @return A description of the external attachment.
+ */
+ ExternalAttachment getExternalAttachment();
+
+ /**
+ * A canonical implementation of an unavailable accessor.
+ */
+ enum Unavailable implements Accessor {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public boolean isAvailable() {
+ return false;
+ }
+
+ @Override
+ public Class<?> getVirtualMachineType() {
+ throw new IllegalStateException("Cannot read the virtual machine type for an unavailable accessor");
+ }
+
+ @Override
+ public ExternalAttachment getExternalAttachment() {
+ throw new IllegalStateException("Cannot read the virtual machine type for an unavailable accessor");
+ }
+ }
+
+ /**
+ * Describes an external attachment to a Java virtual machine.
+ */
+ @EqualsAndHashCode
+ class ExternalAttachment {
+
+ /**
+ * The fully-qualified binary name of the virtual machine type.
+ */
+ private final String virtualMachineType;
+
+ /**
+ * The class path elements required for loading the supplied virtual machine type.
+ */
+ private final List<File> classPath;
+
+ /**
+ * Creates an external attachment.
+ *
+ * @param virtualMachineType The fully-qualified binary name of the virtual machine type.
+ * @param classPath The class path elements required for loading the supplied virtual machine type.
+ */
+ public ExternalAttachment(String virtualMachineType, List<File> classPath) {
+ this.virtualMachineType = virtualMachineType;
+ this.classPath = classPath;
+ }
+
+ /**
+ * Returns the fully-qualified binary name of the virtual machine type.
+ *
+ * @return The fully-qualified binary name of the virtual machine type.
+ */
+ public String getVirtualMachineType() {
+ return virtualMachineType;
+ }
+
+ /**
+ * Returns the class path elements required for loading the supplied virtual machine type.
+ *
+ * @return The class path elements required for loading the supplied virtual machine type.
+ */
+ public List<File> getClassPath() {
+ return classPath;
+ }
+ }
+
+ /**
+ * A simple implementation of an accessible accessor.
+ */
+ @EqualsAndHashCode
+ abstract class Simple implements Accessor {
+
+ /**
+ * A {@code VirtualMachine} class.
+ */
+ protected final Class<?> virtualMachineType;
+
+ /**
+ * Creates a new simple accessor.
+ *
+ * @param virtualMachineType A {@code VirtualMachine} class.
+ */
+ protected Simple(Class<?> virtualMachineType) {
+ this.virtualMachineType = virtualMachineType;
+ }
+
+ /**
+ * <p>
+ * Creates an accessor by reading the process id from the JMX runtime bean and by attempting
+ * to load the {@code com.sun.tools.attach.VirtualMachine} class from the provided class loader.
+ * </p>
+ * <p>
+ * This accessor is supposed to work on any implementation of the OpenJDK or Oracle JDK.
+ * </p>
+ *
+ * @param classLoader A class loader that is capable of loading the virtual machine type.
+ * @param classPath The class path required to load the virtual machine class.
+ * @return An appropriate accessor.
+ */
+ public static Accessor of(ClassLoader classLoader, File... classPath) {
+ try {
+ return new Simple.WithExternalAttachment(classLoader.loadClass(VIRTUAL_MACHINE_TYPE_NAME),
+ Arrays.asList(classPath));
+ } catch (ClassNotFoundException ignored) {
+ return Unavailable.INSTANCE;
+ }
+ }
+
+ /**
+ * <p>
+ * Creates an accessor by reading the process id from the JMX runtime bean and by attempting
+ * to load the {@code com.ibm.tools.attach.VirtualMachine} class from the provided class loader.
+ * </p>
+ * <p>
+ * This accessor is supposed to work on any implementation of IBM's J9.
+ * </p>
+ *
+ * @return An appropriate accessor.
+ */
+ public static Accessor ofJ9() {
+ try {
+ return new Simple.WithExternalAttachment(ClassLoader.getSystemClassLoader().loadClass(VIRTUAL_MACHINE_TYPE_NAME_J9),
+ Collections.<File>emptyList());
+ } catch (ClassNotFoundException ignored) {
+ return Unavailable.INSTANCE;
+ }
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return true;
+ }
+
+ @Override
+ public Class<?> getVirtualMachineType() {
+ return virtualMachineType;
+ }
+
+ /**
+ * A simple implementation of an accessible accessor that allows for external attachment.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ protected static class WithExternalAttachment extends Simple {
+
+ /**
+ * The class path required for loading the virtual machine type.
+ */
+ private final List<File> classPath;
+
+ /**
+ * Creates a new simple accessor that allows for external attachment.
+ *
+ * @param virtualMachineType The {@code com.sun.tools.attach.VirtualMachine} class.
+ * @param classPath The class path required for loading the virtual machine type.
+ */
+ public WithExternalAttachment(Class<?> virtualMachineType, List<File> classPath) {
+ super(virtualMachineType);
+ this.classPath = classPath;
+ }
+
+ @Override
+ public ExternalAttachment getExternalAttachment() {
+ return new ExternalAttachment(virtualMachineType.getName(), classPath);
+ }
+ }
+
+ /**
+ * A simple implementation of an accessible accessor that does not allow for external attachment.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ protected static class WithoutExternalAttachment extends Simple {
+
+ /**
+ * Creates a new simple accessor that does not allow for external attachment.
+ *
+ * @param virtualMachineType A {@code VirtualMachine} class.
+ */
+ public WithoutExternalAttachment(Class<?> virtualMachineType) {
+ super(virtualMachineType);
+ }
+
+ @Override
+ public ExternalAttachment getExternalAttachment() {
+ throw new IllegalStateException("Cannot read the virtual machine type for an unavailable accessor");
+ }
+ }
+ }
+ }
+
+ /**
+ * An attachment provider that locates the attach API directly from the system class loader.
+ */
+ enum ForJigsawVm implements AttachmentProvider {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Accessor attempt() {
+ return Accessor.Simple.of(ClassLoader.getSystemClassLoader());
+ }
+ }
+
+ /**
+ * An attachment provider that locates the attach API directly from the system class loader expecting
+ * an IBM J9 VM.
+ */
+ enum ForJ9Vm implements AttachmentProvider {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Accessor attempt() {
+ return Accessor.Simple.ofJ9();
+ }
+ }
+
+ /**
+ * An attachment provider that is dependant on the existence of a <i>tools.jar</i> file on the local
+ * file system.
+ */
+ enum ForToolsJarVm implements AttachmentProvider {
+
+ /**
+ * An attachment provider that locates the <i>tools.jar</i> from a Java home directory.
+ */
+ JVM_ROOT("../lib/tools.jar"),
+
+ /**
+ * An attachment provider that locates the <i>tools.jar</i> from a Java installation directory.
+ * In practice, several virtual machines do not return the JRE's location for the
+ * <i>java.home</i> property against the property's specification.
+ */
+ JDK_ROOT("lib/tools.jar"),
+
+ /**
+ * An attachment provider that locates the <i>tools.jar</i> as it is set for several JVM
+ * installations on Apple Macintosh computers.
+ */
+ MACINTOSH("../Classes/classes.jar");
+
+ /**
+ * The Java home system property.
+ */
+ private static final String JAVA_HOME_PROPERTY = "java.home";
+
+ /**
+ * The path to the <i>tools.jar</i> file, starting from the Java home directory.
+ */
+ private final String toolsJarPath;
+
+ /**
+ * Creates a new attachment provider that loads the virtual machine class from the <i>tools.jar</i>.
+ *
+ * @param toolsJarPath The path to the <i>tools.jar</i> file, starting from the Java home directory.
+ */
+ ForToolsJarVm(String toolsJarPath) {
+ this.toolsJarPath = toolsJarPath;
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED", justification = "Privilege is explicit user responsibility")
+ public Accessor attempt() {
+ File toolsJar = new File(System.getProperty(JAVA_HOME_PROPERTY), toolsJarPath);
+ try {
+ return toolsJar.isFile() && toolsJar.canRead()
+ ? Accessor.Simple.of(new URLClassLoader(new URL[]{toolsJar.toURI().toURL()}, BOOTSTRAP_CLASS_LOADER), toolsJar)
+ : Accessor.Unavailable.INSTANCE;
+ } catch (MalformedURLException exception) {
+ throw new IllegalStateException("Could not represent " + toolsJar + " as URL");
+ }
+ }
+ }
+
+ /**
+ * An attachment provider using a custom protocol implementation for HotSpot on Unix.
+ */
+ enum ForUnixHotSpotVm implements AttachmentProvider {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Accessor attempt() {
+ try {
+ return new Accessor.Simple.WithoutExternalAttachment(VirtualMachine.ForHotSpot.OnUnix.assertAvailability());
+ } catch (Throwable ignored) {
+ return Accessor.Unavailable.INSTANCE;
+ }
+ }
+ }
+
+ /**
+ * A compound attachment provider that attempts the attachment by delegation to other providers. If
+ * none of the providers of this compound provider is capable of providing a valid accessor, an
+ * non-available accessor is returned.
+ */
+ @EqualsAndHashCode
+ class Compound implements AttachmentProvider {
+
+ /**
+ * A list of attachment providers in the order of their application.
+ */
+ private final List<AttachmentProvider> attachmentProviders;
+
+ /**
+ * Creates a new compound attachment provider.
+ *
+ * @param attachmentProvider A list of attachment providers in the order of their application.
+ */
+ public Compound(AttachmentProvider... attachmentProvider) {
+ this(Arrays.asList(attachmentProvider));
+ }
+
+ /**
+ * Creates a new compound attachment provider.
+ *
+ * @param attachmentProviders A list of attachment providers in the order of their application.
+ */
+ public Compound(List<? extends AttachmentProvider> attachmentProviders) {
+ this.attachmentProviders = new ArrayList<AttachmentProvider>();
+ for (AttachmentProvider attachmentProvider : attachmentProviders) {
+ if (attachmentProvider instanceof Compound) {
+ this.attachmentProviders.addAll(((Compound) attachmentProvider).attachmentProviders);
+ } else {
+ this.attachmentProviders.add(attachmentProvider);
+ }
+ }
+ }
+
+ @Override
+ public Accessor attempt() {
+ for (AttachmentProvider attachmentProvider : attachmentProviders) {
+ Accessor accessor = attachmentProvider.attempt();
+ if (accessor.isAvailable()) {
+ return accessor;
+ }
+ }
+ return Accessor.Unavailable.INSTANCE;
+ }
+ }
+ }
+
+ /**
+ * A process provider is responsible for providing the process id of the current VM.
+ */
+ public interface ProcessProvider {
+
+ /**
+ * Resolves a process id for the current JVM.
+ *
+ * @return The resolved process id.
+ */
+ String resolve();
+
+ /**
+ * Supplies the current VM's process id.
+ */
+ enum ForCurrentVm implements ProcessProvider {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ /**
+ * The best process provider for the current VM.
+ */
+ private final ProcessProvider dispatcher;
+
+ /**
+ * Creates a process provider that supplies the current VM's process id.
+ */
+ ForCurrentVm() {
+ dispatcher = ForJava9CapableVm.make();
+ }
+
+ @Override
+ public String resolve() {
+ return dispatcher.resolve();
+ }
+
+ /**
+ * A process provider for a legacy VM that reads the process id from its JMX properties.
+ */
+ protected enum ForLegacyVm implements ProcessProvider {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public String resolve() {
+ String runtimeName = ManagementFactory.getRuntimeMXBean().getName();
+ int processIdIndex = runtimeName.indexOf('@');
+ if (processIdIndex == -1) {
+ throw new IllegalStateException("Cannot extract process id from runtime management bean");
+ } else {
+ return runtimeName.substring(0, processIdIndex);
+ }
+ }
+ }
+
+ /**
+ * A process provider for a Java 9 capable VM with access to the introduced process API.
+ */
+ @EqualsAndHashCode
+ protected static class ForJava9CapableVm implements ProcessProvider {
+
+ /**
+ * The {@code java.lang.ProcessHandle#current()} method.
+ */
+ private final Method current;
+
+ /**
+ * The {@code java.lang.ProcessHandle#pid()} method.
+ */
+ private final Method pid;
+
+ /**
+ * Creates a new Java 9 capable dispatcher for reading the current process's id.
+ *
+ * @param current The {@code java.lang.ProcessHandle#current()} method.
+ * @param pid The {@code java.lang.ProcessHandle#pid()} method.
+ */
+ protected ForJava9CapableVm(Method current, Method pid) {
+ this.current = current;
+ this.pid = pid;
+ }
+
+ /**
+ * Attempts to create a dispatcher for a Java 9 VM and falls back to a legacy dispatcher
+ * if this is not possible.
+ *
+ * @return A dispatcher for the current VM.
+ */
+ @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback")
+ public static ProcessProvider make() {
+ try {
+ return new ForJava9CapableVm(Class.forName("java.lang.ProcessHandle").getMethod("current"),
+ Class.forName("java.lang.ProcessHandle").getMethod("pid"));
+ } catch (Exception ignored) {
+ return ForLegacyVm.INSTANCE;
+ }
+ }
+
+ @Override
+ public String resolve() {
+ try {
+ return pid.invoke(current.invoke(STATIC_MEMBER)).toString();
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access Java 9 process API", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error when accessing Java 9 process API", exception.getCause());
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * An agent provider is responsible for handling and providing the jar file of an agent that is being attached.
+ */
+ protected interface AgentProvider {
+
+ /**
+ * Provides an agent jar file for attachment.
+ *
+ * @return The provided agent.
+ * @throws IOException If the agent cannot be written to disk.
+ */
+ File resolve() throws IOException;
+
+ /**
+ * An agent provider for a temporary Byte Buddy agent.
+ */
+ enum ForByteBuddyAgent implements AgentProvider {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ /**
+ * The default prefix of the Byte Buddy agent jar file.
+ */
+ private static final String AGENT_FILE_NAME = "byteBuddyAgent";
+
+ /**
+ * The jar file extension.
+ */
+ private static final String JAR_FILE_EXTENSION = ".jar";
+
+ @Override
+ public File resolve() throws IOException {
+ File agentJar;
+ InputStream inputStream = Installer.class.getResourceAsStream('/' + Installer.class.getName().replace('.', '/') + CLASS_FILE_EXTENSION);
+ if (inputStream == null) {
+ throw new IllegalStateException("Cannot locate class file for Byte Buddy installer");
+ }
+ try {
+ agentJar = File.createTempFile(AGENT_FILE_NAME, JAR_FILE_EXTENSION);
+ agentJar.deleteOnExit(); // Agent jar is required until VM shutdown due to lazy class loading.
+ Manifest manifest = new Manifest();
+ manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, MANIFEST_VERSION_VALUE);
+ manifest.getMainAttributes().put(new Attributes.Name(AGENT_CLASS_PROPERTY), Installer.class.getName());
+ manifest.getMainAttributes().put(new Attributes.Name(CAN_REDEFINE_CLASSES_PROPERTY), Boolean.TRUE.toString());
+ manifest.getMainAttributes().put(new Attributes.Name(CAN_RETRANSFORM_CLASSES_PROPERTY), Boolean.TRUE.toString());
+ manifest.getMainAttributes().put(new Attributes.Name(CAN_SET_NATIVE_METHOD_PREFIX), Boolean.TRUE.toString());
+ JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(agentJar), manifest);
+ try {
+ jarOutputStream.putNextEntry(new JarEntry(Installer.class.getName().replace('.', '/') + CLASS_FILE_EXTENSION));
+ byte[] buffer = new byte[BUFFER_SIZE];
+ int index;
+ while ((index = inputStream.read(buffer)) != END_OF_FILE) {
+ jarOutputStream.write(buffer, START_INDEX, index);
+ }
+ jarOutputStream.closeEntry();
+ } finally {
+ jarOutputStream.close();
+ }
+ } finally {
+ inputStream.close();
+ }
+ return agentJar;
+ }
+ }
+
+ /**
+ * An agent provider that supplies an existing agent that is not deleted after attachment.
+ */
+ @EqualsAndHashCode
+ class ForExistingAgent implements AgentProvider {
+
+ /**
+ * The supplied agent.
+ */
+ private File agent;
+
+ /**
+ * Creates an agent provider for an existing agent.
+ *
+ * @param agent The supplied agent.
+ */
+ protected ForExistingAgent(File agent) {
+ this.agent = agent;
+ }
+
+ @Override
+ public File resolve() {
+ return agent;
+ }
+ }
+ }
+
+ /**
+ * An attachment evaluator is responsible for deciding if an agent can be attached from the current process.
+ */
+ protected interface AttachmentTypeEvaluator {
+
+ /**
+ * Checks if the current VM requires external attachment for the supplied process id.
+ *
+ * @param processId The process id of the process to which to attach.
+ * @return {@code true} if the current VM requires external attachment for the supplied process.
+ */
+ boolean requiresExternalAttachment(String processId);
+
+ /**
+ * An installation action for creating an attachment type evaluator.
+ */
+ enum InstallationAction implements PrivilegedAction<AttachmentTypeEvaluator> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ /**
+ * The OpenJDK's property for specifying the legality of self-attachment.
+ */
+ private static final String JDK_ALLOW_SELF_ATTACH = "jdk.attach.allowAttachSelf";
+
+ @Override
+ @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback")
+ public AttachmentTypeEvaluator run() {
+ try {
+ if (Boolean.getBoolean(JDK_ALLOW_SELF_ATTACH)) {
+ return Disabled.INSTANCE;
+ } else {
+ return new ForJava9CapableVm(Class.forName("java.lang.ProcessHandle").getMethod("current"),
+ Class.forName("java.lang.ProcessHandle").getMethod("pid"));
+ }
+ } catch (Exception ignored) {
+ return Disabled.INSTANCE;
+ }
+ }
+ }
+
+ /**
+ * An attachment type evaluator that never requires external attachment.
+ */
+ enum Disabled implements AttachmentTypeEvaluator {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public boolean requiresExternalAttachment(String processId) {
+ return false;
+ }
+ }
+
+ /**
+ * An attachment type evaluator that checks a process id against the current process id.
+ */
+ @EqualsAndHashCode
+ class ForJava9CapableVm implements AttachmentTypeEvaluator {
+
+ /**
+ * The {@code java.lang.ProcessHandle#current()} method.
+ */
+ private final Method current;
+
+ /**
+ * The {@code java.lang.ProcessHandle#pid()} method.
+ */
+ private final Method getPid;
+
+ /**
+ * Creates a new attachment type evaluator.
+ *
+ * @param current The {@code java.lang.ProcessHandle#current()} method.
+ * @param getPid The {@code java.lang.ProcessHandle#pid()} method.
+ */
+ protected ForJava9CapableVm(Method current, Method getPid) {
+ this.current = current;
+ this.getPid = getPid;
+ }
+
+ @Override
+ public boolean requiresExternalAttachment(String processId) {
+ try {
+ return getPid.invoke(current.invoke(STATIC_MEMBER)).equals(processId);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access Java 9 process API", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error when accessing Java 9 process API", exception.getCause());
+ }
+ }
+ }
+ }
+}
diff --git a/byte-buddy-agent/src/main/java/net/bytebuddy/agent/Installer.java b/byte-buddy-agent/src/main/java/net/bytebuddy/agent/Installer.java
new file mode 100644
index 0000000..e785f7c
--- /dev/null
+++ b/byte-buddy-agent/src/main/java/net/bytebuddy/agent/Installer.java
@@ -0,0 +1,71 @@
+package net.bytebuddy.agent;
+
+import java.lang.instrument.Instrumentation;
+
+/**
+ * An installer class which defined the hook-in methods that are required by the Java agent specification.
+ */
+public class Installer {
+
+ /**
+ * A field for carrying the {@link java.lang.instrument.Instrumentation} that was loaded by the Byte Buddy
+ * agent. Note that this field must never be accessed directly as the agent is injected into the VM's
+ * system class loader. This way, the field of this class might be {@code null} even after the installation
+ * of the Byte Buddy agent as this class might be loaded by a different class loader than the system class
+ * loader.
+ */
+ @SuppressWarnings("unused")
+ private static volatile Instrumentation instrumentation;
+
+ /**
+ * The installer provides only {@code static} hook-in methods and should not be instantiated.
+ */
+ private Installer() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * <p>
+ * Returns the instrumentation that was loaded by the Byte Buddy agent. When a security manager is active,
+ * the {@link RuntimePermission} for {@code getInstrumentation} is required by the caller.
+ * </p>
+ * <p>
+ * <b>Important</b>: This method must only be invoked via the {@link ClassLoader#getSystemClassLoader()} where any
+ * Java agent is loaded. It is possible that two versions of this class exist for different class loaders.
+ * </p>
+ *
+ * @return The instrumentation instance of the Byte Buddy agent.
+ */
+ public static Instrumentation getInstrumentation() {
+ SecurityManager securityManager = System.getSecurityManager();
+ if (securityManager != null) {
+ securityManager.checkPermission(new RuntimePermission("getInstrumentation"));
+ }
+ Instrumentation instrumentation = Installer.instrumentation;
+ if (instrumentation == null) {
+ throw new IllegalStateException("The Byte Buddy agent is not loaded or this method is not called via the system class loader");
+ }
+ return instrumentation;
+ }
+
+ /**
+ * Allows the installation of this agent via a command line argument.
+ *
+ * @param agentArguments The unused agent arguments.
+ * @param instrumentation The instrumentation instance.
+ */
+ public static void premain(String agentArguments, Instrumentation instrumentation) {
+ Installer.instrumentation = instrumentation;
+ }
+
+ /**
+ * Allows the installation of this agent via the Attach API.
+ *
+ * @param agentArguments The unused agent arguments.
+ * @param instrumentation The instrumentation instance.
+ */
+ @SuppressWarnings("unused")
+ public static void agentmain(String agentArguments, Instrumentation instrumentation) {
+ Installer.instrumentation = instrumentation;
+ }
+}
diff --git a/byte-buddy-agent/src/main/java/net/bytebuddy/agent/VirtualMachine.java b/byte-buddy-agent/src/main/java/net/bytebuddy/agent/VirtualMachine.java
new file mode 100644
index 0000000..ebb0dcd
--- /dev/null
+++ b/byte-buddy-agent/src/main/java/net/bytebuddy/agent/VirtualMachine.java
@@ -0,0 +1,338 @@
+package net.bytebuddy.agent;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import org.newsclub.net.unix.AFUNIXSocket;
+import org.newsclub.net.unix.AFUNIXSocketAddress;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.Locale;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * <p>
+ * An implementation for attachment on a virtual machine. This interface mimics the tooling API's virtual
+ * machine interface to allow for similar usage by {@link ByteBuddyAgent} where all calls are made via
+ * reflection such that this structural typing suffices for interoperability.
+ * </p>
+ * <p>
+ * <b>Note</b>: Implementations are required to declare a static method {@code attach(String)} returning an
+ * instance of a class that declares the methods defined by {@link VirtualMachine}.
+ * </p>
+ */
+public interface VirtualMachine {
+
+ /**
+ * Loads an agent into the represented virtual machine.
+ *
+ * @param jarFile The jar file to attach.
+ * @param argument The argument to provide or {@code null} if no argument should be provided.
+ * @throws IOException If an I/O exception occurs.
+ */
+ @SuppressWarnings("unused")
+ void loadAgent(String jarFile, String argument) throws IOException;
+
+ /**
+ * Detaches this virtual machine representation.
+ *
+ * @throws IOException If an I/O exception occurs.
+ */
+ @SuppressWarnings("unused")
+ void detach() throws IOException;
+
+ /**
+ * A virtual machine implementation for a HotSpot VM or any compatible VM.
+ */
+ abstract class ForHotSpot implements VirtualMachine {
+
+ /**
+ * The UTF-8 charset.
+ */
+ private static final Charset UTF_8 = Charset.forName("UTF-8");
+
+ /**
+ * The protocol version to use for communication.
+ */
+ private static final String PROTOCOL_VERSION = "1";
+
+ /**
+ * The {@code load} command.
+ */
+ private static final String LOAD_COMMAND = "load";
+
+ /**
+ * The {@code instrument} command.
+ */
+ private static final String INSTRUMENT_COMMAND = "instrument";
+
+ /**
+ * A delimiter to be used for attachment.
+ */
+ private static final String ARGUMENT_DELIMITER = "=";
+
+ /**
+ * A blank line argument.
+ */
+ private static final byte[] BLANK = new byte[]{0};
+
+ /**
+ * The target process's id.
+ */
+ protected final String processId;
+
+ /**
+ * Creates a new HotSpot-compatible VM implementation.
+ *
+ * @param processId The target process's id.
+ */
+ protected ForHotSpot(String processId) {
+ this.processId = processId;
+ }
+
+ @Override
+ public void loadAgent(String jarFile, String argument) throws IOException {
+ connect();
+ write(PROTOCOL_VERSION.getBytes(UTF_8));
+ write(BLANK);
+ write(LOAD_COMMAND.getBytes(UTF_8));
+ write(BLANK);
+ write(INSTRUMENT_COMMAND.getBytes(UTF_8));
+ write(BLANK);
+ write(Boolean.FALSE.toString().getBytes(UTF_8));
+ write(BLANK);
+ write((argument == null
+ ? jarFile
+ : jarFile + ARGUMENT_DELIMITER + argument).getBytes(UTF_8));
+ write(BLANK);
+ byte[] buffer = new byte[1];
+ StringBuilder stringBuilder = new StringBuilder();
+ int length;
+ while ((length = read(buffer)) != -1) {
+ if (length > 0) {
+ if (buffer[0] == 10) {
+ break;
+ }
+ stringBuilder.append((char) buffer[0]);
+ }
+ }
+ switch (Integer.parseInt(stringBuilder.toString())) {
+ case 0:
+ return;
+ case 101:
+ throw new IOException("Protocol mismatch with target VM");
+ default:
+ buffer = new byte[1024];
+ stringBuilder = new StringBuilder();
+ while ((length = read(buffer)) != -1) {
+ stringBuilder.append(new String(buffer, 0, length, UTF_8));
+ }
+ throw new IllegalStateException(stringBuilder.toString());
+ }
+ }
+
+ /**
+ * Connects to the target VM.
+ *
+ * @throws IOException If an I/O exception occurs.
+ */
+ protected abstract void connect() throws IOException;
+
+ /**
+ * Reads from the communication channel.
+ *
+ * @param buffer The buffer to read into.
+ * @return The amount of bytes read.
+ * @throws IOException If an I/O exception occurs.
+ */
+ protected abstract int read(byte[] buffer) throws IOException;
+
+ /**
+ * Writes to the communication channel.
+ *
+ * @param buffer The buffer to write from.
+ * @throws IOException If an I/O exception occurs.
+ */
+ protected abstract void write(byte[] buffer) throws IOException;
+
+ /**
+ * A virtual machine implementation for a HotSpot VM running on Unix.
+ */
+ public static class OnUnix extends ForHotSpot {
+
+ /**
+ * The default amount of attempts to connect.
+ */
+ private static final int DEFAULT_ATTEMPTS = 10;
+
+ /**
+ * The default pause between two attempts.
+ */
+ private static final long DEFAULT_PAUSE = 200;
+
+ /**
+ * The default socket timeout.
+ */
+ private static final long DEFAULT_TIMEOUT = 5000;
+
+ /**
+ * The temporary directory on Unix systems.
+ */
+ private static final String TEMPORARY_DIRECTORY = "/tmp";
+
+ /**
+ * The name prefix for a socket.
+ */
+ private static final String SOCKET_FILE_PREFIX = ".java_pid";
+
+ /**
+ * The name prefix for an attachment file indicator.
+ */
+ private static final String ATTACH_FILE_PREFIX = ".attach_pid";
+
+ /**
+ * The Unix socket to use for communication. The containing object is supposed to be an instance
+ * of {@link AFUNIXSocket} which is however not set to avoid eager loading
+ */
+ private final Object socket;
+
+ /**
+ * The number of attempts to connect.
+ */
+ private final int attempts;
+
+ /**
+ * The time to pause between attempts.
+ */
+ private final long pause;
+
+ /**
+ * The socket timeout.
+ */
+ private final long timeout;
+
+ /**
+ * The time unit of the pause time.
+ */
+ private final TimeUnit timeUnit;
+
+ /**
+ * Creates a new VM implementation for a HotSpot VM running on Unix.
+ *
+ * @param processId The process id of the target VM.
+ * @param socket The Unix socket to use for communication.
+ * @param attempts The number of attempts to connect.
+ * @param pause The pause time between two VMs.
+ * @param timeout The socket timeout.
+ * @param timeUnit The time unit of the pause time.
+ */
+ public OnUnix(String processId, Object socket, int attempts, long pause, long timeout, TimeUnit timeUnit) {
+ super(processId);
+ this.socket = socket;
+ this.attempts = attempts;
+ this.pause = pause;
+ this.timeout = timeout;
+ this.timeUnit = timeUnit;
+ }
+
+ /**
+ * Asserts the availability of this virtual machine implementation. If the Unix socket library is missing or
+ * if this VM does not support Unix socket communication, a {@link Throwable} is thrown.
+ *
+ * @return This virtual machine type.
+ * @throws Throwable If this VM does not support POSIX sockets or is not running on a HotSpot VM.
+ */
+ public static Class<?> assertAvailability() throws Throwable {
+ if (!AFUNIXSocket.isSupported()) {
+ throw new IllegalStateException("POSIX sockets are not supported on the current system");
+ } else if (!System.getProperty("java.vm.name").toLowerCase(Locale.US).contains("hotspot")) {
+ throw new IllegalStateException("Cannot apply attachment on non-Hotspot compatible VM");
+ } else {
+ return OnUnix.class;
+ }
+ }
+
+ /**
+ * Attaches to the supplied VM process.
+ *
+ * @param processId The process id of the target VM.
+ * @return An appropriate virtual machine implementation.
+ * @throws IOException If an I/O exception occurs.
+ */
+ public static VirtualMachine attach(String processId) throws IOException {
+ return new OnUnix(processId, AFUNIXSocket.newInstance(), DEFAULT_ATTEMPTS, DEFAULT_PAUSE, DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "DMI_HARDCODED_ABSOLUTE_FILENAME", justification = "This is a Unix-specific implementation")
+ protected void connect() throws IOException {
+ File socketFile = new File(TEMPORARY_DIRECTORY, SOCKET_FILE_PREFIX + processId);
+ if (!socketFile.exists()) {
+ String target = ATTACH_FILE_PREFIX + processId, path = "/proc/" + processId + "/cwd/" + target;
+ File attachFile = new File(path);
+ try {
+ if (!attachFile.createNewFile() && !attachFile.isFile()) {
+ throw new IllegalStateException("Could not create attach file: " + attachFile);
+ }
+ } catch (IOException ignored) {
+ attachFile = new File(TEMPORARY_DIRECTORY, target);
+ if (!attachFile.createNewFile() && !attachFile.isFile()) {
+ throw new IllegalStateException("Could not create attach file: " + attachFile);
+ }
+ }
+ try {
+ // The HotSpot attachment API attempts to send the signal to all children of a process
+ Process process = Runtime.getRuntime().exec("kill -3 " + processId);
+ int attempts = this.attempts;
+ boolean killed = false;
+ do {
+ try {
+ if (process.exitValue() != 0) {
+ throw new IllegalStateException("Error while sending signal to target VM: " + processId);
+ }
+ killed = true;
+ break;
+ } catch (IllegalThreadStateException ignored) {
+ attempts -= 1;
+ Thread.sleep(timeUnit.toMillis(pause));
+ }
+ } while (attempts > 0);
+ if (!killed) {
+ throw new IllegalStateException("Target VM did not respond to signal: " + processId);
+ }
+ attempts = this.attempts;
+ while (attempts-- > 0 && !socketFile.exists()) {
+ Thread.sleep(timeUnit.toMillis(pause));
+ }
+ if (!socketFile.exists()) {
+ throw new IllegalStateException("Target VM did not respond: " + processId);
+ }
+ } catch (InterruptedException exception) {
+ throw new IllegalStateException("Interrupted during wait for process", exception);
+ } finally {
+ if (!attachFile.delete()) {
+ attachFile.deleteOnExit();
+ }
+ }
+ }
+ ((AFUNIXSocket) socket).setSoTimeout((int) timeUnit.toMillis(timeout));
+ ((AFUNIXSocket) socket).connect(new AFUNIXSocketAddress(socketFile));
+ }
+
+ @Override
+ public int read(byte[] buffer) throws IOException {
+ return ((AFUNIXSocket) this.socket).getInputStream().read(buffer);
+ }
+
+ @Override
+ public void write(byte[] buffer) throws IOException {
+ ((AFUNIXSocket) this.socket).getOutputStream().write(buffer);
+ }
+
+ @Override
+ public void detach() throws IOException {
+ ((AFUNIXSocket) this.socket).close();
+ }
+ }
+ }
+}
diff --git a/byte-buddy-agent/src/main/java/net/bytebuddy/agent/package-info.java b/byte-buddy-agent/src/main/java/net/bytebuddy/agent/package-info.java
new file mode 100644
index 0000000..152c230
--- /dev/null
+++ b/byte-buddy-agent/src/main/java/net/bytebuddy/agent/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * The Byte Buddy agent allows the redefinition of classes at runtime.
+ */
+package net.bytebuddy.agent;
diff --git a/byte-buddy-agent/src/test/java/net/bytebuddy/agent/AttacherTest.java b/byte-buddy-agent/src/test/java/net/bytebuddy/agent/AttacherTest.java
new file mode 100644
index 0000000..7440ecf
--- /dev/null
+++ b/byte-buddy-agent/src/test/java/net/bytebuddy/agent/AttacherTest.java
@@ -0,0 +1,93 @@
+package net.bytebuddy.agent;
+
+import org.junit.AssumptionViolatedException;
+import org.junit.Test;
+
+import java.io.File;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+import static org.junit.Assert.fail;
+
+public class AttacherTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux", BAZ = "baz";
+
+ @Test
+ public void testPseudoAttachment() throws Exception {
+ PseudoAttacher.ERROR.set(null);
+ Attacher.main(new String[]{PseudoAttacher.class.getName(), FOO, BAR, "=" + QUX, BAZ});
+ if (PseudoAttacher.ERROR.get() != null) {
+ throw new AssertionError(PseudoAttacher.ERROR.get());
+ }
+ }
+
+ @Test
+ public void testPseudoAttachmentNoArgument() throws Exception {
+ PseudoAttacherNoArgument.ERROR.set(null);
+ Attacher.main(new String[]{PseudoAttacherNoArgument.class.getName(), FOO, BAR, ""});
+ if (PseudoAttacherNoArgument.ERROR.get() != null) {
+ throw new AssertionError(PseudoAttacherNoArgument.ERROR.get());
+ }
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testConstructorThrowsException() throws Exception {
+ Constructor<?> constructor = Attacher.class.getDeclaredConstructor();
+ constructor.setAccessible(true);
+ try {
+ constructor.newInstance();
+ fail();
+ } catch (InvocationTargetException exception) {
+ throw (Exception) exception.getCause();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class PseudoAttacher {
+
+ static final ThreadLocal<String> ERROR = new ThreadLocal<String>();
+
+ public static PseudoAttacher attach(String processId) {
+ if (!processId.equals(FOO)) {
+ ERROR.set("Unexpected process id: " + processId);
+ }
+ return new PseudoAttacher();
+ }
+
+ public void loadAgent(String path, String argument) {
+ if (!path.equals(new File(BAR).getAbsolutePath())) {
+ ERROR.set("Unexpected file: " + path);
+ } else if (!argument.equals(QUX + " " + BAZ)) {
+ ERROR.set("Unexpected argument: " + argument);
+ }
+ }
+
+ public void detach() {
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class PseudoAttacherNoArgument {
+
+ static final ThreadLocal<String> ERROR = new ThreadLocal<String>();
+
+ public static PseudoAttacherNoArgument attach(String processId) {
+ if (!processId.equals(FOO)) {
+ ERROR.set("Unexpected process id: " + processId);
+ }
+ return new PseudoAttacherNoArgument();
+ }
+
+ public void loadAgent(String path, String argument) {
+ if (!path.equals(new File(BAR).getAbsolutePath())) {
+ ERROR.set("Unexpected file: " + path);
+ } else if (argument != null) {
+ ERROR.set("Unexpected argument: " + argument);
+ }
+ }
+
+ public void detach() {
+ }
+ }
+}
\ No newline at end of file
diff --git a/byte-buddy-agent/src/test/java/net/bytebuddy/agent/ByteBuddyAgentAgentProviderTest.java b/byte-buddy-agent/src/test/java/net/bytebuddy/agent/ByteBuddyAgentAgentProviderTest.java
new file mode 100644
index 0000000..3e64f29
--- /dev/null
+++ b/byte-buddy-agent/src/test/java/net/bytebuddy/agent/ByteBuddyAgentAgentProviderTest.java
@@ -0,0 +1,36 @@
+package net.bytebuddy.agent;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.io.File;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ByteBuddyAgentAgentProviderTest {
+
+ private static final String FOO = "foo";
+
+ @Test
+ public void testKnownAgent() throws Exception {
+ File agent = mock(File.class);
+ when(agent.getAbsolutePath()).thenReturn(FOO);
+ ByteBuddyAgent.AgentProvider.ForExistingAgent provider = new ByteBuddyAgent.AgentProvider.ForExistingAgent(agent);
+ assertThat(provider.resolve(), is(agent));
+ }
+
+ @Test
+ public void testKnownAccessor() throws Exception {
+ ByteBuddyAgent.AgentProvider provider = ByteBuddyAgent.AgentProvider.ForByteBuddyAgent.INSTANCE;
+ assertThat(provider.resolve().isFile(), is(true));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ByteBuddyAgent.AgentProvider.ForExistingAgent.class).apply();
+ ObjectPropertyAssertion.of(ByteBuddyAgent.AgentProvider.ForByteBuddyAgent.class).apply();
+ }
+}
diff --git a/byte-buddy-agent/src/test/java/net/bytebuddy/agent/ByteBuddyAgentAttachmentProviderTest.java b/byte-buddy-agent/src/test/java/net/bytebuddy/agent/ByteBuddyAgentAttachmentProviderTest.java
new file mode 100644
index 0000000..d90bb30
--- /dev/null
+++ b/byte-buddy-agent/src/test/java/net/bytebuddy/agent/ByteBuddyAgentAttachmentProviderTest.java
@@ -0,0 +1,79 @@
+package net.bytebuddy.agent;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class ByteBuddyAgentAttachmentProviderTest {
+
+ @Test
+ public void testSimpleAccessor() throws Exception {
+ File file = mock(File.class);
+ ByteBuddyAgent.AttachmentProvider.Accessor accessor =
+ new ByteBuddyAgent.AttachmentProvider.Accessor.Simple.WithExternalAttachment(Void.class, Collections.singletonList(file));
+ assertThat(accessor.isAvailable(), is(true));
+ assertThat(accessor.getVirtualMachineType(), CoreMatchers.<Class<?>>is(Void.class));
+ assertThat(accessor.getExternalAttachment().getVirtualMachineType(), is(Void.class.getName()));
+ assertThat(accessor.getExternalAttachment().getClassPath(), is(Collections.singletonList(file)));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testSimpleAccessorWithoutExternalAttachment() throws Exception {
+ new ByteBuddyAgent.AttachmentProvider.Accessor.Simple.WithoutExternalAttachment(Void.class).getExternalAttachment();
+ }
+
+ @Test
+ public void testUnavailableAccessor() throws Exception {
+ assertThat(ByteBuddyAgent.AttachmentProvider.Accessor.Unavailable.INSTANCE.isAvailable(), is(false));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUnavailableAccessorThrowsExceptionForType() throws Exception {
+ ByteBuddyAgent.AttachmentProvider.Accessor.Unavailable.INSTANCE.getVirtualMachineType();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUnavailableAccessorThrowsExceptionForExternalAttachment() throws Exception {
+ ByteBuddyAgent.AttachmentProvider.Accessor.Unavailable.INSTANCE.getExternalAttachment();
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ByteBuddyAgent.AttachmentProvider.ForJigsawVm.class).apply();
+ ObjectPropertyAssertion.of(ByteBuddyAgent.AttachmentProvider.ForJ9Vm.class).apply();
+ ObjectPropertyAssertion.of(ByteBuddyAgent.AttachmentProvider.ForToolsJarVm.class).apply();
+ ObjectPropertyAssertion.of(ByteBuddyAgent.AttachmentProvider.ForUnixHotSpotVm.class).apply();
+ ObjectPropertyAssertion.of(ByteBuddyAgent.AttachmentProvider.Compound.class).create(new ObjectPropertyAssertion.Creator<List<?>>() {
+ @Override
+ public List<?> create() {
+ return Collections.singletonList(mock(ByteBuddyAgent.AttachmentProvider.class));
+ }
+ }).apply();
+ final Iterator<Class<?>> types = Arrays.<Class<?>>asList(Void.class, Object.class, String.class, Integer.class).iterator();
+ ObjectPropertyAssertion.of(ByteBuddyAgent.AttachmentProvider.Accessor.Simple.WithExternalAttachment.class)
+ .create(new ObjectPropertyAssertion.Creator<Class<?>>() {
+ @Override
+ public Class<?> create() {
+ return types.next();
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(ByteBuddyAgent.AttachmentProvider.Accessor.Simple.WithoutExternalAttachment.class)
+ .create(new ObjectPropertyAssertion.Creator<Class<?>>() {
+ @Override
+ public Class<?> create() {
+ return types.next();
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(ByteBuddyAgent.AttachmentProvider.Accessor.Unavailable.class).apply();
+ }
+}
diff --git a/byte-buddy-agent/src/test/java/net/bytebuddy/agent/ByteBuddyAgentAttachmentTypeEvaluator.java b/byte-buddy-agent/src/test/java/net/bytebuddy/agent/ByteBuddyAgentAttachmentTypeEvaluator.java
new file mode 100644
index 0000000..567efcf
--- /dev/null
+++ b/byte-buddy-agent/src/test/java/net/bytebuddy/agent/ByteBuddyAgentAttachmentTypeEvaluator.java
@@ -0,0 +1,31 @@
+package net.bytebuddy.agent;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Iterator;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class ByteBuddyAgentAttachmentTypeEvaluator {
+
+ @Test
+ public void testDisabled() throws Exception {
+ assertThat(ByteBuddyAgent.AttachmentTypeEvaluator.Disabled.INSTANCE.requiresExternalAttachment("foo"), is(false));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ final Iterator<Method> iterator = Arrays.asList(Object.class.getDeclaredMethods()).iterator();
+ ObjectPropertyAssertion.of(ByteBuddyAgent.AttachmentTypeEvaluator.ForJava9CapableVm.class)
+ .create(new ObjectPropertyAssertion.Creator<Method>() {
+ @Override
+ public Method create() {
+ return iterator.next();
+ }
+ }).apply();
+ }
+}
diff --git a/byte-buddy-agent/src/test/java/net/bytebuddy/agent/ByteBuddyAgentInstallationTest.java b/byte-buddy-agent/src/test/java/net/bytebuddy/agent/ByteBuddyAgentInstallationTest.java
new file mode 100644
index 0000000..62f9315
--- /dev/null
+++ b/byte-buddy-agent/src/test/java/net/bytebuddy/agent/ByteBuddyAgentInstallationTest.java
@@ -0,0 +1,23 @@
+package net.bytebuddy.agent;
+
+import net.bytebuddy.test.utility.AgentAttachmentRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+
+import java.lang.instrument.Instrumentation;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class ByteBuddyAgentInstallationTest {
+
+ @Rule
+ public MethodRule agentAttachmentRule = new AgentAttachmentRule();
+
+ @Test
+ @AgentAttachmentRule.Enforce
+ public void testAgentInstallation() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ }
+}
diff --git a/byte-buddy-agent/src/test/java/net/bytebuddy/agent/ByteBuddyAgentProcessProviderTest.java b/byte-buddy-agent/src/test/java/net/bytebuddy/agent/ByteBuddyAgentProcessProviderTest.java
new file mode 100644
index 0000000..a92db00
--- /dev/null
+++ b/byte-buddy-agent/src/test/java/net/bytebuddy/agent/ByteBuddyAgentProcessProviderTest.java
@@ -0,0 +1,25 @@
+package net.bytebuddy.agent;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Iterator;
+
+public class ByteBuddyAgentProcessProviderTest {
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ByteBuddyAgent.ProcessProvider.ForCurrentVm.ForLegacyVm.class).apply();
+ final Iterator<Method> methods = Arrays.asList(Object.class.getDeclaredMethods()).iterator();
+ ObjectPropertyAssertion.of(ByteBuddyAgent.ProcessProvider.ForCurrentVm.ForJava9CapableVm.class)
+ .create(new ObjectPropertyAssertion.Creator<Method>() {
+ @Override
+ public Method create() {
+ return methods.next();
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(ByteBuddyAgent.AttachmentProvider.Accessor.Unavailable.class).apply();
+ }
+}
diff --git a/byte-buddy-agent/src/test/java/net/bytebuddy/agent/ByteBuddyAgentTest.java b/byte-buddy-agent/src/test/java/net/bytebuddy/agent/ByteBuddyAgentTest.java
new file mode 100644
index 0000000..c930d80
--- /dev/null
+++ b/byte-buddy-agent/src/test/java/net/bytebuddy/agent/ByteBuddyAgentTest.java
@@ -0,0 +1,67 @@
+package net.bytebuddy.agent;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.lang.instrument.Instrumentation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+
+public class ByteBuddyAgentTest {
+
+ private static final String INSTRUMENTATION = "instrumentation";
+
+ private static final Object STATIC_FIELD = null;
+
+ private Instrumentation actualInstrumentation;
+
+ @Before
+ public void setUp() throws Exception {
+ Field field = Installer.class.getDeclaredField(INSTRUMENTATION);
+ field.setAccessible(true);
+ actualInstrumentation = (Instrumentation) field.get(STATIC_FIELD);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ Field field = Installer.class.getDeclaredField(INSTRUMENTATION);
+ field.setAccessible(true);
+ field.set(STATIC_FIELD, actualInstrumentation);
+ }
+
+ @Test
+ public void testInstrumentationExtraction() throws Exception {
+ Field field = Installer.class.getDeclaredField(INSTRUMENTATION);
+ field.setAccessible(true);
+ Instrumentation instrumentation = mock(Instrumentation.class);
+ field.set(STATIC_FIELD, instrumentation);
+ assertThat(ByteBuddyAgent.getInstrumentation(), is(instrumentation));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMissingInstrumentationThrowsException() throws Exception {
+ Field field = Installer.class.getDeclaredField(INSTRUMENTATION);
+ field.setAccessible(true);
+ field.set(STATIC_FIELD, null);
+ ByteBuddyAgent.getInstrumentation();
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testConstructorThrowsException() throws Exception {
+ Constructor<?> constructor = ByteBuddyAgent.class.getDeclaredConstructor();
+ constructor.setAccessible(true);
+ try {
+ constructor.newInstance();
+ fail();
+ } catch (InvocationTargetException exception) {
+ throw (Exception) exception.getCause();
+ }
+ }
+}
diff --git a/byte-buddy-agent/src/test/java/net/bytebuddy/agent/InstallerTest.java b/byte-buddy-agent/src/test/java/net/bytebuddy/agent/InstallerTest.java
new file mode 100644
index 0000000..a92dd6e
--- /dev/null
+++ b/byte-buddy-agent/src/test/java/net/bytebuddy/agent/InstallerTest.java
@@ -0,0 +1,92 @@
+package net.bytebuddy.agent;
+
+import net.bytebuddy.test.utility.MockitoRule;
+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.lang.instrument.Instrumentation;
+import java.lang.reflect.*;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
+
+public class InstallerTest {
+
+ private static final String FOO = "foo";
+
+ private static final String INSTRUMENTATION = "instrumentation";
+
+ private static final Object STATIC_FIELD = null;
+
+ @Rule
+ public final TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private Instrumentation instrumentation;
+
+ private Instrumentation actualInstrumentation;
+
+ @Before
+ public void setUp() throws Exception {
+ Field field = Installer.class.getDeclaredField(INSTRUMENTATION);
+ field.setAccessible(true);
+ actualInstrumentation = (Instrumentation) field.get(STATIC_FIELD);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ Field field = Installer.class.getDeclaredField(INSTRUMENTATION);
+ field.setAccessible(true);
+ field.set(STATIC_FIELD, actualInstrumentation);
+ }
+
+ @Test
+ public void testPreMain() throws Exception {
+ Installer.premain(FOO, instrumentation);
+ assertThat(ByteBuddyAgent.getInstrumentation(), is(instrumentation));
+ }
+
+ @Test
+ public void testAgentMain() throws Exception {
+ Installer.agentmain(FOO, instrumentation);
+ assertThat(ByteBuddyAgent.getInstrumentation(), is(instrumentation));
+ }
+
+ @Test
+ public void testAgentInstallerIsPublic() throws Exception {
+ Class<?> type = Installer.class;
+ assertThat(Modifier.isPublic(type.getModifiers()), is(true));
+ assertThat(type.getDeclaringClass(), nullValue(Class.class));
+ assertThat(type.getDeclaredClasses().length, is(0));
+ }
+
+ @Test
+ public void testAgentInstallerStoreIsPrivate() throws Exception {
+ Field field = Installer.class.getDeclaredField("instrumentation");
+ assertThat(Modifier.isPrivate(field.getModifiers()), is(true));
+ }
+
+ @Test
+ public void testAgentInstallerGetterIsPublic() throws Exception {
+ Method method = Installer.class.getDeclaredMethod("getInstrumentation");
+ assertThat(Modifier.isPublic(method.getModifiers()), is(true));
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testConstructorThrowsException() throws Exception {
+ Constructor<?> constructor = Installer.class.getDeclaredConstructor();
+ constructor.setAccessible(true);
+ try {
+ constructor.newInstance();
+ fail();
+ } catch (InvocationTargetException exception) {
+ throw (Exception) exception.getCause();
+ }
+ }
+}
diff --git a/byte-buddy-agent/src/test/java/net/bytebuddy/agent/VirtualMachineForHotSpotTest.java b/byte-buddy-agent/src/test/java/net/bytebuddy/agent/VirtualMachineForHotSpotTest.java
new file mode 100644
index 0000000..7f9ddeb
--- /dev/null
+++ b/byte-buddy-agent/src/test/java/net/bytebuddy/agent/VirtualMachineForHotSpotTest.java
@@ -0,0 +1,120 @@
+package net.bytebuddy.agent;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.test.utility.UnixSocketRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+import org.mockito.InOrder;
+
+import java.io.IOException;
+
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.spy;
+
+public class VirtualMachineForHotSpotTest {
+
+ @Rule
+ public MethodRule unixSocketRule = new UnixSocketRule();
+
+ @Test
+ @UnixSocketRule.Enforce
+ public void testAttachment() throws Exception {
+ VirtualMachine.ForHotSpot virtualMachine = spy(new PseudoMachine(
+ "0".getBytes("UTF-8"),
+ new byte[]{10}
+ ));
+ virtualMachine.loadAgent("foo", "bar");
+ InOrder order = inOrder(virtualMachine);
+ order.verify(virtualMachine).connect();
+ order.verify(virtualMachine).write("1".getBytes("UTF-8"));
+ order.verify(virtualMachine).write(new byte[1]);
+ order.verify(virtualMachine).write("load".getBytes("UTF-8"));
+ order.verify(virtualMachine).write(new byte[1]);
+ order.verify(virtualMachine).write("instrument".getBytes("UTF-8"));
+ order.verify(virtualMachine).write(new byte[1]);
+ order.verify(virtualMachine).write(Boolean.FALSE.toString().getBytes("UTF-8"));
+ order.verify(virtualMachine).write(new byte[1]);
+ order.verify(virtualMachine).write("foo=bar".getBytes("UTF-8"));
+ order.verify(virtualMachine).write(new byte[1]);
+ }
+
+ @Test
+ @UnixSocketRule.Enforce
+ public void testAttachmentWithoutArgument() throws Exception {
+ VirtualMachine.ForHotSpot virtualMachine = spy(new PseudoMachine(
+ "0".getBytes("UTF-8"),
+ new byte[]{10}
+ ));
+ virtualMachine.loadAgent("foo", null);
+ InOrder order = inOrder(virtualMachine);
+ order.verify(virtualMachine).connect();
+ order.verify(virtualMachine).write("1".getBytes("UTF-8"));
+ order.verify(virtualMachine).write(new byte[1]);
+ order.verify(virtualMachine).write("load".getBytes("UTF-8"));
+ order.verify(virtualMachine).write(new byte[1]);
+ order.verify(virtualMachine).write("instrument".getBytes("UTF-8"));
+ order.verify(virtualMachine).write(new byte[1]);
+ order.verify(virtualMachine).write(Boolean.FALSE.toString().getBytes("UTF-8"));
+ order.verify(virtualMachine).write(new byte[1]);
+ order.verify(virtualMachine).write("foo".getBytes("UTF-8"));
+ order.verify(virtualMachine).write(new byte[1]);
+ }
+
+ @Test(expected = IOException.class)
+ @UnixSocketRule.Enforce
+ public void testAttachmentIncompatibleProtocol() throws Exception {
+ new PseudoMachine(
+ "1".getBytes("UTF-8"),
+ "0".getBytes("UTF-8"),
+ "1".getBytes("UTF-8"),
+ new byte[]{10}
+ ).loadAgent("foo", null);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ @UnixSocketRule.Enforce
+ public void testAttachmentUnknownError() throws Exception {
+ new PseudoMachine(
+ "1".getBytes("UTF-8"),
+ new byte[]{10},
+ "foo".getBytes("UTF-8")
+ ).loadAgent("foo", null);
+ }
+
+ private static class PseudoMachine extends VirtualMachine.ForHotSpot {
+
+ private final byte[][] read;
+
+ private int index;
+
+ private PseudoMachine(byte[]... read) {
+ super(null);
+ this.read = read;
+ }
+
+ @Override
+ public void detach() throws IOException {
+ }
+
+ @Override
+ protected void connect() throws IOException {
+
+ }
+
+ @Override
+ protected int read(byte[] buffer) throws IOException {
+ if (index == read.length) {
+ return -1;
+ }
+ byte[] read = this.read[index++];
+ System.arraycopy(read, 0, buffer, 0, read.length);
+ return read.length;
+ }
+
+ @Override
+ protected void write(byte[] buffer) throws IOException {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/byte-buddy-agent/src/test/java/net/bytebuddy/test/utility/AgentAttachmentRule.java b/byte-buddy-agent/src/test/java/net/bytebuddy/test/utility/AgentAttachmentRule.java
new file mode 100644
index 0000000..0780991
--- /dev/null
+++ b/byte-buddy-agent/src/test/java/net/bytebuddy/test/utility/AgentAttachmentRule.java
@@ -0,0 +1,71 @@
+package net.bytebuddy.test.utility;
+
+import net.bytebuddy.agent.ByteBuddyAgent;
+import org.junit.rules.MethodRule;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.instrument.Instrumentation;
+import java.security.AccessController;
+import java.util.logging.Logger;
+
+/**
+ * This rules assures that the running JVM is a JDK JVM with an available
+ * <a href="https://blogs.oracle.com/CoreJavaTechTips/entry/the_attach_api">attach API</a>.
+ */
+public class AgentAttachmentRule implements MethodRule {
+
+ private final boolean available;
+
+ public AgentAttachmentRule() {
+ available = ByteBuddyAgent.AttachmentProvider.DEFAULT.attempt().isAvailable();
+ }
+
+ @Override
+ public Statement apply(Statement base, FrameworkMethod method, Object target) {
+ Enforce enforce = method.getAnnotation(Enforce.class);
+ if (enforce != null) {
+ if (!available) {
+ return new NoOpStatement("The executing JVM does not support runtime attachment");
+ }
+ Instrumentation instrumentation = ByteBuddyAgent.install(ByteBuddyAgent.AttachmentProvider.DEFAULT);
+ if (enforce.redefinesClasses() && !instrumentation.isRedefineClassesSupported()) {
+ return new NoOpStatement("The executing JVM does not support class redefinition");
+ } else if (enforce.retransformsClasses() && !instrumentation.isRetransformClassesSupported()) {
+ return new NoOpStatement("The executing JVM does not support class retransformation");
+ } else if (enforce.nativeMethodPrefix() && !instrumentation.isNativeMethodPrefixSupported()) {
+ return new NoOpStatement("The executing JVM does not support class native method prefixes");
+ }
+ }
+ return base;
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ public @interface Enforce {
+
+ boolean redefinesClasses() default false;
+
+ boolean retransformsClasses() default false;
+
+ boolean nativeMethodPrefix() default false;
+ }
+
+ private static class NoOpStatement extends Statement {
+
+ private final String reason;
+
+ public NoOpStatement(String reason) {
+ this.reason = reason;
+ }
+
+ @Override
+ public void evaluate() throws Throwable {
+ Logger.getLogger("net.bytebuddy").warning("Ignoring test case: " + reason);
+ }
+ }
+}
diff --git a/byte-buddy-agent/src/test/java/net/bytebuddy/test/utility/MockitoRule.java b/byte-buddy-agent/src/test/java/net/bytebuddy/test/utility/MockitoRule.java
new file mode 100644
index 0000000..10df0c0
--- /dev/null
+++ b/byte-buddy-agent/src/test/java/net/bytebuddy/test/utility/MockitoRule.java
@@ -0,0 +1,30 @@
+package net.bytebuddy.test.utility;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * A rule that applies Mockito's annotations to any test. This is preferred over the Mockito runner since it allows
+ * to use tests with parameters that require a specific runner.
+ */
+public class MockitoRule implements TestRule {
+
+ private final Object target;
+
+ public MockitoRule(Object target) {
+ this.target = target;
+ }
+
+ @Override
+ public Statement apply(final Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ MockitoAnnotations.initMocks(target);
+ base.evaluate();
+ }
+ };
+ }
+}
diff --git a/byte-buddy-agent/src/test/java/net/bytebuddy/test/utility/ObjectPropertyAssertion.java b/byte-buddy-agent/src/test/java/net/bytebuddy/test/utility/ObjectPropertyAssertion.java
new file mode 100644
index 0000000..9930c3f
--- /dev/null
+++ b/byte-buddy-agent/src/test/java/net/bytebuddy/test/utility/ObjectPropertyAssertion.java
@@ -0,0 +1,329 @@
+package net.bytebuddy.test.utility;
+
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class ObjectPropertyAssertion<T> {
+
+ private static final boolean DEFAULT_BOOLEAN = false, OTHER_BOOLEAN = true;
+
+ private static final byte DEFAULT_BYTE = 1, OTHER_BYTE = 42;
+
+ private static final char DEFAULT_CHAR = 1, OTHER_CHAR = 42;
+
+ private static final short DEFAULT_SHORT = 1, OTHER_SHORT = 42;
+
+ private static final int DEFAULT_INT = 1, OTHER_INT = 42;
+
+ private static final long DEFAULT_LONG = 1, OTHER_LONG = 42;
+
+ private static final float DEFAULT_FLOAT = 1, OTHER_FLOAT = 42;
+
+ private static final double DEFAULT_DOUBLE = 1, OTHER_DOUBLE = 42;
+
+ private static final String DEFAULT_STRING = "foo", OTHER_STRING = "bar";
+
+ private final Class<T> type;
+
+ private final ApplicableRefinement refinement;
+
+ private final ApplicableGenerator generator;
+
+ private final ApplicableCreator creator;
+
+ private final boolean skipSynthetic;
+
+ private final String optionalToStringRegex;
+
+ private ObjectPropertyAssertion(Class<T> type,
+ ApplicableGenerator generator,
+ ApplicableRefinement refinement,
+ ApplicableCreator creator,
+ boolean skipSynthetic,
+ String optionalToStringRegex) {
+ this.type = type;
+ this.generator = generator;
+ this.refinement = refinement;
+ this.creator = creator;
+ this.skipSynthetic = skipSynthetic;
+ this.optionalToStringRegex = optionalToStringRegex;
+ }
+
+ public static <S> ObjectPropertyAssertion<S> of(Class<S> type) {
+ return new ObjectPropertyAssertion<S>(type,
+ new ApplicableGenerator(),
+ new ApplicableRefinement(),
+ new ApplicableCreator(),
+ false,
+ null);
+ }
+
+ public ObjectPropertyAssertion<T> refine(Refinement<?> refinement) {
+ return new ObjectPropertyAssertion<T>(type,
+ generator,
+ this.refinement.with(refinement),
+ creator,
+ skipSynthetic,
+ optionalToStringRegex);
+ }
+
+ public ObjectPropertyAssertion<T> generate(Generator<?> generator) {
+ return new ObjectPropertyAssertion<T>(type,
+ this.generator.with(generator),
+ refinement,
+ creator,
+ skipSynthetic,
+ optionalToStringRegex);
+ }
+
+ public ObjectPropertyAssertion<T> create(Creator<?> creator) {
+ return new ObjectPropertyAssertion<T>(type,
+ generator,
+ refinement,
+ this.creator.with(creator),
+ skipSynthetic,
+ optionalToStringRegex);
+ }
+
+ public ObjectPropertyAssertion<T> skipSynthetic() {
+ return new ObjectPropertyAssertion<T>(type, generator, refinement, creator, true, optionalToStringRegex);
+ }
+
+ public ObjectPropertyAssertion<T> specificToString(String stringRegex) {
+ return new ObjectPropertyAssertion<T>(type, generator, refinement, creator, skipSynthetic, stringRegex);
+ }
+
+ public void apply() throws IllegalAccessException, InvocationTargetException, InstantiationException {
+ if (type.isEnum()) {
+ return;
+ }
+ for (Constructor<?> constructor : type.getDeclaredConstructors()) {
+ if (constructor.isSynthetic() && skipSynthetic) {
+ continue;
+ }
+ constructor.setAccessible(true);
+ Class<?>[] parameterTypes = constructor.getParameterTypes();
+ Object[] actualArguments = new Object[parameterTypes.length];
+ Object[] otherArguments = new Object[parameterTypes.length];
+ int index = 0;
+ for (Class<?> parameterType : parameterTypes) {
+ putInstance(parameterType, actualArguments, otherArguments, index++);
+ }
+ int testIndex = 0;
+ @SuppressWarnings("unchecked")
+ T instance = (T) constructor.newInstance(actualArguments);
+ assertThat(instance, is(instance));
+ assertThat(instance, not(is((Object) null)));
+ assertThat(instance, not(is(new Object())));
+ Object similarInstance = constructor.newInstance(actualArguments);
+ assertThat(instance.hashCode(), is(similarInstance.hashCode()));
+ assertThat(instance, is(similarInstance));
+ if (optionalToStringRegex != null) {
+ assertThat(instance.toString(), new RegexMatcher(optionalToStringRegex));
+ }
+ for (Object otherArgument : otherArguments) {
+ Object[] compareArguments = new Object[actualArguments.length];
+ int argumentIndex = 0;
+ for (Object actualArgument : actualArguments) {
+ if (argumentIndex == testIndex) {
+ compareArguments[argumentIndex] = otherArgument;
+ } else {
+ compareArguments[argumentIndex] = actualArgument;
+ }
+ argumentIndex++;
+ }
+ Object unlikeInstance = constructor.newInstance(compareArguments);
+ assertThat(instance.hashCode(), not(is(unlikeInstance)));
+ assertThat(instance, not(is(unlikeInstance)));
+ testIndex++;
+ }
+ }
+ }
+
+ private void putInstance(Class<?> parameterType, Object actualArguments, Object otherArguments, int index) {
+ Object actualArgument, otherArgument;
+ if (parameterType == boolean.class) {
+ actualArgument = DEFAULT_BOOLEAN;
+ otherArgument = OTHER_BOOLEAN;
+ } else if (parameterType == byte.class) {
+ actualArgument = DEFAULT_BYTE;
+ otherArgument = OTHER_BYTE;
+ } else if (parameterType == char.class) {
+ actualArgument = DEFAULT_CHAR;
+ otherArgument = OTHER_CHAR;
+ } else if (parameterType == short.class) {
+ actualArgument = DEFAULT_SHORT;
+ otherArgument = OTHER_SHORT;
+ } else if (parameterType == int.class) {
+ actualArgument = DEFAULT_INT;
+ otherArgument = OTHER_INT;
+ } else if (parameterType == long.class) {
+ actualArgument = DEFAULT_LONG;
+ otherArgument = OTHER_LONG;
+ } else if (parameterType == float.class) {
+ actualArgument = DEFAULT_FLOAT;
+ otherArgument = OTHER_FLOAT;
+ } else if (parameterType == double.class) {
+ actualArgument = DEFAULT_DOUBLE;
+ otherArgument = OTHER_DOUBLE;
+ } else if (parameterType.isEnum()) {
+ Object[] enumConstants = parameterType.getEnumConstants();
+ if (enumConstants.length == 1) {
+ throw new IllegalArgumentException("Enum with only one constant: " + parameterType);
+ }
+ actualArgument = enumConstants[0];
+ otherArgument = enumConstants[1];
+ } else if (parameterType.isArray()) {
+ actualArgument = Array.newInstance(parameterType.getComponentType(), 1);
+ otherArgument = Array.newInstance(parameterType.getComponentType(), 1);
+ putInstance(parameterType.getComponentType(), actualArgument, otherArgument, 0);
+ } else {
+ actualArgument = creator.replace(parameterType, generator, false);
+ refinement.apply(actualArgument);
+ otherArgument = creator.replace(parameterType, generator, true);
+ refinement.apply(otherArgument);
+ }
+ Array.set(actualArguments, index, actualArgument);
+ Array.set(otherArguments, index, otherArgument);
+ }
+
+ public interface Refinement<T> {
+
+ void apply(T mock);
+ }
+
+ public interface Generator<T> {
+
+ Class<? extends T> generate();
+ }
+
+ public interface Creator<T> {
+
+ T create();
+ }
+
+ private static class ApplicableRefinement {
+
+ private final List<Refinement<?>> refinements;
+
+ private ApplicableRefinement() {
+ refinements = Collections.emptyList();
+ }
+
+ private ApplicableRefinement(List<Refinement<?>> refinements) {
+ this.refinements = refinements;
+ }
+
+ @SuppressWarnings("unchecked")
+ private void apply(Object mock) {
+ for (Refinement refinement : refinements) {
+ ParameterizedType generic = (ParameterizedType) refinement.getClass().getGenericInterfaces()[0];
+ Class<?> restrained = generic.getActualTypeArguments()[0] instanceof ParameterizedType
+ ? (Class<?>) ((ParameterizedType) generic.getActualTypeArguments()[0]).getRawType()
+ : (Class<?>) generic.getActualTypeArguments()[0];
+ if (restrained.isInstance(mock)) {
+ refinement.apply(mock);
+ }
+ }
+ }
+
+ private ApplicableRefinement with(Refinement<?> refinement) {
+ return new ApplicableRefinement(join(refinements, refinement));
+ }
+ }
+
+ private static class ApplicableGenerator {
+
+ private final List<Generator<?>> generators;
+
+ private ApplicableGenerator() {
+ generators = Collections.emptyList();
+ }
+
+ private ApplicableGenerator(List<Generator<?>> generators) {
+ this.generators = generators;
+ }
+
+ private Object generate(Class<?> type, boolean alternative) {
+ for (Generator<?> generator : generators) {
+ ParameterizedType generic = (ParameterizedType) generator.getClass().getGenericInterfaces()[0];
+ Class<?> restrained = generic.getActualTypeArguments()[0] instanceof ParameterizedType
+ ? (Class<?>) ((ParameterizedType) generic.getActualTypeArguments()[0]).getRawType()
+ : (Class<?>) generic.getActualTypeArguments()[0];
+ if (type.isAssignableFrom(restrained)) {
+ type = generator.generate();
+ }
+ }
+ return type == String.class
+ ? alternative ? OTHER_STRING : DEFAULT_STRING
+ : mock(type);
+ }
+
+ private ApplicableGenerator with(Generator<?> generator) {
+ return new ApplicableGenerator(join(generators, generator));
+ }
+ }
+
+ private static class ApplicableCreator {
+
+ private final List<Creator<?>> creators;
+
+ private ApplicableCreator() {
+ creators = Collections.emptyList();
+ }
+
+ private ApplicableCreator(List<Creator<?>> creators) {
+ this.creators = creators;
+ }
+
+ private Object replace(Class<?> type, ApplicableGenerator generator, boolean alternative) {
+ for (Creator<?> creator : creators) {
+ ParameterizedType generic = (ParameterizedType) creator.getClass().getGenericInterfaces()[0];
+ Class<?> restrained = generic.getActualTypeArguments()[0] instanceof ParameterizedType
+ ? (Class<?>) ((ParameterizedType) generic.getActualTypeArguments()[0]).getRawType()
+ : (Class<?>) generic.getActualTypeArguments()[0];
+ if (type.isAssignableFrom(restrained)) {
+ return creator.create();
+ }
+ }
+ return generator.generate(type, alternative);
+ }
+
+ private ApplicableCreator with(Creator<?> creator) {
+ return new ApplicableCreator(join(creators, creator));
+ }
+ }
+
+ private static class RegexMatcher extends TypeSafeMatcher<String> {
+
+ private final String regex;
+
+ public RegexMatcher(final String regex) {
+ this.regex = regex;
+ }
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText("matches regex='" + regex + "'");
+ }
+
+ @Override
+ public boolean matchesSafely(final String string) {
+ return string.matches(regex);
+ }
+ }
+
+ private static <T> List<T> join(List<? extends T> list, T element) {
+ List<T> result = new ArrayList<T>(list.size() + 1);
+ result.addAll(list);
+ result.add(element);
+ return result;
+ }
+}
diff --git a/byte-buddy-agent/src/test/java/net/bytebuddy/test/utility/UnixSocketRule.java b/byte-buddy-agent/src/test/java/net/bytebuddy/test/utility/UnixSocketRule.java
new file mode 100644
index 0000000..2cbdd0c
--- /dev/null
+++ b/byte-buddy-agent/src/test/java/net/bytebuddy/test/utility/UnixSocketRule.java
@@ -0,0 +1,50 @@
+package net.bytebuddy.test.utility;
+
+import org.junit.rules.MethodRule;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+import org.newsclub.net.unix.AFUNIXSocket;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.logging.Logger;
+
+public class UnixSocketRule implements MethodRule {
+
+ private final boolean enabled;
+
+ public UnixSocketRule() {
+ boolean enabled;
+ try {
+ Class.forName(AFUNIXSocket.class.getName(), true, UnixSocketRule.class.getClassLoader());
+ enabled = true;
+ } catch (Throwable ignored) {
+ enabled = false;
+ }
+ this.enabled = enabled;
+ }
+
+ @Override
+ public Statement apply(Statement base, FrameworkMethod method, Object target) {
+ return enabled || method.getAnnotation(Enforce.class) == null
+ ? base
+ : new NoOpStatement();
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ public @interface Enforce {
+
+ }
+
+ private static class NoOpStatement extends Statement {
+
+ @Override
+ public void evaluate() throws Throwable {
+ Logger.getLogger("net.bytebuddy").warning("Ignoring use Unix sockets on this VM");
+ }
+ }
+}
+
diff --git a/byte-buddy-android-test/AndroidManifest.xml b/byte-buddy-android-test/AndroidManifest.xml
new file mode 100644
index 0000000..4ed9d4c
--- /dev/null
+++ b/byte-buddy-android-test/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="net.bytebuddy.android.test"
+ android:versionCode="1"
+ android:versionName="1.01">
+
+ <application
+ android:icon="@drawable/icon"
+ android:label="@string/app_name">
+ <activity android:name=".TestActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
+
diff --git a/byte-buddy-android-test/pom.xml b/byte-buddy-android-test/pom.xml
new file mode 100644
index 0000000..9ccc2a0
--- /dev/null
+++ b/byte-buddy-android-test/pom.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns="http://maven.apache.org/POM/4.0.0"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy-parent</artifactId>
+ <!-- The version number is not updated by the release plugin as this artifact is not released! -->
+ <version>1.6.2-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>byte-buddy-android-test</artifactId>
+ <packaging>apk</packaging>
+
+ <name>Byte Buddy for Android (test application)</name>
+ <description>An Android test application that runs Byte Buddy in an Android environment.</description>
+
+ <properties>
+ <version.maven.android>3.8.2</version.maven.android>
+ <version.android.sdk.platform>4</version.android.sdk.platform>
+ </properties>
+
+ <!--
+ Be aware that not all IDEs automatically add all dependencies and their transitive dependencies to the
+ build path of the built APK. Therefore, the dx.jar dependency sometimes needs to be added explicitly
+ to the project build. This is further described here:
+ http://stackoverflow.com/questions/14765910/could-not-find-class-xxx-referenced-from-method-xxx-yyy
+
+ This module is activated by the 'android' Maven profile.
+ -->
+
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>byte-buddy-android</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.android</groupId>
+ <artifactId>android</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <resources>
+ <resource>
+ <directory>src/main/resources</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ <plugins>
+ <!-- Build Android application from Maven. -->
+ <plugin>
+ <groupId>com.jayway.maven.plugins.android.generation2</groupId>
+ <artifactId>android-maven-plugin</artifactId>
+ <version>${version.maven.android}</version>
+ <extensions>true</extensions>
+ <configuration>
+ <run>
+ <debug>true</debug>
+ </run>
+ <sdk>
+ <platform>${version.android.sdk.platform}</platform>
+ </sdk>
+ <undeployBeforeDeploy>true</undeployBeforeDeploy>
+ </configuration>
+ </plugin>
+ <!-- This artifact should not be deployed. -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <version>${version.plugin.deploy}</version>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ <!-- Mutation testing is not required for this example project. -->
+ <plugin>
+ <groupId>org.pitest</groupId>
+ <artifactId>pitest-maven</artifactId>
+ <version>${version.plugin.pitest}</version>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ <plugin>
+ <artifactId>maven-resources-plugin</artifactId>
+ <version>2.5</version>
+ <executions>
+ <execution>
+ <phase>initialize</phase>
+ <goals>
+ <goal>resources</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/byte-buddy-android-test/res/drawable/icon.png b/byte-buddy-android-test/res/drawable/icon.png
new file mode 100644
index 0000000..fbe42b3
Binary files /dev/null and b/byte-buddy-android-test/res/drawable/icon.png differ
diff --git a/byte-buddy-android-test/res/layout/main.xml b/byte-buddy-android-test/res/layout/main.xml
new file mode 100644
index 0000000..47521d0
--- /dev/null
+++ b/byte-buddy-android-test/res/layout/main.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+ <TextView
+ android:layout_marginTop="5pt"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/info"/>
+ <TextView
+ android:id="@+id/maven_info"
+ android:layout_marginTop="5pt"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"/>
+ <Button
+ android:id="@+id/run_test_wrapping"
+ android:layout_marginTop="10pt"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="5pt"
+ android:paddingRight="5pt"
+ android:text="@string/run_test_wrapping"/>
+ <Button
+ android:id="@+id/run_test_injecting"
+ android:layout_marginTop="10pt"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="5pt"
+ android:paddingRight="5pt"
+ android:text="@string/run_test_injecting"/>
+</LinearLayout>
diff --git a/byte-buddy-android-test/res/values/strings.xml b/byte-buddy-android-test/res/values/strings.xml
new file mode 100644
index 0000000..e189f4c
--- /dev/null
+++ b/byte-buddy-android-test/res/values/strings.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">Byte Buddy Android test</string>
+ <string name="version_info">Build version: %1$s</string>
+ <string name="info">
+ This application creates a runtime subclass using Byte Buddy and loads the generated classes using Byte
+ Buddy\'s class loading strategy for Android. For this, it uses version the Android SDK\'s dex compiler
+ in the form it is available on Maven Central.
+ </string>
+ <string name="run_test_wrapping">Run test: wrapping</string>
+ <string name="run_test_injecting">Run test: injecting</string>
+</resources>
diff --git a/byte-buddy-android-test/src/main/java/net/bytebuddy/android/test/TestActivity.java b/byte-buddy-android-test/src/main/java/net/bytebuddy/android/test/TestActivity.java
new file mode 100644
index 0000000..4d2ba86
--- /dev/null
+++ b/byte-buddy-android-test/src/main/java/net/bytebuddy/android/test/TestActivity.java
@@ -0,0 +1,192 @@
+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);
+ }
+ }
+ }
+}
+
diff --git a/byte-buddy-android-test/src/main/java/net/bytebuddy/android/test/package-info.java b/byte-buddy-android-test/src/main/java/net/bytebuddy/android/test/package-info.java
new file mode 100644
index 0000000..5bf50db
--- /dev/null
+++ b/byte-buddy-android-test/src/main/java/net/bytebuddy/android/test/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * This package contains an example application for running Byte Buddy on an Android device.
+ */
+package net.bytebuddy.android.test;
diff --git a/byte-buddy-android-test/src/main/resources/maven.properties b/byte-buddy-android-test/src/main/resources/maven.properties
new file mode 100644
index 0000000..defbd48
--- /dev/null
+++ b/byte-buddy-android-test/src/main/resources/maven.properties
@@ -0,0 +1 @@
+version=${project.version}
diff --git a/byte-buddy-android/pom.xml b/byte-buddy-android/pom.xml
new file mode 100644
index 0000000..281944b
--- /dev/null
+++ b/byte-buddy-android/pom.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <artifactId>byte-buddy-parent</artifactId>
+ <groupId>net.bytebuddy</groupId>
+ <version>1.7.1</version>
+ </parent>
+
+ <artifactId>byte-buddy-android</artifactId>
+ <packaging>jar</packaging>
+
+ <properties>
+ <!-- Newer versions require Java 7 -->
+ <version.android.sdk.dx>1</version.android.sdk.dx>
+ </properties>
+
+ <name>Byte Buddy for Android</name>
+ <description>Byte Buddy Android allows for limited support of code generation on an Android runtime.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>byte-buddy</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.android</groupId>
+ <artifactId>android</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.jakewharton.android.repackaged</groupId>
+ <artifactId>dalvik-dx</artifactId>
+ <version>${version.android.sdk.dx}</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <scope>test</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy-agent</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/byte-buddy-android/src/main/java/net/bytebuddy/android/AndroidClassLoadingStrategy.java b/byte-buddy-android/src/main/java/net/bytebuddy/android/AndroidClassLoadingStrategy.java
new file mode 100644
index 0000000..9b11d0d
--- /dev/null
+++ b/byte-buddy-android/src/main/java/net/bytebuddy/android/AndroidClassLoadingStrategy.java
@@ -0,0 +1,414 @@
+package net.bytebuddy.android;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+import com.android.dx.cf.direct.DirectClassFile;
+import com.android.dx.cf.direct.StdAttributeFactory;
+import com.android.dx.dex.DexOptions;
+import com.android.dx.dex.cf.CfOptions;
+import com.android.dx.dex.cf.CfTranslator;
+import com.android.dx.dex.file.DexFile;
+import dalvik.system.DexClassLoader;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.utility.RandomString;
+
+import java.io.*;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import java.util.logging.Logger;
+
+/**
+ * <p>
+ * A class loading strategy that allows to load a dynamically created class at the runtime of an Android
+ * application. For this, a {@link dalvik.system.DexClassLoader} is used under the covers.
+ * </p>
+ * <p>
+ * This class loader requires to write files to the file system which are then processed by the Android VM. It is
+ * <b>not</b> permitted by Android's security checks to store these files in a shared folder where they could be
+ * manipulated by a third application what would break Android's sandbox model. An example for a forbidden storage
+ * would therefore be the external storage. Instead, the class loading application must either supply a designated
+ * directory, such as by creating a directory using {@link android.content.Context#getDir(String, int)} with specifying
+ * {@link android.content.Context#MODE_PRIVATE} visibility for the created folder or by using the
+ * {@code getCodeCacheDir} directory which is exposed for Android API versions 21 or higher.
+ * </p>
+ * <p>
+ * By default, this Android {@link net.bytebuddy.dynamic.loading.ClassLoadingStrategy} uses the Android SDK's dex compiler in
+ * <i>version 1.7</i> which requires the Java class files in version {@link net.bytebuddy.ClassFileVersion#JAVA_V6} as
+ * its input. This version is slightly outdated but newer versions are not available in Maven Central which is why this
+ * outdated version is included with this class loading strategy. Newer version can however be easily adapted by
+ * implementing the methods of a {@link net.bytebuddy.android.AndroidClassLoadingStrategy.DexProcessor} to
+ * appropriately delegate to the newer dex compiler. In case that the dex compiler's API was not altered, it would
+ * even be sufficient to include the newer dex compiler to the Android application's build path while also excluding
+ * the version that ships with this class loading strategy. While most parts of the Android SDK's components are
+ * licensed under the <i>Apache 2.0 license</i>, please also note
+ * <a href="https://developer.android.com/sdk/terms.html">their terms and conditions</a>.
+ * </p>
+ */
+public abstract class AndroidClassLoadingStrategy implements ClassLoadingStrategy<ClassLoader> {
+
+ /**
+ * The name of the dex file that the {@link dalvik.system.DexClassLoader} expects to find inside of a jar file
+ * that is handed to it as its argument.
+ */
+ private static final String DEX_CLASS_FILE = "classes.dex";
+
+ /**
+ * The file name extension of a jar file.
+ */
+ private static final String JAR_FILE_EXTENSION = ".jar";
+
+ /**
+ * A value for a {@link dalvik.system.DexClassLoader} to indicate that the library path is empty.
+ */
+ private static final String EMPTY_LIBRARY_PATH = null;
+
+ /**
+ * The dex creator to be used by this Android class loading strategy.
+ */
+ private final DexProcessor dexProcessor;
+
+ /**
+ * A directory that is <b>not shared with other applications</b> to be used for storing generated classes and
+ * their processed forms.
+ */
+ protected final File privateDirectory;
+
+ /**
+ * A generator for random string values.
+ */
+ protected final RandomString randomString;
+
+ /**
+ * Creates a new Android class loading strategy that uses the given folder for storing classes. The directory is not cleared
+ * by Byte Buddy after the application terminates. This remains the responsibility of the user.
+ *
+ * @param privateDirectory A directory that is <b>not shared with other applications</b> to be used for storing
+ * generated classes and their processed forms.
+ * @param dexProcessor The dex processor to be used for creating a dex file out of Java files.
+ */
+ protected AndroidClassLoadingStrategy(File privateDirectory, DexProcessor dexProcessor) {
+ if (!privateDirectory.isDirectory()) {
+ throw new IllegalArgumentException("Not a directory " + privateDirectory);
+ }
+ this.privateDirectory = privateDirectory;
+ this.dexProcessor = dexProcessor;
+ randomString = new RandomString();
+ }
+
+ @Override
+ public Map<TypeDescription, Class<?>> load(ClassLoader classLoader, Map<TypeDescription, byte[]> types) {
+ DexProcessor.Conversion conversion = dexProcessor.create();
+ for (Map.Entry<TypeDescription, byte[]> entry : types.entrySet()) {
+ conversion.register(entry.getKey().getName(), entry.getValue());
+ }
+ File jar = new File(privateDirectory, randomString.nextString() + JAR_FILE_EXTENSION);
+ try {
+ if (!jar.createNewFile()) {
+ throw new IllegalStateException("Cannot create " + jar);
+ }
+ JarOutputStream zipOutputStream = new JarOutputStream(new FileOutputStream(jar));
+ try {
+ zipOutputStream.putNextEntry(new JarEntry(DEX_CLASS_FILE));
+ conversion.drainTo(zipOutputStream);
+ zipOutputStream.closeEntry();
+ } finally {
+ zipOutputStream.close();
+ }
+ return doLoad(classLoader, types.keySet(), jar);
+ } catch (IOException exception) {
+ throw new IllegalStateException("Cannot write to zip file " + jar, exception);
+ } finally {
+ if (!jar.delete()) {
+ Logger.getLogger("net.bytebuddy").warning("Could not delete " + jar);
+ }
+ }
+ }
+
+ /**
+ * Applies the actual class loading.
+ *
+ * @param classLoader The target class loader.
+ * @param typeDescriptions Descriptions of the loaded types.
+ * @param jar A jar file containing the supplied types as dex files.
+ * @return A mapping of all type descriptions to their loaded types.
+ * @throws IOException If an I/O exception occurs.
+ */
+ protected abstract Map<TypeDescription, Class<?>> doLoad(ClassLoader classLoader, Set<TypeDescription> typeDescriptions, File jar) throws IOException;
+
+ /**
+ * A dex processor is responsible for converting a collection of Java class files into a Android dex file.
+ */
+ public interface DexProcessor {
+
+ /**
+ * Creates a new conversion process which allows to store several Java class files in the created dex
+ * file before writing this dex file to a specified {@link java.io.OutputStream}.
+ *
+ * @return A mutable conversion process.
+ */
+ Conversion create();
+
+ /**
+ * Represents an ongoing conversion of several Java class files into an Android dex file.
+ */
+ interface Conversion {
+
+ /**
+ * Adds a Java class to the generated dex file.
+ *
+ * @param name The binary name of the Java class.
+ * @param binaryRepresentation The binary representation of this class.
+ */
+ void register(String name, byte[] binaryRepresentation);
+
+ /**
+ * Writes an Android dex file containing all registered Java classes to the provided output stream.
+ *
+ * @param outputStream The output stream to write the generated dex file to.
+ * @throws IOException If an error occurs while writing the file.
+ */
+ void drainTo(OutputStream outputStream) throws IOException;
+ }
+
+ /**
+ * An implementation of a dex processor based on the Android SDK's <i>dx.jar</i> with an API that is
+ * compatible to version 1.7.
+ */
+ @EqualsAndHashCode
+ class ForSdkCompiler implements DexProcessor {
+
+ /**
+ * An API version for a DEX file that ensures compatibility to the underlying compiler.
+ */
+ private static final int DEX_COMPATIBLE_API_VERSION = 13;
+
+ /**
+ * Creates a default dex processor that ensures API version compatibility.
+ *
+ * @return A dex processor using an SDK compiler that ensures compatibility.
+ */
+ protected static DexProcessor makeDefault() {
+ DexOptions dexOptions = new DexOptions();
+ dexOptions.targetApiLevel = DEX_COMPATIBLE_API_VERSION;
+ return new ForSdkCompiler(dexOptions, new CfOptions());
+ }
+
+ /**
+ * The file name extension of a Java class file.
+ */
+ private static final String CLASS_FILE_EXTENSION = ".class";
+
+ /**
+ * Indicates that a dex file should be written without providing a human readable output.
+ */
+ private static final Writer NO_PRINT_OUTPUT = null;
+
+ /**
+ * Indicates that the dex file creation should not be verbose.
+ */
+ private static final boolean NOT_VERBOSE = false;
+
+ /**
+ * The dex file options to be applied when converting a Java class file.
+ */
+ private final DexOptions dexFileOptions;
+
+ /**
+ * The dex compiler options to be applied when converting a Java class file.
+ */
+ private final CfOptions dexCompilerOptions;
+
+ /**
+ * Creates a new Android SDK dex compiler-based dex processor.
+ *
+ * @param dexFileOptions The dex file options to apply.
+ * @param dexCompilerOptions The dex compiler options to apply.
+ */
+ public ForSdkCompiler(DexOptions dexFileOptions, CfOptions dexCompilerOptions) {
+ this.dexFileOptions = dexFileOptions;
+ this.dexCompilerOptions = dexCompilerOptions;
+ }
+
+ @Override
+ public DexProcessor.Conversion create() {
+ return new Conversion(new DexFile(dexFileOptions));
+ }
+
+ /**
+ * Represents a to-dex-file-conversion of a
+ * {@link net.bytebuddy.android.AndroidClassLoadingStrategy.DexProcessor.ForSdkCompiler}.
+ */
+ protected class Conversion implements DexProcessor.Conversion {
+
+ /**
+ * Indicates non-strict parsing of a class file.
+ */
+ private static final boolean NON_STRICT = false;
+
+ /**
+ * The dex file that is created by this conversion.
+ */
+ private final DexFile dexFile;
+
+ /**
+ * Creates a new ongoing to-dex-file conversion.
+ *
+ * @param dexFile The dex file that is created by this conversion.
+ */
+ protected Conversion(DexFile dexFile) {
+ this.dexFile = dexFile;
+ }
+
+ @Override
+ public void register(String name, byte[] binaryRepresentation) {
+ DirectClassFile directClassFile = new DirectClassFile(binaryRepresentation, name.replace('.', '/') + CLASS_FILE_EXTENSION, NON_STRICT);
+ directClassFile.setAttributeFactory(new StdAttributeFactory());
+ dexFile.add(CfTranslator.translate(directClassFile,
+ binaryRepresentation,
+ dexCompilerOptions,
+ dexFileOptions,
+ new DexFile(dexFileOptions)));
+ }
+
+ @Override
+ public void drainTo(OutputStream outputStream) throws IOException {
+ dexFile.writeTo(outputStream, NO_PRINT_OUTPUT, NOT_VERBOSE);
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private ForSdkCompiler getOuter() {
+ return ForSdkCompiler.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && ForSdkCompiler.this.equals(((Conversion) other).getOuter())
+ && dexFile.equals(((Conversion) other).dexFile);
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return dexFile.hashCode() + 31 * ForSdkCompiler.this.hashCode();
+ }
+ }
+ }
+ }
+
+ /**
+ * An Android class loading strategy that creates a wrapper class loader that loads any type.
+ */
+ @TargetApi(Build.VERSION_CODES.CUPCAKE)
+ public static class Wrapping extends AndroidClassLoadingStrategy {
+
+ /**
+ * Creates a new wrapping class loading strategy for Android that uses the default SDK-compiler based dex processor.
+ *
+ * @param privateDirectory A directory that is <b>not shared with other applications</b> to be used for storing
+ * generated classes and their processed forms.
+ */
+ public Wrapping(File privateDirectory) {
+ this(privateDirectory, DexProcessor.ForSdkCompiler.makeDefault());
+ }
+
+ /**
+ * Creates a new wrapping class loading strategy for Android.
+ *
+ * @param privateDirectory A directory that is <b>not shared with other applications</b> to be used for storing
+ * generated classes and their processed forms.
+ * @param dexProcessor The dex processor to be used for creating a dex file out of Java files.
+ */
+ public Wrapping(File privateDirectory, DexProcessor dexProcessor) {
+ super(privateDirectory, dexProcessor);
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED", justification = "Android discourages the use of access controllers")
+ protected Map<TypeDescription, Class<?>> doLoad(ClassLoader classLoader, Set<TypeDescription> typeDescriptions, File jar) {
+ ClassLoader dexClassLoader = new DexClassLoader(jar.getAbsolutePath(), privateDirectory.getAbsolutePath(), EMPTY_LIBRARY_PATH, classLoader);
+ Map<TypeDescription, Class<?>> loadedTypes = new HashMap<TypeDescription, Class<?>>();
+ for (TypeDescription typeDescription : typeDescriptions) {
+ try {
+ loadedTypes.put(typeDescription, Class.forName(typeDescription.getName(), false, dexClassLoader));
+ } catch (ClassNotFoundException exception) {
+ throw new IllegalStateException("Cannot load " + typeDescription, exception);
+ }
+ }
+ return loadedTypes;
+ }
+ }
+
+ /**
+ * An Android class loading strategy that injects types into the target class loader.
+ */
+ @TargetApi(Build.VERSION_CODES.CUPCAKE)
+ public static class Injecting extends AndroidClassLoadingStrategy {
+
+ /**
+ * A constant indicating the use of no flags.
+ */
+ private static final int NO_FLAGS = 0;
+
+ /**
+ * A file extension used for holding Android's optimized data.
+ */
+ private static final String EXTENSION = ".data";
+
+ /**
+ * Creates a new injecting class loading strategy for Android that uses the default SDK-compiler based dex processor.
+ *
+ * @param privateDirectory A directory that is <b>not shared with other applications</b> to be used for storing
+ * generated classes and their processed forms.
+ */
+ public Injecting(File privateDirectory) {
+ this(privateDirectory, DexProcessor.ForSdkCompiler.makeDefault());
+ }
+
+ /**
+ * Creates a new injecting class loading strategy for Android.
+ *
+ * @param privateDirectory A directory that is <b>not shared with other applications</b> to be used for storing
+ * generated classes and their processed forms.
+ * @param dexProcessor The dex processor to be used for creating a dex file out of Java files.
+ */
+ public Injecting(File privateDirectory, DexProcessor dexProcessor) {
+ super(privateDirectory, dexProcessor);
+ }
+
+ @Override
+ public Map<TypeDescription, Class<?>> load(ClassLoader classLoader, Map<TypeDescription, byte[]> types) {
+ if (classLoader == null) {
+ throw new IllegalArgumentException("Cannot inject classes into the bootstrap class loader on Android");
+ }
+ return super.load(classLoader, types);
+ }
+
+ @Override
+ protected Map<TypeDescription, Class<?>> doLoad(ClassLoader classLoader, Set<TypeDescription> typeDescriptions, File jar) throws IOException {
+ dalvik.system.DexFile dexFile = dalvik.system.DexFile.loadDex(jar.getAbsolutePath(),
+ new File(privateDirectory.getAbsolutePath(), randomString.nextString() + EXTENSION).getAbsolutePath(),
+ NO_FLAGS);
+ Map<TypeDescription, Class<?>> loadedTypes = new HashMap<TypeDescription, Class<?>>();
+ for (TypeDescription typeDescription : typeDescriptions) {
+ synchronized (classLoader) { // Guaranteed to be non-null by check in 'load' method.
+ Class<?> type = dexFile.loadClass(typeDescription.getName(), classLoader);
+ if (type == null) {
+ throw new IllegalStateException("Could not load " + typeDescription);
+ }
+ loadedTypes.put(typeDescription, type);
+ }
+ }
+ return loadedTypes;
+ }
+ }
+}
diff --git a/byte-buddy-android/src/main/java/net/bytebuddy/android/package-info.java b/byte-buddy-android/src/main/java/net/bytebuddy/android/package-info.java
new file mode 100644
index 0000000..97565be
--- /dev/null
+++ b/byte-buddy-android/src/main/java/net/bytebuddy/android/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * This package is dedicated to supporting Byte Buddy on Android devices.
+ */
+package net.bytebuddy.android;
diff --git a/byte-buddy-android/src/test/java/net/bytebuddy/android/AndroidClassLoadingStrategyTest.java b/byte-buddy-android/src/test/java/net/bytebuddy/android/AndroidClassLoadingStrategyTest.java
new file mode 100644
index 0000000..89953c4
--- /dev/null
+++ b/byte-buddy-android/src/test/java/net/bytebuddy/android/AndroidClassLoadingStrategyTest.java
@@ -0,0 +1,153 @@
+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 */
+ }
+}
diff --git a/byte-buddy-android/src/test/java/net/bytebuddy/test/utility/MockitoRule.java b/byte-buddy-android/src/test/java/net/bytebuddy/test/utility/MockitoRule.java
new file mode 100644
index 0000000..10df0c0
--- /dev/null
+++ b/byte-buddy-android/src/test/java/net/bytebuddy/test/utility/MockitoRule.java
@@ -0,0 +1,30 @@
+package net.bytebuddy.test.utility;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * A rule that applies Mockito's annotations to any test. This is preferred over the Mockito runner since it allows
+ * to use tests with parameters that require a specific runner.
+ */
+public class MockitoRule implements TestRule {
+
+ private final Object target;
+
+ public MockitoRule(Object target) {
+ this.target = target;
+ }
+
+ @Override
+ public Statement apply(final Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ MockitoAnnotations.initMocks(target);
+ base.evaluate();
+ }
+ };
+ }
+}
diff --git a/byte-buddy-android/src/test/java/net/bytebuddy/test/utility/ObjectPropertyAssertion.java b/byte-buddy-android/src/test/java/net/bytebuddy/test/utility/ObjectPropertyAssertion.java
new file mode 100644
index 0000000..6440328
--- /dev/null
+++ b/byte-buddy-android/src/test/java/net/bytebuddy/test/utility/ObjectPropertyAssertion.java
@@ -0,0 +1,324 @@
+package net.bytebuddy.test.utility;
+
+import net.bytebuddy.utility.CompoundList;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class ObjectPropertyAssertion<T> {
+
+ private static final boolean DEFAULT_BOOLEAN = false, OTHER_BOOLEAN = true;
+
+ private static final byte DEFAULT_BYTE = 1, OTHER_BYTE = 42;
+
+ private static final char DEFAULT_CHAR = 1, OTHER_CHAR = 42;
+
+ private static final short DEFAULT_SHORT = 1, OTHER_SHORT = 42;
+
+ private static final int DEFAULT_INT = 1, OTHER_INT = 42;
+
+ private static final long DEFAULT_LONG = 1, OTHER_LONG = 42;
+
+ private static final float DEFAULT_FLOAT = 1, OTHER_FLOAT = 42;
+
+ private static final double DEFAULT_DOUBLE = 1, OTHER_DOUBLE = 42;
+
+ private static final String DEFAULT_STRING = "foo", OTHER_STRING = "bar";
+
+ private final Class<T> type;
+
+ private final ApplicableRefinement refinement;
+
+ private final ApplicableGenerator generator;
+
+ private final ApplicableCreator creator;
+
+ private final boolean skipSynthetic;
+
+ private final String optionalToStringRegex;
+
+ private ObjectPropertyAssertion(Class<T> type,
+ ApplicableGenerator generator,
+ ApplicableRefinement refinement,
+ ApplicableCreator creator,
+ boolean skipSynthetic,
+ String optionalToStringRegex) {
+ this.type = type;
+ this.generator = generator;
+ this.refinement = refinement;
+ this.creator = creator;
+ this.skipSynthetic = skipSynthetic;
+ this.optionalToStringRegex = optionalToStringRegex;
+ }
+
+ public static <S> ObjectPropertyAssertion<S> of(Class<S> type) {
+ return new ObjectPropertyAssertion<S>(type,
+ new ApplicableGenerator(),
+ new ApplicableRefinement(),
+ new ApplicableCreator(),
+ false,
+ null
+ );
+ }
+
+ public ObjectPropertyAssertion<T> refine(Refinement<?> refinement) {
+ return new ObjectPropertyAssertion<T>(type,
+ generator,
+ this.refinement.with(refinement),
+ creator,
+ skipSynthetic,
+ optionalToStringRegex
+ );
+ }
+
+ public ObjectPropertyAssertion<T> generate(Generator<?> generator) {
+ return new ObjectPropertyAssertion<T>(type,
+ this.generator.with(generator),
+ refinement,
+ creator,
+ skipSynthetic,
+ optionalToStringRegex
+ );
+ }
+
+ public ObjectPropertyAssertion<T> create(Creator<?> creator) {
+ return new ObjectPropertyAssertion<T>(type,
+ generator,
+ refinement,
+ this.creator.with(creator),
+ skipSynthetic,
+ optionalToStringRegex
+ );
+ }
+
+ public ObjectPropertyAssertion<T> skipSynthetic() {
+ return new ObjectPropertyAssertion<T>(type, generator, refinement, creator, true, optionalToStringRegex);
+ }
+
+ public ObjectPropertyAssertion<T> specificToString(String stringRegex) {
+ return new ObjectPropertyAssertion<T>(type, generator, refinement, creator, skipSynthetic, stringRegex);
+ }
+
+ public void apply() throws IllegalAccessException, InvocationTargetException, InstantiationException {
+ for (Constructor<?> constructor : type.getDeclaredConstructors()) {
+ if (constructor.isSynthetic() && skipSynthetic) {
+ continue;
+ }
+ constructor.setAccessible(true);
+ Class<?>[] parameterTypes = constructor.getParameterTypes();
+ Object[] actualArguments = new Object[parameterTypes.length];
+ Object[] otherArguments = new Object[parameterTypes.length];
+ int index = 0;
+ for (Class<?> parameterType : parameterTypes) {
+ putInstance(parameterType, actualArguments, otherArguments, index++);
+ }
+ int testIndex = 0;
+ @SuppressWarnings("unchecked")
+ T instance = (T) constructor.newInstance(actualArguments);
+ assertThat(instance, is(instance));
+ assertThat(instance, not(is((Object) null)));
+ assertThat(instance, not(is(new Object())));
+ Object similarInstance = constructor.newInstance(actualArguments);
+ assertThat(instance.hashCode(), is(similarInstance.hashCode()));
+ assertThat(instance, is(similarInstance));
+ if (optionalToStringRegex != null) {
+ assertThat(instance.toString(), new RegexMatcher(optionalToStringRegex));
+ }
+ for (Object otherArgument : otherArguments) {
+ Object[] compareArguments = new Object[actualArguments.length];
+ int argumentIndex = 0;
+ for (Object actualArgument : actualArguments) {
+ if (argumentIndex == testIndex) {
+ compareArguments[argumentIndex] = otherArgument;
+ } else {
+ compareArguments[argumentIndex] = actualArgument;
+ }
+ argumentIndex++;
+ }
+ Object unlikeInstance = constructor.newInstance(compareArguments);
+ assertThat(instance.hashCode(), not(is(unlikeInstance)));
+ assertThat(instance, not(is(unlikeInstance)));
+ testIndex++;
+ }
+ }
+ }
+
+ private void putInstance(Class<?> parameterType, Object actualArguments, Object otherArguments, int index) {
+ Object actualArgument, otherArgument;
+ if (parameterType == boolean.class) {
+ actualArgument = DEFAULT_BOOLEAN;
+ otherArgument = OTHER_BOOLEAN;
+ } else if (parameterType == byte.class) {
+ actualArgument = DEFAULT_BYTE;
+ otherArgument = OTHER_BYTE;
+ } else if (parameterType == char.class) {
+ actualArgument = DEFAULT_CHAR;
+ otherArgument = OTHER_CHAR;
+ } else if (parameterType == short.class) {
+ actualArgument = DEFAULT_SHORT;
+ otherArgument = OTHER_SHORT;
+ } else if (parameterType == int.class) {
+ actualArgument = DEFAULT_INT;
+ otherArgument = OTHER_INT;
+ } else if (parameterType == long.class) {
+ actualArgument = DEFAULT_LONG;
+ otherArgument = OTHER_LONG;
+ } else if (parameterType == float.class) {
+ actualArgument = DEFAULT_FLOAT;
+ otherArgument = OTHER_FLOAT;
+ } else if (parameterType == double.class) {
+ actualArgument = DEFAULT_DOUBLE;
+ otherArgument = OTHER_DOUBLE;
+ } else if (parameterType.isEnum()) {
+ Object[] enumConstants = parameterType.getEnumConstants();
+ if (enumConstants.length == 1) {
+ throw new IllegalArgumentException("Enum with only one constant: " + parameterType);
+ }
+ actualArgument = enumConstants[0];
+ otherArgument = enumConstants[1];
+ } else if (parameterType.isArray()) {
+ actualArgument = Array.newInstance(parameterType.getComponentType(), 1);
+ otherArgument = Array.newInstance(parameterType.getComponentType(), 1);
+ putInstance(parameterType.getComponentType(), actualArgument, otherArgument, 0);
+ } else {
+ actualArgument = creator.replace(parameterType, generator, false);
+ refinement.apply(actualArgument);
+ otherArgument = creator.replace(parameterType, generator, true);
+ refinement.apply(otherArgument);
+ }
+ Array.set(actualArguments, index, actualArgument);
+ Array.set(otherArguments, index, otherArgument);
+ }
+
+ public interface Refinement<T> {
+
+ void apply(T mock);
+ }
+
+ public interface Generator<T> {
+
+ Class<? extends T> generate();
+ }
+
+ public interface Creator<T> {
+
+ T create();
+ }
+
+ private static class ApplicableRefinement {
+
+ private final List<Refinement<?>> refinements;
+
+ private ApplicableRefinement() {
+ refinements = Collections.emptyList();
+ }
+
+ private ApplicableRefinement(List<Refinement<?>> refinements) {
+ this.refinements = refinements;
+ }
+
+ @SuppressWarnings("unchecked")
+ private void apply(Object mock) {
+ for (Refinement refinement : refinements) {
+ ParameterizedType generic = (ParameterizedType) refinement.getClass().getGenericInterfaces()[0];
+ Class<?> restrained = generic.getActualTypeArguments()[0] instanceof ParameterizedType
+ ? (Class<?>) ((ParameterizedType) generic.getActualTypeArguments()[0]).getRawType()
+ : (Class<?>) generic.getActualTypeArguments()[0];
+ if (restrained.isInstance(mock)) {
+ refinement.apply(mock);
+ }
+ }
+ }
+
+ private ApplicableRefinement with(Refinement<?> refinement) {
+ return new ApplicableRefinement(CompoundList.of(refinements, refinement));
+ }
+ }
+
+ private static class ApplicableGenerator {
+
+ private final List<Generator<?>> generators;
+
+ private ApplicableGenerator() {
+ generators = Collections.emptyList();
+ }
+
+ private ApplicableGenerator(List<Generator<?>> generators) {
+ this.generators = generators;
+ }
+
+ private Object generate(Class<?> type, boolean alternative) {
+ for (Generator<?> generator : generators) {
+ ParameterizedType generic = (ParameterizedType) generator.getClass().getGenericInterfaces()[0];
+ Class<?> restrained = generic.getActualTypeArguments()[0] instanceof ParameterizedType
+ ? (Class<?>) ((ParameterizedType) generic.getActualTypeArguments()[0]).getRawType()
+ : (Class<?>) generic.getActualTypeArguments()[0];
+ if (type.isAssignableFrom(restrained)) {
+ type = generator.generate();
+ }
+ }
+ return type == String.class
+ ? alternative ? OTHER_STRING : DEFAULT_STRING
+ : mock(type);
+ }
+
+ private ApplicableGenerator with(Generator<?> generator) {
+ return new ApplicableGenerator(CompoundList.of(generators, generator));
+ }
+ }
+
+ private static class ApplicableCreator {
+
+ private final List<Creator<?>> creators;
+
+ private ApplicableCreator() {
+ creators = Collections.emptyList();
+ }
+
+ private ApplicableCreator(List<Creator<?>> creators) {
+ this.creators = creators;
+ }
+
+ private Object replace(Class<?> type, ApplicableGenerator generator, boolean alternative) {
+ for (Creator<?> creator : creators) {
+ ParameterizedType generic = (ParameterizedType) creator.getClass().getGenericInterfaces()[0];
+ Class<?> restrained = generic.getActualTypeArguments()[0] instanceof ParameterizedType
+ ? (Class<?>) ((ParameterizedType) generic.getActualTypeArguments()[0]).getRawType()
+ : (Class<?>) generic.getActualTypeArguments()[0];
+ if (type.isAssignableFrom(restrained)) {
+ return creator.create();
+ }
+ }
+ return generator.generate(type, alternative);
+ }
+
+ private ApplicableCreator with(Creator<?> creator) {
+ return new ApplicableCreator(CompoundList.of(creators, creator));
+ }
+ }
+
+ private static class RegexMatcher extends TypeSafeMatcher<String> {
+
+ private final String regex;
+
+ public RegexMatcher(final String regex) {
+ this.regex = regex;
+ }
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText("matches regex='" + regex + "'");
+ }
+
+ @Override
+ public boolean matchesSafely(final String string) {
+ return string.matches(regex);
+ }
+ }
+}
diff --git a/byte-buddy-benchmark/pom.xml b/byte-buddy-benchmark/pom.xml
new file mode 100644
index 0000000..4e2c7c2
--- /dev/null
+++ b/byte-buddy-benchmark/pom.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <artifactId>byte-buddy-parent</artifactId>
+ <groupId>net.bytebuddy</groupId>
+ <version>1.7.1</version>
+ </parent>
+
+ <artifactId>byte-buddy-benchmark</artifactId>
+
+ <name>Byte Buddy benchmarks</name>
+ <description>A benchmark of Byte Buddy using the JMH.</description>
+
+ <!--
+ The benchmarks of this module should be changed with care, especially in relation to recommendations for
+ code optimizations by IDEs or 'find bugs'. When changing the benchmarks, it is advisable to analyze the
+ tests' assembly code for validating what iis measured.
+
+ For running the benchmarks, build the project with the 'extras' profile and execute 'target/byte-buddy-benchmarks.jar'
+ from the command line. Running the benchmarks should happen on an idle machine and requires several hours of runtime.
+ -->
+
+ <properties>
+ <benchmark.mainClass>net.bytebuddy.benchmark.runner.BenchmarkRunner</benchmark.mainClass>
+ <!-- Newer versions require Java 7. -->
+ <version.jmh>1.16</version.jmh>
+ <version.cglib>3.2.4</version.cglib>
+ <version.javassist>3.21.0-GA</version.javassist>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>byte-buddy-dep</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>cglib</groupId>
+ <artifactId>cglib-nodep</artifactId>
+ <version>${version.cglib}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.javassist</groupId>
+ <artifactId>javassist</artifactId>
+ <version>${version.javassist}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.openjdk.jmh</groupId>
+ <artifactId>jmh-core</artifactId>
+ <version>${version.jmh}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.openjdk.jmh</groupId>
+ <artifactId>jmh-generator-annprocess</artifactId>
+ <version>${version.jmh}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <!-- Mutation testing is not adding information on benchmarking quality. -->
+ <plugin>
+ <groupId>org.pitest</groupId>
+ <artifactId>pitest-maven</artifactId>
+ <version>${version.plugin.pitest}</version>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ <!-- For micro-benchmarks, a static code analysis is of no value. -->
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>findbugs-maven-plugin</artifactId>
+ <version>${version.plugin.findbugs}</version>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <!-- Shade the benchmark artifact. -->
+ <profile>
+ <id>extras</id>
+ <activation>
+ <activeByDefault>false</activeByDefault>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <version>${version.plugin.shade}</version>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ <configuration>
+ <transformers>
+ <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+ <mainClass>org.openjdk.jmh.Main</mainClass>
+ </transformer>
+ </transformers>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/byte-buddy-benchmark/result.txt b/byte-buddy-benchmark/result.txt
new file mode 100644
index 0000000..1dad2fd
--- /dev/null
+++ b/byte-buddy-benchmark/result.txt
@@ -0,0 +1,34 @@
+Benchmark Mode Cnt Score Error Units
+ClassByExtensionBenchmark.baseline avgt 200 0,003 ± 0,001 us/op
+ClassByExtensionBenchmark.benchmarkByteBuddySpecialized avgt 200 707,334 ± 6,104 us/op
+ClassByExtensionBenchmark.benchmarkByteBuddyWithAccessor avgt 200 1803,221 ± 17,213 us/op
+ClassByExtensionBenchmark.benchmarkByteBuddyWithAccessorAndReusedDelegator avgt 200 1739,410 ± 16,667 us/op
+ClassByExtensionBenchmark.benchmarkByteBuddyWithPrefix avgt 200 753,239 ± 5,758 us/op
+ClassByExtensionBenchmark.benchmarkByteBuddyWithPrefixAndReusedDelegator avgt 200 740,982 ± 6,319 us/op
+ClassByExtensionBenchmark.benchmarkByteBuddyWithProxy avgt 200 3511,561 ± 42,849 us/op
+ClassByExtensionBenchmark.benchmarkByteBuddyWithProxyAndReusedDelegator avgt 200 3530,712 ± 47,175 us/op
+ClassByExtensionBenchmark.benchmarkCglib avgt 200 1052,087 ± 8,333 us/op
+ClassByExtensionBenchmark.benchmarkJavassist avgt 200 550,898 ± 5,262 us/op
+ClassByImplementationBenchmark.baseline avgt 200 0,003 ± 0,001 us/op
+ClassByImplementationBenchmark.benchmarkByteBuddy avgt 200 929,258 ± 7,931 us/op
+ClassByImplementationBenchmark.benchmarkCglib avgt 200 777,752 ± 11,258 us/op
+ClassByImplementationBenchmark.benchmarkJavassist avgt 200 868,235 ± 54,211 us/op
+ClassByImplementationBenchmark.benchmarkJdkProxy avgt 200 831,841 ± 9,064 us/op
+StubInvocationBenchmark.baseline avgt 200 0,002 ± 0,001 us/op
+StubInvocationBenchmark.benchmarkByteBuddy avgt 200 0,002 ± 0,001 us/op
+StubInvocationBenchmark.benchmarkCglib avgt 200 0,003 ± 0,001 us/op
+StubInvocationBenchmark.benchmarkJavassist avgt 200 0,009 ± 0,001 us/op
+StubInvocationBenchmark.benchmarkJdkProxy avgt 200 0,007 ± 0,001 us/op
+SuperClassInvocationBenchmark.baseline avgt 200 0,003 ± 0,001 us/op
+SuperClassInvocationBenchmark.benchmarkByteBuddySpecialized avgt 200 0,003 ± 0,001 us/op
+SuperClassInvocationBenchmark.benchmarkByteBuddyWithAccessor avgt 200 0,020 ± 0,001 us/op
+SuperClassInvocationBenchmark.benchmarkByteBuddyWithPrefix avgt 200 0,003 ± 0,001 us/op
+SuperClassInvocationBenchmark.benchmarkByteBuddyWithProxy avgt 200 0,003 ± 0,001 us/op
+SuperClassInvocationBenchmark.benchmarkCglib avgt 200 0,018 ± 0,001 us/op
+SuperClassInvocationBenchmark.benchmarkJavassist avgt 200 0,023 ± 0,001 us/op
+TrivialClassCreationBenchmark.baseline avgt 200 0,003 ± 0,001 us/op
+TrivialClassCreationBenchmark.benchmarkByteBuddy avgt 200 97,950 ± 0,622 us/op
+TrivialClassCreationBenchmark.benchmarkCglib avgt 200 424,517 ± 21,662 us/op
+TrivialClassCreationBenchmark.benchmarkJavassist avgt 200 137,201 ± 2,149 us/op
+TrivialClassCreationBenchmark.benchmarkJdkProxy avgt 200 50,867 ± 0,404 us/op
+
diff --git a/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/ClassByExtensionBenchmark.java b/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/ClassByExtensionBenchmark.java
new file mode 100644
index 0000000..539950b
--- /dev/null
+++ b/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/ClassByExtensionBenchmark.java
@@ -0,0 +1,572 @@
+package net.bytebuddy.benchmark;
+
+import javassist.util.proxy.MethodFilter;
+import javassist.util.proxy.MethodHandler;
+import javassist.util.proxy.ProxyFactory;
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.benchmark.specimen.ExampleClass;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.dynamic.scaffold.TypeValidation;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.MethodDelegation;
+import net.bytebuddy.implementation.SuperMethodCall;
+import net.bytebuddy.implementation.bind.annotation.*;
+import net.bytebuddy.pool.TypePool;
+import net.sf.cglib.proxy.*;
+import org.openjdk.jmh.annotations.*;
+
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static net.bytebuddy.matcher.ElementMatchers.none;
+
+/**
+ * <p>
+ * This benchmark dynamically creates a subclass of {@link ExampleClass} which overrides all methods to invoke the
+ * direct super class's implementation. The benchmark furthermore creates an instance of this class since some
+ * code generation frameworks rely on this property. Because this benchmark requires the creation of a subclass,
+ * the JDK proxy is not included in this benchmark.
+ * </p>
+ * <p>
+ * Note that this class defines all values that are accessed by benchmark methods as instance fields. This way, the JIT
+ * compiler's capability of constant folding is limited in order to produce more comparable test results.
+ * </p>
+ */
+ at State(Scope.Thread)
+ at BenchmarkMode(Mode.AverageTime)
+ at OutputTimeUnit(TimeUnit.MICROSECONDS)
+public class ClassByExtensionBenchmark {
+
+ /**
+ * The base class to be subclassed in all benchmarks.
+ */
+ public static final Class<? extends ExampleClass> BASE_CLASS = ExampleClass.class;
+
+ /**
+ * The base class to be subclassed in all benchmarks.
+ */
+ private Class<? extends ExampleClass> baseClass = BASE_CLASS;
+
+ /**
+ * The zero-length of the class loader's URL.
+ */
+ private int urlLength = 0;
+
+ /**
+ * Creates a new class loader. By using a fresh class loader for each creation, we avoid name space issues.
+ * A class loader's creation is part of the benchmark but since any test creates a class loader exactly once,
+ * the benchmark remains valid.
+ *
+ * @return A new class loader.
+ */
+ private ClassLoader newClassLoader() {
+ return new URLClassLoader(new URL[urlLength]);
+ }
+
+ /**
+ * An implementation to be used by {@link ClassByExtensionBenchmark#benchmarkByteBuddyWithProxyAndReusedDelegator()}.
+ */
+ private Implementation proxyInterceptor;
+
+ /**
+ * An implementation to be used by {@link ClassByExtensionBenchmark#benchmarkByteBuddyWithAccessorAndReusedDelegator()}.
+ */
+ private Implementation accessInterceptor;
+
+ /**
+ * An implementation to be used by {@link ClassByExtensionBenchmark#benchmarkByteBuddyWithPrefixAndReusedDelegator()}.
+ */
+ private Implementation.Composable prefixInterceptor;
+
+ /**
+ * A description of {@link ClassByExtensionBenchmark#baseClass}.
+ */
+ private TypeDescription baseClassDescription;
+
+ /**
+ * A description of {@link ByteBuddyProxyInterceptor}.
+ */
+ private TypeDescription proxyClassDescription;
+
+ /**
+ * A description of {@link ByteBuddyAccessInterceptor}.
+ */
+ private TypeDescription accessClassDescription;
+
+ /**
+ * A description of {@link ByteBuddyPrefixInterceptor}.
+ */
+ private TypeDescription prefixClassDescription;
+
+ /**
+ * A method delegation to {@link ByteBuddyProxyInterceptor}.
+ */
+ private Implementation proxyInterceptorDescription;
+
+ /**
+ * A method delegation to {@link ByteBuddyAccessInterceptor}.
+ */
+ private Implementation accessInterceptorDescription;
+
+ /**
+ * A method delegation to {@link ByteBuddyPrefixInterceptor}.
+ */
+ private Implementation.Composable prefixInterceptorDescription;
+
+ /**
+ * A setup method to create precomputed delegator.
+ */
+ @Setup
+ public void setup() {
+ proxyInterceptor = MethodDelegation.to(ByteBuddyProxyInterceptor.class);
+ accessInterceptor = MethodDelegation.to(ByteBuddyAccessInterceptor.class);
+ prefixInterceptor = MethodDelegation.to(ByteBuddyPrefixInterceptor.class);
+ baseClassDescription = TypePool.Default.ofClassPath().describe(baseClass.getName()).resolve();
+ proxyClassDescription = TypePool.Default.ofClassPath().describe(ByteBuddyProxyInterceptor.class.getName()).resolve();
+ accessClassDescription = TypePool.Default.ofClassPath().describe(ByteBuddyAccessInterceptor.class.getName()).resolve();
+ prefixClassDescription = TypePool.Default.ofClassPath().describe(ByteBuddyPrefixInterceptor.class.getName()).resolve();
+ proxyInterceptorDescription = MethodDelegation.to(proxyClassDescription);
+ accessInterceptorDescription = MethodDelegation.to(accessClassDescription);
+ prefixInterceptorDescription = MethodDelegation.to(prefixClassDescription);
+ }
+
+ /**
+ * Creates a baseline for the benchmark.
+ *
+ * @return A simple object that is not transformed.
+ */
+ @Benchmark
+ public ExampleClass baseline() {
+ return new ExampleClass();
+ }
+
+ /**
+ * Performs a benchmark of a class extension using Byte Buddy. This benchmark creates proxy classes for the invocation
+ * of super methods which requires the creation of auxiliary classes.
+ *
+ * @return The created instance, in order to avoid JIT removal.
+ * @throws java.lang.Exception If the invocation causes an exception.
+ */
+ @Benchmark
+ public ExampleClass benchmarkByteBuddyWithProxy() throws Exception {
+ return new ByteBuddy()
+ .with(TypeValidation.DISABLED)
+ .ignore(none())
+ .subclass(baseClass)
+ .method(isDeclaredBy(baseClass)).intercept(MethodDelegation.to(ByteBuddyProxyInterceptor.class))
+ .make()
+ .load(newClassLoader(), ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance();
+ }
+
+ /**
+ * Performs a benchmark of a class extension using Byte Buddy. This benchmark also uses the annotation-based approach
+ * but creates delegation methods which do not require the creation of additional classes. This benchmark reuses a
+ * precomputed delegator.
+ *
+ * @return The created instance, in order to avoid JIT removal.
+ * @throws Exception If the invocation causes an exception.
+ */
+ @Benchmark
+ public ExampleClass benchmarkByteBuddyWithProxyAndReusedDelegator() throws Exception {
+ return new ByteBuddy()
+ .with(TypeValidation.DISABLED)
+ .ignore(none())
+ .subclass(baseClass)
+ .method(isDeclaredBy(baseClass)).intercept(proxyInterceptor)
+ .make()
+ .load(newClassLoader(), ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance();
+ }
+
+ /**
+ * Performs a benchmark of a class extension using Byte Buddy. This benchmark creates proxy classes for the invocation
+ * of super methods which requires the creation of auxiliary classes. This benchmark uses a type pool to compare against
+ * usage of the reflection API.
+ *
+ * @return The created instance, in order to avoid JIT removal.
+ * @throws java.lang.Exception If the invocation causes an exception.
+ */
+ @Benchmark
+ public ExampleClass benchmarkByteBuddyWithProxyWithTypePool() throws Exception {
+ return (ExampleClass) new ByteBuddy()
+ .with(TypeValidation.DISABLED)
+ .ignore(none())
+ .subclass(baseClassDescription)
+ .method(isDeclaredBy(baseClassDescription)).intercept(MethodDelegation.to(proxyClassDescription))
+ .make()
+ .load(newClassLoader(), ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance();
+ }
+
+ /**
+ * Performs a benchmark of a class extension using Byte Buddy. This benchmark also uses the annotation-based approach
+ * but creates delegation methods which do not require the creation of additional classes. This benchmark reuses a
+ * precomputed delegator. This benchmark uses a type pool to compare against usage of the reflection API.
+ *
+ * @return The created instance, in order to avoid JIT removal.
+ * @throws Exception If the invocation causes an exception.
+ */
+ @Benchmark
+ public ExampleClass benchmarkByteBuddyWithProxyAndReusedDelegatorWithTypePool() throws Exception {
+ return (ExampleClass) new ByteBuddy()
+ .with(TypeValidation.DISABLED)
+ .ignore(none())
+ .subclass(baseClassDescription)
+ .method(isDeclaredBy(baseClassDescription)).intercept(proxyInterceptorDescription)
+ .make()
+ .load(newClassLoader(), ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance();
+ }
+
+ /**
+ * Performs a benchmark of a class extension using Byte Buddy. This benchmark also uses the annotation-based approach
+ * but creates delegation methods which do not require the creation of additional classes.
+ *
+ * @return The created instance, in order to avoid JIT removal.
+ * @throws Exception If the invocation causes an exception.
+ */
+ @Benchmark
+ public ExampleClass benchmarkByteBuddyWithAccessor() throws Exception {
+ return new ByteBuddy()
+ .with(TypeValidation.DISABLED)
+ .ignore(none())
+ .subclass(baseClass)
+ .method(isDeclaredBy(baseClass)).intercept(MethodDelegation.to(ByteBuddyAccessInterceptor.class))
+ .make()
+ .load(newClassLoader(), ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance();
+ }
+
+ /**
+ * Performs a benchmark of a class extension using Byte Buddy. This benchmark also uses the annotation-based approach
+ * but creates delegation methods which do not require the creation of additional classes. This benchmark reuses a
+ * precomputed delegator.
+ *
+ * @return The created instance, in order to avoid JIT removal.
+ * @throws Exception If the invocation causes an exception.
+ */
+ @Benchmark
+ public ExampleClass benchmarkByteBuddyWithAccessorAndReusedDelegator() throws Exception {
+ return new ByteBuddy()
+ .with(TypeValidation.DISABLED)
+ .ignore(none())
+ .subclass(baseClass)
+ .method(isDeclaredBy(baseClass)).intercept(accessInterceptor)
+ .make()
+ .load(newClassLoader(), ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance();
+ }
+
+ /**
+ * Performs a benchmark of a class extension using Byte Buddy. This benchmark also uses the annotation-based approach
+ * but creates delegation methods which do not require the creation of additional classes. This benchmark uses a type
+ * pool to compare against usage of the reflection API.
+ *
+ * @return The created instance, in order to avoid JIT removal.
+ * @throws Exception If the invocation causes an exception.
+ */
+ @Benchmark
+ public ExampleClass benchmarkByteBuddyWithAccessorWithTypePool() throws Exception {
+ return (ExampleClass) new ByteBuddy()
+ .with(TypeValidation.DISABLED)
+ .ignore(none())
+ .subclass(baseClassDescription)
+ .method(isDeclaredBy(baseClassDescription)).intercept(MethodDelegation.to(accessClassDescription))
+ .make()
+ .load(newClassLoader(), ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance();
+ }
+
+ /**
+ * Performs a benchmark of a class extension using Byte Buddy. This benchmark also uses the annotation-based approach
+ * but creates delegation methods which do not require the creation of additional classes. This benchmark reuses a
+ * precomputed delegator. This benchmark uses a type pool to compare against usage of the reflection API.
+ *
+ * @return The created instance, in order to avoid JIT removal.
+ * @throws Exception If the invocation causes an exception.
+ */
+ @Benchmark
+ public ExampleClass benchmarkByteBuddyWithAccessorAndReusedDelegatorWithTypePool() throws Exception {
+ return (ExampleClass) new ByteBuddy()
+ .with(TypeValidation.DISABLED)
+ .ignore(none())
+ .subclass(baseClassDescription)
+ .method(isDeclaredBy(baseClassDescription)).intercept(accessInterceptorDescription)
+ .make()
+ .load(newClassLoader(), ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance();
+ }
+
+ /**
+ * Performs a benchmark of a class extension using Byte Buddy. This benchmark uses delegation but completes with a
+ * hard-coded super method call.
+ *
+ * @return The created instance, in order to avoid JIT removal.
+ * @throws Exception If the invocation causes an exception.
+ */
+ @Benchmark
+ public ExampleClass benchmarkByteBuddyWithPrefix() throws Exception {
+ return new ByteBuddy()
+ .with(TypeValidation.DISABLED)
+ .ignore(none())
+ .subclass(baseClass)
+ .method(isDeclaredBy(baseClass)).intercept(MethodDelegation.to(ByteBuddyPrefixInterceptor.class).andThen(SuperMethodCall.INSTANCE))
+ .make()
+ .load(newClassLoader(), ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance();
+ }
+
+ /**
+ * Performs a benchmark of a class extension using Byte Buddy. This benchmark uses delegation but completes with a
+ * hard-coded super method call. This benchmark reuses a precomputed delegator.
+ *
+ * @return The created instance, in order to avoid JIT removal.
+ * @throws Exception If the invocation causes an exception.
+ */
+ @Benchmark
+ public ExampleClass benchmarkByteBuddyWithPrefixAndReusedDelegator() throws Exception {
+ return new ByteBuddy()
+ .with(TypeValidation.DISABLED)
+ .ignore(none())
+ .subclass(baseClass)
+ .method(isDeclaredBy(baseClass)).intercept(prefixInterceptor.andThen(SuperMethodCall.INSTANCE))
+ .make()
+ .load(newClassLoader(), ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance();
+ }
+
+ /**
+ * Performs a benchmark of a class extension using Byte Buddy. This benchmark uses delegation but completes with a
+ * hard-coded super method call. This benchmark uses a type pool to compare against usage of the reflection API.
+ *
+ * @return The created instance, in order to avoid JIT removal.
+ * @throws Exception If the invocation causes an exception.
+ */
+ @Benchmark
+ public ExampleClass benchmarkByteBuddyWithPrefixWithTypePool() throws Exception {
+ return (ExampleClass) new ByteBuddy()
+ .with(TypeValidation.DISABLED)
+ .ignore(none())
+ .subclass(baseClassDescription)
+ .method(isDeclaredBy(baseClassDescription)).intercept(MethodDelegation.to(prefixClassDescription).andThen(SuperMethodCall.INSTANCE))
+ .make()
+ .load(newClassLoader(), ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance();
+ }
+
+ /**
+ * Performs a benchmark of a class extension using Byte Buddy. This benchmark uses delegation but completes with a
+ * hard-coded super method call. This benchmark reuses a precomputed delegator. This benchmark uses a type pool to
+ * compare against usage of the reflection API.
+ *
+ * @return The created instance, in order to avoid JIT removal.
+ * @throws Exception If the invocation causes an exception.
+ */
+ @Benchmark
+ public ExampleClass benchmarkByteBuddyWithPrefixAndReusedDelegatorWithTypePool() throws Exception {
+ return (ExampleClass) new ByteBuddy()
+ .with(TypeValidation.DISABLED)
+ .ignore(none())
+ .subclass(baseClassDescription)
+ .method(isDeclaredBy(baseClassDescription)).intercept(prefixInterceptorDescription.andThen(SuperMethodCall.INSTANCE))
+ .make()
+ .load(newClassLoader(), ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance();
+ }
+
+ /**
+ * Performs a benchmark of a class extension using Byte Buddy. This benchmark uses a specialized interception
+ * strategy which is easier to inline by the compiler.
+ *
+ * @return The created instance, in order to avoid JIT removal.
+ * @throws java.lang.Exception If the invocation causes an exception.
+ */
+ @Benchmark
+ public ExampleClass benchmarkByteBuddySpecialized() throws Exception {
+ return new ByteBuddy()
+ .with(TypeValidation.DISABLED)
+ .ignore(none())
+ .subclass(baseClass)
+ .method(isDeclaredBy(baseClass)).intercept(SuperMethodCall.INSTANCE)
+ .make()
+ .load(newClassLoader(), ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance();
+ }
+
+ /**
+ * Performs a benchmark of a class extension using cglib.
+ *
+ * @return The created instance, in order to avoid JIT removal.
+ */
+ @Benchmark
+ public ExampleClass benchmarkCglib() {
+ Enhancer enhancer = new Enhancer();
+ enhancer.setUseCache(false);
+ enhancer.setUseFactory(false);
+ enhancer.setInterceptDuringConstruction(true);
+ enhancer.setClassLoader(newClassLoader());
+ enhancer.setSuperclass(baseClass);
+ CallbackHelper callbackHelper = new CallbackHelper(baseClass, new Class[0]) {
+ @Override
+ protected Object getCallback(Method method) {
+ if (method.getDeclaringClass() == baseClass) {
+ return new MethodInterceptor() {
+ @Override
+ public Object intercept(Object object,
+ Method method,
+ Object[] arguments,
+ MethodProxy methodProxy) throws Throwable {
+ return methodProxy.invokeSuper(object, arguments);
+ }
+ };
+ } else {
+ return NoOp.INSTANCE;
+ }
+ }
+ };
+ enhancer.setCallbackFilter(callbackHelper);
+ enhancer.setCallbacks(callbackHelper.getCallbacks());
+ return (ExampleClass) enhancer.create();
+ }
+
+ /**
+ * Performs a benchmark of a class extension using javassist proxies.
+ *
+ * @return The created instance, in order to avoid JIT removal.
+ * @throws java.lang.Exception If the invocation causes an exception.
+ */
+ @Benchmark
+ public ExampleClass benchmarkJavassist() throws Exception {
+ ProxyFactory proxyFactory = new ProxyFactory() {
+ @Override
+ protected ClassLoader getClassLoader() {
+ return newClassLoader();
+ }
+ };
+ proxyFactory.setUseCache(false);
+ proxyFactory.setSuperclass(baseClass);
+ proxyFactory.setFilter(new MethodFilter() {
+ public boolean isHandled(Method method) {
+ return method.getDeclaringClass() == baseClass;
+ }
+ });
+ @SuppressWarnings("unchecked")
+ Object instance = proxyFactory.createClass().getDeclaredConstructor().newInstance();
+ ((javassist.util.proxy.Proxy) instance).setHandler(new MethodHandler() {
+ public Object invoke(Object self,
+ Method thisMethod,
+ Method proceed,
+ Object[] args) throws Throwable {
+ return proceed.invoke(self, args);
+ }
+ });
+ return (ExampleClass) instance;
+ }
+
+ /**
+ * Instead of using the {@link net.bytebuddy.implementation.SuperMethodCall} implementation, we are using
+ * a delegate in order to emulate the interception approach of other instrumentation libraries. Otherwise,
+ * this benchmark would be biased in favor of Byte Buddy.
+ */
+ public static class ByteBuddyProxyInterceptor {
+
+ /**
+ * The interceptor's constructor is not supposed to be invoked.
+ */
+ private ByteBuddyProxyInterceptor() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Call the super method.
+ *
+ * @param zuper A proxy for invoking the super method.
+ * @return The return value of the super method invocation.
+ * @throws Exception As declared by {@link java.util.concurrent.Callable}'s contract.
+ */
+ @RuntimeType
+ public static Object intercept(@SuperCall Callable<?> zuper) throws Exception {
+ return zuper.call();
+ }
+ }
+
+ /**
+ * Instead of using the {@link net.bytebuddy.implementation.SuperMethodCall} implementation, we are creating
+ * delegate methods that allow the invocation of the original code.
+ */
+ public static class ByteBuddyAccessInterceptor {
+
+ /**
+ * The interceptor's constructor is not supposed to be invoked.
+ */
+ private ByteBuddyAccessInterceptor() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Calls the super method.
+ *
+ * @param target The target instance.
+ * @param arguments The arguments to the method.
+ * @param method A method for invoking the original code.
+ * @return The return value of the method.
+ * @throws Exception If the super method call yields an exception.
+ */
+ @RuntimeType
+ public static Object intercept(@This Object target, @AllArguments Object[] arguments, @SuperMethod Method method) throws Exception {
+ return method.invoke(target, arguments);
+ }
+ }
+
+ /**
+ * An interceptor that is invoked prior to a super method call.
+ */
+ public static class ByteBuddyPrefixInterceptor {
+
+ /**
+ * The interceptor's constructor is not supposed to be invoked.
+ */
+ private ByteBuddyPrefixInterceptor() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Invoked prior to a method call.
+ */
+ public static void intercept() {
+ /* do nothing */
+ }
+ }
+}
diff --git a/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/ClassByImplementationBenchmark.java b/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/ClassByImplementationBenchmark.java
new file mode 100644
index 0000000..6e95f99
--- /dev/null
+++ b/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/ClassByImplementationBenchmark.java
@@ -0,0 +1,444 @@
+package net.bytebuddy.benchmark;
+
+import javassist.util.proxy.MethodFilter;
+import javassist.util.proxy.MethodHandler;
+import javassist.util.proxy.ProxyFactory;
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.benchmark.specimen.ExampleInterface;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.dynamic.scaffold.TypeValidation;
+import net.bytebuddy.implementation.StubMethod;
+import net.bytebuddy.pool.TypePool;
+import net.sf.cglib.proxy.CallbackHelper;
+import net.sf.cglib.proxy.Enhancer;
+import net.sf.cglib.proxy.FixedValue;
+import net.sf.cglib.proxy.NoOp;
+import org.openjdk.jmh.annotations.*;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.concurrent.TimeUnit;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static net.bytebuddy.matcher.ElementMatchers.none;
+
+/**
+ * <p>
+ * This benchmark dynamically creates a class which implements {@link net.bytebuddy.benchmark.specimen.ExampleInterface}
+ * which overrides all methods to invoke the direct super class's implementation. The benchmark furthermore creates an
+ * instance of this class since some code generation frameworks rely on this property.
+ * </p>
+ * <p>
+ * Note that this class defines all values that are accessed by benchmark methods as instance fields. This way, the JIT
+ * compiler's capability of constant folding is limited in order to produce more comparable test results.
+ * </p>
+ */
+ at State(Scope.Thread)
+ at BenchmarkMode(Mode.AverageTime)
+ at OutputTimeUnit(TimeUnit.MICROSECONDS)
+public class ClassByImplementationBenchmark {
+
+ /**
+ * The base class to be subclassed in all benchmarks.
+ */
+ public static final Class<? extends ExampleInterface> BASE_CLASS = ExampleInterface.class;
+
+ /**
+ * The default reference value. By defining the default reference value as a string type instead of as an object
+ * type, the field is inlined by the compiler, similar to the primitive values.
+ */
+ public static final String DEFAULT_REFERENCE_VALUE = null;
+
+ /**
+ * The default {@code boolean} value.
+ */
+ public static final boolean DEFAULT_BOOLEAN_VALUE = false;
+
+ /**
+ * The default {@code byte} value.
+ */
+ public static final byte DEFAULT_BYTE_VALUE = 0;
+
+ /**
+ * The default {@code short} value.
+ */
+ public static final short DEFAULT_SHORT_VALUE = 0;
+
+ /**
+ * The default {@code char} value.
+ */
+ public static final char DEFAULT_CHAR_VALUE = 0;
+
+ /**
+ * The default {@code int} value.
+ */
+ public static final int DEFAULT_INT_VALUE = 0;
+
+ /**
+ * The default {@code long} value.
+ */
+ public static final long DEFAULT_LONG_VALUE = 0L;
+
+ /**
+ * The default {@code float} value.
+ */
+ public static final float DEFAULT_FLOAT_VALUE = 0f;
+
+ /**
+ * The default {@code double} value.
+ */
+ public static final double DEFAULT_DOUBLE_VALUE = 0d;
+
+ /**
+ * The base class to be subclassed in all benchmarks.
+ */
+ private Class<? extends ExampleInterface> baseClass = BASE_CLASS;
+
+ /**
+ * The default reference value. By defining the default reference value as a string type instead of as an object
+ * type, the field is inlined by the compiler, similar to the primitive values.
+ */
+ private String defaultReferenceValue = DEFAULT_REFERENCE_VALUE;
+
+ /**
+ * The default {@code boolean} value.
+ */
+ private boolean defaultBooleanValue = DEFAULT_BOOLEAN_VALUE;
+
+ /**
+ * The default {@code byte} value.
+ */
+ private byte defaultByteValue = DEFAULT_BYTE_VALUE;
+
+ /**
+ * The default {@code short} value.
+ */
+ private short defaultShortValue = DEFAULT_SHORT_VALUE;
+
+ /**
+ * The default {@code char} value.
+ */
+ private char defaultCharValue = DEFAULT_CHAR_VALUE;
+
+ /**
+ * The default {@code int} value.
+ */
+ private int defaultIntValue = DEFAULT_INT_VALUE;
+
+ /**
+ * The default {@code long} value.
+ */
+ private long defaultLongValue = DEFAULT_LONG_VALUE;
+
+ /**
+ * The default {@code float} value.
+ */
+ private float defaultFloatValue = DEFAULT_FLOAT_VALUE;
+
+ /**
+ * The default {@code double} value.
+ */
+ private double defaultDoubleValue = DEFAULT_DOUBLE_VALUE;
+
+ /**
+ * The zero-length of the class loader's URL.
+ */
+ private int urlLength = 0;
+
+ /**
+ * Creates a new class loader. By using a fresh class loader for each creation, we avoid name space issues.
+ * A class loader's creation is part of the benchmark but since any test creates a class loader exactly once,
+ * the benchmark remains valid.
+ *
+ * @return A new class loader.
+ */
+ private ClassLoader newClassLoader() {
+ return new URLClassLoader(new URL[urlLength]);
+ }
+
+ /**
+ * A description of {@link ClassByExtensionBenchmark#baseClass}.
+ */
+ private TypeDescription baseClassDescription;
+
+ /**
+ * Sets up this benchmark.
+ */
+ @Setup
+ public void setup() {
+ baseClassDescription = TypePool.Default.ofClassPath().describe(baseClass.getName()).resolve();
+ }
+
+ /**
+ * Creates a baseline for the benchmark.
+ *
+ * @return A simple object that is not transformed.
+ */
+ @Benchmark
+ public ExampleInterface baseline() {
+ return new ExampleInterface() {
+ @Override
+ public boolean method(boolean arg) {
+ return false;
+ }
+
+ @Override
+ public byte method(byte arg) {
+ return 0;
+ }
+
+ @Override
+ public short method(short arg) {
+ return 0;
+ }
+
+ @Override
+ public int method(int arg) {
+ return 0;
+ }
+
+ @Override
+ public char method(char arg) {
+ return 0;
+ }
+
+ @Override
+ public long method(long arg) {
+ return 0;
+ }
+
+ @Override
+ public float method(float arg) {
+ return 0;
+ }
+
+ @Override
+ public double method(double arg) {
+ return 0;
+ }
+
+ @Override
+ public Object method(Object arg) {
+ return null;
+ }
+
+ @Override
+ public boolean[] method(boolean arg1, boolean arg2, boolean arg3) {
+ return null;
+ }
+
+ @Override
+ public byte[] method(byte arg1, byte arg2, byte arg3) {
+ return null;
+ }
+
+ @Override
+ public short[] method(short arg1, short arg2, short arg3) {
+ return null;
+ }
+
+ @Override
+ public int[] method(int arg1, int arg2, int arg3) {
+ return null;
+ }
+
+ @Override
+ public char[] method(char arg1, char arg2, char arg3) {
+ return null;
+ }
+
+ @Override
+ public long[] method(long arg1, long arg2, long arg3) {
+ return null;
+ }
+
+ @Override
+ public float[] method(float arg1, float arg2, float arg3) {
+ return null;
+ }
+
+ @Override
+ public double[] method(double arg1, double arg2, double arg3) {
+ return null;
+ }
+
+ @Override
+ public Object[] method(Object arg1, Object arg2, Object arg3) {
+ return null;
+ }
+ };
+ }
+
+ /**
+ * Performs a benchmark of an interface implementation using Byte Buddy.
+ *
+ * @return The created instance, in order to avoid JIT removal.
+ * @throws java.lang.Exception If the reflective invocation causes an exception.
+ */
+ @Benchmark
+ public ExampleInterface benchmarkByteBuddy() throws Exception {
+ return new ByteBuddy()
+ .with(TypeValidation.DISABLED)
+ .ignore(none())
+ .subclass(baseClass)
+ .method(isDeclaredBy(baseClass)).intercept(StubMethod.INSTANCE)
+ .make()
+ .load(newClassLoader(), ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance();
+ }
+
+ /**
+ * Performs a benchmark of an interface implementation using Byte Buddy. This benchmark uses a type pool to compare against
+ * usage of the reflection API.
+ *
+ * @return The created instance, in order to avoid JIT removal.
+ * @throws java.lang.Exception If the reflective invocation causes an exception.
+ */
+ @Benchmark
+ public ExampleInterface benchmarkByteBuddyWithTypePool() throws Exception {
+ return (ExampleInterface) new ByteBuddy()
+ .with(TypeValidation.DISABLED)
+ .ignore(none())
+ .subclass(baseClassDescription)
+ .method(isDeclaredBy(baseClassDescription)).intercept(StubMethod.INSTANCE)
+ .make()
+ .load(newClassLoader(), ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance();
+ }
+
+ /**
+ * Performs a benchmark of an interface implementation using cglib.
+ *
+ * @return The created instance, in order to avoid JIT removal.
+ */
+ @Benchmark
+ public ExampleInterface benchmarkCglib() {
+ Enhancer enhancer = new Enhancer();
+ enhancer.setUseCache(false);
+ enhancer.setClassLoader(newClassLoader());
+ enhancer.setSuperclass(baseClass);
+ CallbackHelper callbackHelper = new CallbackHelper(Object.class, new Class[]{baseClass}) {
+ @Override
+ protected Object getCallback(Method method) {
+ if (method.getDeclaringClass() == baseClass) {
+ return new FixedValue() {
+ @Override
+ public Object loadObject() throws Exception {
+ return null;
+ }
+ };
+ } else {
+ return NoOp.INSTANCE;
+ }
+ }
+ };
+ enhancer.setCallbackFilter(callbackHelper);
+ enhancer.setCallbacks(callbackHelper.getCallbacks());
+ return (ExampleInterface) enhancer.create();
+ }
+
+ /**
+ * Performs a benchmark of an interface implementation using javassist proxies.
+ *
+ * @return The created instance, in order to avoid JIT removal.
+ * @throws java.lang.Exception If the reflective invocation causes an exception.
+ */
+ @Benchmark
+ public ExampleInterface benchmarkJavassist() throws Exception {
+ ProxyFactory proxyFactory = new ProxyFactory();
+ proxyFactory.setUseCache(false);
+ ProxyFactory.classLoaderProvider = new ProxyFactory.ClassLoaderProvider() {
+ @Override
+ public ClassLoader get(ProxyFactory proxyFactory) {
+ return newClassLoader();
+ }
+ };
+ proxyFactory.setSuperclass(Object.class);
+ proxyFactory.setInterfaces(new Class<?>[]{baseClass});
+ proxyFactory.setFilter(new MethodFilter() {
+ public boolean isHandled(Method method) {
+ return true;
+ }
+ });
+ @SuppressWarnings("unchecked")
+ Object instance = proxyFactory.createClass().getDeclaredConstructor().newInstance();
+ ((javassist.util.proxy.Proxy) instance).setHandler(new MethodHandler() {
+ public Object invoke(Object self,
+ Method thisMethod,
+ Method proceed,
+ Object[] args) throws Throwable {
+ Class<?> returnType = thisMethod.getReturnType();
+ if (returnType.isPrimitive()) {
+ if (returnType == boolean.class) {
+ return defaultBooleanValue;
+ } else if (returnType == byte.class) {
+ return defaultByteValue;
+ } else if (returnType == short.class) {
+ return defaultShortValue;
+ } else if (returnType == char.class) {
+ return defaultCharValue;
+ } else if (returnType == int.class) {
+ return defaultIntValue;
+ } else if (returnType == long.class) {
+ return defaultLongValue;
+ } else if (returnType == float.class) {
+ return defaultFloatValue;
+ } else {
+ return defaultDoubleValue;
+ }
+ } else {
+ return defaultReferenceValue;
+ }
+ }
+ });
+ return (ExampleInterface) instance;
+ }
+
+ /**
+ * Performs a benchmark of an interface implementation using the Java Class Library's utilities.
+ *
+ * @return The created instance, in order to avoid JIT removal.
+ * @throws java.lang.Exception If the reflective invocation causes an exception.
+ */
+ @Benchmark
+ public ExampleInterface benchmarkJdkProxy() throws Exception {
+ return (ExampleInterface) Proxy.newProxyInstance(newClassLoader(),
+ new Class<?>[]{baseClass},
+ new InvocationHandler() {
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ Class<?> returnType = method.getReturnType();
+ if (returnType.isPrimitive()) {
+ if (returnType == boolean.class) {
+ return defaultBooleanValue;
+ } else if (returnType == byte.class) {
+ return defaultByteValue;
+ } else if (returnType == short.class) {
+ return defaultShortValue;
+ } else if (returnType == char.class) {
+ return defaultCharValue;
+ } else if (returnType == int.class) {
+ return defaultIntValue;
+ } else if (returnType == long.class) {
+ return defaultLongValue;
+ } else if (returnType == float.class) {
+ return defaultFloatValue;
+ } else {
+ return defaultDoubleValue;
+ }
+ } else {
+ return defaultReferenceValue;
+ }
+ }
+ }
+ );
+ }
+}
diff --git a/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/StubInvocationBenchmark.java b/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/StubInvocationBenchmark.java
new file mode 100644
index 0000000..2a875e2
--- /dev/null
+++ b/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/StubInvocationBenchmark.java
@@ -0,0 +1,260 @@
+package net.bytebuddy.benchmark;
+
+import net.bytebuddy.benchmark.specimen.ExampleInterface;
+import org.openjdk.jmh.annotations.*;
+import org.openjdk.jmh.infra.Blackhole;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * <p>
+ * This benchmark measures the invocation speed of stub method invocations. All classes implement
+ * {@link net.bytebuddy.benchmark.specimen.ExampleInterface} and implement all methods to return the return type's
+ * default value, independently of the arguments.
+ * </p>
+ * <p>
+ * Note that this class defines all values that are accessed by benchmark methods as instance fields. This way, the JIT
+ * compiler's capability of constant folding is limited in order to produce more comparable test results.
+ * </p>
+ */
+ at State(Scope.Thread)
+ at BenchmarkMode(Mode.AverageTime)
+ at OutputTimeUnit(TimeUnit.MICROSECONDS)
+public class StubInvocationBenchmark {
+
+ /**
+ * A generic {@link String} value.
+ */
+ private String stringValue = "foo";
+
+ /**
+ * A generic {@code boolean} value.
+ */
+ private boolean booleanValue = true;
+
+ /**
+ * A generic {@code byte} value.
+ */
+ private byte byteValue = 42;
+
+ /**
+ * A generic {@code short} value.
+ */
+ private short shortValue = 42;
+
+ /**
+ * A generic {@code char} value.
+ */
+ private char charValue = '@';
+
+ /**
+ * A generic {@code int} value.
+ */
+ private int intValue = 42;
+
+ /**
+ * A generic {@code long} value.
+ */
+ private long longValue = 42L;
+
+ /**
+ * A generic {@code float} value.
+ */
+ private float floatValue = 42f;
+
+ /**
+ * A generic {@code double} value.
+ */
+ private double doubleValue = 42d;
+
+ /**
+ * A casual instance that serves as a baseline.
+ */
+ private ExampleInterface baselineInstance;
+
+ /**
+ * An instance created by Byte Buddy for performing benchmarks on.
+ */
+ private ExampleInterface byteBuddyInstance;
+
+ /**
+ * An instance created by cglib for performing benchmarks on.
+ */
+ private ExampleInterface cglibInstance;
+
+ /**
+ * An instance created by javassist for performing benchmarks on.
+ */
+ private ExampleInterface javassistInstance;
+
+ /**
+ * An instance created by the JDK proxy for performing benchmarks on.
+ */
+ private ExampleInterface jdkProxyInstance;
+
+ /**
+ * Creates an instance for each code generation library. The classes are extracted from the
+ * {@link net.bytebuddy.benchmark.ClassByImplementationBenchmark}.
+ *
+ * @throws Exception Covers the exception declarations of the setup methods.
+ */
+ @Setup
+ public void setUp() throws Exception {
+ ClassByImplementationBenchmark classByImplementationBenchmark = new ClassByImplementationBenchmark();
+ baselineInstance = classByImplementationBenchmark.baseline();
+ byteBuddyInstance = classByImplementationBenchmark.benchmarkByteBuddy();
+ cglibInstance = classByImplementationBenchmark.benchmarkCglib();
+ javassistInstance = classByImplementationBenchmark.benchmarkJavassist();
+ jdkProxyInstance = classByImplementationBenchmark.benchmarkJdkProxy();
+ }
+
+ /**
+ * Performs a benchmark for a casual class as a baseline.
+ *
+ * @param blackHole A black hole for avoiding JIT erasure.
+ */
+ @Benchmark
+ @OperationsPerInvocation(20)
+ public void baseline(Blackhole blackHole) {
+ blackHole.consume(baselineInstance.method(booleanValue));
+ blackHole.consume(baselineInstance.method(byteValue));
+ blackHole.consume(baselineInstance.method(shortValue));
+ blackHole.consume(baselineInstance.method(intValue));
+ blackHole.consume(baselineInstance.method(charValue));
+ blackHole.consume(baselineInstance.method(intValue));
+ blackHole.consume(baselineInstance.method(longValue));
+ blackHole.consume(baselineInstance.method(floatValue));
+ blackHole.consume(baselineInstance.method(doubleValue));
+ blackHole.consume(baselineInstance.method(stringValue));
+ blackHole.consume(baselineInstance.method(booleanValue, booleanValue, booleanValue));
+ blackHole.consume(baselineInstance.method(byteValue, byteValue, byteValue));
+ blackHole.consume(baselineInstance.method(shortValue, shortValue, shortValue));
+ blackHole.consume(baselineInstance.method(intValue, intValue, intValue));
+ blackHole.consume(baselineInstance.method(charValue, charValue, charValue));
+ blackHole.consume(baselineInstance.method(intValue, intValue, intValue));
+ blackHole.consume(baselineInstance.method(longValue, longValue, longValue));
+ blackHole.consume(baselineInstance.method(floatValue, floatValue, floatValue));
+ blackHole.consume(baselineInstance.method(doubleValue, doubleValue, doubleValue));
+ blackHole.consume(baselineInstance.method(stringValue, stringValue, stringValue));
+ }
+
+ /**
+ * Performs a benchmark for a trivial class creation using Byte Buddy.
+ *
+ * @param blackHole A black hole for avoiding JIT erasure.
+ */
+ @Benchmark
+ @OperationsPerInvocation(20)
+ public void benchmarkByteBuddy(Blackhole blackHole) {
+ blackHole.consume(byteBuddyInstance.method(booleanValue));
+ blackHole.consume(byteBuddyInstance.method(byteValue));
+ blackHole.consume(byteBuddyInstance.method(shortValue));
+ blackHole.consume(byteBuddyInstance.method(intValue));
+ blackHole.consume(byteBuddyInstance.method(charValue));
+ blackHole.consume(byteBuddyInstance.method(intValue));
+ blackHole.consume(byteBuddyInstance.method(longValue));
+ blackHole.consume(byteBuddyInstance.method(floatValue));
+ blackHole.consume(byteBuddyInstance.method(doubleValue));
+ blackHole.consume(byteBuddyInstance.method(stringValue));
+ blackHole.consume(byteBuddyInstance.method(booleanValue, booleanValue, booleanValue));
+ blackHole.consume(byteBuddyInstance.method(byteValue, byteValue, byteValue));
+ blackHole.consume(byteBuddyInstance.method(shortValue, shortValue, shortValue));
+ blackHole.consume(byteBuddyInstance.method(intValue, intValue, intValue));
+ blackHole.consume(byteBuddyInstance.method(charValue, charValue, charValue));
+ blackHole.consume(byteBuddyInstance.method(intValue, intValue, intValue));
+ blackHole.consume(byteBuddyInstance.method(longValue, longValue, longValue));
+ blackHole.consume(byteBuddyInstance.method(floatValue, floatValue, floatValue));
+ blackHole.consume(byteBuddyInstance.method(doubleValue, doubleValue, doubleValue));
+ blackHole.consume(byteBuddyInstance.method(stringValue, stringValue, stringValue));
+ }
+
+ /**
+ * Performs a benchmark for a trivial class creation using cglib.
+ *
+ * @param blackHole A black hole for avoiding JIT erasure.
+ */
+ @Benchmark
+ @OperationsPerInvocation(20)
+ public void benchmarkCglib(Blackhole blackHole) {
+ blackHole.consume(cglibInstance.method(booleanValue));
+ blackHole.consume(cglibInstance.method(byteValue));
+ blackHole.consume(cglibInstance.method(shortValue));
+ blackHole.consume(cglibInstance.method(intValue));
+ blackHole.consume(cglibInstance.method(charValue));
+ blackHole.consume(cglibInstance.method(intValue));
+ blackHole.consume(cglibInstance.method(longValue));
+ blackHole.consume(cglibInstance.method(floatValue));
+ blackHole.consume(cglibInstance.method(doubleValue));
+ blackHole.consume(cglibInstance.method(stringValue));
+ blackHole.consume(cglibInstance.method(booleanValue, booleanValue, booleanValue));
+ blackHole.consume(cglibInstance.method(byteValue, byteValue, byteValue));
+ blackHole.consume(cglibInstance.method(shortValue, shortValue, shortValue));
+ blackHole.consume(cglibInstance.method(intValue, intValue, intValue));
+ blackHole.consume(cglibInstance.method(charValue, charValue, charValue));
+ blackHole.consume(cglibInstance.method(intValue, intValue, intValue));
+ blackHole.consume(cglibInstance.method(longValue, longValue, longValue));
+ blackHole.consume(cglibInstance.method(floatValue, floatValue, floatValue));
+ blackHole.consume(cglibInstance.method(doubleValue, doubleValue, doubleValue));
+ blackHole.consume(cglibInstance.method(stringValue, stringValue, stringValue));
+ }
+
+ /**
+ * Performs a benchmark for a trivial class creation using javassist.
+ *
+ * @param blackHole A black hole for avoiding JIT erasure.
+ */
+ @Benchmark
+ @OperationsPerInvocation(20)
+ public void benchmarkJavassist(Blackhole blackHole) {
+ blackHole.consume(javassistInstance.method(booleanValue));
+ blackHole.consume(javassistInstance.method(byteValue));
+ blackHole.consume(javassistInstance.method(shortValue));
+ blackHole.consume(javassistInstance.method(intValue));
+ blackHole.consume(javassistInstance.method(charValue));
+ blackHole.consume(javassistInstance.method(intValue));
+ blackHole.consume(javassistInstance.method(longValue));
+ blackHole.consume(javassistInstance.method(floatValue));
+ blackHole.consume(javassistInstance.method(doubleValue));
+ blackHole.consume(javassistInstance.method(stringValue));
+ blackHole.consume(javassistInstance.method(booleanValue, booleanValue, booleanValue));
+ blackHole.consume(javassistInstance.method(byteValue, byteValue, byteValue));
+ blackHole.consume(javassistInstance.method(shortValue, shortValue, shortValue));
+ blackHole.consume(javassistInstance.method(intValue, intValue, intValue));
+ blackHole.consume(javassistInstance.method(charValue, charValue, charValue));
+ blackHole.consume(javassistInstance.method(intValue, intValue, intValue));
+ blackHole.consume(javassistInstance.method(longValue, longValue, longValue));
+ blackHole.consume(javassistInstance.method(floatValue, floatValue, floatValue));
+ blackHole.consume(javassistInstance.method(doubleValue, doubleValue, doubleValue));
+ blackHole.consume(javassistInstance.method(stringValue, stringValue, stringValue));
+ }
+
+ /**
+ * Performs a benchmark for a trivial class creation using the Java Class Library's utilities.
+ *
+ * @param blackHole A black hole for avoiding JIT erasure.
+ */
+ @Benchmark
+ @OperationsPerInvocation(20)
+ public void benchmarkJdkProxy(Blackhole blackHole) {
+ blackHole.consume(jdkProxyInstance.method(booleanValue));
+ blackHole.consume(jdkProxyInstance.method(byteValue));
+ blackHole.consume(jdkProxyInstance.method(shortValue));
+ blackHole.consume(jdkProxyInstance.method(intValue));
+ blackHole.consume(jdkProxyInstance.method(charValue));
+ blackHole.consume(jdkProxyInstance.method(intValue));
+ blackHole.consume(jdkProxyInstance.method(longValue));
+ blackHole.consume(jdkProxyInstance.method(floatValue));
+ blackHole.consume(jdkProxyInstance.method(doubleValue));
+ blackHole.consume(jdkProxyInstance.method(stringValue));
+ blackHole.consume(jdkProxyInstance.method(booleanValue, booleanValue, booleanValue));
+ blackHole.consume(jdkProxyInstance.method(byteValue, byteValue, byteValue));
+ blackHole.consume(jdkProxyInstance.method(shortValue, shortValue, shortValue));
+ blackHole.consume(jdkProxyInstance.method(intValue, intValue, intValue));
+ blackHole.consume(jdkProxyInstance.method(charValue, charValue, charValue));
+ blackHole.consume(jdkProxyInstance.method(intValue, intValue, intValue));
+ blackHole.consume(jdkProxyInstance.method(longValue, longValue, longValue));
+ blackHole.consume(jdkProxyInstance.method(floatValue, floatValue, floatValue));
+ blackHole.consume(jdkProxyInstance.method(doubleValue, doubleValue, doubleValue));
+ blackHole.consume(jdkProxyInstance.method(stringValue, stringValue, stringValue));
+ }
+}
diff --git a/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/SuperClassInvocationBenchmark.java b/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/SuperClassInvocationBenchmark.java
new file mode 100644
index 0000000..343ace3
--- /dev/null
+++ b/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/SuperClassInvocationBenchmark.java
@@ -0,0 +1,340 @@
+package net.bytebuddy.benchmark;
+
+import net.bytebuddy.benchmark.specimen.ExampleClass;
+import org.openjdk.jmh.annotations.*;
+import org.openjdk.jmh.infra.Blackhole;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * <p>
+ * This benchmark measures the invocation speed of stub method invocations. All classes implement
+ * {@link net.bytebuddy.benchmark.specimen.ExampleClass} and call this class's super method invocation. Since it
+ * is not possible to create a subclass with the JDK proxy utilities, the latter is omitted from the benchmark.
+ * </p>
+ * <p>
+ * Note that this class defines all values that are accessed by benchmark methods as instance fields. This way, the JIT
+ * compiler's capability of constant folding is limited in order to produce more comparable test results.
+ * </p>
+ */
+ at State(Scope.Thread)
+ at BenchmarkMode(Mode.AverageTime)
+ at OutputTimeUnit(TimeUnit.MICROSECONDS)
+public class SuperClassInvocationBenchmark {
+
+ /**
+ * A generic {@link String} value.
+ */
+ private String stringValue = "foo";
+
+ /**
+ * A generic {@code boolean} value.
+ */
+ private boolean booleanValue = true;
+
+ /**
+ * A generic {@code byte} value.
+ */
+ private byte byteValue = 42;
+
+ /**
+ * A generic {@code short} value.
+ */
+ private short shortValue = 42;
+
+ /**
+ * A generic {@code char} value.
+ */
+ private char charValue = '@';
+
+ /**
+ * A generic {@code int} value.
+ */
+ private int intValue = 42;
+
+ /**
+ * A generic {@code long} value.
+ */
+ private long longValue = 42L;
+
+ /**
+ * A generic {@code float} value.
+ */
+ private float floatValue = 42f;
+
+ /**
+ * A generic {@code double} value.
+ */
+ private double doubleValue = 42d;
+
+ /**
+ * A casual instance that serves as a baseline.
+ */
+ private ExampleClass baselineInstance;
+
+ /**
+ * An instance created by Byte Buddy for performing benchmarks on. This instance is created by adding
+ * auxiliary classes that allow for an invocation of a method from a delegation target.
+ */
+ private ExampleClass byteBuddyWithProxyInstance;
+
+ /**
+ * An instance created by Byte Buddy for performing benchmarks on. This instance is created by adding
+ * super invocation methods which are exposed via the reflection API.
+ */
+ private ExampleClass byteBuddyWithAccessorInstance;
+
+ /**
+ * An instance created by Byte Buddy for performing benchmarks on. This instance is created by a delegation
+ * followed by a hard-coded super method call.
+ */
+ private ExampleClass byteBuddyWithPrefixInstance;
+
+ /**
+ * An instance created by Byte Buddy for performing benchmarks on. This instance is created by hard-coding
+ * a super method invocation into the intercepted method.
+ */
+ private ExampleClass byteBuddySpecializedInstance;
+
+ /**
+ * An instance created by cglib for performing benchmarks on.
+ */
+ private ExampleClass cglibInstance;
+
+ /**
+ * An instance created by javassist for performing benchmarks on.
+ */
+ private ExampleClass javassistInstance;
+
+ /**
+ * Creates an instance for each code generation library. The classes are extracted from the
+ * {@link net.bytebuddy.benchmark.ClassByExtensionBenchmark}.
+ *
+ * @throws Exception Covers the exception declarations of the setup methods.
+ */
+ @Setup
+ public void setUp() throws Exception {
+ ClassByExtensionBenchmark classByExtensionBenchmark = new ClassByExtensionBenchmark();
+ baselineInstance = classByExtensionBenchmark.baseline();
+ byteBuddyWithProxyInstance = classByExtensionBenchmark.benchmarkByteBuddyWithProxy();
+ byteBuddyWithAccessorInstance = classByExtensionBenchmark.benchmarkByteBuddyWithAccessor();
+ byteBuddyWithPrefixInstance = classByExtensionBenchmark.benchmarkByteBuddyWithPrefix();
+ byteBuddySpecializedInstance = classByExtensionBenchmark.benchmarkByteBuddySpecialized();
+ cglibInstance = classByExtensionBenchmark.benchmarkCglib();
+ javassistInstance = classByExtensionBenchmark.benchmarkJavassist();
+ }
+
+ /**
+ * Performs a benchmark for a casual class as a baseline.
+ *
+ * @param blackHole A black hole for avoiding JIT erasure.
+ */
+ @Benchmark
+ @OperationsPerInvocation(20)
+ public void baseline(Blackhole blackHole) {
+ blackHole.consume(baselineInstance.method(booleanValue));
+ blackHole.consume(baselineInstance.method(byteValue));
+ blackHole.consume(baselineInstance.method(shortValue));
+ blackHole.consume(baselineInstance.method(intValue));
+ blackHole.consume(baselineInstance.method(charValue));
+ blackHole.consume(baselineInstance.method(intValue));
+ blackHole.consume(baselineInstance.method(longValue));
+ blackHole.consume(baselineInstance.method(floatValue));
+ blackHole.consume(baselineInstance.method(doubleValue));
+ blackHole.consume(baselineInstance.method(stringValue));
+ blackHole.consume(baselineInstance.method(booleanValue, booleanValue, booleanValue));
+ blackHole.consume(baselineInstance.method(byteValue, byteValue, byteValue));
+ blackHole.consume(baselineInstance.method(shortValue, shortValue, shortValue));
+ blackHole.consume(baselineInstance.method(intValue, intValue, intValue));
+ blackHole.consume(baselineInstance.method(charValue, charValue, charValue));
+ blackHole.consume(baselineInstance.method(intValue, intValue, intValue));
+ blackHole.consume(baselineInstance.method(longValue, longValue, longValue));
+ blackHole.consume(baselineInstance.method(floatValue, floatValue, floatValue));
+ blackHole.consume(baselineInstance.method(doubleValue, doubleValue, doubleValue));
+ blackHole.consume(baselineInstance.method(stringValue, stringValue, stringValue));
+ }
+
+ /**
+ * Performs a benchmark of a super method invocation using Byte Buddy. This benchmark uses an annotation-based
+ * approach which is more difficult to optimize by the JIT compiler.
+ *
+ * @param blackHole A black hole for avoiding JIT erasure.
+ */
+ @Benchmark
+ @OperationsPerInvocation(20)
+ public void benchmarkByteBuddyWithProxy(Blackhole blackHole) {
+ blackHole.consume(byteBuddyWithProxyInstance.method(booleanValue));
+ blackHole.consume(byteBuddyWithProxyInstance.method(byteValue));
+ blackHole.consume(byteBuddyWithProxyInstance.method(shortValue));
+ blackHole.consume(byteBuddyWithProxyInstance.method(intValue));
+ blackHole.consume(byteBuddyWithProxyInstance.method(charValue));
+ blackHole.consume(byteBuddyWithProxyInstance.method(intValue));
+ blackHole.consume(byteBuddyWithProxyInstance.method(longValue));
+ blackHole.consume(byteBuddyWithProxyInstance.method(floatValue));
+ blackHole.consume(byteBuddyWithProxyInstance.method(doubleValue));
+ blackHole.consume(byteBuddyWithProxyInstance.method(stringValue));
+ blackHole.consume(byteBuddyWithProxyInstance.method(booleanValue, booleanValue, booleanValue));
+ blackHole.consume(byteBuddyWithProxyInstance.method(byteValue, byteValue, byteValue));
+ blackHole.consume(byteBuddyWithProxyInstance.method(shortValue, shortValue, shortValue));
+ blackHole.consume(byteBuddyWithProxyInstance.method(intValue, intValue, intValue));
+ blackHole.consume(byteBuddyWithProxyInstance.method(charValue, charValue, charValue));
+ blackHole.consume(byteBuddyWithProxyInstance.method(intValue, intValue, intValue));
+ blackHole.consume(byteBuddyWithProxyInstance.method(longValue, longValue, longValue));
+ blackHole.consume(byteBuddyWithProxyInstance.method(floatValue, floatValue, floatValue));
+ blackHole.consume(byteBuddyWithProxyInstance.method(doubleValue, doubleValue, doubleValue));
+ blackHole.consume(byteBuddyWithProxyInstance.method(stringValue, stringValue, stringValue));
+ }
+
+ /**
+ * Performs a benchmark of a super method invocation using Byte Buddy. This benchmark also uses the annotation-based approach
+ * but creates delegation methods which do not require the creation of additional classes.
+ *
+ * @param blackHole A black hole for avoiding JIT erasure.
+ */
+ @Benchmark
+ @OperationsPerInvocation(20)
+ public void benchmarkByteBuddyWithAccessor(Blackhole blackHole) {
+ blackHole.consume(byteBuddyWithAccessorInstance.method(booleanValue));
+ blackHole.consume(byteBuddyWithAccessorInstance.method(byteValue));
+ blackHole.consume(byteBuddyWithAccessorInstance.method(shortValue));
+ blackHole.consume(byteBuddyWithAccessorInstance.method(intValue));
+ blackHole.consume(byteBuddyWithAccessorInstance.method(charValue));
+ blackHole.consume(byteBuddyWithAccessorInstance.method(intValue));
+ blackHole.consume(byteBuddyWithAccessorInstance.method(longValue));
+ blackHole.consume(byteBuddyWithAccessorInstance.method(floatValue));
+ blackHole.consume(byteBuddyWithAccessorInstance.method(doubleValue));
+ blackHole.consume(byteBuddyWithAccessorInstance.method(stringValue));
+ blackHole.consume(byteBuddyWithAccessorInstance.method(booleanValue, booleanValue, booleanValue));
+ blackHole.consume(byteBuddyWithAccessorInstance.method(byteValue, byteValue, byteValue));
+ blackHole.consume(byteBuddyWithAccessorInstance.method(shortValue, shortValue, shortValue));
+ blackHole.consume(byteBuddyWithAccessorInstance.method(intValue, intValue, intValue));
+ blackHole.consume(byteBuddyWithAccessorInstance.method(charValue, charValue, charValue));
+ blackHole.consume(byteBuddyWithAccessorInstance.method(intValue, intValue, intValue));
+ blackHole.consume(byteBuddyWithAccessorInstance.method(longValue, longValue, longValue));
+ blackHole.consume(byteBuddyWithAccessorInstance.method(floatValue, floatValue, floatValue));
+ blackHole.consume(byteBuddyWithAccessorInstance.method(doubleValue, doubleValue, doubleValue));
+ blackHole.consume(byteBuddyWithAccessorInstance.method(stringValue, stringValue, stringValue));
+ }
+
+ /**
+ * Performs a benchmark of a super method invocation using Byte Buddy. This benchmark also uses the annotation-based approach
+ * but hard-codes the super method call subsequently to the method.
+ *
+ * @param blackHole A black hole for avoiding JIT erasure.
+ */
+ @Benchmark
+ @OperationsPerInvocation(20)
+ public void benchmarkByteBuddyWithPrefix(Blackhole blackHole) {
+ blackHole.consume(byteBuddyWithPrefixInstance.method(booleanValue));
+ blackHole.consume(byteBuddyWithPrefixInstance.method(byteValue));
+ blackHole.consume(byteBuddyWithPrefixInstance.method(shortValue));
+ blackHole.consume(byteBuddyWithPrefixInstance.method(intValue));
+ blackHole.consume(byteBuddyWithPrefixInstance.method(charValue));
+ blackHole.consume(byteBuddyWithPrefixInstance.method(intValue));
+ blackHole.consume(byteBuddyWithPrefixInstance.method(longValue));
+ blackHole.consume(byteBuddyWithPrefixInstance.method(floatValue));
+ blackHole.consume(byteBuddyWithPrefixInstance.method(doubleValue));
+ blackHole.consume(byteBuddyWithPrefixInstance.method(stringValue));
+ blackHole.consume(byteBuddyWithPrefixInstance.method(booleanValue, booleanValue, booleanValue));
+ blackHole.consume(byteBuddyWithPrefixInstance.method(byteValue, byteValue, byteValue));
+ blackHole.consume(byteBuddyWithPrefixInstance.method(shortValue, shortValue, shortValue));
+ blackHole.consume(byteBuddyWithPrefixInstance.method(intValue, intValue, intValue));
+ blackHole.consume(byteBuddyWithPrefixInstance.method(charValue, charValue, charValue));
+ blackHole.consume(byteBuddyWithPrefixInstance.method(intValue, intValue, intValue));
+ blackHole.consume(byteBuddyWithPrefixInstance.method(longValue, longValue, longValue));
+ blackHole.consume(byteBuddyWithPrefixInstance.method(floatValue, floatValue, floatValue));
+ blackHole.consume(byteBuddyWithPrefixInstance.method(doubleValue, doubleValue, doubleValue));
+ blackHole.consume(byteBuddyWithPrefixInstance.method(stringValue, stringValue, stringValue));
+ }
+
+ /**
+ * Performs a benchmark of a super method invocation using Byte Buddy. This benchmark uses a specialized
+ * interception strategy which is easier to inline by the compiler.
+ *
+ * @param blackHole A black hole for avoiding JIT erasure.
+ */
+ @Benchmark
+ @OperationsPerInvocation(20)
+ public void benchmarkByteBuddySpecialized(Blackhole blackHole) {
+ blackHole.consume(byteBuddySpecializedInstance.method(booleanValue));
+ blackHole.consume(byteBuddySpecializedInstance.method(byteValue));
+ blackHole.consume(byteBuddySpecializedInstance.method(shortValue));
+ blackHole.consume(byteBuddySpecializedInstance.method(intValue));
+ blackHole.consume(byteBuddySpecializedInstance.method(charValue));
+ blackHole.consume(byteBuddySpecializedInstance.method(intValue));
+ blackHole.consume(byteBuddySpecializedInstance.method(longValue));
+ blackHole.consume(byteBuddySpecializedInstance.method(floatValue));
+ blackHole.consume(byteBuddySpecializedInstance.method(doubleValue));
+ blackHole.consume(byteBuddySpecializedInstance.method(stringValue));
+ blackHole.consume(byteBuddySpecializedInstance.method(booleanValue, booleanValue, booleanValue));
+ blackHole.consume(byteBuddySpecializedInstance.method(byteValue, byteValue, byteValue));
+ blackHole.consume(byteBuddySpecializedInstance.method(shortValue, shortValue, shortValue));
+ blackHole.consume(byteBuddySpecializedInstance.method(intValue, intValue, intValue));
+ blackHole.consume(byteBuddySpecializedInstance.method(charValue, charValue, charValue));
+ blackHole.consume(byteBuddySpecializedInstance.method(intValue, intValue, intValue));
+ blackHole.consume(byteBuddySpecializedInstance.method(longValue, longValue, longValue));
+ blackHole.consume(byteBuddySpecializedInstance.method(floatValue, floatValue, floatValue));
+ blackHole.consume(byteBuddySpecializedInstance.method(doubleValue, doubleValue, doubleValue));
+ blackHole.consume(byteBuddySpecializedInstance.method(stringValue, stringValue, stringValue));
+ }
+
+ /**
+ * Performs a benchmark of a super method invocation using cglib.
+ *
+ * @param blackHole A black hole for avoiding JIT erasure.
+ */
+ @Benchmark
+ @OperationsPerInvocation(20)
+ public void benchmarkCglib(Blackhole blackHole) {
+ blackHole.consume(cglibInstance.method(booleanValue));
+ blackHole.consume(cglibInstance.method(byteValue));
+ blackHole.consume(cglibInstance.method(shortValue));
+ blackHole.consume(cglibInstance.method(intValue));
+ blackHole.consume(cglibInstance.method(charValue));
+ blackHole.consume(cglibInstance.method(intValue));
+ blackHole.consume(cglibInstance.method(longValue));
+ blackHole.consume(cglibInstance.method(floatValue));
+ blackHole.consume(cglibInstance.method(doubleValue));
+ blackHole.consume(cglibInstance.method(stringValue));
+ blackHole.consume(cglibInstance.method(booleanValue, booleanValue, booleanValue));
+ blackHole.consume(cglibInstance.method(byteValue, byteValue, byteValue));
+ blackHole.consume(cglibInstance.method(shortValue, shortValue, shortValue));
+ blackHole.consume(cglibInstance.method(intValue, intValue, intValue));
+ blackHole.consume(cglibInstance.method(charValue, charValue, charValue));
+ blackHole.consume(cglibInstance.method(intValue, intValue, intValue));
+ blackHole.consume(cglibInstance.method(longValue, longValue, longValue));
+ blackHole.consume(cglibInstance.method(floatValue, floatValue, floatValue));
+ blackHole.consume(cglibInstance.method(doubleValue, doubleValue, doubleValue));
+ blackHole.consume(cglibInstance.method(stringValue, stringValue, stringValue));
+ }
+
+ /**
+ * Performs a benchmark of a super method invocation using javassist.
+ *
+ * @param blackHole A black hole for avoiding JIT erasure.
+ */
+ @Benchmark
+ @OperationsPerInvocation(20)
+ public void benchmarkJavassist(Blackhole blackHole) {
+ blackHole.consume(javassistInstance.method(booleanValue));
+ blackHole.consume(javassistInstance.method(byteValue));
+ blackHole.consume(javassistInstance.method(shortValue));
+ blackHole.consume(javassistInstance.method(intValue));
+ blackHole.consume(javassistInstance.method(charValue));
+ blackHole.consume(javassistInstance.method(intValue));
+ blackHole.consume(javassistInstance.method(longValue));
+ blackHole.consume(javassistInstance.method(floatValue));
+ blackHole.consume(javassistInstance.method(doubleValue));
+ blackHole.consume(javassistInstance.method(stringValue));
+ blackHole.consume(javassistInstance.method(booleanValue, booleanValue, booleanValue));
+ blackHole.consume(javassistInstance.method(byteValue, byteValue, byteValue));
+ blackHole.consume(javassistInstance.method(shortValue, shortValue, shortValue));
+ blackHole.consume(javassistInstance.method(intValue, intValue, intValue));
+ blackHole.consume(javassistInstance.method(charValue, charValue, charValue));
+ blackHole.consume(javassistInstance.method(intValue, intValue, intValue));
+ blackHole.consume(javassistInstance.method(longValue, longValue, longValue));
+ blackHole.consume(javassistInstance.method(floatValue, floatValue, floatValue));
+ blackHole.consume(javassistInstance.method(doubleValue, doubleValue, doubleValue));
+ blackHole.consume(javassistInstance.method(stringValue, stringValue, stringValue));
+ }
+}
diff --git a/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/TrivialClassCreationBenchmark.java b/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/TrivialClassCreationBenchmark.java
new file mode 100644
index 0000000..a5bcebb
--- /dev/null
+++ b/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/TrivialClassCreationBenchmark.java
@@ -0,0 +1,136 @@
+package net.bytebuddy.benchmark;
+
+import javassist.util.proxy.MethodFilter;
+import javassist.util.proxy.ProxyFactory;
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.dynamic.scaffold.TypeValidation;
+import net.sf.cglib.proxy.Enhancer;
+import net.sf.cglib.proxy.NoOp;
+import org.openjdk.jmh.annotations.*;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.concurrent.TimeUnit;
+
+import static net.bytebuddy.matcher.ElementMatchers.any;
+
+/**
+ * <p>
+ * A benchmark for creating plain subclasses of {@link Object} that do not override any methods. This benchmark
+ * intends to measure the general overhead of each library.
+ * </p>
+ * <p>
+ * Note that this class defines all values that are accessed by benchmark methods as instance fields. This way, the JIT
+ * compiler's capability of constant folding is limited in order to produce more comparable test results.
+ * </p>
+ */
+ at State(Scope.Thread)
+ at BenchmarkMode(Mode.AverageTime)
+ at OutputTimeUnit(TimeUnit.MICROSECONDS)
+public class TrivialClassCreationBenchmark {
+
+ /**
+ * The base class to be subclassed in all benchmarks.
+ */
+ public static final Class<?> BASE_CLASS = Object.class;
+
+ /**
+ * The base class to be subclassed in all benchmarks.
+ */
+ private Class<?> baseClass = BASE_CLASS;
+
+ /**
+ * The zero-length of the class loader's URL.
+ */
+ private int urlLength = 0;
+
+ /**
+ * Creates a new class loader. By using a fresh class loader for each creation, we avoid name space issues.
+ * A class loader's creation is part of the benchmark but since any test creates a class loader exactly once,
+ * the benchmark remains valid.
+ *
+ * @return A new class loader.
+ */
+ private ClassLoader newClassLoader() {
+ return new URLClassLoader(new URL[urlLength]);
+ }
+
+ /**
+ * Returns a non-instrumented class as a baseline.
+ *
+ * @return A reference to {@link Object}.
+ */
+ @Benchmark
+ public Class<?> baseline() {
+ return Object.class;
+ }
+
+ /**
+ * Performs a benchmark for a trivial class creation using Byte Buddy.
+ *
+ * @return The created instance, in order to avoid JIT removal.
+ */
+ @Benchmark
+ public Class<?> benchmarkByteBuddy() {
+ return new ByteBuddy()
+ .with(TypeValidation.DISABLED)
+ .ignore(any())
+ .subclass(baseClass)
+ .make()
+ .load(newClassLoader(), ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded();
+ }
+
+ /**
+ * Performs a benchmark for a trivial class creation using cglib.
+ *
+ * @return The created instance, in order to avoid JIT removal.
+ */
+ @Benchmark
+ public Class<?> benchmarkCglib() {
+ Enhancer enhancer = new Enhancer();
+ enhancer.setUseCache(false);
+ enhancer.setClassLoader(newClassLoader());
+ enhancer.setSuperclass(baseClass);
+ enhancer.setCallbackType(NoOp.class);
+ return enhancer.createClass();
+ }
+
+ /**
+ * Performs a benchmark for a trivial class creation using javassist proxies.
+ *
+ * @return The created instance, in order to avoid JIT removal.
+ */
+ @Benchmark
+ public Class<?> benchmarkJavassist() {
+ ProxyFactory proxyFactory = new ProxyFactory();
+ proxyFactory.setUseCache(false);
+ ProxyFactory.classLoaderProvider = new ProxyFactory.ClassLoaderProvider() {
+ @Override
+ public ClassLoader get(ProxyFactory proxyFactory) {
+ return newClassLoader();
+ }
+ };
+ proxyFactory.setSuperclass(baseClass);
+ proxyFactory.setFilter(new MethodFilter() {
+ public boolean isHandled(Method method) {
+ return false;
+ }
+ });
+ return proxyFactory.createClass();
+ }
+
+ /**
+ * Performs a benchmark for a trivial class creation using the Java Class Library's utilities.
+ *
+ * @return The created instance, in order to avoid JIT removal.
+ */
+ @Benchmark
+ @SuppressWarnings("deprecation")
+ public Class<?> benchmarkJdkProxy() {
+ return Proxy.getProxyClass(newClassLoader(), new Class<?>[urlLength]);
+ }
+}
diff --git a/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/package-info.java b/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/package-info.java
new file mode 100644
index 0000000..0f76c54
--- /dev/null
+++ b/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * A package containing benchmarks for Byte Buddy and other code generation libraries.
+ */
+package net.bytebuddy.benchmark;
diff --git a/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/runner/QuickRunner.java b/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/runner/QuickRunner.java
new file mode 100644
index 0000000..3f08279
--- /dev/null
+++ b/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/runner/QuickRunner.java
@@ -0,0 +1,43 @@
+package net.bytebuddy.benchmark.runner;
+
+import net.bytebuddy.benchmark.*;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.RunnerException;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+
+/**
+ * A runner for completing a benchmark with only one JMH fork. This benchmark completes rather quick and can give
+ * a great first performance indication. A published benchmark should rather be backed by an execution with additional
+ * forks.
+ */
+public class QuickRunner {
+
+ /**
+ * A wildcard for the identification of a benchmark by JMH.
+ */
+ private static final String WILDCARD = ".*";
+
+ /**
+ * This class is not supposed to be constructed.
+ */
+ private QuickRunner() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Executes the benchmark.
+ *
+ * @param args Unused arguments.
+ * @throws RunnerException If the benchmark causes an exception.
+ */
+ public static void main(String[] args) throws RunnerException {
+ new Runner(new OptionsBuilder()
+ .include(WILDCARD + SuperClassInvocationBenchmark.class.getSimpleName() + WILDCARD)
+ .include(WILDCARD + StubInvocationBenchmark.class.getSimpleName() + WILDCARD)
+ .include(WILDCARD + ClassByImplementationBenchmark.class.getSimpleName() + WILDCARD)
+ .include(WILDCARD + ClassByExtensionBenchmark.class.getSimpleName() + WILDCARD)
+ .include(WILDCARD + TrivialClassCreationBenchmark.class.getSimpleName() + WILDCARD)
+ .forks(0) // Should rather be 1 but there seems to be a bug in JMH.
+ .build()).run();
+ }
+}
diff --git a/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/runner/package-info.java b/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/runner/package-info.java
new file mode 100644
index 0000000..cf86830
--- /dev/null
+++ b/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/runner/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * A package dedicated to running benchmarks.
+ */
+package net.bytebuddy.benchmark.runner;
diff --git a/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/specimen/ExampleClass.java b/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/specimen/ExampleClass.java
new file mode 100644
index 0000000..c8e3432
--- /dev/null
+++ b/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/specimen/ExampleClass.java
@@ -0,0 +1,205 @@
+package net.bytebuddy.benchmark.specimen;
+
+/**
+ * An example class with several methods which is used as a specimen in benchmarks.
+ */
+public class ExampleClass {
+
+ /**
+ * An example method.
+ *
+ * @param arg An argument.
+ * @return The input argument.
+ */
+ public boolean method(boolean arg) {
+ return arg;
+ }
+
+ /**
+ * An example method.
+ *
+ * @param arg An argument.
+ * @return The input argument.
+ */
+ public byte method(byte arg) {
+ return arg;
+ }
+
+ /**
+ * An example method.
+ *
+ * @param arg An argument.
+ * @return The input argument.
+ */
+ public short method(short arg) {
+ return arg;
+ }
+
+ /**
+ * An example method.
+ *
+ * @param arg An argument.
+ * @return The input argument.
+ */
+ public int method(int arg) {
+ return arg;
+ }
+
+ /**
+ * An example method.
+ *
+ * @param arg An argument.
+ * @return The input argument.
+ */
+ public char method(char arg) {
+ return arg;
+ }
+
+ /**
+ * An example method.
+ *
+ * @param arg An argument.
+ * @return The input argument.
+ */
+ public long method(long arg) {
+ return arg;
+ }
+
+ /**
+ * An example method.
+ *
+ * @param arg An argument.
+ * @return The input argument.
+ */
+ public float method(float arg) {
+ return arg;
+ }
+
+ /**
+ * An example method.
+ *
+ * @param arg An argument.
+ * @return The input argument.
+ */
+ public double method(double arg) {
+ return arg;
+ }
+
+ /**
+ * An example method.
+ *
+ * @param arg An argument.
+ * @return The input argument.
+ */
+ public Object method(Object arg) {
+ return arg;
+ }
+
+ /**
+ * An example method.
+ *
+ * @param arg1 An argument.
+ * @param arg2 An argument.
+ * @param arg3 An argument.
+ * @return All arguments stored in an array.
+ */
+ public boolean[] method(boolean arg1, boolean arg2, boolean arg3) {
+ return new boolean[]{arg1, arg2, arg3};
+ }
+
+ /**
+ * An example method.
+ *
+ * @param arg1 An argument.
+ * @param arg2 An argument.
+ * @param arg3 An argument.
+ * @return All arguments stored in an array.
+ */
+ public byte[] method(byte arg1, byte arg2, byte arg3) {
+ return new byte[]{arg1, arg2, arg3};
+ }
+
+ /**
+ * An example method.
+ *
+ * @param arg1 An argument.
+ * @param arg2 An argument.
+ * @param arg3 An argument.
+ * @return All arguments stored in an array.
+ */
+ public short[] method(short arg1, short arg2, short arg3) {
+ return new short[]{arg1, arg2, arg3};
+ }
+
+ /**
+ * An example method.
+ *
+ * @param arg1 An argument.
+ * @param arg2 An argument.
+ * @param arg3 An argument.
+ * @return All arguments stored in an array.
+ */
+ public int[] method(int arg1, int arg2, int arg3) {
+ return new int[]{arg1, arg2, arg3};
+ }
+
+ /**
+ * An example method.
+ *
+ * @param arg1 An argument.
+ * @param arg2 An argument.
+ * @param arg3 An argument.
+ * @return All arguments stored in an array.
+ */
+ public char[] method(char arg1, char arg2, char arg3) {
+ return new char[]{arg1, arg2, arg3};
+ }
+
+ /**
+ * An example method.
+ *
+ * @param arg1 An argument.
+ * @param arg2 An argument.
+ * @param arg3 An argument.
+ * @return All arguments stored in an array.
+ */
+ public long[] method(long arg1, long arg2, long arg3) {
+ return new long[]{arg1, arg2, arg3};
+ }
+
+ /**
+ * An example method.
+ *
+ * @param arg1 An argument.
+ * @param arg2 An argument.
+ * @param arg3 An argument.
+ * @return All arguments stored in an array.
+ */
+ public float[] method(float arg1, float arg2, float arg3) {
+ return new float[]{arg1, arg2, arg3};
+ }
+
+ /**
+ * An example method.
+ *
+ * @param arg1 An argument.
+ * @param arg2 An argument.
+ * @param arg3 An argument.
+ * @return All arguments stored in an array.
+ */
+ public double[] method(double arg1, double arg2, double arg3) {
+ return new double[]{arg1, arg2, arg3};
+ }
+
+ /**
+ * An example method.
+ *
+ * @param arg1 An argument.
+ * @param arg2 An argument.
+ * @param arg3 An argument.
+ * @return All arguments stored in an array.
+ */
+ public Object[] method(Object arg1, Object arg2, Object arg3) {
+ return new Object[]{arg1, arg2, arg3};
+ }
+}
diff --git a/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/specimen/ExampleInterface.java b/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/specimen/ExampleInterface.java
new file mode 100644
index 0000000..b130bbd
--- /dev/null
+++ b/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/specimen/ExampleInterface.java
@@ -0,0 +1,169 @@
+package net.bytebuddy.benchmark.specimen;
+
+/**
+ * An example interface with several methods which is used as a specimen in benchmarks.
+ */
+public interface ExampleInterface {
+
+ /**
+ * An example method.
+ *
+ * @param arg An argument.
+ * @return The input argument.
+ */
+ boolean method(boolean arg);
+
+ /**
+ * An example method.
+ *
+ * @param arg An argument.
+ * @return The input argument.
+ */
+ byte method(byte arg);
+
+ /**
+ * An example method.
+ *
+ * @param arg An argument.
+ * @return The input argument.
+ */
+ short method(short arg);
+
+ /**
+ * An example method.
+ *
+ * @param arg An argument.
+ * @return The input argument.
+ */
+ int method(int arg);
+
+ /**
+ * An example method.
+ *
+ * @param arg An argument.
+ * @return The input argument.
+ */
+ char method(char arg);
+
+ /**
+ * An example method.
+ *
+ * @param arg An argument.
+ * @return The input argument.
+ */
+ long method(long arg);
+
+ /**
+ * An example method.
+ *
+ * @param arg An argument.
+ * @return The input argument.
+ */
+ float method(float arg);
+
+ /**
+ * An example method.
+ *
+ * @param arg An argument.
+ * @return The input argument.
+ */
+ double method(double arg);
+
+ /**
+ * An example method.
+ *
+ * @param arg An argument.
+ * @return The input argument.
+ */
+ Object method(Object arg);
+
+ /**
+ * An example method.
+ *
+ * @param arg1 An argument.
+ * @param arg2 An argument.
+ * @param arg3 An argument.
+ * @return All arguments stored in an array.
+ */
+ boolean[] method(boolean arg1, boolean arg2, boolean arg3);
+
+ /**
+ * An example method.
+ *
+ * @param arg1 An argument.
+ * @param arg2 An argument.
+ * @param arg3 An argument.
+ * @return All arguments stored in an array.
+ */
+ byte[] method(byte arg1, byte arg2, byte arg3);
+
+ /**
+ * An example method.
+ *
+ * @param arg1 An argument.
+ * @param arg2 An argument.
+ * @param arg3 An argument.
+ * @return All arguments stored in an array.
+ */
+ short[] method(short arg1, short arg2, short arg3);
+
+ /**
+ * An example method.
+ *
+ * @param arg1 An argument.
+ * @param arg2 An argument.
+ * @param arg3 An argument.
+ * @return All arguments stored in an array.
+ */
+ int[] method(int arg1, int arg2, int arg3);
+
+ /**
+ * An example method.
+ *
+ * @param arg1 An argument.
+ * @param arg2 An argument.
+ * @param arg3 An argument.
+ * @return All arguments stored in an array.
+ */
+ char[] method(char arg1, char arg2, char arg3);
+
+ /**
+ * An example method.
+ *
+ * @param arg1 An argument.
+ * @param arg2 An argument.
+ * @param arg3 An argument.
+ * @return All arguments stored in an array.
+ */
+ long[] method(long arg1, long arg2, long arg3);
+
+ /**
+ * An example method.
+ *
+ * @param arg1 An argument.
+ * @param arg2 An argument.
+ * @param arg3 An argument.
+ * @return All arguments stored in an array.
+ */
+ float[] method(float arg1, float arg2, float arg3);
+
+ /**
+ * An example method.
+ *
+ * @param arg1 An argument.
+ * @param arg2 An argument.
+ * @param arg3 An argument.
+ * @return All arguments stored in an array.
+ */
+ double[] method(double arg1, double arg2, double arg3);
+
+ /**
+ * An example method.
+ *
+ * @param arg1 An argument.
+ * @param arg2 An argument.
+ * @param arg3 An argument.
+ * @return All arguments stored in an array.
+ */
+ Object[] method(Object arg1, Object arg2, Object arg3);
+}
diff --git a/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/specimen/package-info.java b/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/specimen/package-info.java
new file mode 100644
index 0000000..62fa38f
--- /dev/null
+++ b/byte-buddy-benchmark/src/main/java/net/bytebuddy/benchmark/specimen/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Specimen classes which are required for some benchmarks.
+ */
+package net.bytebuddy.benchmark.specimen;
diff --git a/byte-buddy-benchmark/src/test/java/net/bytebuddy/benchmark/AbstractBlackHoleTest.java b/byte-buddy-benchmark/src/test/java/net/bytebuddy/benchmark/AbstractBlackHoleTest.java
new file mode 100644
index 0000000..b72b3b7
--- /dev/null
+++ b/byte-buddy-benchmark/src/test/java/net/bytebuddy/benchmark/AbstractBlackHoleTest.java
@@ -0,0 +1,19 @@
+package net.bytebuddy.benchmark;
+
+import org.junit.Before;
+import org.openjdk.jmh.infra.Blackhole;
+
+/**
+ * Unfortunately, the JMH is not very test friendly. Thus, we need to do some tricks to run test cases. Fortunately,
+ * we are testing a code generation framework such that we already have the tools to generate the required classes
+ * on our class path.
+ */
+public abstract class AbstractBlackHoleTest {
+
+ protected Blackhole blackHole;
+
+ @Before
+ public void setUpBlackHole() throws Exception {
+ blackHole = new Blackhole("Today\'s password is swordfish. I understand instantiating Blackholes directly is dangerous.");
+ }
+}
diff --git a/byte-buddy-benchmark/src/test/java/net/bytebuddy/benchmark/ClassByExtensionBenchmarkByteBuddyInterceptorTest.java b/byte-buddy-benchmark/src/test/java/net/bytebuddy/benchmark/ClassByExtensionBenchmarkByteBuddyInterceptorTest.java
new file mode 100644
index 0000000..a7d95f4
--- /dev/null
+++ b/byte-buddy-benchmark/src/test/java/net/bytebuddy/benchmark/ClassByExtensionBenchmarkByteBuddyInterceptorTest.java
@@ -0,0 +1,35 @@
+package net.bytebuddy.benchmark;
+
+import org.junit.Test;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+import static org.junit.Assert.fail;
+
+public class ClassByExtensionBenchmarkByteBuddyInterceptorTest {
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testProxyInterceptor() throws Exception {
+ Constructor<?> constructor = ClassByExtensionBenchmark.ByteBuddyProxyInterceptor.class.getDeclaredConstructor();
+ constructor.setAccessible(true);
+ try {
+ constructor.newInstance();
+ fail();
+ } catch (InvocationTargetException exception) {
+ throw (UnsupportedOperationException) exception.getCause();
+ }
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testAccessorInterceptor() throws Exception {
+ Constructor<?> constructor = ClassByExtensionBenchmark.ByteBuddyAccessInterceptor.class.getDeclaredConstructor();
+ constructor.setAccessible(true);
+ try {
+ constructor.newInstance();
+ fail();
+ } catch (InvocationTargetException exception) {
+ throw (UnsupportedOperationException) exception.getCause();
+ }
+ }
+}
diff --git a/byte-buddy-benchmark/src/test/java/net/bytebuddy/benchmark/ClassByExtensionBenchmarkTest.java b/byte-buddy-benchmark/src/test/java/net/bytebuddy/benchmark/ClassByExtensionBenchmarkTest.java
new file mode 100644
index 0000000..ec62e44
--- /dev/null
+++ b/byte-buddy-benchmark/src/test/java/net/bytebuddy/benchmark/ClassByExtensionBenchmarkTest.java
@@ -0,0 +1,211 @@
+package net.bytebuddy.benchmark;
+
+import net.bytebuddy.benchmark.specimen.ExampleClass;
+import org.hamcrest.CoreMatchers;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class ClassByExtensionBenchmarkTest {
+
+ private static final boolean BOOLEAN_VALUE = true;
+
+ private static final byte BYTE_VALUE = 42;
+
+ private static final short SHORT_VALUE = 42;
+
+ private static final char CHAR_VALUE = '@';
+
+ private static final int INT_VALUE = 42;
+
+ private static final long LONG_VALUE = 42L;
+
+ private static final float FLOAT_VALUE = 42f;
+
+ private static final double DOUBLE_VALUE = 42d;
+
+ private static final Object REFERENCE_VALUE = "foo";
+
+ private ClassByExtensionBenchmark classByExtensionBenchmark;
+
+ private static void assertReturnValues(ExampleClass exampleClass) {
+ assertThat(exampleClass.method(BOOLEAN_VALUE), is(BOOLEAN_VALUE));
+ assertThat(exampleClass.method(BYTE_VALUE), is(BYTE_VALUE));
+ assertThat(exampleClass.method(SHORT_VALUE), is(SHORT_VALUE));
+ assertThat(exampleClass.method(CHAR_VALUE), is(CHAR_VALUE));
+ assertThat(exampleClass.method(INT_VALUE), is(INT_VALUE));
+ assertThat(exampleClass.method(LONG_VALUE), is(LONG_VALUE));
+ assertThat(exampleClass.method(FLOAT_VALUE), is(FLOAT_VALUE));
+ assertThat(exampleClass.method(DOUBLE_VALUE), is(DOUBLE_VALUE));
+ assertThat(exampleClass.method(REFERENCE_VALUE), is(REFERENCE_VALUE));
+ assertThat(exampleClass.method(BOOLEAN_VALUE, BOOLEAN_VALUE, BOOLEAN_VALUE),
+ is(new boolean[]{BOOLEAN_VALUE, BOOLEAN_VALUE, BOOLEAN_VALUE}));
+ assertThat(exampleClass.method(BYTE_VALUE, BYTE_VALUE, BYTE_VALUE),
+ is(new byte[]{BYTE_VALUE, BYTE_VALUE, BYTE_VALUE}));
+ assertThat(exampleClass.method(SHORT_VALUE, SHORT_VALUE, SHORT_VALUE),
+ is(new short[]{SHORT_VALUE, SHORT_VALUE, SHORT_VALUE}));
+ assertThat(exampleClass.method(CHAR_VALUE, CHAR_VALUE, CHAR_VALUE),
+ is(new char[]{CHAR_VALUE, CHAR_VALUE, CHAR_VALUE}));
+ assertThat(exampleClass.method(INT_VALUE, INT_VALUE, INT_VALUE),
+ is(new int[]{INT_VALUE, INT_VALUE, INT_VALUE}));
+ assertThat(exampleClass.method(LONG_VALUE, LONG_VALUE, LONG_VALUE),
+ is(new long[]{LONG_VALUE, LONG_VALUE, LONG_VALUE}));
+ assertThat(exampleClass.method(FLOAT_VALUE, FLOAT_VALUE, FLOAT_VALUE),
+ is(new float[]{FLOAT_VALUE, FLOAT_VALUE, FLOAT_VALUE}));
+ assertThat(exampleClass.method(DOUBLE_VALUE, DOUBLE_VALUE, DOUBLE_VALUE),
+ is(new double[]{DOUBLE_VALUE, DOUBLE_VALUE, DOUBLE_VALUE}));
+ assertThat(exampleClass.method(REFERENCE_VALUE, REFERENCE_VALUE, REFERENCE_VALUE),
+ is(new Object[]{REFERENCE_VALUE, REFERENCE_VALUE, REFERENCE_VALUE}));
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ classByExtensionBenchmark = new ClassByExtensionBenchmark();
+ classByExtensionBenchmark.setup();
+ }
+
+ @Test
+ public void testBaseline() throws Exception {
+ ExampleClass instance = classByExtensionBenchmark.baseline();
+ assertThat(instance.getClass(), CoreMatchers.<Class<?>>is(ClassByExtensionBenchmark.BASE_CLASS));
+ assertReturnValues(instance);
+ }
+
+ @Test
+ public void testByteBuddyWithProxiesClassCreation() throws Exception {
+ ExampleClass instance = classByExtensionBenchmark.benchmarkByteBuddyWithProxy();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(ClassByExtensionBenchmark.BASE_CLASS)));
+ assertThat(instance.getClass().getSuperclass(), CoreMatchers.<Class<?>>is(ClassByExtensionBenchmark.BASE_CLASS));
+ assertThat(classByExtensionBenchmark.benchmarkByteBuddyWithProxy().getClass(), not(CoreMatchers.<Class<?>>is(instance.getClass())));
+ assertReturnValues(instance);
+ }
+
+ @Test
+ public void testByteBuddyWithProxiesClassCreationCached() throws Exception {
+ ExampleClass instance = classByExtensionBenchmark.benchmarkByteBuddyWithProxyAndReusedDelegator();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(ClassByExtensionBenchmark.BASE_CLASS)));
+ assertThat(instance.getClass().getSuperclass(), CoreMatchers.<Class<?>>is(ClassByExtensionBenchmark.BASE_CLASS));
+ assertThat(classByExtensionBenchmark.benchmarkByteBuddyWithProxy().getClass(), not(CoreMatchers.<Class<?>>is(instance.getClass())));
+ assertReturnValues(instance);
+ }
+
+ @Test
+ public void testByteBuddyWithProxiesClassCreationWithTypePool() throws Exception {
+ ExampleClass instance = classByExtensionBenchmark.benchmarkByteBuddyWithProxyWithTypePool();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(ClassByExtensionBenchmark.BASE_CLASS)));
+ assertThat(instance.getClass().getSuperclass(), CoreMatchers.<Class<?>>is(ClassByExtensionBenchmark.BASE_CLASS));
+ assertThat(classByExtensionBenchmark.benchmarkByteBuddyWithProxy().getClass(), not(CoreMatchers.<Class<?>>is(instance.getClass())));
+ assertReturnValues(instance);
+ }
+
+ @Test
+ public void testByteBuddyWithProxiesClassCreationCachedWithTypePool() throws Exception {
+ ExampleClass instance = classByExtensionBenchmark.benchmarkByteBuddyWithProxyAndReusedDelegatorWithTypePool();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(ClassByExtensionBenchmark.BASE_CLASS)));
+ assertThat(instance.getClass().getSuperclass(), CoreMatchers.<Class<?>>is(ClassByExtensionBenchmark.BASE_CLASS));
+ assertThat(classByExtensionBenchmark.benchmarkByteBuddyWithProxy().getClass(), not(CoreMatchers.<Class<?>>is(instance.getClass())));
+ assertReturnValues(instance);
+ }
+
+ @Test
+ public void testByteBuddyWithAccessorClassCreation() throws Exception {
+ ExampleClass instance = classByExtensionBenchmark.benchmarkByteBuddyWithAccessor();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(ClassByExtensionBenchmark.BASE_CLASS)));
+ assertThat(instance.getClass().getSuperclass(), CoreMatchers.<Class<?>>is(ClassByExtensionBenchmark.BASE_CLASS));
+ assertThat(classByExtensionBenchmark.benchmarkByteBuddyWithProxy().getClass(), not(CoreMatchers.<Class<?>>is(instance.getClass())));
+ assertReturnValues(instance);
+ }
+
+ @Test
+ public void testByteBuddyWithAccessorClassCreationCached() throws Exception {
+ ExampleClass instance = classByExtensionBenchmark.benchmarkByteBuddyWithAccessorAndReusedDelegator();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(ClassByExtensionBenchmark.BASE_CLASS)));
+ assertThat(instance.getClass().getSuperclass(), CoreMatchers.<Class<?>>is(ClassByExtensionBenchmark.BASE_CLASS));
+ assertThat(classByExtensionBenchmark.benchmarkByteBuddyWithProxy().getClass(), not(CoreMatchers.<Class<?>>is(instance.getClass())));
+ assertReturnValues(instance);
+ }
+
+ @Test
+ public void testByteBuddyWithAccessorClassCreationWithTypePool() throws Exception {
+ ExampleClass instance = classByExtensionBenchmark.benchmarkByteBuddyWithAccessorWithTypePool();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(ClassByExtensionBenchmark.BASE_CLASS)));
+ assertThat(instance.getClass().getSuperclass(), CoreMatchers.<Class<?>>is(ClassByExtensionBenchmark.BASE_CLASS));
+ assertThat(classByExtensionBenchmark.benchmarkByteBuddyWithProxy().getClass(), not(CoreMatchers.<Class<?>>is(instance.getClass())));
+ assertReturnValues(instance);
+ }
+
+ @Test
+ public void testByteBuddyWithAccessorClassCreationCachedWithTypePool() throws Exception {
+ ExampleClass instance = classByExtensionBenchmark.benchmarkByteBuddyWithAccessorAndReusedDelegatorWithTypePool();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(ClassByExtensionBenchmark.BASE_CLASS)));
+ assertThat(instance.getClass().getSuperclass(), CoreMatchers.<Class<?>>is(ClassByExtensionBenchmark.BASE_CLASS));
+ assertThat(classByExtensionBenchmark.benchmarkByteBuddyWithProxy().getClass(), not(CoreMatchers.<Class<?>>is(instance.getClass())));
+ assertReturnValues(instance);
+ }
+
+ @Test
+ public void testByteBuddyWithPrefixClassCreation() throws Exception {
+ ExampleClass instance = classByExtensionBenchmark.benchmarkByteBuddyWithPrefix();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(ClassByExtensionBenchmark.BASE_CLASS)));
+ assertThat(instance.getClass().getSuperclass(), CoreMatchers.<Class<?>>is(ClassByExtensionBenchmark.BASE_CLASS));
+ assertThat(classByExtensionBenchmark.benchmarkByteBuddyWithProxy().getClass(), not(CoreMatchers.<Class<?>>is(instance.getClass())));
+ assertReturnValues(instance);
+ }
+
+ @Test
+ public void testByteBuddyWithPrefixClassCreationCached() throws Exception {
+ ExampleClass instance = classByExtensionBenchmark.benchmarkByteBuddyWithPrefixAndReusedDelegator();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(ClassByExtensionBenchmark.BASE_CLASS)));
+ assertThat(instance.getClass().getSuperclass(), CoreMatchers.<Class<?>>is(ClassByExtensionBenchmark.BASE_CLASS));
+ assertThat(classByExtensionBenchmark.benchmarkByteBuddyWithProxy().getClass(), not(CoreMatchers.<Class<?>>is(instance.getClass())));
+ assertReturnValues(instance);
+ }
+
+ @Test
+ public void testByteBuddyWithPrefixClassCreationWithTypePool() throws Exception {
+ ExampleClass instance = classByExtensionBenchmark.benchmarkByteBuddyWithPrefixWithTypePool();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(ClassByExtensionBenchmark.BASE_CLASS)));
+ assertThat(instance.getClass().getSuperclass(), CoreMatchers.<Class<?>>is(ClassByExtensionBenchmark.BASE_CLASS));
+ assertThat(classByExtensionBenchmark.benchmarkByteBuddyWithProxy().getClass(), not(CoreMatchers.<Class<?>>is(instance.getClass())));
+ assertReturnValues(instance);
+ }
+
+ @Test
+ public void testByteBuddyWithPrefixClassCreationCachedWithTypePool() throws Exception {
+ ExampleClass instance = classByExtensionBenchmark.benchmarkByteBuddyWithPrefixAndReusedDelegatorWithTypePool();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(ClassByExtensionBenchmark.BASE_CLASS)));
+ assertThat(instance.getClass().getSuperclass(), CoreMatchers.<Class<?>>is(ClassByExtensionBenchmark.BASE_CLASS));
+ assertThat(classByExtensionBenchmark.benchmarkByteBuddyWithProxy().getClass(), not(CoreMatchers.<Class<?>>is(instance.getClass())));
+ assertReturnValues(instance);
+ }
+
+ @Test
+ public void testByteBuddySpecializedClassCreation() throws Exception {
+ ExampleClass instance = classByExtensionBenchmark.benchmarkByteBuddySpecialized();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(ClassByExtensionBenchmark.BASE_CLASS)));
+ assertThat(instance.getClass().getSuperclass(), CoreMatchers.<Class<?>>is(ClassByExtensionBenchmark.BASE_CLASS));
+ assertThat(classByExtensionBenchmark.benchmarkByteBuddySpecialized().getClass(), not(CoreMatchers.<Class<?>>is(instance.getClass())));
+ assertReturnValues(instance);
+ }
+
+ @Test
+ public void testCglibClassCreation() throws Exception {
+ ExampleClass instance = classByExtensionBenchmark.benchmarkCglib();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(ClassByExtensionBenchmark.BASE_CLASS)));
+ assertThat(instance.getClass().getSuperclass(), CoreMatchers.<Class<?>>is(ClassByExtensionBenchmark.BASE_CLASS));
+ assertThat(classByExtensionBenchmark.benchmarkCglib().getClass(), not(CoreMatchers.<Class<?>>is(instance.getClass())));
+ assertReturnValues(instance);
+ }
+
+ @Test
+ public void testJavassistClassCreation() throws Exception {
+ ExampleClass instance = classByExtensionBenchmark.benchmarkJavassist();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(ClassByExtensionBenchmark.BASE_CLASS)));
+ assertThat(instance.getClass().getSuperclass(), CoreMatchers.<Class<?>>is(ClassByExtensionBenchmark.BASE_CLASS));
+ assertThat(classByExtensionBenchmark.benchmarkJavassist().getClass(), not(CoreMatchers.<Class<?>>is(instance.getClass())));
+ assertReturnValues(instance);
+ }
+}
diff --git a/byte-buddy-benchmark/src/test/java/net/bytebuddy/benchmark/ClassByImplementationBenchmarkTest.java b/byte-buddy-benchmark/src/test/java/net/bytebuddy/benchmark/ClassByImplementationBenchmarkTest.java
new file mode 100644
index 0000000..e8036d4
--- /dev/null
+++ b/byte-buddy-benchmark/src/test/java/net/bytebuddy/benchmark/ClassByImplementationBenchmarkTest.java
@@ -0,0 +1,125 @@
+package net.bytebuddy.benchmark;
+
+import net.bytebuddy.benchmark.specimen.ExampleInterface;
+import org.hamcrest.CoreMatchers;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class ClassByImplementationBenchmarkTest {
+
+ private static final boolean BOOLEAN_VALUE = true;
+
+ private static final byte BYTE_VALUE = 42;
+
+ private static final short SHORT_VALUE = 42;
+
+ private static final char CHAR_VALUE = '@';
+
+ private static final int INT_VALUE = 42;
+
+ private static final long LONG_VALUE = 42L;
+
+ private static final float FLOAT_VALUE = 42f;
+
+ private static final double DOUBLE_VALUE = 42d;
+
+ private static final Object REFERENCE_VALUE = "foo";
+
+ private ClassByImplementationBenchmark classByImplementationBenchmark;
+
+ private static void assertReturnValues(ExampleInterface exampleInterface) {
+ assertThat(exampleInterface.method(BOOLEAN_VALUE), is(ClassByImplementationBenchmark.DEFAULT_BOOLEAN_VALUE));
+ assertThat(exampleInterface.method(BYTE_VALUE), is(ClassByImplementationBenchmark.DEFAULT_BYTE_VALUE));
+ assertThat(exampleInterface.method(SHORT_VALUE), is(ClassByImplementationBenchmark.DEFAULT_SHORT_VALUE));
+ assertThat(exampleInterface.method(CHAR_VALUE), is(ClassByImplementationBenchmark.DEFAULT_CHAR_VALUE));
+ assertThat(exampleInterface.method(INT_VALUE), is(ClassByImplementationBenchmark.DEFAULT_INT_VALUE));
+ assertThat(exampleInterface.method(LONG_VALUE), is(ClassByImplementationBenchmark.DEFAULT_LONG_VALUE));
+ assertThat(exampleInterface.method(FLOAT_VALUE), is(ClassByImplementationBenchmark.DEFAULT_FLOAT_VALUE));
+ assertThat(exampleInterface.method(DOUBLE_VALUE), is(ClassByImplementationBenchmark.DEFAULT_DOUBLE_VALUE));
+ assertThat(exampleInterface.method(REFERENCE_VALUE), is((Object) ClassByImplementationBenchmark.DEFAULT_REFERENCE_VALUE));
+ assertThat(exampleInterface.method(BOOLEAN_VALUE, BOOLEAN_VALUE, BOOLEAN_VALUE),
+ is((boolean[]) (Object) ClassByImplementationBenchmark.DEFAULT_REFERENCE_VALUE));
+ assertThat(exampleInterface.method(BYTE_VALUE, BYTE_VALUE, BYTE_VALUE),
+ is((byte[]) (Object) ClassByImplementationBenchmark.DEFAULT_REFERENCE_VALUE));
+ assertThat(exampleInterface.method(SHORT_VALUE, SHORT_VALUE, SHORT_VALUE),
+ is((short[]) (Object) ClassByImplementationBenchmark.DEFAULT_REFERENCE_VALUE));
+ assertThat(exampleInterface.method(CHAR_VALUE, CHAR_VALUE, CHAR_VALUE),
+ is((char[]) (Object) ClassByImplementationBenchmark.DEFAULT_REFERENCE_VALUE));
+ assertThat(exampleInterface.method(INT_VALUE, INT_VALUE, INT_VALUE),
+ is((int[]) (Object) ClassByImplementationBenchmark.DEFAULT_REFERENCE_VALUE));
+ assertThat(exampleInterface.method(LONG_VALUE, LONG_VALUE, LONG_VALUE),
+ is((long[]) (Object) ClassByImplementationBenchmark.DEFAULT_REFERENCE_VALUE));
+ assertThat(exampleInterface.method(FLOAT_VALUE, FLOAT_VALUE, FLOAT_VALUE),
+ is((float[]) (Object) ClassByImplementationBenchmark.DEFAULT_REFERENCE_VALUE));
+ assertThat(exampleInterface.method(DOUBLE_VALUE, DOUBLE_VALUE, DOUBLE_VALUE),
+ is((double[]) (Object) ClassByImplementationBenchmark.DEFAULT_REFERENCE_VALUE));
+ assertThat(exampleInterface.method(REFERENCE_VALUE, REFERENCE_VALUE, REFERENCE_VALUE),
+ is((Object[]) (Object) ClassByImplementationBenchmark.DEFAULT_REFERENCE_VALUE));
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ classByImplementationBenchmark = new ClassByImplementationBenchmark();
+ classByImplementationBenchmark.setup();
+ }
+
+ @Test
+ public void testBaseline() throws Exception {
+ ExampleInterface instance = classByImplementationBenchmark.baseline();
+ assertThat(Arrays.asList(instance.getClass().getInterfaces()), hasItem(ClassByImplementationBenchmark.BASE_CLASS));
+ assertThat(instance.getClass().getSuperclass(), CoreMatchers.<Class<?>>is(Object.class));
+ assertThat(classByImplementationBenchmark.benchmarkByteBuddy().getClass(), not(CoreMatchers.<Class<?>>is(instance.getClass())));
+ assertReturnValues(instance);
+ }
+
+ @Test
+ public void testByteBuddyClassCreation() throws Exception {
+ ExampleInterface instance = classByImplementationBenchmark.benchmarkByteBuddy();
+ assertThat(Arrays.asList(instance.getClass().getInterfaces()), hasItem(ClassByImplementationBenchmark.BASE_CLASS));
+ assertThat(instance.getClass().getSuperclass(), CoreMatchers.<Class<?>>is(Object.class));
+ assertThat(classByImplementationBenchmark.benchmarkByteBuddy().getClass(), not(CoreMatchers.<Class<?>>is(instance.getClass())));
+ assertReturnValues(instance);
+ }
+
+ @Test
+ public void testByteBuddyClassCreationWithTypePool() throws Exception {
+ ExampleInterface instance = classByImplementationBenchmark.benchmarkByteBuddyWithTypePool();
+ assertThat(Arrays.asList(instance.getClass().getInterfaces()), hasItem(ClassByImplementationBenchmark.BASE_CLASS));
+ assertThat(instance.getClass().getSuperclass(), CoreMatchers.<Class<?>>is(Object.class));
+ assertThat(classByImplementationBenchmark.benchmarkByteBuddy().getClass(), not(CoreMatchers.<Class<?>>is(instance.getClass())));
+ assertReturnValues(instance);
+ }
+
+ @Test
+ public void testCglibClassCreation() throws Exception {
+ ExampleInterface instance = classByImplementationBenchmark.benchmarkCglib();
+ assertThat(Arrays.asList(instance.getClass().getInterfaces()), hasItem(ClassByImplementationBenchmark.BASE_CLASS));
+ assertThat(instance.getClass().getSuperclass(), CoreMatchers.<Class<?>>is(Object.class));
+ assertThat(classByImplementationBenchmark.benchmarkCglib().getClass(), not(CoreMatchers.<Class<?>>is(instance.getClass())));
+ assertReturnValues(instance);
+ }
+
+ @Test
+ public void testJavassistClassCreation() throws Exception {
+ ExampleInterface instance = classByImplementationBenchmark.benchmarkJavassist();
+ assertThat(Arrays.asList(instance.getClass().getInterfaces()), hasItem(ClassByImplementationBenchmark.BASE_CLASS));
+ assertThat(instance.getClass().getSuperclass(), CoreMatchers.<Class<?>>is(Object.class));
+ assertThat(classByImplementationBenchmark.benchmarkJavassist().getClass(), not(CoreMatchers.<Class<?>>is(instance.getClass())));
+ assertReturnValues(instance);
+ }
+
+ @Test
+ public void testJdkProxyClassCreation() throws Exception {
+ ExampleInterface instance = classByImplementationBenchmark.benchmarkJdkProxy();
+ assertThat(Arrays.asList(instance.getClass().getInterfaces()), hasItem(ClassByImplementationBenchmark.BASE_CLASS));
+ assertThat(instance.getClass().getSuperclass(), CoreMatchers.<Class<?>>is(Proxy.class));
+ assertThat(classByImplementationBenchmark.benchmarkByteBuddy().getClass(), not(CoreMatchers.<Class<?>>is(instance.getClass())));
+ assertReturnValues(instance);
+ }
+}
diff --git a/byte-buddy-benchmark/src/test/java/net/bytebuddy/benchmark/StubInvocationBenchmarkTest.java b/byte-buddy-benchmark/src/test/java/net/bytebuddy/benchmark/StubInvocationBenchmarkTest.java
new file mode 100644
index 0000000..c15d4c6
--- /dev/null
+++ b/byte-buddy-benchmark/src/test/java/net/bytebuddy/benchmark/StubInvocationBenchmarkTest.java
@@ -0,0 +1,40 @@
+package net.bytebuddy.benchmark;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class StubInvocationBenchmarkTest extends AbstractBlackHoleTest {
+
+ private StubInvocationBenchmark stubInvocationBenchmark;
+
+ @Before
+ public void setUp() throws Exception {
+ stubInvocationBenchmark = new StubInvocationBenchmark();
+ stubInvocationBenchmark.setUp();
+ }
+
+ @Test
+ public void testBaseline() throws Exception {
+ stubInvocationBenchmark.baseline(blackHole);
+ }
+
+ @Test
+ public void testByteBuddyBenchmark() throws Exception {
+ stubInvocationBenchmark.benchmarkByteBuddy(blackHole);
+ }
+
+ @Test
+ public void testCglibBenchmark() throws Exception {
+ stubInvocationBenchmark.benchmarkCglib(blackHole);
+ }
+
+ @Test
+ public void testJavassistBenchmark() throws Exception {
+ stubInvocationBenchmark.benchmarkJavassist(blackHole);
+ }
+
+ @Test
+ public void testJdkProxyBenchmark() throws Exception {
+ stubInvocationBenchmark.benchmarkJdkProxy(blackHole);
+ }
+}
diff --git a/byte-buddy-benchmark/src/test/java/net/bytebuddy/benchmark/SuperClassInvocationBenchmarkTest.java b/byte-buddy-benchmark/src/test/java/net/bytebuddy/benchmark/SuperClassInvocationBenchmarkTest.java
new file mode 100644
index 0000000..58f48fb
--- /dev/null
+++ b/byte-buddy-benchmark/src/test/java/net/bytebuddy/benchmark/SuperClassInvocationBenchmarkTest.java
@@ -0,0 +1,50 @@
+package net.bytebuddy.benchmark;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class SuperClassInvocationBenchmarkTest extends AbstractBlackHoleTest {
+
+ private SuperClassInvocationBenchmark superClassInvocationBenchmark;
+
+ @Before
+ public void setUp() throws Exception {
+ superClassInvocationBenchmark = new SuperClassInvocationBenchmark();
+ superClassInvocationBenchmark.setUp();
+ }
+
+ @Test
+ public void testBaseline() throws Exception {
+ superClassInvocationBenchmark.baseline(blackHole);
+ }
+
+ @Test
+ public void testByteBuddyWithProxiesBenchmark() throws Exception {
+ superClassInvocationBenchmark.benchmarkByteBuddyWithProxy(blackHole);
+ }
+
+ @Test
+ public void testByteBuddyWithAccessorsBenchmark() throws Exception {
+ superClassInvocationBenchmark.benchmarkByteBuddyWithAccessor(blackHole);
+ }
+
+ @Test
+ public void testByteBuddyWithPrefixBenchmark() throws Exception {
+ superClassInvocationBenchmark.benchmarkByteBuddyWithPrefix(blackHole);
+ }
+
+ @Test
+ public void testByteBuddySpecializedBenchmark() throws Exception {
+ superClassInvocationBenchmark.benchmarkByteBuddySpecialized(blackHole);
+ }
+
+ @Test
+ public void testCglibBenchmark() throws Exception {
+ superClassInvocationBenchmark.benchmarkCglib(blackHole);
+ }
+
+ @Test
+ public void testJavassistBenchmark() throws Exception {
+ superClassInvocationBenchmark.benchmarkJavassist(blackHole);
+ }
+}
diff --git a/byte-buddy-benchmark/src/test/java/net/bytebuddy/benchmark/TrivialClassCreationBenchmarkTest.java b/byte-buddy-benchmark/src/test/java/net/bytebuddy/benchmark/TrivialClassCreationBenchmarkTest.java
new file mode 100644
index 0000000..6f1fdc8
--- /dev/null
+++ b/byte-buddy-benchmark/src/test/java/net/bytebuddy/benchmark/TrivialClassCreationBenchmarkTest.java
@@ -0,0 +1,58 @@
+package net.bytebuddy.benchmark;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.lang.reflect.Proxy;
+
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class TrivialClassCreationBenchmarkTest {
+
+ private TrivialClassCreationBenchmark trivialClassCreationBenchmark;
+
+ @Before
+ public void setUp() throws Exception {
+ trivialClassCreationBenchmark = new TrivialClassCreationBenchmark();
+ }
+
+ @Test
+ public void testBaseline() throws Exception {
+ Class<?> type = trivialClassCreationBenchmark.baseline();
+ assertThat(type, CoreMatchers.<Class<?>>is(TrivialClassCreationBenchmark.BASE_CLASS));
+ }
+
+ @Test
+ public void testByteBuddyClassCreation() throws Exception {
+ Class<?> type = trivialClassCreationBenchmark.benchmarkByteBuddy();
+ assertThat(type, not(CoreMatchers.<Class<?>>is(TrivialClassCreationBenchmark.BASE_CLASS)));
+ assertThat(type.getSuperclass(), CoreMatchers.<Class<?>>is(TrivialClassCreationBenchmark.BASE_CLASS));
+ assertThat(trivialClassCreationBenchmark.benchmarkByteBuddy(), not(CoreMatchers.<Class<?>>is(type)));
+ }
+
+ @Test
+ public void testCglibClassCreation() throws Exception {
+ Class<?> type = trivialClassCreationBenchmark.benchmarkCglib();
+ assertThat(type, not(CoreMatchers.<Class<?>>is(TrivialClassCreationBenchmark.BASE_CLASS)));
+ assertThat(type.getSuperclass(), CoreMatchers.<Class<?>>is(TrivialClassCreationBenchmark.BASE_CLASS));
+ assertThat(trivialClassCreationBenchmark.benchmarkCglib(), not(CoreMatchers.<Class<?>>is(type)));
+ }
+
+ @Test
+ public void testJavassistClassCreation() throws Exception {
+ Class<?> type = trivialClassCreationBenchmark.benchmarkJavassist();
+ assertThat(type, not(CoreMatchers.<Class<?>>is(TrivialClassCreationBenchmark.BASE_CLASS)));
+ assertThat(type.getSuperclass(), CoreMatchers.<Class<?>>is(TrivialClassCreationBenchmark.BASE_CLASS));
+ assertThat(trivialClassCreationBenchmark.benchmarkJavassist(), not(CoreMatchers.<Class<?>>is(type)));
+ }
+
+ @Test
+ public void testJdkProxyClassCreation() throws Exception {
+ Class<?> type = trivialClassCreationBenchmark.benchmarkJdkProxy();
+ assertThat(type, not(CoreMatchers.<Class<?>>is(TrivialClassCreationBenchmark.BASE_CLASS)));
+ assertThat(type.getSuperclass(), CoreMatchers.<Class<?>>is(Proxy.class));
+ assertThat(trivialClassCreationBenchmark.benchmarkJdkProxy(), not(CoreMatchers.<Class<?>>is(type)));
+ }
+}
diff --git a/byte-buddy-dep/pom.xml b/byte-buddy-dep/pom.xml
new file mode 100644
index 0000000..84c953a
--- /dev/null
+++ b/byte-buddy-dep/pom.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy-parent</artifactId>
+ <version>1.7.1</version>
+ </parent>
+
+ <artifactId>byte-buddy-dep</artifactId>
+ <packaging>jar</packaging>
+
+ <name>Byte Buddy (with dependencies)</name>
+ <description>
+ Byte Buddy is a Java library for creating Java classes at run time.
+ This artifact is a build of Byte Buddy with a remaining dependency onto ASM.
+ You should never depend on this module without repackaging Byte Buddy and ASM into your own namespace.
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.ow2.asm</groupId>
+ <artifactId>asm</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.ow2.asm</groupId>
+ <artifactId>asm-commons</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.ow2.asm</groupId>
+ <artifactId>asm-tree</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.ow2.asm</groupId>
+ <artifactId>asm-util</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <scope>test</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy-agent</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.ow2.asm</groupId>
+ <artifactId>asm-analysis</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>byte-buddy-agent</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/ByteBuddy.java b/byte-buddy-dep/src/main/java/net/bytebuddy/ByteBuddy.java
new file mode 100644
index 0000000..95b555e
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/ByteBuddy.java
@@ -0,0 +1,1231 @@
+package net.bytebuddy;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.modifier.*;
+import net.bytebuddy.description.type.PackageDescription;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.TargetType;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.dynamic.scaffold.MethodGraph;
+import net.bytebuddy.dynamic.scaffold.TypeValidation;
+import net.bytebuddy.dynamic.scaffold.inline.MethodNameTransformer;
+import net.bytebuddy.dynamic.scaffold.inline.RebaseDynamicTypeBuilder;
+import net.bytebuddy.dynamic.scaffold.inline.RedefinitionDynamicTypeBuilder;
+import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
+import net.bytebuddy.dynamic.scaffold.subclass.SubclassDynamicTypeBuilder;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.MethodCall;
+import net.bytebuddy.implementation.SuperMethodCall;
+import net.bytebuddy.implementation.attribute.AnnotationRetention;
+import net.bytebuddy.implementation.attribute.AnnotationValueFilter;
+import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.implementation.bytecode.Duplication;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.TypeCreation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.assign.TypeCasting;
+import net.bytebuddy.implementation.bytecode.collection.ArrayFactory;
+import net.bytebuddy.implementation.bytecode.constant.IntegerConstant;
+import net.bytebuddy.implementation.bytecode.constant.TextConstant;
+import net.bytebuddy.implementation.bytecode.member.FieldAccess;
+import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
+import net.bytebuddy.implementation.bytecode.member.MethodReturn;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.matcher.LatentMatcher;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.*;
+
+import static net.bytebuddy.matcher.ElementMatchers.*;
+
+/**
+ * Instances of this class serve as a focus point for configuration of the library's behavior and as an entry point
+ * to any form of code generation using the library. For this purpose, Byte Buddy offers a fluent API which allows
+ * for the step-wise generation of a new Java type. A type is generated either by:
+ * <ul>
+ * <li><b>Subclassing</b> some type: A subclass - as the name suggests - extends another, existing Java type. Virtual
+ * members of the generated type's super types can be overridden. Subclasses can also be interface extensions of one
+ * or several interfaces.</li>
+ * <li><b>Redefining</b> a type: By redefining a type, it is not only possible to override virtual methods of the
+ * redefined type but also to redefine existing methods. This way, it is also possible to change the behavior of
+ * non-virtual methods and constructors of the redefined type.</li>
+ * <li><b>Rebasing</b> a type: Rebasing a type works similar to creating a subclass, i.e. any method being overridden
+ * is still capable of invoking any original code of the rebased type. Any rebased method is however inlined into the
+ * rebased type and any original code is preserved automatically. This way, the type's identity does not change.</li>
+ * </ul>
+ * Byte Buddy's API does not change when a type is rebased, redefined or subclassed. All types are created via the
+ * {@link net.bytebuddy.dynamic.DynamicType.Builder} interface. Byte Buddy's API is expressed by fully immutable
+ * components and is therefore thread-safe. As a consequence, method calls must be chained for all of Byte Buddy's
+ * component, e.g. a method call like the following has no effect:
+ * <pre>
+ * ByteBuddy byteBuddy = new ByteBuddy();
+ * byteBuddy.foo()</pre>
+ * Instead, the following method chain is corrent use of the API:
+ * <pre>
+ * ByteBuddy byteBuddy = new ByteBuddy().foo();</pre>
+ * <p>
+ * For the creation of Java agents, Byte Buddy offers a convenience API implemented by the
+ * {@link net.bytebuddy.agent.builder.AgentBuilder}. The API wraps a {@link ByteBuddy} instance and offers agent-specific
+ * configuration opportunities by integrating against the {@link java.lang.instrument.Instrumentation} API.
+ * </p>
+ *
+ * @see net.bytebuddy.agent.builder.AgentBuilder
+ */
+ at EqualsAndHashCode
+public class ByteBuddy {
+
+ /**
+ * The default prefix for the default {@link net.bytebuddy.NamingStrategy}.
+ */
+ private static final String BYTE_BUDDY_DEFAULT_PREFIX = "ByteBuddy";
+
+ /**
+ * The default suffix when defining a {@link AuxiliaryType.NamingStrategy}.
+ */
+ private static final String BYTE_BUDDY_DEFAULT_SUFFIX = "auxiliary";
+
+ /**
+ * The class file version to use for types that are not based on an existing class file.
+ */
+ protected final ClassFileVersion classFileVersion;
+
+ /**
+ * The naming strategy to use.
+ */
+ protected final NamingStrategy namingStrategy;
+
+ /**
+ * The naming strategy to use for naming auxiliary types.
+ */
+ protected final AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy;
+
+ /**
+ * The annotation value filter factory to use.
+ */
+ protected final AnnotationValueFilter.Factory annotationValueFilterFactory;
+
+ /**
+ * The annotation retention strategy to use.
+ */
+ protected final AnnotationRetention annotationRetention;
+
+ /**
+ * The implementation context factory to use.
+ */
+ protected final Implementation.Context.Factory implementationContextFactory;
+
+ /**
+ * The method graph compiler to use.
+ */
+ protected final MethodGraph.Compiler methodGraphCompiler;
+
+ /**
+ * The instrumented type factory to use.
+ */
+ protected final InstrumentedType.Factory instrumentedTypeFactory;
+
+ /**
+ * A matcher for identifying methods that should be excluded from instrumentation.
+ */
+ protected final LatentMatcher<? super MethodDescription> ignoredMethods;
+
+ /**
+ * Determines if a type should be explicitly validated.
+ */
+ protected final TypeValidation typeValidation;
+
+ /**
+ * <p>
+ * Creates a new Byte Buddy instance with a default configuration that is suitable for most use cases.
+ * </p>
+ * <p>
+ * When creating this configuration, Byte Buddy attempts to discover the current JVM's version. If this
+ * is not possible, class files are created Java 6-compatible.
+ * </p>
+ *
+ * @see ClassFileVersion#ofThisVm(ClassFileVersion)
+ */
+ public ByteBuddy() {
+ this(ClassFileVersion.ofThisVm(ClassFileVersion.JAVA_V6));
+ }
+
+ /**
+ * Creates a new Byte Buddy instance with a default configuration that is suitable for most use cases.
+ *
+ * @param classFileVersion The class file version to use for types that are not based on an existing class file.
+ */
+ public ByteBuddy(ClassFileVersion classFileVersion) {
+ this(classFileVersion,
+ new NamingStrategy.SuffixingRandom(BYTE_BUDDY_DEFAULT_PREFIX),
+ new AuxiliaryType.NamingStrategy.SuffixingRandom(BYTE_BUDDY_DEFAULT_SUFFIX),
+ AnnotationValueFilter.Default.APPEND_DEFAULTS,
+ AnnotationRetention.ENABLED,
+ Implementation.Context.Default.Factory.INSTANCE,
+ MethodGraph.Compiler.DEFAULT,
+ InstrumentedType.Factory.Default.MODIFIABLE,
+ TypeValidation.ENABLED,
+ new LatentMatcher.Resolved<MethodDescription>(isSynthetic().or(isDefaultFinalizer())));
+ }
+
+ /**
+ * Creates a new Byte Buddy instance.
+ *
+ * @param classFileVersion The class file version to use for types that are not based on an existing class file.
+ * @param namingStrategy The naming strategy to use.
+ * @param auxiliaryTypeNamingStrategy The naming strategy to use for naming auxiliary types.
+ * @param annotationValueFilterFactory The annotation value filter factory to use.
+ * @param annotationRetention The annotation retention strategy to use.
+ * @param implementationContextFactory The implementation context factory to use.
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @param instrumentedTypeFactory The instrumented type factory to use.
+ * @param typeValidation Determines if a type should be explicitly validated.
+ * @param ignoredMethods A matcher for identifying methods that should be excluded from instrumentation.
+ */
+ protected ByteBuddy(ClassFileVersion classFileVersion,
+ NamingStrategy namingStrategy,
+ AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
+ AnnotationValueFilter.Factory annotationValueFilterFactory,
+ AnnotationRetention annotationRetention,
+ Implementation.Context.Factory implementationContextFactory,
+ MethodGraph.Compiler methodGraphCompiler,
+ InstrumentedType.Factory instrumentedTypeFactory,
+ TypeValidation typeValidation,
+ LatentMatcher<? super MethodDescription> ignoredMethods) {
+ this.classFileVersion = classFileVersion;
+ this.namingStrategy = namingStrategy;
+ this.auxiliaryTypeNamingStrategy = auxiliaryTypeNamingStrategy;
+ this.annotationValueFilterFactory = annotationValueFilterFactory;
+ this.annotationRetention = annotationRetention;
+ this.implementationContextFactory = implementationContextFactory;
+ this.methodGraphCompiler = methodGraphCompiler;
+ this.instrumentedTypeFactory = instrumentedTypeFactory;
+ this.ignoredMethods = ignoredMethods;
+ this.typeValidation = typeValidation;
+ }
+
+ /**
+ * <p>
+ * Creates a new builder for subclassing the provided type. If the provided type is an interface, a new class implementing
+ * this interface type is created.
+ * </p>
+ * <p>
+ * When extending a class, Byte Buddy imitates all visible constructors of the subclassed type. Any constructor is implemented
+ * to only invoke its super type constructor of equal signature. Another behavior can be specified by supplying an explicit
+ * {@link ConstructorStrategy} by {@link ByteBuddy#subclass(Class, ConstructorStrategy)}.
+ * </p>
+ * <p>
+ * <b>Note</b>: This methods implements the supplied types in a generified state if they declare type variables or an owner type.
+ * </p>
+ * <p>
+ * <b>Note</b>: Byte Buddy does not cache previous subclasses but will attempt the generation of a new subclass. For caching
+ * types, a external cache or {@link TypeCache} should be used.
+ * </p>
+ *
+ * @param superType The super class or interface type to extend.
+ * @param <T> A loaded type that the generated class is guaranteed to inherit.
+ * @return A type builder for creating a new class extending the provided class or interface.
+ */
+ @SuppressWarnings("unchecked")
+ public <T> DynamicType.Builder<T> subclass(Class<T> superType) {
+ return (DynamicType.Builder<T>) subclass(new TypeDescription.ForLoadedType(superType));
+ }
+
+ /**
+ * <p>
+ * Creates a new builder for subclassing the provided type. If the provided type is an interface, a new class implementing
+ * this interface type is created.
+ * </p>
+ * <p>
+ * <b>Note</b>: This methods implements the supplied types in a generified state if they declare type variables or an owner type.
+ * </p>
+ * <p>
+ * <b>Note</b>: Byte Buddy does not cache previous subclasses but will attempt the generation of a new subclass. For caching
+ * types, a external cache or {@link TypeCache} should be used.
+ * </p>
+ *
+ * @param superType The super class or interface type to extend.
+ * @param constructorStrategy A constructor strategy that determines the
+ * @param <T> A loaded type that the generated class is guaranteed to inherit.
+ * @return A type builder for creating a new class extending the provided class or interface.
+ */
+ @SuppressWarnings("unchecked")
+ public <T> DynamicType.Builder<T> subclass(Class<T> superType, ConstructorStrategy constructorStrategy) {
+ return (DynamicType.Builder<T>) subclass(new TypeDescription.ForLoadedType(superType), constructorStrategy);
+ }
+
+ /**
+ * <p>
+ * Creates a new builder for subclassing the provided type. If the provided type is an interface, a new class implementing
+ * this interface type is created.
+ * </p>
+ * <p>
+ * When extending a class, Byte Buddy imitates all visible constructors of the subclassed type. Any constructor is implemented
+ * to only invoke its super type constructor of equal signature. Another behavior can be specified by supplying an explicit
+ * {@link ConstructorStrategy} by {@link ByteBuddy#subclass(Type, ConstructorStrategy)}.
+ * </p>
+ * <p>
+ * <b>Note</b>: This methods implements the supplied types <i>as is</i>, i.e. any {@link Class} values are implemented
+ * as raw types if they declare type variables.
+ * </p>
+ * <p>
+ * <b>Note</b>: Byte Buddy does not cache previous subclasses but will attempt the generation of a new subclass. For caching
+ * types, a external cache or {@link TypeCache} should be used.
+ * </p>
+ *
+ * @param superType The super class or interface type to extend. The type must be a raw type or parameterized type. All type
+ * variables that are referenced by the generic type must be declared by the generated subclass before creating
+ * the type.
+ * @return A type builder for creating a new class extending the provided class or interface.
+ */
+ public DynamicType.Builder<?> subclass(Type superType) {
+ return subclass(TypeDefinition.Sort.describe(superType));
+ }
+
+ /**
+ * <p>
+ * Creates a new builder for subclassing the provided type. If the provided type is an interface, a new class implementing
+ * this interface type is created.
+ * </p>
+ * <p>
+ * <b>Note</b>: This methods implements the supplied types <i>as is</i>, i.e. any {@link Class} values are implemented
+ * as raw types if they declare type variables.
+ * </p>
+ * <p>
+ * <b>Note</b>: Byte Buddy does not cache previous subclasses but will attempt the generation of a new subclass. For caching
+ * types, a external cache or {@link TypeCache} should be used.
+ * </p>
+ *
+ * @param superType The super class or interface type to extend. The type must be a raw type or parameterized
+ * type. All type variables that are referenced by the generic type must be declared by the
+ * generated subclass before creating the type.
+ * @param constructorStrategy A constructor strategy that determines the
+ * @return A type builder for creating a new class extending the provided class or interface.
+ */
+ public DynamicType.Builder<?> subclass(Type superType, ConstructorStrategy constructorStrategy) {
+ return subclass(TypeDefinition.Sort.describe(superType), constructorStrategy);
+ }
+
+ /**
+ * <p>
+ * Creates a new builder for subclassing the provided type. If the provided type is an interface, a new class implementing
+ * this interface type is created.
+ * </p>
+ * <p>
+ * When extending a class, Byte Buddy imitates all visible constructors of the subclassed type and sets them to be {@code public}.
+ * Any constructor is implemented to only invoke its super type constructor of equal signature. Another behavior can be specified by
+ * supplying an explicit {@link ConstructorStrategy} by {@link ByteBuddy#subclass(TypeDefinition, ConstructorStrategy)}.
+ * </p>
+ * <p>
+ * <b>Note</b>: This methods implements the supplied types <i>as is</i>, i.e. any {@link TypeDescription} values are implemented
+ * as raw types if they declare type variables.
+ * </p>
+ * <p>
+ * <b>Note</b>: Byte Buddy does not cache previous subclasses but will attempt the generation of a new subclass. For caching
+ * types, a external cache or {@link TypeCache} should be used.
+ * </p>
+ *
+ * @param superType The super class or interface type to extend. The type must be a raw type or parameterized type. All type
+ * variables that are referenced by the generic type must be declared by the generated subclass before creating
+ * the type.
+ * @return A type builder for creating a new class extending the provided class or interface.
+ */
+ public DynamicType.Builder<?> subclass(TypeDefinition superType) {
+ return subclass(superType, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING);
+ }
+
+ /**
+ * <p>
+ * Creates a new builder for subclassing the provided type. If the provided type is an interface, a new class implementing
+ * this interface type is created.
+ * </p>
+ * <p>
+ * <b>Note</b>: This methods implements the supplied types <i>as is</i>, i.e. any {@link TypeDescription} values are implemented
+ * as raw types if they declare type variables.
+ * </p>
+ * <p>
+ * <b>Note</b>: Byte Buddy does not cache previous subclasses but will attempt the generation of a new subclass. For caching
+ * types, a external cache or {@link TypeCache} should be used.
+ * </p>
+ *
+ * @param superType The super class or interface type to extend. The type must be a raw type or parameterized
+ * type. All type variables that are referenced by the generic type must be declared by the
+ * generated subclass before creating the type.
+ * @param constructorStrategy A constructor strategy that determines the
+ * @return A type builder for creating a new class extending the provided class or interface.
+ */
+ public DynamicType.Builder<?> subclass(TypeDefinition superType, ConstructorStrategy constructorStrategy) {
+ TypeDescription.Generic actualSuperType;
+ TypeList.Generic interfaceTypes;
+ if (superType.isPrimitive() || superType.isArray() || superType.isFinal()) {
+ throw new IllegalArgumentException("Cannot subclass primitive, array or final types: " + superType);
+ } else if (superType.isInterface()) {
+ actualSuperType = TypeDescription.Generic.OBJECT;
+ interfaceTypes = new TypeList.Generic.Explicit(superType);
+ } else {
+ actualSuperType = superType.asGenericType();
+ interfaceTypes = new TypeList.Generic.Empty();
+ }
+ return new SubclassDynamicTypeBuilder<Object>(instrumentedTypeFactory.subclass(namingStrategy.subclass(superType.asGenericType()),
+ ModifierContributor.Resolver.of(Visibility.PUBLIC, TypeManifestation.PLAIN).resolve(superType.getModifiers()),
+ actualSuperType).withInterfaces(interfaceTypes),
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ typeValidation,
+ ignoredMethods,
+ constructorStrategy);
+ }
+
+ /**
+ * <p>
+ * Creates a new, plain interface type.
+ * </p>
+ * <p>
+ * <b>Note</b>: Byte Buddy does not cache previous subclasses but will attempt the generation of a new subclass. For caching
+ * types, a external cache or {@link TypeCache} should be used.
+ * </p>
+ *
+ * @return A type builder that creates a new interface type.
+ */
+ public DynamicType.Builder<?> makeInterface() {
+ return makeInterface(Collections.<TypeDescription>emptyList());
+ }
+
+ /**
+ * <p>
+ * Creates a new interface type that extends the provided interface.
+ * </p>
+ * <p>
+ * <b>Note</b>: This methods implements the supplied types in a generified state if they declare type variables or an owner type.
+ * </p>
+ * <p>
+ * <b>Note</b>: Byte Buddy does not cache previous subclasses but will attempt the generation of a new subclass. For caching
+ * types, a external cache or {@link TypeCache} should be used.
+ * </p>
+ *
+ * @param interfaceType An interface type that the generated interface implements.
+ * @param <T> A loaded type that the generated interface is guaranteed to inherit.
+ * @return A type builder that creates a new interface type.
+ */
+ @SuppressWarnings("unchecked")
+ public <T> DynamicType.Builder<T> makeInterface(Class<T> interfaceType) {
+ return (DynamicType.Builder<T>) makeInterface(Collections.<Type>singletonList(interfaceType));
+ }
+
+ /**
+ * <p>
+ * Creates a new interface type that extends the provided interface.
+ * </p>
+ * <p>
+ * <b>Note</b>: This methods implements the supplied types <i>as is</i>, i.e. any {@link Class} values are implemented
+ * as raw types if they declare type variables or an owner type.
+ * </p>
+ * <p>
+ * <b>Note</b>: Byte Buddy does not cache previous subclasses but will attempt the generation of a new subclass. For caching
+ * types, a external cache or {@link TypeCache} should be used.
+ * </p>
+ *
+ * @param interfaceType The interface types to implement. The types must be raw or parameterized types. All type
+ * variables that are referenced by a parameterized type must be declared by the generated
+ * subclass before creating the type.
+ * @return A type builder that creates a new interface type.
+ */
+ public DynamicType.Builder<?> makeInterface(Type... interfaceType) {
+ return makeInterface(Arrays.asList(interfaceType));
+ }
+
+ /**
+ * <p>
+ * Creates a new interface type that extends the provided interface.
+ * </p>
+ * <p>
+ * <b>Note</b>: This methods implements the supplied types <i>as is</i>, i.e. any {@link Class} values are implemented
+ * as raw types if they declare type variables or an owner type.
+ * </p>
+ * <p>
+ * <b>Note</b>: Byte Buddy does not cache previous subclasses but will attempt the generation of a new subclass. For caching
+ * types, a external cache or {@link TypeCache} should be used.
+ * </p>
+ *
+ * @param interfaceTypes The interface types to implement. The types must be raw or parameterized types. All
+ * type variables that are referenced by a parameterized type must be declared by the
+ * generated subclass before creating the type.
+ * @return A type builder that creates a new interface type.
+ */
+ public DynamicType.Builder<?> makeInterface(List<? extends Type> interfaceTypes) {
+ return makeInterface(new TypeList.Generic.ForLoadedTypes(interfaceTypes));
+ }
+
+ /**
+ * <p>
+ * Creates a new interface type that extends the provided interface.
+ * </p>
+ * <p>
+ * <b>Note</b>: This methods implements the supplied types <i>as is</i>, i.e. any {@link TypeDescription} values are implemented
+ * as raw types if they declare type variables or an owner type.
+ * </p>
+ * <p>
+ * <b>Note</b>: Byte Buddy does not cache previous subclasses but will attempt the generation of a new subclass. For caching
+ * types, a external cache or {@link TypeCache} should be used.
+ * </p>
+ *
+ * @param interfaceType The interface types to implement. The types must be raw or parameterized types. All
+ * type variables that are referenced by a parameterized type must be declared by the
+ * generated subclass before creating the type.
+ * @return A type builder that creates a new interface type.
+ */
+ public DynamicType.Builder<?> makeInterface(TypeDefinition... interfaceType) {
+ return makeInterface(Arrays.asList(interfaceType));
+ }
+
+ /**
+ * <p>
+ * Creates a new interface type that extends the provided interface.
+ * </p>
+ * <p>
+ * <b>Note</b>: This methods implements the supplied types <i>as is</i>, i.e. any {@link TypeDescription} values are implemented
+ * as raw types if they declare type variables or an owner type.
+ * </p>
+ * <p>
+ * <b>Note</b>: Byte Buddy does not cache previous subclasses but will attempt the generation of a new subclass. For caching
+ * types, a external cache or {@link TypeCache} should be used.
+ * </p>
+ *
+ * @param interfaceTypes The interface types to implement. The types must be raw or parameterized types. All
+ * type variables that are referenced by a parameterized type must be declared by the
+ * generated subclass before creating the type.
+ * @return A type builder that creates a new interface type.
+ */
+ public DynamicType.Builder<?> makeInterface(Collection<? extends TypeDefinition> interfaceTypes) {
+ return subclass(Object.class, ConstructorStrategy.Default.NO_CONSTRUCTORS).implement(interfaceTypes).modifiers(TypeManifestation.INTERFACE, Visibility.PUBLIC);
+ }
+
+ /**
+ * <p>
+ * Creates a new package definition. Package definitions are defined by classes named {@code package-info}
+ * without any methods or fields but permit annotations. Any field or method definition will cause an
+ * {@link IllegalStateException} to be thrown when the type is created.
+ * </p>
+ * <p>
+ * <b>Note</b>: Byte Buddy does not cache previous subclasses but will attempt the generation of a new subclass. For caching
+ * types, a external cache or {@link TypeCache} should be used.
+ * </p>
+ *
+ * @param name The fully qualified name of the package.
+ * @return A type builder that creates a {@code package-info} class file.
+ */
+ public DynamicType.Builder<?> makePackage(String name) {
+ return new SubclassDynamicTypeBuilder<Object>(instrumentedTypeFactory.subclass(name + "." + PackageDescription.PACKAGE_CLASS_NAME,
+ PackageDescription.PACKAGE_MODIFIERS,
+ TypeDescription.Generic.OBJECT),
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ typeValidation,
+ ignoredMethods,
+ ConstructorStrategy.Default.NO_CONSTRUCTORS);
+ }
+
+ /**
+ * <p>
+ * Creates a new {@link Annotation} type. Annotation properties are implemented as non-static, public methods with the
+ * property type being defined as the return type.
+ * </p>
+ * <p>
+ * <b>Note</b>: Byte Buddy does not cache previous subclasses but will attempt the generation of a new subclass. For caching
+ * types, a external cache or {@link TypeCache} should be used.
+ * </p>
+ *
+ * @return A type builder that creates a new {@link Annotation} type.
+ */
+ public DynamicType.Builder<? extends Annotation> makeAnnotation() {
+ return new SubclassDynamicTypeBuilder<Annotation>(instrumentedTypeFactory.subclass(namingStrategy.subclass(TypeDescription.Generic.ANNOTATION),
+ ModifierContributor.Resolver.of(Visibility.PUBLIC, TypeManifestation.ANNOTATION).resolve(),
+ TypeDescription.Generic.OBJECT).withInterfaces(new TypeList.Generic.Explicit(TypeDescription.Generic.ANNOTATION)),
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ typeValidation,
+ ignoredMethods,
+ ConstructorStrategy.Default.NO_CONSTRUCTORS);
+ }
+
+ /**
+ * <p>
+ * Creates a new {@link Enum} type.
+ * </p>
+ * <p>
+ * <b>Note</b>: Byte Buddy does not cache previous subclasses but will attempt the generation of a new subclass. For caching
+ * types, a external cache or {@link TypeCache} should be used.
+ * </p>
+ *
+ * @param value The names of the type's enumeration constants
+ * @return A type builder for creating an enumeration type.
+ */
+ public DynamicType.Builder<? extends Enum<?>> makeEnumeration(String... value) {
+ return makeEnumeration(Arrays.asList(value));
+ }
+
+ /**
+ * <p>
+ * Creates a new {@link Enum} type.
+ * </p>
+ * <p>
+ * <b>Note</b>: Byte Buddy does not cache previous subclasses but will attempt the generation of a new subclass. For caching
+ * types, a external cache or {@link TypeCache} should be used.
+ * </p>
+ *
+ * @param values The names of the type's enumeration constants
+ * @return A type builder for creating an enumeration type.
+ */
+ public DynamicType.Builder<? extends Enum<?>> makeEnumeration(Collection<? extends String> values) {
+ if (values.isEmpty()) {
+ throw new IllegalArgumentException("Require at least one enumeration constant");
+ }
+ TypeDescription.Generic enumType = TypeDescription.Generic.Builder.parameterizedType(Enum.class, TargetType.class).build();
+ return new SubclassDynamicTypeBuilder<Enum<?>>(instrumentedTypeFactory.subclass(namingStrategy.subclass(enumType),
+ ModifierContributor.Resolver.of(Visibility.PUBLIC, TypeManifestation.FINAL, EnumerationState.ENUMERATION).resolve(),
+ enumType),
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ typeValidation,
+ ignoredMethods,
+ ConstructorStrategy.Default.NO_CONSTRUCTORS)
+ .defineConstructor(Visibility.PRIVATE).withParameters(String.class, int.class)
+ .intercept(SuperMethodCall.INSTANCE)
+ .defineMethod(EnumerationImplementation.ENUM_VALUE_OF_METHOD_NAME,
+ TargetType.class,
+ Visibility.PUBLIC, Ownership.STATIC).withParameters(String.class)
+ .intercept(MethodCall.invoke(enumType.getDeclaredMethods()
+ .filter(named(EnumerationImplementation.ENUM_VALUE_OF_METHOD_NAME).and(takesArguments(Class.class, String.class))).getOnly())
+ .withOwnType().withArgument(0)
+ .withAssigner(Assigner.DEFAULT, Assigner.Typing.DYNAMIC))
+ .defineMethod(EnumerationImplementation.ENUM_VALUES_METHOD_NAME,
+ TargetType[].class,
+ Visibility.PUBLIC, Ownership.STATIC)
+ .intercept(new EnumerationImplementation(new ArrayList<String>(values)));
+ }
+
+ /**
+ * <p>
+ * Redefines the given type where any intercepted method that is declared by the redefined type is fully replaced
+ * by the new implementation.
+ * </p>
+ * <p>
+ * The class file of the redefined type is located by querying the redefined type's class loader by name. For specifying an
+ * alternative {@link ClassFileLocator}, use {@link ByteBuddy#redefine(Class, ClassFileLocator)}.
+ * </p>
+ * <p>
+ * <b>Note</b>: When a user redefines a class with the purpose of reloading this class using a {@link net.bytebuddy.dynamic.loading.ClassReloadingStrategy},
+ * it is important that no fields or methods are added to the redefined class. Note that some {@link Implementation}s implicitly add fields or methods.
+ * Finally, Byte Buddy might be forced to add a method if a redefined class already defines a class initializer. This can be disabled by setting
+ * {@link ByteBuddy#with(Implementation.Context.Factory)} to use a {@link net.bytebuddy.implementation.Implementation.Context.Disabled.Factory}
+ * where the class initializer is retained <i>as is</i>.
+ * </p>
+ *
+ * @param type The type that is being redefined.
+ * @param <T> The loaded type of the redefined type.
+ * @return A type builder for redefining the provided type.
+ */
+ public <T> DynamicType.Builder<T> redefine(Class<T> type) {
+ return redefine(type, ClassFileLocator.ForClassLoader.of(type.getClassLoader()));
+ }
+
+ /**
+ * <p>
+ * Redefines the given type where any intercepted method that is declared by the redefined type is fully replaced
+ * by the new implementation.
+ * </p>
+ * <p>
+ * <b>Note</b>: When a user redefines a class with the purpose of reloading this class using a {@link net.bytebuddy.dynamic.loading.ClassReloadingStrategy},
+ * it is important that no fields or methods are added to the redefined class. Note that some {@link Implementation}s implicitly add fields or methods.
+ * Finally, Byte Buddy might be forced to add a method if a redefined class already defines a class initializer. This can be disabled by setting
+ * {@link ByteBuddy#with(Implementation.Context.Factory)} to use a {@link net.bytebuddy.implementation.Implementation.Context.Disabled.Factory}
+ * where the class initializer is retained <i>as is</i>.
+ * </p>
+ *
+ * @param type The type that is being redefined.
+ * @param classFileLocator The class file locator that is queried for the redefined type's class file.
+ * @param <T> The loaded type of the redefined type.
+ * @return A type builder for redefining the provided type.
+ */
+ public <T> DynamicType.Builder<T> redefine(Class<T> type, ClassFileLocator classFileLocator) {
+ return redefine(new TypeDescription.ForLoadedType(type), classFileLocator);
+ }
+
+ /**
+ * <p>
+ * Redefines the given type where any intercepted method that is declared by the redefined type is fully replaced
+ * by the new implementation.
+ * </p>
+ * <p>
+ * <b>Note</b>: When a user redefines a class with the purpose of reloading this class using a {@link net.bytebuddy.dynamic.loading.ClassReloadingStrategy},
+ * it is important that no fields or methods are added to the redefined class. Note that some {@link Implementation}s implicitly add fields or methods.
+ * Finally, Byte Buddy might be forced to add a method if a redefined class already defines a class initializer. This can be disabled by setting
+ * {@link ByteBuddy#with(Implementation.Context.Factory)} to use a {@link net.bytebuddy.implementation.Implementation.Context.Disabled.Factory}
+ * where the class initializer is retained <i>as is</i>.
+ * </p>
+ *
+ * @param type The type that is being redefined.
+ * @param classFileLocator The class file locator that is queried for the redefined type's class file.
+ * @param <T> The loaded type of the redefined type.
+ * @return A type builder for redefining the provided type.
+ */
+ public <T> DynamicType.Builder<T> redefine(TypeDescription type, ClassFileLocator classFileLocator) {
+ return new RedefinitionDynamicTypeBuilder<T>(instrumentedTypeFactory.represent(type),
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ typeValidation,
+ ignoredMethods,
+ type,
+ classFileLocator);
+ }
+
+ /**
+ * <p>
+ * Rebases the given type where any intercepted method that is declared by the redefined type is preserved within the
+ * rebased type's class such that the class's original can be invoked from the new method implementations. Rebasing a
+ * type can be seen similarly to creating a subclass where the subclass is later merged with the original class file.
+ * </p>
+ * <p>
+ * The class file of the rebased type is located by querying the rebased type's class loader by name. For specifying an
+ * alternative {@link ClassFileLocator}, use {@link ByteBuddy#redefine(Class, ClassFileLocator)}.
+ * </p>
+ *
+ * @param type The type that is being rebased.
+ * @param <T> The loaded type of the rebased type.
+ * @return A type builder for rebasing the provided type.
+ */
+ public <T> DynamicType.Builder<T> rebase(Class<T> type) {
+ return rebase(type, ClassFileLocator.ForClassLoader.of(type.getClassLoader()));
+ }
+
+ /**
+ * <p>
+ * Rebases the given type where any intercepted method that is declared by the redefined type is preserved within the
+ * rebased type's class such that the class's original can be invoked from the new method implementations. Rebasing a
+ * type can be seen similarly to creating a subclass where the subclass is later merged with the original class file.
+ * </p>
+ * <p>
+ * When a method is rebased, the original method is copied into a new method with a different name. These names are
+ * generated automatically by Byte Buddy unless a {@link MethodNameTransformer} is specified explicitly.
+ * Use {@link ByteBuddy#rebase(Class, ClassFileLocator, MethodNameTransformer)} for doing so.
+ * </p>
+ *
+ * @param type The type that is being rebased.
+ * @param classFileLocator The class file locator that is queried for the rebased type's class file.
+ * @param <T> The loaded type of the rebased type.
+ * @return A type builder for rebasing the provided type.
+ */
+ public <T> DynamicType.Builder<T> rebase(Class<T> type, ClassFileLocator classFileLocator) {
+ return rebase(new TypeDescription.ForLoadedType(type), classFileLocator);
+ }
+
+ /**
+ * Rebases the given type where any intercepted method that is declared by the redefined type is preserved within the
+ * rebased type's class such that the class's original can be invoked from the new method implementations. Rebasing a
+ * type can be seen similarly to creating a subclass where the subclass is later merged with the original class file.
+ *
+ * @param type The type that is being rebased.
+ * @param classFileLocator The class file locator that is queried for the rebased type's class file.
+ * @param methodNameTransformer The method name transformer for renaming a method that is rebased.
+ * @param <T> The loaded type of the rebased type.
+ * @return A type builder for rebasing the provided type.
+ */
+ public <T> DynamicType.Builder<T> rebase(Class<T> type, ClassFileLocator classFileLocator, MethodNameTransformer methodNameTransformer) {
+ return rebase(new TypeDescription.ForLoadedType(type), classFileLocator, methodNameTransformer);
+ }
+
+ /**
+ * <p>
+ * Rebases the given type where any intercepted method that is declared by the redefined type is preserved within the
+ * rebased type's class such that the class's original can be invoked from the new method implementations. Rebasing a
+ * type can be seen similarly to creating a subclass where the subclass is later merged with the original class file.
+ * </p>
+ * <p>
+ * When a method is rebased, the original method is copied into a new method with a different name. These names are
+ * generated automatically by Byte Buddy unless a {@link MethodNameTransformer} is specified explicitly.
+ * Use {@link ByteBuddy#rebase(TypeDescription, ClassFileLocator, MethodNameTransformer)} for doing so.
+ * </p>
+ *
+ * @param type The type that is being rebased.
+ * @param classFileLocator The class file locator that is queried for the rebased type's class file.
+ * @param <T> The loaded type of the rebased type.
+ * @return A type builder for rebasing the provided type.
+ */
+ public <T> DynamicType.Builder<T> rebase(TypeDescription type, ClassFileLocator classFileLocator) {
+ return rebase(type, classFileLocator, MethodNameTransformer.Suffixing.withRandomSuffix());
+ }
+
+ /**
+ * Rebases the given type where any intercepted method that is declared by the redefined type is preserved within the
+ * rebased type's class such that the class's original can be invoked from the new method implementations. Rebasing a
+ * type can be seen similarly to creating a subclass where the subclass is later merged with the original class file.
+ *
+ * @param type The type that is being rebased.
+ * @param classFileLocator The class file locator that is queried for the rebased type's class file.
+ * @param methodNameTransformer The method name transformer for renaming a method that is rebased.
+ * @param <T> The loaded type of the rebased type.
+ * @return A type builder for rebasing the provided type.
+ */
+ public <T> DynamicType.Builder<T> rebase(TypeDescription type, ClassFileLocator classFileLocator, MethodNameTransformer methodNameTransformer) {
+ return new RebaseDynamicTypeBuilder<T>(instrumentedTypeFactory.represent(type),
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ typeValidation,
+ ignoredMethods,
+ type,
+ classFileLocator,
+ methodNameTransformer);
+ }
+
+ /**
+ * Rebases a package. This offers an opportunity to add annotations to the package definition. Packages are defined
+ * by classes named {@code package-info} without any methods or fields but permit annotations. Any field or method
+ * definition will cause an {@link IllegalStateException} to be thrown when the type is created.
+ *
+ * @param aPackage The package that is being rebased.
+ * @param classFileLocator The class file locator to use for locating the package's class file.
+ * @return A type builder for rebasing the given package.
+ */
+ public DynamicType.Builder<?> rebase(Package aPackage, ClassFileLocator classFileLocator) {
+ return rebase(new PackageDescription.ForLoadedPackage(aPackage), classFileLocator);
+ }
+
+ /**
+ * Rebases a package. This offers an opportunity to add annotations to the package definition. Packages are defined
+ * by classes named {@code package-info} without any methods or fields but permit annotations. Any field or method
+ * definition will cause an {@link IllegalStateException} to be thrown when the type is created.
+ *
+ * @param aPackage The package that is being rebased.
+ * @param classFileLocator The class file locator to use for locating the package's class file.
+ * @return A type builder for rebasing the given package.
+ */
+ public DynamicType.Builder<?> rebase(PackageDescription aPackage, ClassFileLocator classFileLocator) {
+ return rebase(new TypeDescription.ForPackageDescription(aPackage), classFileLocator);
+ }
+
+ /**
+ * Creates a new configuration where all class files that are not based on an existing class file are created
+ * using the supplied class file version. When creating a Byte Buddy instance by {@link ByteBuddy#ByteBuddy()}, the class
+ * file version is detected automatically. If the class file version is known before creating a Byte Buddy instance, the
+ * {@link ByteBuddy#ByteBuddy(ClassFileVersion)} constructor should be used.
+ *
+ * @param classFileVersion The class file version to use for types that are not based on an existing class file.
+ * @return A new Byte Buddy instance that uses the supplied class file version.
+ */
+ public ByteBuddy with(ClassFileVersion classFileVersion) {
+ return new ByteBuddy(classFileVersion,
+ namingStrategy,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ instrumentedTypeFactory,
+ typeValidation,
+ ignoredMethods);
+ }
+
+ /**
+ * Creates a new configuration where new types are named by applying the given naming strategy. By default, Byte Buddy
+ * simply retains the name of rebased and redefined types but adds a random suffix to the name of created subclasses or
+ * -interfaces. If a type is defined within the {@code java.*} namespace, Byte Buddy also adds a suffix to the generated
+ * class because this namespace is only available for the bootstrap class loader.
+ *
+ * @param namingStrategy The naming strategy to apply when creating a new dynamic type.
+ * @return A new Byte Buddy instance that uses the supplied naming strategy.
+ */
+ public ByteBuddy with(NamingStrategy namingStrategy) {
+ return new ByteBuddy(classFileVersion,
+ namingStrategy,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ instrumentedTypeFactory,
+ typeValidation,
+ ignoredMethods);
+ }
+
+ /**
+ * Creates a new configuration where auxiliary types are named by applying the given naming strategy. Auxiliary types
+ * are helper types that might be required for implementing certain {@link Implementation}s. By default, Byte Buddy
+ * adds a random suffix to the instrumented type's name when naming its auxiliary types.
+ *
+ * @param auxiliaryTypeNamingStrategy The naming strategy to apply when creating a new auxiliary type.
+ * @return A new Byte Buddy instance that uses the supplied naming strategy for auxiliary types.
+ */
+ public ByteBuddy with(AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy) {
+ return new ByteBuddy(classFileVersion,
+ namingStrategy,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ instrumentedTypeFactory,
+ typeValidation,
+ ignoredMethods);
+ }
+
+ /**
+ * Creates a new configuration where annotation values are written according to the given filter factory. Using
+ * a filter factory, it is for example possible not to include certain values into a class file such that the
+ * runtime returns an annotation type's default value. By default, Byte Buddy includes all values into a class file,
+ * also such values for which a default value exists.
+ *
+ * @param annotationValueFilterFactory The annotation value filter factory to use.
+ * @return A new Byte Buddy instance that uses the supplied annotation value filter factory.
+ */
+ public ByteBuddy with(AnnotationValueFilter.Factory annotationValueFilterFactory) {
+ return new ByteBuddy(classFileVersion,
+ namingStrategy,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ instrumentedTypeFactory,
+ typeValidation,
+ ignoredMethods);
+ }
+
+ /**
+ * <p>
+ * Creates a new configuration where annotations that are found in an existing class file are or are not preserved
+ * in the format they are discovered, i.e. rewritten in the format they were already present in the class file.
+ * By default, Byte Buddy retains annotations when a class is rebased or redefined.
+ * </p>
+ * <p>
+ * <b>Warning</b>: Retaining annotations can cause problems when annotations of a field or method are added based
+ * on the annotations of a matched method. Doing so, Byte Buddy might write the annotations of the field or method
+ * explicitly to a class file while simultaneously retaining the existing annotation what results in duplicates.
+ * When matching fields or methods while adding annotations, disabling annotation retention might be required.
+ * </p>
+ *
+ * @param annotationRetention The annotation retention strategy to use.
+ * @return A new Byte Buddy instance that uses the supplied annotation retention strategy.
+ */
+ public ByteBuddy with(AnnotationRetention annotationRetention) {
+ return new ByteBuddy(classFileVersion,
+ namingStrategy,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ instrumentedTypeFactory,
+ typeValidation,
+ ignoredMethods);
+ }
+
+ /**
+ * Creates a new configuration where the {@link net.bytebuddy.implementation.Implementation.Context} of any created
+ * type is a product of the given implementation context factory. An implementation context might imply unwanted
+ * side-effects, for example, the creation of an additional synthetic methods in order to support specific features
+ * for realizing an {@link Implementation}. By default, Byte Buddy supplies a factory that enables all features. When
+ * redefining a loaded class, it is however required by the JVM that no additional members are added such that a
+ * {@link net.bytebuddy.implementation.Implementation.Context.Disabled} factory might be more appropriate.
+ *
+ * @param implementationContextFactory The implementation context factory to use for defining an instrumented type.
+ * @return A new Byte Buddy instance that uses the supplied implementation context factory.
+ */
+ public ByteBuddy with(Implementation.Context.Factory implementationContextFactory) {
+ return new ByteBuddy(classFileVersion,
+ namingStrategy,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ instrumentedTypeFactory,
+ typeValidation,
+ ignoredMethods);
+ }
+
+ /**
+ * Creates a new configuration where the {@link MethodGraph.Compiler} is used for creating a {@link MethodGraph}
+ * of the instrumented type. A method graph is a representation of a type's virtual methods, including all information
+ * on bridge methods that are inserted by the Java compiler. Creating a method graph is a rather expensive operation
+ * and more efficient strategies might exist for certain types or ava types that are created by alternative JVM
+ * languages. By default, a general purpose method graph compiler is used that uses the information that is exposed
+ * by the generic type information that is embedded in any class file.
+ *
+ * @param methodGraphCompiler The method graph compiler to use for analyzing the instrumented type.
+ * @return A new Byte Buddy instance that uses the supplied method graph compiler.
+ */
+ public ByteBuddy with(MethodGraph.Compiler methodGraphCompiler) {
+ return new ByteBuddy(classFileVersion,
+ namingStrategy,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ instrumentedTypeFactory,
+ typeValidation,
+ ignoredMethods);
+ }
+
+ /**
+ * Configures Byte Buddy to use the specified factory for creating {@link InstrumentedType}s. Doing so, more efficient
+ * representations can be chosen when only certain operations are required. By default, all operations are supported.
+ *
+ * @param instrumentedTypeFactory The factory to use when creating instrumented types.
+ * @return A new Byte Buddy instance that uses the supplied factory for creating instrumented types.
+ */
+ public ByteBuddy with(InstrumentedType.Factory instrumentedTypeFactory) {
+ return new ByteBuddy(classFileVersion,
+ namingStrategy,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ instrumentedTypeFactory,
+ typeValidation,
+ ignoredMethods);
+ }
+
+ /**
+ * Creates a new configuration that applies the supplied type validation. By default, explicitly type validation is applied
+ * by Byte Buddy but it might be disabled for performance reason or for voluntarily creating illegal types. The Java virtual
+ * machine applies its own type validation where some {@link Error} is thrown if a type is invalid, while Byte Buddy throws
+ * some {@link RuntimeException}.
+ *
+ * @param typeValidation The type validation to apply during type creation.
+ * @return A new Byte Buddy instance that applies the supplied type validation.
+ */
+ public ByteBuddy with(TypeValidation typeValidation) {
+ return new ByteBuddy(classFileVersion,
+ namingStrategy,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ instrumentedTypeFactory,
+ typeValidation,
+ ignoredMethods);
+ }
+
+ /**
+ * Creates a new configuration where any {@link MethodDescription} that matches the provided method matcher is excluded
+ * from instrumentation. Any previous matcher for ignored methods is replaced. By default, Byte Buddy ignores any
+ * synthetic method (bridge methods are handled automatically) and the {@link Object#finalize()} method.
+ *
+ * @param ignoredMethods A matcher for identifying methods to be excluded from instrumentation.
+ * @return A new Byte Buddy instance that excludes any method from instrumentation if it is matched by the supplied matcher.
+ */
+ public ByteBuddy ignore(ElementMatcher<? super MethodDescription> ignoredMethods) {
+ return ignore(new LatentMatcher.Resolved<MethodDescription>(ignoredMethods));
+ }
+
+ /**
+ * <p>
+ * Creates a new configuration where any {@link MethodDescription} that matches the provided method matcher is excluded
+ * from instrumentation. Any previous matcher for ignored methods is replaced. By default, Byte Buddy ignores any
+ * synthetic method (bridge methods are handled automatically) and the {@link Object#finalize()} method. Using a latent
+ * matcher gives opportunity to resolve an {@link ElementMatcher} based on the instrumented type before applying the matcher.
+ * </p>
+ *
+ * @param ignoredMethods A matcher for identifying methods to be excluded from instrumentation.
+ * @return A new Byte Buddy instance that excludes any method from instrumentation if it is matched by the supplied matcher.
+ */
+ public ByteBuddy ignore(LatentMatcher<? super MethodDescription> ignoredMethods) {
+ return new ByteBuddy(classFileVersion,
+ namingStrategy,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ instrumentedTypeFactory,
+ typeValidation,
+ ignoredMethods);
+ }
+
+ /**
+ * An implementation fo the {@code values} method of an enumeration type.
+ */
+ @EqualsAndHashCode
+ protected static class EnumerationImplementation implements Implementation {
+
+ /**
+ * The name of the {@link java.lang.Object#clone()} method.
+ */
+ protected static final String CLONE_METHOD_NAME = "clone";
+
+ /**
+ * The name of the {@code valueOf} method that is defined for any enumeration.
+ */
+ protected static final String ENUM_VALUE_OF_METHOD_NAME = "valueOf";
+
+ /**
+ * The name of the {@code values} method that is defined for any enumeration.
+ */
+ protected static final String ENUM_VALUES_METHOD_NAME = "values";
+
+ /**
+ * The field modifiers to use for any field that is added to an enumeration.
+ */
+ private static final int ENUM_FIELD_MODIFIERS = Opcodes.ACC_FINAL | Opcodes.ACC_STATIC | Opcodes.ACC_PUBLIC;
+
+ /**
+ * The name of the field containing an array of all enumeration values.
+ */
+ private static final String ENUM_VALUES = "$VALUES";
+
+ /**
+ * The names of the enumerations to define for the enumeration.
+ */
+ private final List<String> values;
+
+ /**
+ * Creates a new implementation of an enumeration type.
+ *
+ * @param values The values of the enumeration.
+ */
+ protected EnumerationImplementation(List<String> values) {
+ this.values = values;
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ for (String value : values) {
+ instrumentedType = instrumentedType.withField(new FieldDescription.Token(value,
+ ENUM_FIELD_MODIFIERS | Opcodes.ACC_ENUM,
+ TargetType.DESCRIPTION.asGenericType()));
+ }
+ return instrumentedType
+ .withField(new FieldDescription.Token(ENUM_VALUES,
+ ENUM_FIELD_MODIFIERS | Opcodes.ACC_SYNTHETIC,
+ TypeDescription.ArrayProjection.of(TargetType.DESCRIPTION).asGenericType()))
+ .withInitializer(new InitializationAppender(values));
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return new ValuesMethodAppender(implementationTarget.getInstrumentedType());
+ }
+
+ /**
+ * A byte code appender for the {@code values} method of any enumeration type.
+ */
+ @EqualsAndHashCode
+ protected static class ValuesMethodAppender implements ByteCodeAppender {
+
+ /**
+ * The instrumented enumeration type.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * Creates a new appender for the {@code values} method.
+ *
+ * @param instrumentedType The instrumented enumeration type.
+ */
+ protected ValuesMethodAppender(TypeDescription instrumentedType) {
+ this.instrumentedType = instrumentedType;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ FieldDescription valuesField = instrumentedType.getDeclaredFields().filter(named(ENUM_VALUES)).getOnly();
+ MethodDescription cloneMethod = TypeDescription.Generic.OBJECT.getDeclaredMethods().filter(named(CLONE_METHOD_NAME)).getOnly();
+ return new Size(new StackManipulation.Compound(
+ FieldAccess.forField(valuesField).read(),
+ MethodInvocation.invoke(cloneMethod).virtual(valuesField.getType().asErasure()),
+ TypeCasting.to(valuesField.getType().asErasure()),
+ MethodReturn.REFERENCE
+ ).apply(methodVisitor, implementationContext).getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+ }
+
+ /**
+ * A byte code appender for the type initializer of any enumeration type.
+ */
+ @EqualsAndHashCode
+ protected static class InitializationAppender implements ByteCodeAppender {
+
+ /**
+ * The values of the enumeration that is being created.
+ */
+ private final List<String> values;
+
+ /**
+ * Creates an appender for an enumerations type initializer.
+ *
+ * @param values The values of the enumeration that is being created.
+ */
+ protected InitializationAppender(List<String> values) {
+ this.values = values;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ TypeDescription instrumentedType = instrumentedMethod.getDeclaringType().asErasure();
+ MethodDescription enumConstructor = instrumentedType.getDeclaredMethods()
+ .filter(isConstructor().and(takesArguments(String.class, int.class)))
+ .getOnly();
+ int ordinal = 0;
+ StackManipulation stackManipulation = StackManipulation.Trivial.INSTANCE;
+ List<FieldDescription> enumerationFields = new ArrayList<FieldDescription>(values.size());
+ for (String value : values) {
+ FieldDescription fieldDescription = instrumentedType.getDeclaredFields().filter(named(value)).getOnly();
+ stackManipulation = new StackManipulation.Compound(stackManipulation,
+ TypeCreation.of(instrumentedType),
+ Duplication.SINGLE,
+ new TextConstant(value),
+ IntegerConstant.forValue(ordinal++),
+ MethodInvocation.invoke(enumConstructor),
+ FieldAccess.forField(fieldDescription).write());
+ enumerationFields.add(fieldDescription);
+ }
+ List<StackManipulation> fieldGetters = new ArrayList<StackManipulation>(values.size());
+ for (FieldDescription fieldDescription : enumerationFields) {
+ fieldGetters.add(FieldAccess.forField(fieldDescription).read());
+ }
+ stackManipulation = new StackManipulation.Compound(
+ stackManipulation,
+ ArrayFactory.forType(instrumentedType.asGenericType()).withValues(fieldGetters),
+ FieldAccess.forField(instrumentedType.getDeclaredFields().filter(named(ENUM_VALUES)).getOnly()).write()
+ );
+ return new Size(stackManipulation.apply(methodVisitor, implementationContext).getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/ClassFileVersion.java b/byte-buddy-dep/src/main/java/net/bytebuddy/ClassFileVersion.java
new file mode 100644
index 0000000..b1d1d7a
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/ClassFileVersion.java
@@ -0,0 +1,412 @@
+package net.bytebuddy;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+/**
+ * A wrapper object for representing a validated class file version in the format that is specified by the
+ * <a href="http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html">JVMS</a>.
+ */
+ at EqualsAndHashCode
+public class ClassFileVersion implements Comparable<ClassFileVersion> {
+
+ /**
+ * Returns the minimal version number that is legal.
+ */
+ protected static final int BASE_VERSION = 44;
+
+ /**
+ * The class file version of Java 1.
+ */
+ public static final ClassFileVersion JAVA_V1 = new ClassFileVersion(Opcodes.V1_1);
+
+ /**
+ * The class file version of Java 2.
+ */
+ public static final ClassFileVersion JAVA_V2 = new ClassFileVersion(Opcodes.V1_2);
+
+ /**
+ * The class file version of Java 3.
+ */
+ public static final ClassFileVersion JAVA_V3 = new ClassFileVersion(Opcodes.V1_3);
+
+ /**
+ * The class file version of Java 4.
+ */
+ public static final ClassFileVersion JAVA_V4 = new ClassFileVersion(Opcodes.V1_4);
+
+ /**
+ * The class file version of Java 5.
+ */
+ public static final ClassFileVersion JAVA_V5 = new ClassFileVersion(Opcodes.V1_5);
+
+ /**
+ * The class file version of Java 6.
+ */
+ public static final ClassFileVersion JAVA_V6 = new ClassFileVersion(Opcodes.V1_6);
+
+ /**
+ * The class file version of Java 7.
+ */
+ public static final ClassFileVersion JAVA_V7 = new ClassFileVersion(Opcodes.V1_7);
+
+ /**
+ * The class file version of Java 8.
+ */
+ public static final ClassFileVersion JAVA_V8 = new ClassFileVersion(Opcodes.V1_8);
+
+ /**
+ * The class file version of Java 9.
+ */
+ public static final ClassFileVersion JAVA_V9 = new ClassFileVersion(53);
+
+ /**
+ * A version locator for the executing JVM.
+ */
+ private static final VersionLocator VERSION_LOCATOR = AccessController.doPrivileged(VersionLocator.CreationAction.INSTANCE);
+
+ /**
+ * The version number that is represented by this class file version instance.
+ */
+ private final int versionNumber;
+
+ /**
+ * Creates a wrapper for a given minor-major release of the Java class file file.
+ *
+ * @param versionNumber The minor-major release number.
+ */
+ protected ClassFileVersion(int versionNumber) {
+ this.versionNumber = versionNumber;
+ }
+
+ /**
+ * Creates a wrapper for a given minor-major release of the Java class file file.
+ *
+ * @param versionNumber The minor-major release number.
+ * @return A representation of the version number.
+ */
+ public static ClassFileVersion ofMinorMajor(int versionNumber) {
+ ClassFileVersion classFileVersion = new ClassFileVersion(versionNumber);
+ if (classFileVersion.getMajorVersion() <= BASE_VERSION) {
+ throw new IllegalArgumentException("Class version " + versionNumber + " is not valid");
+ }
+ return classFileVersion;
+ }
+
+ /**
+ * Creates a class file version for a given major release of Java. Currently, all versions reaching from
+ * Java 1 to Java 8 are supported.
+ *
+ * @param javaVersion The Java version.
+ * @return A wrapper for the given Java class file version.
+ */
+ public static ClassFileVersion ofJavaVersion(int javaVersion) {
+ switch (javaVersion) {
+ case 1:
+ return JAVA_V1;
+ case 2:
+ return JAVA_V2;
+ case 3:
+ return JAVA_V3;
+ case 4:
+ return JAVA_V4;
+ case 5:
+ return JAVA_V5;
+ case 6:
+ return JAVA_V6;
+ case 7:
+ return JAVA_V7;
+ case 8:
+ return JAVA_V8;
+ case 9:
+ return JAVA_V9;
+ default:
+ throw new IllegalArgumentException("Unknown Java version: " + javaVersion);
+ }
+ }
+
+ /**
+ * Finds the highest class file version that is compatible to the current JVM version. Prior to Java 9, this is achieved
+ * by parsing the {@code java.version} property which is provided by {@link java.lang.System#getProperty(String)}. If the system
+ * property is not available, an {@link IllegalStateException} is thrown.
+ *
+ * @return The currently running Java process's class file version.
+ */
+ public static ClassFileVersion ofThisVm() {
+ return VERSION_LOCATOR.locate();
+ }
+
+ /**
+ * Finds the highest class file version that is compatible to the current JVM version. Prior to Java 9, this is achieved
+ * by parsing the {@code java.version} property which is provided by {@link java.lang.System#getProperty(String)}. If the system
+ * property is not available, the {@code fallback} version is returned.
+ *
+ * @param fallback The version to fallback to if locating a class file version is not possible.
+ * @return The currently running Java process's class file version or the fallback if locating this version is impossible.
+ */
+ @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback")
+ public static ClassFileVersion ofThisVm(ClassFileVersion fallback) {
+ try {
+ return ofThisVm();
+ } catch (Exception ignored) {
+ return fallback;
+ }
+ }
+
+ /**
+ * Extracts a class' class version. The class' byte code is located by querying the {@link ClassLoader} of the class.
+ *
+ * @param type The type for which to locate a class file version.
+ * @return The type's class file version.
+ * @throws IOException If an error occurs while reading the class file.
+ */
+ public static ClassFileVersion of(Class<?> type) throws IOException {
+ return of(type, ClassFileLocator.ForClassLoader.of(type.getClassLoader()));
+ }
+
+ /**
+ * Extracts a class' class version.
+ *
+ * @param type The type for which to locate a class file version.
+ * @param classFileLocator The class file locator to query for a class file.
+ * @return The type's class file version.
+ * @throws IOException If an error occurs while reading the class file.
+ */
+ public static ClassFileVersion of(Class<?> type, ClassFileLocator classFileLocator) throws IOException {
+ return of(new TypeDescription.ForLoadedType(type), classFileLocator);
+ }
+
+ /**
+ * Extracts a class' class version.
+ *
+ * @param typeDescription The type for which to locate a class file version.
+ * @param classFileLocator The class file locator to query for a class file.
+ * @return The type's class file version.
+ * @throws IOException If an error occurs while reading the class file.
+ */
+ public static ClassFileVersion of(TypeDescription typeDescription, ClassFileLocator classFileLocator) throws IOException {
+ ClassReader classReader = new ClassReader(classFileLocator.locate(typeDescription.getName()).resolve());
+ VersionExtractor versionExtractor = new VersionExtractor();
+ classReader.accept(versionExtractor, ClassReader.SKIP_CODE);
+ return ClassFileVersion.ofMinorMajor(versionExtractor.getClassFileVersionNumber());
+ }
+
+ /**
+ * Returns the minor-major release number of this class file version.
+ *
+ * @return The minor-major release number of this class file version.
+ */
+ public int getMinorMajorVersion() {
+ return versionNumber;
+ }
+
+ /**
+ * Returns the major version this instance represents.
+ *
+ * @return The major version this instance represents.
+ */
+ public int getMajorVersion() {
+ return versionNumber & 0xFF;
+ }
+
+ /**
+ * Returns the minor version this instance represents.
+ *
+ * @return The minor version this instance represents.
+ */
+ public int getMinorVersion() {
+ return versionNumber >> 16;
+ }
+
+ /**
+ * Returns the Java runtime version number of this class file version.
+ *
+ * @return The Java runtime version.
+ */
+ public int getJavaVersion() {
+ return getMajorVersion() - BASE_VERSION;
+ }
+
+ /**
+ * Checks if this class file version is at least of the provided version.
+ *
+ * @param classFileVersion The version to check against.
+ * @return {@code true} if this version is at least of the given version.
+ */
+ public boolean isAtLeast(ClassFileVersion classFileVersion) {
+ return compareTo(classFileVersion) > -1;
+ }
+
+ /**
+ * Checks if this class file version is less than the provided version.
+ *
+ * @param classFileVersion The version to check against.
+ * @return {@code true} if this version is less than the given version.
+ */
+ public boolean isLessThan(ClassFileVersion classFileVersion) {
+ return compareTo(classFileVersion) < 0;
+ }
+
+ @Override
+ public int compareTo(ClassFileVersion other) {
+ return Integer.signum(getMajorVersion() == other.getMajorVersion()
+ ? getMinorVersion() - other.getMinorVersion()
+ : getMajorVersion() - other.getMajorVersion());
+ }
+
+ /**
+ * A locator for the executing VM's Java version.
+ */
+ protected interface VersionLocator {
+
+ /**
+ * Locates the current VM's major version number.
+ *
+ * @return The current VM's major version number.
+ */
+ ClassFileVersion locate();
+
+ /**
+ * A creation action for a version locator.
+ */
+ enum CreationAction implements PrivilegedAction<VersionLocator> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback")
+ public VersionLocator run() {
+ try {
+ return new VersionLocator.ForJava9CapableVm(Runtime.class.getMethod("version"),
+ Class.forName("java.lang.Runtime$Version").getMethod("major"));
+ } catch (Exception ignored) {
+ return VersionLocator.ForLegacyVm.INSTANCE;
+ }
+ }
+ }
+
+ /**
+ * A version locator for a JVM of at least version 9.
+ */
+ @EqualsAndHashCode
+ class ForJava9CapableVm implements VersionLocator {
+
+ /**
+ * Indicates that a reflective method call invokes a static method.
+ */
+ private static final Object STATIC_METHOD = null;
+
+ /**
+ * The {@code java java.lang.Runtime#current()} method.
+ */
+ private final Method current;
+
+ /**
+ * The {@code java.lang.Runtime.Version#major()} method.
+ */
+ private final Method major;
+
+ /**
+ * Creates a new version locator for a Java 9 capable VM.
+ *
+ * @param current The {@code java.lang.Runtime#current()} method.
+ * @param major The {@code java.lang.Runtime.Version#major()} method.
+ */
+ protected ForJava9CapableVm(Method current, Method major) {
+ this.current = current;
+ this.major = major;
+ }
+
+ @Override
+ public ClassFileVersion locate() {
+ try {
+ return ClassFileVersion.ofJavaVersion((Integer) major.invoke(current.invoke(STATIC_METHOD)));
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Could not access VM version lookup", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Could not look up VM version", exception.getCause());
+ }
+ }
+ }
+
+ /**
+ * A version locator for a JVM that does not provide the {@code jdk.Version} class.
+ */
+ enum ForLegacyVm implements VersionLocator, PrivilegedAction<String> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ /**
+ * The system property for this JVM's Java version.
+ */
+ private static final String JAVA_VERSION_PROPERTY = "java.version";
+
+ @Override
+ public ClassFileVersion locate() {
+ String versionString = AccessController.doPrivileged(this);
+ int[] versionIndex = {-1, 0, 0};
+ for (int i = 1; i < 3; i++) {
+ versionIndex[i] = versionString.indexOf('.', versionIndex[i - 1] + 1);
+ if (versionIndex[i] == -1) {
+ throw new IllegalStateException("This JVM's version string does not seem to be valid: " + versionString);
+ }
+ }
+ return ClassFileVersion.ofJavaVersion(Integer.parseInt(versionString.substring(versionIndex[1] + 1, versionIndex[2])));
+ }
+
+ @Override
+ public String run() {
+ return System.getProperty(JAVA_VERSION_PROPERTY);
+ }
+ }
+ }
+
+ /**
+ * A simple visitor that extracts the class file version of a class file.
+ */
+ protected static class VersionExtractor extends ClassVisitor {
+
+ /**
+ * The class file version extracted from a class.
+ */
+ private int classFileVersionNumber;
+
+ /**
+ * Creates a new extractor.
+ */
+ protected VersionExtractor() {
+ super(Opcodes.ASM5);
+ }
+
+ @Override
+ public void visit(int classFileVersionNumber, int modifier, String internalName, String signature, String superTypeName, String[] interfaceName) {
+ this.classFileVersionNumber = classFileVersionNumber;
+ }
+
+ /**
+ * Returns the class file version number found in a class file.
+ *
+ * @return The class file version number found in a class file.
+ */
+ protected int getClassFileVersionNumber() {
+ return classFileVersionNumber;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/NamingStrategy.java b/byte-buddy-dep/src/main/java/net/bytebuddy/NamingStrategy.java
new file mode 100644
index 0000000..c4b26ac
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/NamingStrategy.java
@@ -0,0 +1,299 @@
+package net.bytebuddy;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.utility.RandomString;
+
+/**
+ * <p>
+ * A naming strategy for determining a fully qualified name for a dynamically created Java type.
+ * </p>
+ * <p>
+ * Note that subclasses that lie within the same package as their superclass can access package-private methods
+ * of super types within the same package.
+ * </p>
+ */
+public interface NamingStrategy {
+
+ /**
+ * Determines a new name when creating a new type that subclasses the provided type.
+ *
+ * @param superClass The super type of the created type.
+ * @return The name of the dynamic type.
+ */
+ String subclass(TypeDescription.Generic superClass);
+
+ /**
+ * Determines a name for the dynamic type when redefining the provided type.
+ *
+ * @param typeDescription The type being redefined.
+ * @return The name of the dynamic type.
+ */
+ String redefine(TypeDescription typeDescription);
+
+ /**
+ * Determines a name for the dynamic type when rebasing the provided type.
+ *
+ * @param typeDescription The type being redefined.
+ * @return The name of the dynamic type.
+ */
+ String rebase(TypeDescription typeDescription);
+
+ /**
+ * An abstract base implementation where the names of redefined and rebased types are retained.
+ */
+ abstract class AbstractBase implements NamingStrategy {
+
+ @Override
+ public String subclass(TypeDescription.Generic superClass) {
+ return name(superClass.asErasure());
+ }
+
+ /**
+ * Determines a new name when creating a new type that subclasses the provided type.
+ *
+ * @param superClass The super type of the created type.
+ * @return The name of the dynamic type.
+ */
+ protected abstract String name(TypeDescription superClass);
+
+ @Override
+ public String redefine(TypeDescription typeDescription) {
+ return typeDescription.getName();
+ }
+
+ @Override
+ public String rebase(TypeDescription typeDescription) {
+ return typeDescription.getName();
+ }
+ }
+
+ /**
+ * A naming strategy that creates a name by concatenating:
+ * <ol>
+ * <li>The super classes package and name</li>
+ * <li>A given suffix string</li>
+ * <li>A random number</li>
+ * </ol>
+ * Between all these elements, a {@code $} sign is included into the name to improve readability. As an exception,
+ * types that subclass classes from the {@code java.**} packages are prefixed with a given package. This is
+ * necessary as it is illegal to define non-bootstrap classes in this name space. The same strategy is applied
+ * when subclassing a signed type which is equally illegal.
+ */
+ @EqualsAndHashCode(callSuper = false, exclude = "randomString")
+ class SuffixingRandom extends AbstractBase {
+
+ /**
+ * The default package for defining types that are renamed to not be contained in the
+ * {@link net.bytebuddy.NamingStrategy.SuffixingRandom#JAVA_PACKAGE} package.
+ */
+ public static final String BYTE_BUDDY_RENAME_PACKAGE = "net.bytebuddy.renamed";
+
+ /**
+ * Indicates that types of the {@code java.*} package should not be prefixed.
+ */
+ public static final String NO_PREFIX = "";
+
+ /**
+ * The package prefix of the {@code java.*} packages for which the definition of
+ * non-bootstrap types is illegal.
+ */
+ private static final String JAVA_PACKAGE = "java.";
+
+ /**
+ * The suffix to attach to a super type name.
+ */
+ private final String suffix;
+
+ /**
+ * The renaming location for types of the {@link net.bytebuddy.NamingStrategy.SuffixingRandom#JAVA_PACKAGE}.
+ */
+ private final String javaLangPackagePrefix;
+
+ /**
+ * An instance for creating random seed values.
+ */
+ private final RandomString randomString;
+
+ /**
+ * A resolver for the base name for naming the unnamed type.
+ */
+ private final BaseNameResolver baseNameResolver;
+
+ /**
+ * Creates an immutable naming strategy with a given suffix but moves types that subclass types within
+ * the {@code java.lang} package into Byte Buddy's package namespace. All names are derived from the
+ * unnamed type's super type.
+ *
+ * @param suffix The suffix for the generated class.
+ */
+ public SuffixingRandom(String suffix) {
+ this(suffix, BaseNameResolver.ForUnnamedType.INSTANCE);
+ }
+
+ /**
+ * Creates an immutable naming strategy with a given suffix but moves types that subclass types within
+ * the {@code java.lang} package into Byte Buddy's package namespace.
+ *
+ * @param suffix The suffix for the generated class.
+ * @param javaLangPackagePrefix The fallback namespace for type's that subclass types within the
+ * {@code java.*} namespace. If The prefix is set to the empty string,
+ * no prefix is added.
+ */
+ public SuffixingRandom(String suffix, String javaLangPackagePrefix) {
+ this(suffix, BaseNameResolver.ForUnnamedType.INSTANCE, javaLangPackagePrefix);
+ }
+
+ /**
+ * Creates an immutable naming strategy with a given suffix but moves types that subclass types within
+ * the {@code java.lang} package into Byte Buddy's package namespace.
+ *
+ * @param suffix The suffix for the generated class.
+ * @param baseNameResolver The base name resolver that is queried for locating the base name.
+ */
+ public SuffixingRandom(String suffix, BaseNameResolver baseNameResolver) {
+ this(suffix, baseNameResolver, BYTE_BUDDY_RENAME_PACKAGE);
+ }
+
+ /**
+ * Creates an immutable naming strategy with a given suffix but moves types that subclass types within
+ * the {@code java.lang} package into a given namespace.
+ *
+ * @param suffix The suffix for the generated class.
+ * @param baseNameResolver The base name resolver that is queried for locating the base name.
+ * @param javaLangPackagePrefix The fallback namespace for type's that subclass types within the
+ * {@code java.*} namespace. If The prefix is set to the empty string,
+ * no prefix is added.
+ */
+ public SuffixingRandom(String suffix, BaseNameResolver baseNameResolver, String javaLangPackagePrefix) {
+ this.suffix = suffix;
+ this.baseNameResolver = baseNameResolver;
+ this.javaLangPackagePrefix = javaLangPackagePrefix;
+ randomString = new RandomString();
+ }
+
+ @Override
+ protected String name(TypeDescription superClass) {
+ String baseName = baseNameResolver.resolve(superClass);
+ if (baseName.startsWith(JAVA_PACKAGE) && !javaLangPackagePrefix.equals("")) {
+ baseName = javaLangPackagePrefix + "." + baseName;
+ }
+ return String.format("%s$%s$%s", baseName, suffix, randomString.nextString());
+ }
+
+ /**
+ * A base name resolver is responsible for resolving a name onto which the suffix is appended.
+ */
+ public interface BaseNameResolver {
+
+ /**
+ * Resolves the base name for a given type description.
+ *
+ * @param typeDescription The type for which the base name is resolved.
+ * @return The base name for the given type.
+ */
+ String resolve(TypeDescription typeDescription);
+
+ /**
+ * Uses the unnamed type's super type's name as the resolved name.
+ */
+ enum ForUnnamedType implements BaseNameResolver {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public String resolve(TypeDescription typeDescription) {
+ return typeDescription.getName();
+ }
+ }
+
+ /**
+ * Uses a specific type's name as the resolved name.
+ */
+ @EqualsAndHashCode
+ class ForGivenType implements BaseNameResolver {
+
+ /**
+ * The type description which represents the resolved name.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new base name resolver that resolves a using the name of a given type.
+ *
+ * @param typeDescription The type description which represents the resolved name.
+ */
+ public ForGivenType(TypeDescription typeDescription) {
+ this.typeDescription = typeDescription;
+ }
+
+ @Override
+ public String resolve(TypeDescription typeDescription) {
+ return this.typeDescription.getName();
+ }
+ }
+
+ /**
+ * A base name resolver that simply returns a fixed value.
+ */
+ @EqualsAndHashCode
+ class ForFixedValue implements BaseNameResolver {
+
+ /**
+ * The fixed base name.
+ */
+ private final String name;
+
+ /**
+ * Creates a new base name resolver for a fixed name.
+ *
+ * @param name The fixed name
+ */
+ public ForFixedValue(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String resolve(TypeDescription typeDescription) {
+ return name;
+ }
+ }
+ }
+ }
+
+ /**
+ * A naming strategy that creates a name by prefixing a given class and its package with another package and
+ * by appending a random number to the class's simple name.
+ */
+ @EqualsAndHashCode(callSuper = false, of = "prefix")
+ class PrefixingRandom extends AbstractBase {
+
+ /**
+ * The package to prefix.
+ */
+ private final String prefix;
+
+ /**
+ * A seed generator.
+ */
+ private final RandomString randomString;
+
+ /**
+ * Creates a new prefixing random naming strategy.
+ *
+ * @param prefix The prefix to append.
+ */
+ public PrefixingRandom(String prefix) {
+ this.prefix = prefix;
+ randomString = new RandomString();
+ }
+
+ @Override
+ protected String name(TypeDescription superClass) {
+ return String.format("%s.%s$%s", prefix, superClass.getName(), randomString.nextString());
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/TypeCache.java b/byte-buddy-dep/src/main/java/net/bytebuddy/TypeCache.java
new file mode 100644
index 0000000..538b1f6
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/TypeCache.java
@@ -0,0 +1,405 @@
+package net.bytebuddy;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.utility.CompoundList;
+
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
+import java.lang.ref.WeakReference;
+import java.util.*;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * <p>
+ * A cache for storing types without strongly referencing any class loader or type.
+ * </p>
+ * <p>
+ * <b>Note</b>: In order to clean obsolete class loader references from the map, {@link TypeCache#expungeStaleEntries()} must be called
+ * regularly. This can happen in a different thread, in custom intervals or on every use of the cache by creating an instance of
+ * {@link WithInlineExpunction}. This cache is fully thread-safe.
+ * </p>
+ * <p>
+ * <b>Important</b>: The behavior of a type cache might not be as expected. A class is only eligible for garbage collection once its class
+ * loader is eligible for garbage collection. At the same time, a garbage collector is only eligible for garbage collection once all of
+ * its classes are eligible for garbage collection. If this cache referenced the cached type strongly, this would never be the case which
+ * is why this cache maintains either strong or weak references. In the latter case, a type is typically retained until the last instance of
+ * the type is not eligible for garbage collection. With soft references, the type is typically retained until the next full garbage collection
+ * where all instances of the type are eligible for garbage collection.
+ * </p>
+ *
+ * @param <T> The type of the key that is used for identifying stored classes per class loader. Such keys must not strongly reference any
+ * types or class loaders without potentially corrupting the garbage eligibility of stored classes. As the storage is segmented
+ * by class loader, it is normally sufficient to store types by their name.
+ * @see WithInlineExpunction
+ * @see SimpleKey
+ */
+public class TypeCache<T> extends ReferenceQueue<ClassLoader> {
+
+ /**
+ * Indicates that a type was not found.
+ */
+ private static final Class<?> NOT_FOUND = null;
+
+ /**
+ * The reference type to use for stored types.
+ */
+ protected final Sort sort;
+
+ /**
+ * The underlying map containing cached objects.
+ */
+ protected final ConcurrentMap<StorageKey, ConcurrentMap<T, Reference<Class<?>>>> cache;
+
+ /**
+ * Creates a new type cache.
+ *
+ * @param sort The reference type to use for stored types.
+ */
+ public TypeCache(Sort sort) {
+ this.sort = sort;
+ cache = new ConcurrentHashMap<StorageKey, ConcurrentMap<T, Reference<Class<?>>>>();
+ }
+
+ /**
+ * Finds a stored type or returns {@code null} if no type was stored.
+ *
+ * @param classLoader The class loader for which this type is stored.
+ * @param key The key for the type in question.
+ * @return The stored type or {@code null} if no type was stored.
+ */
+ @SuppressFBWarnings(value = "GC_UNRELATED_TYPES", justification = "Cross-comparison is intended")
+ public Class<?> find(ClassLoader classLoader, T key) {
+ ConcurrentMap<T, Reference<Class<?>>> storage = cache.get(new LookupKey(classLoader));
+ if (storage == null) {
+ return NOT_FOUND;
+ } else {
+ Reference<Class<?>> reference = storage.get(key);
+ if (reference == null) {
+ return NOT_FOUND;
+ } else {
+ return reference.get();
+ }
+ }
+ }
+
+ /**
+ * Inserts a new type into the cache. If a type with the same class loader and key was inserted previously, the cache is not updated.
+ *
+ * @param classLoader The class loader for which this type is stored.
+ * @param key The key for the type in question.
+ * @param type The type to insert of no previous type was stored in the cache.
+ * @return The supplied type or a previously submitted type for the same class loader and key combination.
+ */
+ @SuppressFBWarnings(value = "GC_UNRELATED_TYPES", justification = "Cross-comparison is intended")
+ public Class<?> insert(ClassLoader classLoader, T key, Class<?> type) {
+ ConcurrentMap<T, Reference<Class<?>>> storage = cache.get(new LookupKey(classLoader));
+ if (storage == null) {
+ storage = new ConcurrentHashMap<T, Reference<Class<?>>>();
+ ConcurrentMap<T, Reference<Class<?>>> previous = cache.putIfAbsent(new StorageKey(classLoader, this), storage);
+ if (previous != null) {
+ storage = previous;
+ }
+ }
+ Reference<Class<?>> reference = sort.wrap(type), previous = storage.putIfAbsent(key, reference);
+ while (previous != null) {
+ Class<?> previousType = previous.get();
+ if (previousType != null) {
+ return previousType;
+ } else if (storage.remove(key, previous)) {
+ previous = storage.putIfAbsent(key, reference);
+ } else {
+ previous = storage.get(key);
+ if (previous == null) {
+ previous = storage.putIfAbsent(key, reference);
+ }
+ }
+ }
+ return type;
+ }
+
+ /**
+ * Finds an existing type or inserts a new one if the previous type was not found.
+ *
+ * @param classLoader The class loader for which this type is stored.
+ * @param key The key for the type in question.
+ * @param lazy A lazy creator for the type to insert of no previous type was stored in the cache.
+ * @return The lazily created type or a previously submitted type for the same class loader and key combination.
+ */
+ public Class<?> findOrInsert(ClassLoader classLoader, T key, Callable<Class<?>> lazy) {
+ Class<?> type = find(classLoader, key);
+ if (type != null) {
+ return type;
+ } else {
+ try {
+ return insert(classLoader, key, lazy.call());
+ } catch (Throwable throwable) {
+ throw new IllegalArgumentException("Could not create type", throwable);
+ }
+ }
+ }
+
+
+ /**
+ * Finds an existing type or inserts a new one if the previous type was not found.
+ *
+ * @param classLoader The class loader for which this type is stored.
+ * @param key The key for the type in question.
+ * @param lazy A lazy creator for the type to insert of no previous type was stored in the cache.
+ * @param monitor A monitor to lock before creating the lazy type.
+ * @return The lazily created type or a previously submitted type for the same class loader and key combination.
+ */
+ public Class<?> findOrInsert(ClassLoader classLoader, T key, Callable<Class<?>> lazy, Object monitor) {
+ Class<?> type = find(classLoader, key);
+ if (type != null) {
+ return type;
+ } else {
+ synchronized (monitor) {
+ return findOrInsert(classLoader, key, lazy);
+ }
+ }
+ }
+
+ /**
+ * Removes any stale class loader entries from the cache.
+ */
+ public void expungeStaleEntries() {
+ Reference<?> reference;
+ while ((reference = poll()) != null) {
+ cache.remove(reference);
+ }
+ }
+
+ /**
+ * Clears the entire cache.
+ */
+ public void clear() {
+ cache.clear();
+ }
+
+ /**
+ * Determines the storage format for a cached type.
+ */
+ public enum Sort {
+
+ /**
+ * Creates a cache where cached types are wrapped by {@link WeakReference}s.
+ */
+ WEAK {
+ @Override
+ protected Reference<Class<?>> wrap(Class<?> type) {
+ return new WeakReference<Class<?>>(type);
+ }
+ },
+
+ /**
+ * Creates a cache where cached types are wrapped by {@link SoftReference}s.
+ */
+ SOFT {
+ @Override
+ protected Reference<Class<?>> wrap(Class<?> type) {
+ return new SoftReference<Class<?>>(type);
+ }
+ };
+
+ /**
+ * Wrapes a type as a {@link Reference}.
+ *
+ * @param type The type to wrap.
+ * @return The reference that represents the type.
+ */
+ protected abstract Reference<Class<?>> wrap(Class<?> type);
+ }
+
+ /**
+ * A key used for looking up a previously inserted class loader cache.
+ */
+ protected static class LookupKey {
+
+ /**
+ * The referenced class loader.
+ */
+ private final ClassLoader classLoader;
+
+ /**
+ * The class loader's identity hash code.
+ */
+ private final int hashCode;
+
+ /**
+ * Creates a new lookup key.
+ *
+ * @param classLoader The represented class loader.
+ */
+ protected LookupKey(ClassLoader classLoader) {
+ this.classLoader = classLoader;
+ hashCode = System.identityHashCode(classLoader);
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "EQ_CHECK_FOR_OPERAND_NOT_COMPATIBLE_WITH_THIS", justification = "Cross-comparison is intended")
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ } else if (other instanceof LookupKey) {
+ return classLoader == ((LookupKey) other).classLoader;
+ } else if (other instanceof StorageKey) {
+ StorageKey storageKey = (StorageKey) other;
+ return hashCode == storageKey.hashCode && classLoader == storageKey.get();
+ } else {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * A key used for storing a class loader cache reference.
+ */
+ protected static class StorageKey extends WeakReference<ClassLoader> {
+
+ /**
+ * The class loader's identity hash code.
+ */
+ private final int hashCode;
+
+ /**
+ * Creates a new storage key.
+ *
+ * @param classLoader The represented class loader.
+ * @param referenceQueue The reference queue to notify upon a garbage collection.
+ */
+ protected StorageKey(ClassLoader classLoader, ReferenceQueue<? super ClassLoader> referenceQueue) {
+ super(classLoader, referenceQueue);
+ hashCode = System.identityHashCode(classLoader);
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "EQ_CHECK_FOR_OPERAND_NOT_COMPATIBLE_WITH_THIS", justification = "Cross-comparison is intended")
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ } else if (other instanceof LookupKey) {
+ LookupKey lookupKey = (LookupKey) other;
+ return hashCode == lookupKey.hashCode && get() == lookupKey.classLoader;
+ } else if (other instanceof StorageKey) {
+ StorageKey storageKey = (StorageKey) other;
+ return hashCode == storageKey.hashCode && get() == storageKey.get();
+ } else {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * An implementation of a {@link TypeCache} where obsolete references are cleared upon any call.
+ *
+ * @param <S> The type of the key that is used for identifying stored classes per class loader. Such keys must not strongly reference any
+ * types or class loaders without potentially corrupting the garbage eligibility of stored classes. As the storage is segmented
+ * by class loader, it is normally sufficient to store types by their name.
+ * @see TypeCache
+ */
+ public static class WithInlineExpunction<S> extends TypeCache<S> {
+
+ /**
+ * Creats a new type cache with inlined expunction.
+ *
+ * @param sort The reference type to use for stored types.
+ */
+ public WithInlineExpunction(Sort sort) {
+ super(sort);
+ }
+
+ @Override
+ public Class<?> find(ClassLoader classLoader, S key) {
+ try {
+ return super.find(classLoader, key);
+ } finally {
+ expungeStaleEntries();
+ }
+ }
+
+ @Override
+ public Class<?> insert(ClassLoader classLoader, S key, Class<?> type) {
+ try {
+ return super.insert(classLoader, key, type);
+ } finally {
+ expungeStaleEntries();
+ }
+ }
+
+ @Override
+ public Class<?> findOrInsert(ClassLoader classLoader, S key, Callable<Class<?>> builder) {
+ try {
+ return super.findOrInsert(classLoader, key, builder);
+ } finally {
+ expungeStaleEntries();
+ }
+ }
+
+ @Override
+ public Class<?> findOrInsert(ClassLoader classLoader, S key, Callable<Class<?>> builder, Object monitor) {
+ try {
+ return super.findOrInsert(classLoader, key, builder, monitor);
+ } finally {
+ expungeStaleEntries();
+ }
+ }
+ }
+
+ /**
+ * A simple key based on a collection of types where no type is strongly referenced.
+ */
+ @EqualsAndHashCode
+ public static class SimpleKey {
+
+ /**
+ * The referenced types.
+ */
+ private final Set<String> types;
+
+ /**
+ * Creates a simple cache key..
+ *
+ * @param type The first type to be represented by this key.
+ * @param additionalType Any additional types to be represented by this key.
+ */
+ public SimpleKey(Class<?> type, Class<?>... additionalType) {
+ this(type, Arrays.asList(additionalType));
+ }
+
+ /**
+ * Creates a simple cache key..
+ *
+ * @param type The first type to be represented by this key.
+ * @param additionalTypes Any additional types to be represented by this key.
+ */
+ public SimpleKey(Class<?> type, Collection<? extends Class<?>> additionalTypes) {
+ this(CompoundList.of(type, new ArrayList<Class<?>>(additionalTypes)));
+ }
+
+ /**
+ * Creates a simple cache key..
+ *
+ * @param types Any types to be represented by this key.
+ */
+ public SimpleKey(Collection<? extends Class<?>> types) {
+ this.types = new HashSet<String>();
+ for (Class<?> type : types) {
+ this.types.add(type.getName());
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/agent/builder/AgentBuilder.java b/byte-buddy-dep/src/main/java/net/bytebuddy/agent/builder/AgentBuilder.java
new file mode 100644
index 0000000..14149b7
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/agent/builder/AgentBuilder.java
@@ -0,0 +1,10342 @@
+package net.bytebuddy.agent.builder;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.asm.AsmVisitorWrapper;
+import net.bytebuddy.build.EntryPoint;
+import net.bytebuddy.build.Plugin;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.modifier.*;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.NexusAccessor;
+import net.bytebuddy.dynamic.TypeResolutionStrategy;
+import net.bytebuddy.dynamic.loading.ClassInjector;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.dynamic.scaffold.inline.MethodNameTransformer;
+import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
+import net.bytebuddy.implementation.ExceptionMethod;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.LoadedTypeInitializer;
+import net.bytebuddy.implementation.MethodCall;
+import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
+import net.bytebuddy.implementation.bytecode.*;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.assign.TypeCasting;
+import net.bytebuddy.implementation.bytecode.collection.ArrayFactory;
+import net.bytebuddy.implementation.bytecode.constant.ClassConstant;
+import net.bytebuddy.implementation.bytecode.constant.IntegerConstant;
+import net.bytebuddy.implementation.bytecode.constant.TextConstant;
+import net.bytebuddy.implementation.bytecode.member.FieldAccess;
+import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
+import net.bytebuddy.implementation.bytecode.member.MethodReturn;
+import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.matcher.LatentMatcher;
+import net.bytebuddy.pool.TypePool;
+import net.bytebuddy.utility.CompoundList;
+import net.bytebuddy.utility.JavaConstant;
+import net.bytebuddy.utility.JavaModule;
+import net.bytebuddy.utility.JavaType;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.io.*;
+import java.lang.instrument.ClassDefinition;
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.Instrumentation;
+import java.lang.instrument.UnmodifiableClassException;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.ProtectionDomain;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import static net.bytebuddy.matcher.ElementMatchers.*;
+
+/**
+ * <p>
+ * An agent builder provides a convenience API for defining a
+ * <a href="http://docs.oracle.com/javase/6/docs/api/java/lang/instrument/package-summary.html">Java agent</a>. By default,
+ * this transformation is applied by rebasing the type if not specified otherwise by setting a
+ * {@link TypeStrategy}.
+ * </p>
+ * <p>
+ * When defining several {@link net.bytebuddy.agent.builder.AgentBuilder.Transformer}s, the agent builder always
+ * applies the transformers that were supplied with the last applicable matcher. Therefore, more general transformers
+ * should be defined first.
+ * </p>
+ * <p>
+ * <b>Note</b>: Any transformation is performed using the {@link AccessControlContext} of an agent's creator.
+ * </p>
+ * <p>
+ * <b>Important</b>: Types that implement lambda expressions (functional interfaces) are not instrumented by default but
+ * only when enabling the builder's {@link LambdaInstrumentationStrategy}.
+ * </p>
+ */
+public interface AgentBuilder {
+
+ /**
+ * Defines the given {@link net.bytebuddy.ByteBuddy} instance to be used by the created agent.
+ *
+ * @param byteBuddy The Byte Buddy instance to be used.
+ * @return A new instance of this agent builder which makes use of the given {@code byteBuddy} instance.
+ */
+ AgentBuilder with(ByteBuddy byteBuddy);
+
+ /**
+ * Defines the given {@link net.bytebuddy.agent.builder.AgentBuilder.Listener} to be notified by the created agent.
+ * The given listener is notified after any other listener that is already registered. If a listener is registered
+ * twice, it is also notified twice.
+ *
+ * @param listener The listener to be notified.
+ * @return A new instance of this agent builder which creates an agent that informs the given listener about
+ * events.
+ */
+ AgentBuilder with(Listener listener);
+
+ /**
+ * Defines a circularity lock that is acquired upon executing code that potentially loads new classes. While the
+ * lock is acquired, any class file transformer refrains from transforming any classes. By default, all created
+ * agents use a shared {@link CircularityLock} to avoid that any classes that are required to execute an agent
+ * causes a {@link ClassCircularityError}.
+ *
+ * @param circularityLock The circularity lock to use.
+ * @return A new instance of this agent builder which creates an agent that uses the supplied circularity lock.
+ */
+ AgentBuilder with(CircularityLock circularityLock);
+
+ /**
+ * Defines the use of the given type locator for locating a {@link TypeDescription} for an instrumented type.
+ *
+ * @param poolStrategy The type locator to use.
+ * @return A new instance of this agent builder which uses the given type locator for looking up class files.
+ */
+ AgentBuilder with(PoolStrategy poolStrategy);
+
+ /**
+ * Defines the use of the given location strategy for locating binary data to given class names.
+ *
+ * @param locationStrategy The location strategy to use.
+ * @return A new instance of this agent builder which uses the given location strategy for looking up class files.
+ */
+ AgentBuilder with(LocationStrategy locationStrategy);
+
+ /**
+ * Defines how types should be transformed, e.g. if they should be rebased or redefined by the created agent.
+ *
+ * @param typeStrategy The type strategy to use.
+ * @return A new instance of this agent builder which uses the given type strategy.
+ */
+ AgentBuilder with(TypeStrategy typeStrategy);
+
+ /**
+ * Defines a given initialization strategy to be applied to generated types. An initialization strategy is responsible
+ * for setting up a type after it was loaded. This initialization must be performed after the transformation because
+ * a Java agent is only invoked before loading a type. By default, the initialization logic is added to a class's type
+ * initializer which queries a global object for any objects that are to be injected into the generated type.
+ *
+ * @param initializationStrategy The initialization strategy to use.
+ * @return A new instance of this agent builder that applies the given initialization strategy.
+ */
+ AgentBuilder with(InitializationStrategy initializationStrategy);
+
+ /**
+ * <p>
+ * Specifies a strategy for modifying types that were already loaded prior to the installation of this transformer.
+ * </p>
+ * <p>
+ * <b>Note</b>: Defining a redefinition strategy resets any refinements of a previously set redefinition strategy.
+ * </p>
+ * <p>
+ * <b>Important</b>: Most JVMs do not support changes of a class's structure after a class was already
+ * loaded. Therefore, it is typically required that this class file transformer was built while enabling
+ * {@link AgentBuilder#disableClassFormatChanges()}.
+ * </p>
+ *
+ * @param redefinitionStrategy The redefinition strategy to apply.
+ * @return A new instance of this agent builder that applies the given redefinition strategy.
+ */
+ RedefinitionListenable.WithoutBatchStrategy with(RedefinitionStrategy redefinitionStrategy);
+
+ /**
+ * <p>
+ * Enables or disables management of the JVM's {@code LambdaMetafactory} which is responsible for creating classes that
+ * implement lambda expressions. Without this feature enabled, classes that are represented by lambda expressions are
+ * not instrumented by the JVM such that Java agents have no effect on them when a lambda expression's class is loaded
+ * for the first time.
+ * </p>
+ * <p>
+ * When activating this feature, Byte Buddy instruments the {@code LambdaMetafactory} and takes over the responsibility
+ * of creating classes that represent lambda expressions. In doing so, Byte Buddy has the opportunity to apply the built
+ * class file transformer. If the current VM does not support lambda expressions, activating this feature has no effect.
+ * </p>
+ * <p>
+ * <b>Important</b>: If this feature is active, it is important to release the built class file transformer when
+ * deactivating it. Normally, it is sufficient to call {@link Instrumentation#removeTransformer(ClassFileTransformer)}.
+ * When this feature is enabled, it is however also required to invoke
+ * {@link LambdaInstrumentationStrategy#release(ClassFileTransformer, Instrumentation)}. Otherwise, the executing VMs class
+ * loader retains a reference to the class file transformer what can cause a memory leak.
+ * </p>
+ *
+ * @param lambdaInstrumentationStrategy {@code true} if this feature should be enabled.
+ * @return A new instance of this agent builder where this feature is explicitly enabled or disabled.
+ */
+ AgentBuilder with(LambdaInstrumentationStrategy lambdaInstrumentationStrategy);
+
+ /**
+ * Specifies a strategy to be used for resolving {@link TypeDescription} for any type handled by the created transformer.
+ *
+ * @param descriptionStrategy The description strategy to use.
+ * @return A new instance of this agent builder that applies the given description strategy.
+ */
+ AgentBuilder with(DescriptionStrategy descriptionStrategy);
+
+ /**
+ * Specifies a fallback strategy to that this agent builder applies upon installing an agent and during class file transformation.
+ *
+ * @param fallbackStrategy The fallback strategy to be used.
+ * @return A new agent builder that applies the supplied fallback strategy.
+ */
+ AgentBuilder with(FallbackStrategy fallbackStrategy);
+
+ /**
+ * Adds an installation listener that is notified during installation events. Installation listeners are only invoked if
+ * a class file transformer is installed using this agent builder's installation methods and uninstalled via the created
+ * {@link ResettableClassFileTransformer}'s {@code reset} methods.
+ *
+ * @param installationListener The installation listener to register.
+ * @return A new agent builder that applies the supplied installation listener.
+ */
+ AgentBuilder with(InstallationListener installationListener);
+
+ /**
+ * Enables class injection of auxiliary classes into the bootstrap class loader.
+ *
+ * @param instrumentation The instrumentation instance that is used for appending jar files to the
+ * bootstrap class path.
+ * @param folder The folder in which jar files of the injected classes are to be stored.
+ * @return An agent builder with bootstrap class loader class injection enabled.
+ */
+ AgentBuilder enableBootstrapInjection(Instrumentation instrumentation, File folder);
+
+ /**
+ * Enables class injection of auxiliary classes into the bootstrap class loader which relies on {@code sun.misc.Unsafe}.
+ *
+ * @return An agent builder with bootstrap class loader class injection enabled.
+ */
+ AgentBuilder enableUnsafeBootstrapInjection();
+
+ /**
+ * Enables the use of the given native method prefix for instrumented methods. Note that this prefix is also
+ * applied when preserving non-native methods. The use of this prefix is also registered when installing the
+ * final agent with an {@link java.lang.instrument.Instrumentation}.
+ *
+ * @param prefix The prefix to be used.
+ * @return A new instance of this agent builder which uses the given native method prefix.
+ */
+ AgentBuilder enableNativeMethodPrefix(String prefix);
+
+ /**
+ * Disables the use of a native method prefix for instrumented methods.
+ *
+ * @return A new instance of this agent builder which does not use a native method prefix.
+ */
+ AgentBuilder disableNativeMethodPrefix();
+
+ /**
+ * Disables injection of auxiliary classes into the bootstrap class path.
+ *
+ * @return A new instance of this agent builder which does not apply bootstrap class loader injection.
+ */
+ AgentBuilder disableBootstrapInjection();
+
+ /**
+ * <p>
+ * Disables all implicit changes on a class file that Byte Buddy would apply for certain instrumentations. When
+ * using this option, it is no longer possible to rebase a method, i.e. intercepted methods are fully replaced. Furthermore,
+ * it is no longer possible to implicitly apply loaded type initializers for explicitly initializing the generated type.
+ * </p>
+ * <p>
+ * This is equivalent to setting {@link InitializationStrategy.NoOp} and {@link TypeStrategy.Default#REDEFINE_FROZEN}
+ * as well as configuring the underlying {@link ByteBuddy} instance to use a {@link net.bytebuddy.implementation.Implementation.Context.Disabled}.
+ * Using this strategy also configures Byte Buddy to create frozen instrumented types and discards any explicit configuration.
+ * </p>
+ *
+ * @return A new instance of this agent builder that does not apply any implicit changes to the received class file.
+ */
+ AgentBuilder disableClassFormatChanges();
+
+ /**
+ * Assures that all modules of the supplied types are read by the module of any instrumented type. If the current VM does not support
+ * the Java module system, calling this method has no effect and this instance is returned.
+ *
+ * @param instrumentation The instrumentation instance that is used for adding a module read-dependency.
+ * @param type The types for which to assure their module-visibility from any instrumented class.
+ * @return A new instance of this agent builder that assures the supplied types module visibility.
+ * @see Listener.ModuleReadEdgeCompleting
+ */
+ AgentBuilder assureReadEdgeTo(Instrumentation instrumentation, Class<?>... type);
+
+ /**
+ * Assures that all supplied modules are read by the module of any instrumented type.
+ *
+ * @param instrumentation The instrumentation instance that is used for adding a module read-dependency.
+ * @param module The modules for which to assure their module-visibility from any instrumented class.
+ * @return A new instance of this agent builder that assures the supplied types module visibility.
+ * @see Listener.ModuleReadEdgeCompleting
+ */
+ AgentBuilder assureReadEdgeTo(Instrumentation instrumentation, JavaModule... module);
+
+ /**
+ * Assures that all supplied modules are read by the module of any instrumented type.
+ *
+ * @param instrumentation The instrumentation instance that is used for adding a module read-dependency.
+ * @param modules The modules for which to assure their module-visibility from any instrumented class.
+ * @return A new instance of this agent builder that assures the supplied types module visibility.
+ * @see Listener.ModuleReadEdgeCompleting
+ */
+ AgentBuilder assureReadEdgeTo(Instrumentation instrumentation, Collection<? extends JavaModule> modules);
+
+ /**
+ * Assures that all modules of the supplied types are read by the module of any instrumented type and vice versa.
+ * If the current VM does not support the Java module system, calling this method has no effect and this instance is returned.
+ *
+ * @param instrumentation The instrumentation instance that is used for adding a module read-dependency.
+ * @param type The types for which to assure their module-visibility from and to any instrumented class.
+ * @return A new instance of this agent builder that assures the supplied types module visibility.
+ * @see Listener.ModuleReadEdgeCompleting
+ */
+ AgentBuilder assureReadEdgeFromAndTo(Instrumentation instrumentation, Class<?>... type);
+
+ /**
+ * Assures that all supplied modules are read by the module of any instrumented type and vice versa.
+ *
+ * @param instrumentation The instrumentation instance that is used for adding a module read-dependency.
+ * @param module The modules for which to assure their module-visibility from and to any instrumented class.
+ * @return A new instance of this agent builder that assures the supplied types module visibility.
+ * @see Listener.ModuleReadEdgeCompleting
+ */
+ AgentBuilder assureReadEdgeFromAndTo(Instrumentation instrumentation, JavaModule... module);
+
+ /**
+ * Assures that all supplied modules are read by the module of any instrumented type and vice versa.
+ *
+ * @param instrumentation The instrumentation instance that is used for adding a module read-dependency.
+ * @param modules The modules for which to assure their module-visibility from and to any instrumented class.
+ * @return A new instance of this agent builder that assures the supplied types module visibility.
+ * @see Listener.ModuleReadEdgeCompleting
+ */
+ AgentBuilder assureReadEdgeFromAndTo(Instrumentation instrumentation, Collection<? extends JavaModule> modules);
+
+ /**
+ * <p>
+ * Matches a type being loaded in order to apply the supplied {@link net.bytebuddy.agent.builder.AgentBuilder.Transformer}s before loading this type.
+ * If several matchers positively match a type only the latest registered matcher is considered for transformation.
+ * </p>
+ * <p>
+ * If this matcher is chained with additional subsequent matchers, this matcher is always executed first whereas the following matchers are
+ * executed in the order of their execution. If any matcher indicates that a type is to be matched, none of the following matchers is still queried.
+ * This behavior can be changed by {@link Identified.Extendable#asDecorator()} where subsequent type matchers are also applied.
+ * </p>
+ * <p>
+ * <b>Note</b>: When applying a matcher, regard the performance implications by {@link AgentBuilder#ignore(ElementMatcher)}. The former
+ * matcher is applied first such that it makes sense to ignore name spaces that are irrelevant to instrumentation. If possible, it is
+ * also recommended, to exclude class loaders such as for example the bootstrap class loader by using
+ * {@link AgentBuilder#type(ElementMatcher, ElementMatcher)} instead.
+ * </p>
+ *
+ * @param typeMatcher An {@link net.bytebuddy.matcher.ElementMatcher} that is applied on the type being loaded that
+ * decides if the entailed {@link net.bytebuddy.agent.builder.AgentBuilder.Transformer}s should
+ * be applied for that type.
+ * @return A definable that represents this agent builder which allows for the definition of one or several
+ * {@link net.bytebuddy.agent.builder.AgentBuilder.Transformer}s to be applied when the given {@code typeMatcher}
+ * indicates a match.
+ */
+ Identified.Narrowable type(ElementMatcher<? super TypeDescription> typeMatcher);
+
+ /**
+ * <p>
+ * Matches a type being loaded in order to apply the supplied {@link net.bytebuddy.agent.builder.AgentBuilder.Transformer}s before loading this type.
+ * If several matchers positively match a type only the latest registered matcher is considered for transformation.
+ * </p>
+ * <p>
+ * If this matcher is chained with additional subsequent matchers, this matcher is always executed first whereas the following matchers are
+ * executed in the order of their execution. If any matcher indicates that a type is to be matched, none of the following matchers is still queried.
+ * This behavior can be changed by {@link Identified.Extendable#asDecorator()} where subsequent type matchers are also applied.
+ * </p>
+ * <p>
+ * <b>Note</b>: When applying a matcher, regard the performance implications by {@link AgentBuilder#ignore(ElementMatcher)}. The former
+ * matcher is applied first such that it makes sense to ignore name spaces that are irrelevant to instrumentation. If possible, it
+ * is also recommended, to exclude class loaders such as for example the bootstrap class loader.
+ * </p>
+ *
+ * @param typeMatcher An {@link net.bytebuddy.matcher.ElementMatcher} that is applied on the type being
+ * loaded that decides if the entailed
+ * {@link net.bytebuddy.agent.builder.AgentBuilder.Transformer}s should be applied for
+ * that type.
+ * @param classLoaderMatcher An {@link net.bytebuddy.matcher.ElementMatcher} that is applied to the
+ * {@link java.lang.ClassLoader} that is loading the type being loaded. This matcher
+ * is always applied first where the type matcher is not applied in case that this
+ * matcher does not indicate a match.
+ * @return A definable that represents this agent builder which allows for the definition of one or several
+ * {@link net.bytebuddy.agent.builder.AgentBuilder.Transformer}s to be applied when both the given
+ * {@code typeMatcher} and {@code classLoaderMatcher} indicate a match.
+ */
+ Identified.Narrowable type(ElementMatcher<? super TypeDescription> typeMatcher, ElementMatcher<? super ClassLoader> classLoaderMatcher);
+
+ /**
+ * <p>
+ * Matches a type being loaded in order to apply the supplied {@link net.bytebuddy.agent.builder.AgentBuilder.Transformer}s before loading this type.
+ * If several matchers positively match a type only the latest registered matcher is considered for transformation.
+ * </p>
+ * <p>
+ * If this matcher is chained with additional subsequent matchers, this matcher is always executed first whereas the following matchers are
+ * executed in the order of their execution. If any matcher indicates that a type is to be matched, none of the following matchers is still queried.
+ * This behavior can be changed by {@link Identified.Extendable#asDecorator()} where subsequent type matchers are also applied.
+ * </p>
+ * <p>
+ * <b>Note</b>: When applying a matcher, regard the performance implications by {@link AgentBuilder#ignore(ElementMatcher)}. The former
+ * matcher is applied first such that it makes sense to ignore name spaces that are irrelevant to instrumentation. If possible, it
+ * is also recommended, to exclude class loaders such as for example the bootstrap class loader.
+ * </p>
+ *
+ * @param typeMatcher An {@link net.bytebuddy.matcher.ElementMatcher} that is applied on the type being
+ * loaded that decides if the entailed
+ * {@link net.bytebuddy.agent.builder.AgentBuilder.Transformer}s should be applied for
+ * that type.
+ * @param classLoaderMatcher An {@link net.bytebuddy.matcher.ElementMatcher} that is applied to the
+ * {@link java.lang.ClassLoader} that is loading the type being loaded. This matcher
+ * is always applied second where the type matcher is not applied in case that this
+ * matcher does not indicate a match.
+ * @param moduleMatcher An {@link net.bytebuddy.matcher.ElementMatcher} that is applied to the {@link JavaModule}
+ * of the type being loaded. This matcher is always applied first where the class loader and
+ * type matchers are not applied in case that this matcher does not indicate a match. On a JVM
+ * that does not support the Java modules system, this matcher is not applied.
+ * @return A definable that represents this agent builder which allows for the definition of one or several
+ * {@link net.bytebuddy.agent.builder.AgentBuilder.Transformer}s to be applied when both the given
+ * {@code typeMatcher} and {@code classLoaderMatcher} indicate a match.
+ */
+ Identified.Narrowable type(ElementMatcher<? super TypeDescription> typeMatcher,
+ ElementMatcher<? super ClassLoader> classLoaderMatcher,
+ ElementMatcher<? super JavaModule> moduleMatcher);
+
+ /**
+ * <p>
+ * Matches a type being loaded in order to apply the supplied {@link net.bytebuddy.agent.builder.AgentBuilder.Transformer}s before loading this type.
+ * If several matchers positively match a type only the latest registered matcher is considered for transformation.
+ * </p>
+ * <p>
+ * If this matcher is chained with additional subsequent matchers, this matcher is always executed first whereas the following matchers are
+ * executed in the order of their execution. If any matcher indicates that a type is to be matched, none of the following matchers is still queried.
+ * </p>
+ * <p>
+ * <b>Note</b>: When applying a matcher, regard the performance implications by {@link AgentBuilder#ignore(ElementMatcher)}. The former
+ * matcher is applied first such that it makes sense to ignore name spaces that are irrelevant to instrumentation. If possible, it
+ * is also recommended, to exclude class loaders such as for example the bootstrap class loader.
+ * </p>
+ *
+ * @param matcher A matcher that decides if the entailed {@link net.bytebuddy.agent.builder.AgentBuilder.Transformer}s should be
+ * applied for a type that is being loaded.
+ * @return A definable that represents this agent builder which allows for the definition of one or several
+ * {@link net.bytebuddy.agent.builder.AgentBuilder.Transformer}s to be applied when the given {@code matcher}
+ * indicates a match.
+ */
+ Identified.Narrowable type(RawMatcher matcher);
+
+ /**
+ * <p>
+ * Excludes any type that is matched by the provided matcher from instrumentation and considers types by all {@link ClassLoader}s.
+ * By default, Byte Buddy does not instrument synthetic types or types that are loaded by the bootstrap class loader.
+ * </p>
+ * <p>
+ * When ignoring a type, any subsequently chained matcher is applied after this matcher in the order of their registration. Also, if
+ * any matcher indicates that a type is to be ignored, none of the following chained matchers is executed.
+ * </p>
+ * <p>
+ * <b>Note</b>: For performance reasons, it is recommended to always include a matcher that excludes as many namespaces
+ * as possible. Byte Buddy can determine a type's name without parsing its class file and can therefore discard such
+ * types with minimal overhead. When a different property of a type - such as for example its modifiers or its annotations
+ * is accessed - Byte Buddy parses the class file lazily in order to allow for such a matching. Therefore, any exclusion
+ * of a name should always be done as a first step and even if it does not influence the selection of what types are
+ * matched. Without changing this property, the class file of every type is being parsed!
+ * </p>
+ * <p>
+ * <b>Warning</b>: If a type is loaded during the instrumentation of the same type, this causes the original call site that loads the type
+ * to remain unbound, causing a {@link LinkageError}. It is therefore important to not instrument types that may be loaded during the application
+ * of a {@link Transformer}. For this reason, it is not recommended to instrument classes of the bootstrap class loader that Byte Buddy might
+ * require for instrumenting a class or to instrument any of Byte Buddy's classes. If such instrumentation is desired, it is important to
+ * assert for each class that they are not loaded during instrumentation.
+ * </p>
+ *
+ * @param typeMatcher A matcher that identifies types that should not be instrumented.
+ * @return A new instance of this agent builder that ignores all types that are matched by the provided matcher.
+ * All previous matchers for ignored types are discarded.
+ */
+ Ignored ignore(ElementMatcher<? super TypeDescription> typeMatcher);
+
+ /**
+ * <p>
+ * Excludes any type that is matched by the provided matcher and is loaded by a class loader matching the second matcher.
+ * By default, Byte Buddy does not instrument synthetic types, types within a {@code net.bytebuddy.*} package or types that
+ * are loaded by the bootstrap class loader.
+ * </p>
+ * <p>
+ * When ignoring a type, any subsequently chained matcher is applied after this matcher in the order of their registration. Also, if
+ * any matcher indicates that a type is to be ignored, none of the following chained matchers is executed.
+ * </p>
+ * <p>
+ * <b>Note</b>: For performance reasons, it is recommended to always include a matcher that excludes as many namespaces
+ * as possible. Byte Buddy can determine a type's name without parsing its class file and can therefore discard such
+ * types with minimal overhead. When a different property of a type - such as for example its modifiers or its annotations
+ * is accessed - Byte Buddy parses the class file lazily in order to allow for such a matching. Therefore, any exclusion
+ * of a name should always be done as a first step and even if it does not influence the selection of what types are
+ * matched. Without changing this property, the class file of every type is being parsed!
+ * </p>
+ * <p>
+ * <b>Warning</b>: If a type is loaded during the instrumentation of the same type, this causes the original call site that loads the type
+ * to remain unbound, causing a {@link LinkageError}. It is therefore important to not instrument types that may be loaded during the application
+ * of a {@link Transformer}. For this reason, it is not recommended to instrument classes of the bootstrap class loader that Byte Buddy might
+ * require for instrumenting a class or to instrument any of Byte Buddy's classes. If such instrumentation is desired, it is important to
+ * assert for each class that they are not loaded during instrumentation.
+ * </p>
+ *
+ * @param typeMatcher A matcher that identifies types that should not be instrumented.
+ * @param classLoaderMatcher A matcher that identifies a class loader that identifies classes that should not be instrumented.
+ * @return A new instance of this agent builder that ignores all types that are matched by the provided matcher.
+ * All previous matchers for ignored types are discarded.
+ */
+ Ignored ignore(ElementMatcher<? super TypeDescription> typeMatcher, ElementMatcher<? super ClassLoader> classLoaderMatcher);
+
+ /**
+ * <p>
+ * Excludes any type that is matched by the provided matcher and is loaded by a class loader matching the second matcher.
+ * By default, Byte Buddy does not instrument synthetic types, types within a {@code net.bytebuddy.*} package or types that
+ * are loaded by the bootstrap class loader.
+ * </p>
+ * <p>
+ * When ignoring a type, any subsequently chained matcher is applied after this matcher in the order of their registration. Also, if
+ * any matcher indicates that a type is to be ignored, none of the following chained matchers is executed.
+ * </p>
+ * <p>
+ * <b>Note</b>: For performance reasons, it is recommended to always include a matcher that excludes as many namespaces
+ * as possible. Byte Buddy can determine a type's name without parsing its class file and can therefore discard such
+ * types with minimal overhead. When a different property of a type - such as for example its modifiers or its annotations
+ * is accessed - Byte Buddy parses the class file lazily in order to allow for such a matching. Therefore, any exclusion
+ * of a name should always be done as a first step and even if it does not influence the selection of what types are
+ * matched. Without changing this property, the class file of every type is being parsed!
+ * </p>
+ * <p>
+ * <b>Warning</b>: If a type is loaded during the instrumentation of the same type, this causes the original call site that loads the type
+ * to remain unbound, causing a {@link LinkageError}. It is therefore important to not instrument types that may be loaded during the application
+ * of a {@link Transformer}. For this reason, it is not recommended to instrument classes of the bootstrap class loader that Byte Buddy might
+ * require for instrumenting a class or to instrument any of Byte Buddy's classes. If such instrumentation is desired, it is important to
+ * assert for each class that they are not loaded during instrumentation.
+ * </p>
+ *
+ * @param typeMatcher A matcher that identifies types that should not be instrumented.
+ * @param classLoaderMatcher A matcher that identifies a class loader that identifies classes that should not be instrumented.
+ * @param moduleMatcher A matcher that identifies a module that identifies classes that should not be instrumented. On a JVM
+ * that does not support the Java modules system, this matcher is not applied.
+ * @return A new instance of this agent builder that ignores all types that are matched by the provided matcher.
+ * All previous matchers for ignored types are discarded.
+ */
+ Ignored ignore(ElementMatcher<? super TypeDescription> typeMatcher,
+ ElementMatcher<? super ClassLoader> classLoaderMatcher,
+ ElementMatcher<? super JavaModule> moduleMatcher);
+
+ /**
+ * <p>
+ * Excludes any type that is matched by the raw matcher provided to this method. By default, Byte Buddy does not
+ * instrument synthetic types, types within a {@code net.bytebuddy.*} package or types that are loaded by the bootstrap class loader.
+ * </p>
+ * <p>
+ * When ignoring a type, any subsequently chained matcher is applied after this matcher in the order of their registration. Also, if
+ * any matcher indicates that a type is to be ignored, none of the following chained matchers is executed.
+ * </p>
+ * <p>
+ * <b>Note</b>: For performance reasons, it is recommended to always include a matcher that excludes as many namespaces
+ * as possible. Byte Buddy can determine a type's name without parsing its class file and can therefore discard such
+ * types with minimal overhead. When a different property of a type - such as for example its modifiers or its annotations
+ * is accessed - Byte Buddy parses the class file lazily in order to allow for such a matching. Therefore, any exclusion
+ * of a name should always be done as a first step and even if it does not influence the selection of what types are
+ * matched. Without changing this property, the class file of every type is being parsed!
+ * </p>
+ * <p>
+ * <b>Warning</b>: If a type is loaded during the instrumentation of the same type, this causes the original call site that loads the type
+ * to remain unbound, causing a {@link LinkageError}. It is therefore important to not instrument types that may be loaded during the application
+ * of a {@link Transformer}. For this reason, it is not recommended to instrument classes of the bootstrap class loader that Byte Buddy might
+ * require for instrumenting a class or to instrument any of Byte Buddy's classes. If such instrumentation is desired, it is important to
+ * assert for each class that they are not loaded during instrumentation.
+ * </p>
+ *
+ * @param rawMatcher A raw matcher that identifies types that should not be instrumented.
+ * @return A new instance of this agent builder that ignores all types that are matched by the provided matcher.
+ * All previous matchers for ignored types are discarded.
+ */
+ Ignored ignore(RawMatcher rawMatcher);
+
+ /**
+ * Creates a {@link java.lang.instrument.ClassFileTransformer} that implements the configuration of this
+ * agent builder. When using a raw class file transformer, the {@link InstallationListener} callbacks are
+ * not invoked and the set {@link RedefinitionStrategy} is not applied onto currently loaded classes.
+ *
+ * @return A class file transformer that implements the configuration of this agent builder.
+ */
+ ClassFileTransformer makeRaw();
+
+ /**
+ * <p>
+ * Creates and installs a {@link java.lang.instrument.ClassFileTransformer} that implements the configuration of
+ * this agent builder with a given {@link java.lang.instrument.Instrumentation}. If retransformation is enabled,
+ * the installation also causes all loaded types to be retransformed.
+ * </p>
+ * <p>
+ * In order to assure the correct handling of the {@link InstallationListener}, an uninstallation should be applied
+ * via the {@link ResettableClassFileTransformer}'s {@code reset} methods.
+ * </p>
+ *
+ * @param instrumentation The instrumentation on which this agent builder's configuration is to be installed.
+ * @return The installed class file transformer.
+ */
+ ResettableClassFileTransformer installOn(Instrumentation instrumentation);
+
+ /**
+ * Creates and installs a {@link java.lang.instrument.ClassFileTransformer} that implements the configuration of
+ * this agent builder with the Byte Buddy-agent which must be installed prior to calling this method.
+ *
+ * @return The installed class file transformer.
+ * @see AgentBuilder#installOn(Instrumentation)
+ */
+ ResettableClassFileTransformer installOnByteBuddyAgent();
+
+ /**
+ * An abstraction for extending a matcher.
+ *
+ * @param <T> The type that is produced by chaining a matcher.
+ */
+ interface Matchable<T extends Matchable<T>> {
+
+ /**
+ * Defines a matching that is positive if both the previous matcher and the supplied matcher are matched. When matching a
+ * type, class loaders are not considered.
+ *
+ * @param typeMatcher A matcher for the type being matched.
+ * @return A chained matcher.
+ */
+ T and(ElementMatcher<? super TypeDescription> typeMatcher);
+
+ /**
+ * Defines a matching that is positive if both the previous matcher and the supplied matcher are matched.
+ *
+ * @param typeMatcher A matcher for the type being matched.
+ * @param classLoaderMatcher A matcher for the type's class loader.
+ * @return A chained matcher.
+ */
+ T and(ElementMatcher<? super TypeDescription> typeMatcher, ElementMatcher<? super ClassLoader> classLoaderMatcher);
+
+ /**
+ * Defines a matching that is positive if both the previous matcher and the supplied matcher are matched.
+ *
+ * @param typeMatcher A matcher for the type being matched.
+ * @param classLoaderMatcher A matcher for the type's class loader.
+ * @param moduleMatcher A matcher for the type's module. On a JVM that does not support modules, the Java module is represented by {@code null}.
+ * @return A chained matcher.
+ */
+ T and(ElementMatcher<? super TypeDescription> typeMatcher,
+ ElementMatcher<? super ClassLoader> classLoaderMatcher,
+ ElementMatcher<? super JavaModule> moduleMatcher);
+
+ /**
+ * Defines a matching that is positive if both the previous matcher and the supplied matcher are matched.
+ *
+ * @param rawMatcher A raw matcher for the type being matched.
+ * @return A chained matcher.
+ */
+ T and(RawMatcher rawMatcher);
+
+ /**
+ * Defines a matching that is positive if the previous matcher or the supplied matcher are matched. When matching a
+ * type, the class loader is not considered.
+ *
+ * @param typeMatcher A matcher for the type being matched.
+ * @return A chained matcher.
+ */
+ T or(ElementMatcher<? super TypeDescription> typeMatcher);
+
+ /**
+ * Defines a matching that is positive if the previous matcher or the supplied matcher are matched.
+ *
+ * @param typeMatcher A matcher for the type being matched.
+ * @param classLoaderMatcher A matcher for the type's class loader.
+ * @return A chained matcher.
+ */
+ T or(ElementMatcher<? super TypeDescription> typeMatcher, ElementMatcher<? super ClassLoader> classLoaderMatcher);
+
+ /**
+ * Defines a matching that is positive if the previous matcher or the supplied matcher are matched.
+ *
+ * @param typeMatcher A matcher for the type being matched.
+ * @param classLoaderMatcher A matcher for the type's class loader.
+ * @param moduleMatcher A matcher for the type's module. On a JVM that does not support modules, the Java module is represented by {@code null}.
+ * @return A chained matcher.
+ */
+ T or(ElementMatcher<? super TypeDescription> typeMatcher,
+ ElementMatcher<? super ClassLoader> classLoaderMatcher,
+ ElementMatcher<? super JavaModule> moduleMatcher);
+
+ /**
+ * Defines a matching that is positive if the previous matcher or the supplied matcher are matched.
+ *
+ * @param rawMatcher A raw matcher for the type being matched.
+ * @return A chained matcher.
+ */
+ T or(RawMatcher rawMatcher);
+
+ /**
+ * An abstract base implementation of a matchable.
+ *
+ * @param <S> The type that is produced by chaining a matcher.
+ */
+ abstract class AbstractBase<S extends Matchable<S>> implements Matchable<S> {
+
+ @Override
+ public S and(ElementMatcher<? super TypeDescription> typeMatcher) {
+ return and(typeMatcher, any());
+ }
+
+ @Override
+ public S and(ElementMatcher<? super TypeDescription> typeMatcher, ElementMatcher<? super ClassLoader> classLoaderMatcher) {
+ return and(typeMatcher, classLoaderMatcher, any());
+ }
+
+ @Override
+ public S and(ElementMatcher<? super TypeDescription> typeMatcher,
+ ElementMatcher<? super ClassLoader> classLoaderMatcher,
+ ElementMatcher<? super JavaModule> moduleMatcher) {
+ return and(new RawMatcher.ForElementMatchers(typeMatcher, classLoaderMatcher, moduleMatcher));
+ }
+
+ @Override
+ public S or(ElementMatcher<? super TypeDescription> typeMatcher) {
+ return or(typeMatcher, any());
+ }
+
+ @Override
+ public S or(ElementMatcher<? super TypeDescription> typeMatcher, ElementMatcher<? super ClassLoader> classLoaderMatcher) {
+ return or(typeMatcher, classLoaderMatcher, any());
+ }
+
+ @Override
+ public S or(ElementMatcher<? super TypeDescription> typeMatcher,
+ ElementMatcher<? super ClassLoader> classLoaderMatcher,
+ ElementMatcher<? super JavaModule> moduleMatcher) {
+ return or(new RawMatcher.ForElementMatchers(typeMatcher, classLoaderMatcher, moduleMatcher));
+ }
+ }
+ }
+
+ /**
+ * Allows to further specify ignored types.
+ */
+ interface Ignored extends Matchable<Ignored>, AgentBuilder {
+ /* this is merely a unionizing interface that does not declare methods */
+ }
+
+ /**
+ * An agent builder configuration that allows the registration of listeners to the redefinition process.
+ */
+ interface RedefinitionListenable extends AgentBuilder {
+
+ /**
+ * <p>
+ * A redefinition listener is invoked before each batch of type redefinitions and on every error as well as
+ * after the redefinition was completed. A redefinition listener can be used for debugging or logging purposes
+ * and to apply actions between each batch, e.g. to pause or wait in order to avoid rendering the current VM
+ * non-responsive if a lot of classes are redefined.
+ * </p>
+ * <p>
+ * Adding several listeners does not replace previous listeners but applies them in the registration order.
+ * </p>
+ *
+ * @param redefinitionListener The listener to register.
+ * @return A new instance of this agent builder which notifies the specified listener upon type redefinitions.
+ */
+ RedefinitionListenable with(RedefinitionStrategy.Listener redefinitionListener);
+
+ /**
+ * Enables resubmission of failed transformations by applying a retransformation of the loaded type. This can be meaningful if
+ * class files cannot be located from the class loader as a resource where the loaded type becomes available.
+ *
+ * @param resubmissionScheduler A scheduler which is responsible for scheduling the resubmission job.
+ * @return A new instance of this agent builder that applies resubmission.
+ */
+ AgentBuilder withResubmission(RedefinitionStrategy.ResubmissionScheduler resubmissionScheduler);
+
+ /**
+ * Enables resubmission of failed transformations by applying a retransformation of the loaded type. This can be meaningful if
+ * class files cannot be located from the class loader as a resource where the loaded type becomes available.
+ *
+ * @param resubmissionScheduler A scheduler which is responsible for scheduling the resubmission job.
+ * @param matcher A matcher that filters throwable instances where non-matched throwables are not triggering a resubmission.
+ * @return A new instance of this agent builder that applies resubmission.
+ */
+ AgentBuilder withResubmission(RedefinitionStrategy.ResubmissionScheduler resubmissionScheduler, ElementMatcher<? super Throwable> matcher);
+
+ /**
+ * An agent builder configuration strategy that allows the definition of a discovery strategy.
+ */
+ interface WithImplicitDiscoveryStrategy extends RedefinitionListenable {
+
+ /**
+ * Limits the redefinition attempt to the specified types.
+ *
+ * @param type The types to consider for redefinition.
+ * @return A new instance of this agent builder which only considers the supplied types for redefinition.
+ */
+ RedefinitionListenable redefineOnly(Class<?>... type);
+
+ /**
+ * A discovery strategy is responsible for locating loaded types that should be considered for redefinition.
+ *
+ * @param redefinitionDiscoveryStrategy The redefinition discovery strategy to use.
+ * @return A new instance of this agent builder which makes use of the specified discovery strategy.
+ */
+ RedefinitionListenable with(RedefinitionStrategy.DiscoveryStrategy redefinitionDiscoveryStrategy);
+ }
+
+ /**
+ * An agent builder configuration that allows the configuration of a batching strategy.
+ */
+ interface WithoutBatchStrategy extends WithImplicitDiscoveryStrategy {
+
+ /**
+ * A batch allocator is responsible for diving a redefining of existing types into several chunks. This allows
+ * to narrow down errors for the redefining of specific types or to apply a {@link RedefinitionStrategy.Listener}
+ * action between chunks.
+ *
+ * @param redefinitionBatchAllocator The batch allocator to use.
+ * @return A new instance of this agent builder which makes use of the specified batch allocator.
+ */
+ WithImplicitDiscoveryStrategy with(RedefinitionStrategy.BatchAllocator redefinitionBatchAllocator);
+ }
+ }
+
+ /**
+ * Describes an {@link net.bytebuddy.agent.builder.AgentBuilder} which was handed a matcher for identifying
+ * types to instrumented in order to supply one or several
+ * {@link net.bytebuddy.agent.builder.AgentBuilder.Transformer}s.
+ */
+ interface Identified {
+
+ /**
+ * Applies the given transformer for the already supplied matcher.
+ *
+ * @param transformer The transformer to apply.
+ * @return A new instance of this agent builder with the transformer being applied when the previously supplied matcher
+ * identified a type for instrumentation which also allows for the registration of subsequent transformers.
+ */
+ Extendable transform(Transformer transformer);
+
+ /**
+ * Allows to specify a type matcher for a type to instrument.
+ */
+ interface Narrowable extends Matchable<Narrowable>, Identified {
+ /* this is merely a unionizing interface that does not declare methods */
+ }
+
+ /**
+ * This interface is used to allow for optionally providing several
+ * {@link net.bytebuddy.agent.builder.AgentBuilder.Transformer} to applied when a matcher identifies a type
+ * to be instrumented. Any subsequent transformers are applied in the order they are registered.
+ */
+ interface Extendable extends AgentBuilder, Identified {
+
+ /**
+ * <p>
+ * Applies the specified transformation as a decorative transformation. For a decorative transformation, the supplied
+ * transformer is prepended to any previous transformation that also matches the instrumented type, i.e. both transformations
+ * are supplied. This procedure is repeated until a transformer is reached that matches the instrumented type but is not
+ * defined as decorating after which no further transformations are considered. If all matching transformations are declared
+ * as decorating, all matching transformers are applied.
+ * </p>
+ * <p>
+ * <b>Note</b>: A decorating transformer is applied <b>after</b> previously registered transformers.
+ * </p>
+ *
+ * @return A new instance of this agent builder with the specified transformation being applied as a decorator.
+ */
+ AgentBuilder asDecorator();
+ }
+ }
+
+ /**
+ * A matcher that allows to determine if a {@link net.bytebuddy.agent.builder.AgentBuilder.Transformer}
+ * should be applied during the execution of a {@link java.lang.instrument.ClassFileTransformer} that was
+ * generated by an {@link net.bytebuddy.agent.builder.AgentBuilder}.
+ */
+ interface RawMatcher {
+
+ /**
+ * Decides if the given {@code typeDescription} should be instrumented with the entailed
+ * {@link net.bytebuddy.agent.builder.AgentBuilder.Transformer}s.
+ *
+ * @param typeDescription A description of the type to be instrumented.
+ * @param classLoader The class loader of the instrumented type. Might be {@code null} if this class
+ * loader represents the bootstrap class loader.
+ * @param module The transformed type's module or {@code null} if the current VM does not support modules.
+ * @param classBeingRedefined The class being redefined which is only not {@code null} if a retransformation
+ * is applied.
+ * @param protectionDomain The protection domain of the type being transformed.
+ * @return {@code true} if the entailed {@link net.bytebuddy.agent.builder.AgentBuilder.Transformer}s should
+ * be applied for the given {@code typeDescription}.
+ */
+ boolean matches(TypeDescription typeDescription,
+ ClassLoader classLoader,
+ JavaModule module,
+ Class<?> classBeingRedefined,
+ ProtectionDomain protectionDomain);
+
+ /**
+ * A matcher that always or never matches a type.
+ */
+ enum Trivial implements RawMatcher {
+
+ /**
+ * Always matches a type.
+ */
+ MATCHING(true),
+
+ /**
+ * Never matches a type.
+ */
+ NON_MATCHING(false);
+
+ /**
+ * {@code true} if this matcher always matches a type.
+ */
+ private final boolean matches;
+
+ /**
+ * Creates a new trivial raw matcher.
+ *
+ * @param matches {@code true} if this matcher always matches a type.
+ */
+ Trivial(boolean matches) {
+ this.matches = matches;
+ }
+
+ @Override
+ public boolean matches(TypeDescription typeDescription,
+ ClassLoader classLoader,
+ JavaModule module,
+ Class<?> classBeingRedefined,
+ ProtectionDomain protectionDomain) {
+ return matches;
+ }
+ }
+
+ /**
+ * A raw matcher indicating the state of a type's class loading.
+ */
+ enum ForLoadState implements RawMatcher {
+
+ /**
+ * Indicates that a type was already loaded.
+ */
+ LOADED(false),
+
+ /**
+ * Indicates that a type was not yet loaded.
+ */
+ UNLOADED(true);
+
+ /**
+ * {@code true} if a type is expected to be unloaded..
+ */
+ private final boolean unloaded;
+
+ /**
+ * Creates a new load state matcher.
+ *
+ * @param unloaded {@code true} if a type is expected to be unloaded..
+ */
+ ForLoadState(boolean unloaded) {
+ this.unloaded = unloaded;
+ }
+
+ @Override
+ public boolean matches(TypeDescription typeDescription,
+ ClassLoader classLoader,
+ JavaModule module,
+ Class<?> classBeingRedefined,
+ ProtectionDomain protectionDomain) {
+ return classBeingRedefined == null == unloaded;
+ }
+ }
+
+ /**
+ * Only matches loaded types that can be fully resolved. Types with missing dependencies might not be
+ * resolvable and can therefore trigger errors during redefinition.
+ */
+ enum ForResolvableTypes implements RawMatcher {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public boolean matches(TypeDescription typeDescription,
+ ClassLoader classLoader,
+ JavaModule module,
+ Class<?> classBeingRedefined,
+ ProtectionDomain protectionDomain) {
+ if (classBeingRedefined != null) {
+ try {
+ return Class.forName(classBeingRedefined.getName(), true, classLoader) == classBeingRedefined;
+ } catch (Throwable ignored) {
+ return false;
+ }
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Returns an inverted version of this matcher.
+ *
+ * @return An inverted version of this matcher.
+ */
+ public RawMatcher inverted() {
+ return new Inversion(this);
+ }
+ }
+
+ /**
+ * A conjunction of two raw matchers.
+ */
+ @EqualsAndHashCode
+ class Conjunction implements RawMatcher {
+
+ /**
+ * The left matcher which is applied first.
+ */
+ private final RawMatcher left;
+
+ /**
+ * The right matcher which is applied second.
+ */
+ private final RawMatcher right;
+
+ /**
+ * Creates a new conjunction of two raw matchers.
+ *
+ * @param left The left matcher which is applied first.
+ * @param right The right matcher which is applied second.
+ */
+ protected Conjunction(RawMatcher left, RawMatcher right) {
+ this.left = left;
+ this.right = right;
+ }
+
+ @Override
+ public boolean matches(TypeDescription typeDescription,
+ ClassLoader classLoader,
+ JavaModule module,
+ Class<?> classBeingRedefined,
+ ProtectionDomain protectionDomain) {
+ return left.matches(typeDescription, classLoader, module, classBeingRedefined, protectionDomain)
+ && right.matches(typeDescription, classLoader, module, classBeingRedefined, protectionDomain);
+ }
+ }
+
+ /**
+ * A disjunction of two raw matchers.
+ */
+ @EqualsAndHashCode
+ class Disjunction implements RawMatcher {
+
+ /**
+ * The left matcher which is applied first.
+ */
+ private final RawMatcher left;
+
+ /**
+ * The right matcher which is applied second.
+ */
+ private final RawMatcher right;
+
+ /**
+ * Creates a new disjunction of two raw matchers.
+ *
+ * @param left The left matcher which is applied first.
+ * @param right The right matcher which is applied second.
+ */
+ protected Disjunction(RawMatcher left, RawMatcher right) {
+ this.left = left;
+ this.right = right;
+ }
+
+ @Override
+ public boolean matches(TypeDescription typeDescription,
+ ClassLoader classLoader,
+ JavaModule module,
+ Class<?> classBeingRedefined,
+ ProtectionDomain protectionDomain) {
+ return left.matches(typeDescription, classLoader, module, classBeingRedefined, protectionDomain)
+ || right.matches(typeDescription, classLoader, module, classBeingRedefined, protectionDomain);
+ }
+ }
+
+ /**
+ * A raw matcher that inverts a raw matcher's result.
+ */
+ @EqualsAndHashCode
+ class Inversion implements RawMatcher {
+
+ /**
+ * The matcher to invert.
+ */
+ private final RawMatcher matcher;
+
+ /**
+ * Creates a raw matcher that inverts its result.
+ *
+ * @param matcher The matcher to invert.
+ */
+ public Inversion(RawMatcher matcher) {
+ this.matcher = matcher;
+ }
+
+ @Override
+ public boolean matches(TypeDescription typeDescription,
+ ClassLoader classLoader,
+ JavaModule module,
+ Class<?> classBeingRedefined,
+ ProtectionDomain protectionDomain) {
+ return !matcher.matches(typeDescription, classLoader, module, classBeingRedefined, protectionDomain);
+ }
+ }
+
+ /**
+ * A raw matcher implementation that checks a {@link TypeDescription}
+ * and its {@link java.lang.ClassLoader} against two suitable matchers in order to determine if the matched
+ * type should be instrumented.
+ */
+ @EqualsAndHashCode
+ class ForElementMatchers implements RawMatcher {
+
+ /**
+ * The type matcher to apply to a {@link TypeDescription}.
+ */
+ private final ElementMatcher<? super TypeDescription> typeMatcher;
+
+ /**
+ * The class loader matcher to apply to a {@link java.lang.ClassLoader}.
+ */
+ private final ElementMatcher<? super ClassLoader> classLoaderMatcher;
+
+ /**
+ * A module matcher to apply to a {@code java.lang.Module}.
+ */
+ private final ElementMatcher<? super JavaModule> moduleMatcher;
+
+
+ /**
+ * Creates a new {@link net.bytebuddy.agent.builder.AgentBuilder.RawMatcher} that only matches the
+ * supplied {@link TypeDescription} against a supplied matcher.
+ *
+ * @param typeMatcher The type matcher to apply to a {@link TypeDescription}.
+ */
+ public ForElementMatchers(ElementMatcher<? super TypeDescription> typeMatcher) {
+ this(typeMatcher, any());
+ }
+
+ /**
+ * Creates a new {@link net.bytebuddy.agent.builder.AgentBuilder.RawMatcher} that only matches the
+ * supplied {@link TypeDescription} and its {@link java.lang.ClassLoader} against two matcher in order
+ * to decided if an instrumentation should be conducted.
+ *
+ * @param typeMatcher The type matcher to apply to a {@link TypeDescription}.
+ * @param classLoaderMatcher The class loader matcher to apply to a {@link java.lang.ClassLoader}.
+ */
+ public ForElementMatchers(ElementMatcher<? super TypeDescription> typeMatcher,
+ ElementMatcher<? super ClassLoader> classLoaderMatcher) {
+ this(typeMatcher, classLoaderMatcher, any());
+ }
+
+ /**
+ * Creates a new {@link net.bytebuddy.agent.builder.AgentBuilder.RawMatcher} that only matches the
+ * supplied {@link TypeDescription}, its {@link java.lang.ClassLoader} and module against element
+ * suitable matchers.
+ *
+ * @param typeMatcher The type matcher to apply to a {@link TypeDescription}.
+ * @param classLoaderMatcher The class loader matcher to apply to a {@link java.lang.ClassLoader}.
+ * @param moduleMatcher A module matcher to apply to a {@code java.lang.Module}.
+ */
+ public ForElementMatchers(ElementMatcher<? super TypeDescription> typeMatcher,
+ ElementMatcher<? super ClassLoader> classLoaderMatcher,
+ ElementMatcher<? super JavaModule> moduleMatcher) {
+ this.typeMatcher = typeMatcher;
+ this.classLoaderMatcher = classLoaderMatcher;
+ this.moduleMatcher = moduleMatcher;
+ }
+
+ @Override
+ public boolean matches(TypeDescription typeDescription,
+ ClassLoader classLoader,
+ JavaModule module,
+ Class<?> classBeingRedefined,
+ ProtectionDomain protectionDomain) {
+ return moduleMatcher.matches(module) && classLoaderMatcher.matches(classLoader) && typeMatcher.matches(typeDescription);
+ }
+ }
+ }
+
+ /**
+ * A listener that is informed about events that occur during an instrumentation process.
+ */
+ interface Listener {
+
+ /**
+ * Indicates that a transformed type is loaded.
+ */
+ boolean LOADED = true;
+
+ /**
+ * Invoked upon a type being supplied to a transformer.
+ *
+ * @param typeName The binary name of the instrumented type.
+ * @param classLoader The class loader which is loading this type.
+ * @param module The instrumented type's module or {@code null} if the current VM does not support modules.
+ * @param loaded {@code true} if the type is already loaded.
+ */
+ void onDiscovery(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded);
+
+ /**
+ * Invoked prior to a successful transformation being applied.
+ *
+ * @param typeDescription The type that is being transformed.
+ * @param classLoader The class loader which is loading this type.
+ * @param module The transformed type's module or {@code null} if the current VM does not support modules.
+ * @param loaded {@code true} if the type is already loaded.
+ * @param dynamicType The dynamic type that was created.
+ */
+ void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded, DynamicType dynamicType);
+
+ /**
+ * Invoked when a type is not transformed but ignored.
+ *
+ * @param typeDescription The type being ignored for transformation.
+ * @param classLoader The class loader which is loading this type.
+ * @param module The ignored type's module or {@code null} if the current VM does not support modules.
+ * @param loaded {@code true} if the type is already loaded.
+ */
+ void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded);
+
+ /**
+ * Invoked when an error has occurred during transformation.
+ *
+ * @param typeName The binary name of the instrumented type.
+ * @param classLoader The class loader which is loading this type.
+ * @param module The instrumented type's module or {@code null} if the current VM does not support modules.
+ * @param loaded {@code true} if the type is already loaded.
+ * @param throwable The occurred error.
+ */
+ void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable);
+
+ /**
+ * Invoked after a class was attempted to be loaded, independently of its treatment.
+ *
+ * @param typeName The binary name of the instrumented type.
+ * @param classLoader The class loader which is loading this type.
+ * @param module The instrumented type's module or {@code null} if the current VM does not support modules.
+ * @param loaded {@code true} if the type is already loaded.
+ */
+ void onComplete(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded);
+
+ /**
+ * A no-op implementation of a {@link net.bytebuddy.agent.builder.AgentBuilder.Listener}.
+ */
+ enum NoOp implements Listener {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public void onDiscovery(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) {
+ /* do nothing */
+ }
+
+ @Override
+ public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded, DynamicType dynamicType) {
+ /* do nothing */
+ }
+
+ @Override
+ public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded) {
+ /* do nothing */
+ }
+
+ @Override
+ public void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable) {
+ /* do nothing */
+ }
+
+ @Override
+ public void onComplete(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) {
+ /* do nothing */
+ }
+ }
+
+ /**
+ * An adapter for a listener wher all methods are implemented as non-operational.
+ */
+ abstract class Adapter implements Listener {
+
+ @Override
+ public void onDiscovery(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) {
+ /* do nothing */
+ }
+
+ @Override
+ public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded, DynamicType dynamicType) {
+ /* do nothing */
+ }
+
+ @Override
+ public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded) {
+ /* do nothing */
+ }
+
+ @Override
+ public void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable) {
+ /* do nothing */
+ }
+
+ @Override
+ public void onComplete(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) {
+ /* do nothing */
+ }
+ }
+
+ /**
+ * A listener that writes events to a {@link PrintStream}. This listener prints a line per event, including the event type and
+ * the name of the type in question.
+ */
+ @EqualsAndHashCode
+ class StreamWriting implements Listener {
+
+ /**
+ * The prefix that is appended to all written messages.
+ */
+ protected static final String PREFIX = "[Byte Buddy]";
+
+ /**
+ * The print stream written to.
+ */
+ private final PrintStream printStream;
+
+ /**
+ * Creates a new stream writing listener.
+ *
+ * @param printStream The print stream written to.
+ */
+ public StreamWriting(PrintStream printStream) {
+ this.printStream = printStream;
+ }
+
+ /**
+ * Creates a new stream writing listener that writes to {@link System#out}.
+ *
+ * @return A listener writing events to the standard output stream.
+ */
+ public static Listener toSystemOut() {
+ return new StreamWriting(System.out);
+ }
+
+ /**
+ * Creates a new stream writing listener that writes to {@link System#err}.
+ *
+ * @return A listener writing events to the standad error stream.
+ */
+ public static Listener toSystemError() {
+ return new StreamWriting(System.err);
+ }
+
+ @Override
+ public void onDiscovery(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) {
+ printStream.printf(PREFIX + " DISCOVERY %s [%s, %s, loaded=%b]%n", typeName, classLoader, module, loaded);
+ }
+
+ @Override
+ public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded, DynamicType dynamicType) {
+ printStream.printf(PREFIX + " TRANSFORM %s [%s, %s, loaded=%b]%n", typeDescription.getName(), classLoader, module, loaded);
+ }
+
+ @Override
+ public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded) {
+ printStream.printf(PREFIX + " IGNORE %s [%s, %s, loaded=%b]%n", typeDescription.getName(), classLoader, module, loaded);
+ }
+
+ @Override
+ public void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable) {
+ synchronized (printStream) {
+ printStream.printf(PREFIX + " ERROR %s [%s, %s, loaded=%b]%n", typeName, classLoader, module, loaded);
+ throwable.printStackTrace(printStream);
+ }
+ }
+
+ @Override
+ public void onComplete(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) {
+ printStream.printf(PREFIX + " COMPLETE %s [%s, %s, loaded=%b]%n", typeName, classLoader, module, loaded);
+ }
+ }
+
+ /**
+ * A listener that filters types with a given name from being logged.
+ */
+ @EqualsAndHashCode
+ class Filtering implements Listener {
+
+ /**
+ * The matcher to decide upon a type should be logged.
+ */
+ private final ElementMatcher<? super String> matcher;
+
+ /**
+ * The delegate listener.
+ */
+ private final Listener delegate;
+
+ /**
+ * Creates a new filtering listener.
+ *
+ * @param matcher The matcher to decide upon a type should be logged.
+ * @param delegate The delegate listener.
+ */
+ public Filtering(ElementMatcher<? super String> matcher, Listener delegate) {
+ this.matcher = matcher;
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void onDiscovery(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) {
+ if (matcher.matches(typeName)) {
+ delegate.onDiscovery(typeName, classLoader, module, loaded);
+ }
+ }
+
+ @Override
+ public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded, DynamicType dynamicType) {
+ if (matcher.matches(typeDescription.getName())) {
+ delegate.onTransformation(typeDescription, classLoader, module, loaded, dynamicType);
+ }
+ }
+
+ @Override
+ public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded) {
+ if (matcher.matches(typeDescription.getName())) {
+ delegate.onIgnored(typeDescription, classLoader, module, loaded);
+ }
+ }
+
+ @Override
+ public void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable) {
+ if (matcher.matches(typeName)) {
+ delegate.onError(typeName, classLoader, module, loaded, throwable);
+ }
+ }
+
+ @Override
+ public void onComplete(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) {
+ if (matcher.matches(typeName)) {
+ delegate.onComplete(typeName, classLoader, module, loaded);
+ }
+ }
+ }
+
+ /**
+ * A listener that adds read-edges to any module of an instrumented class upon its transformation.
+ */
+ @EqualsAndHashCode(callSuper = false)
+ class ModuleReadEdgeCompleting extends Listener.Adapter {
+
+ /**
+ * The instrumentation instance used for adding read edges.
+ */
+ private final Instrumentation instrumentation;
+
+ /**
+ * {@code true} if the listener should also add a read-edge from the supplied modules to the instrumented type's module.
+ */
+ private final boolean addTargetEdge;
+
+ /**
+ * The modules to add as a read edge to any transformed class's module.
+ */
+ private final Set<? extends JavaModule> modules;
+
+ /**
+ * Creates a new module read-edge completing listener.
+ *
+ * @param instrumentation The instrumentation instance used for adding read edges.
+ * @param addTargetEdge {@code true} if the listener should also add a read-edge from the supplied modules
+ * to the instrumented type's module.
+ * @param modules The modules to add as a read edge to any transformed class's module.
+ */
+ public ModuleReadEdgeCompleting(Instrumentation instrumentation, boolean addTargetEdge, Set<? extends JavaModule> modules) {
+ this.instrumentation = instrumentation;
+ this.addTargetEdge = addTargetEdge;
+ this.modules = modules;
+ }
+
+ /**
+ * Resolves a listener that adds module edges from and to the instrumented type's module.
+ *
+ * @param instrumentation The instrumentation instance used for adding read edges.
+ * @param addTargetEdge {@code true} if the listener should also add a read-edge from the supplied
+ * modules to the instrumented type's module.
+ * @param type The types for which to extract the modules.
+ * @return An appropriate listener.
+ */
+ protected static Listener of(Instrumentation instrumentation, boolean addTargetEdge, Class<?>... type) {
+ Set<JavaModule> modules = new HashSet<JavaModule>();
+ for (Class<?> aType : type) {
+ JavaModule module = JavaModule.ofType(aType);
+ if (module.isNamed()) {
+ modules.add(module);
+ }
+ }
+ return modules.isEmpty()
+ ? Listener.NoOp.INSTANCE
+ : new Listener.ModuleReadEdgeCompleting(instrumentation, addTargetEdge, modules);
+ }
+
+ @Override
+ public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded, DynamicType dynamicType) {
+ if (module != JavaModule.UNSUPPORTED && module.isNamed()) {
+ for (JavaModule target : modules) {
+ if (!module.canRead(target)) {
+ module.addReads(instrumentation, target);
+ }
+ if (addTargetEdge && !target.canRead(module)) {
+ target.addReads(instrumentation, module);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * A compound listener that allows to group several listeners in one instance.
+ */
+ @EqualsAndHashCode
+ class Compound implements Listener {
+
+ /**
+ * The listeners that are represented by this compound listener in their application order.
+ */
+ private final List<Listener> listeners;
+
+ /**
+ * Creates a new compound listener.
+ *
+ * @param listener The listeners to apply in their application order.
+ */
+ public Compound(Listener... listener) {
+ this(Arrays.asList(listener));
+ }
+
+ /**
+ * Creates a new compound listener.
+ *
+ * @param listeners The listeners to apply in their application order.
+ */
+ public Compound(List<? extends Listener> listeners) {
+ this.listeners = new ArrayList<Listener>();
+ for (Listener listener : listeners) {
+ if (listener instanceof Compound) {
+ this.listeners.addAll(((Compound) listener).listeners);
+ } else if (!(listener instanceof NoOp)) {
+ this.listeners.add(listener);
+ }
+ }
+ }
+
+ @Override
+ public void onDiscovery(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) {
+ for (Listener listener : listeners) {
+ listener.onDiscovery(typeName, classLoader, module, loaded);
+ }
+ }
+
+ @Override
+ public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded, DynamicType dynamicType) {
+ for (Listener listener : listeners) {
+ listener.onTransformation(typeDescription, classLoader, module, loaded, dynamicType);
+ }
+ }
+
+ @Override
+ public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded) {
+ for (Listener listener : listeners) {
+ listener.onIgnored(typeDescription, classLoader, module, loaded);
+ }
+ }
+
+ @Override
+ public void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable) {
+ for (Listener listener : listeners) {
+ listener.onError(typeName, classLoader, module, loaded, throwable);
+ }
+ }
+
+ @Override
+ public void onComplete(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) {
+ for (Listener listener : listeners) {
+ listener.onComplete(typeName, classLoader, module, loaded);
+ }
+ }
+ }
+ }
+
+ /**
+ * A circularity lock is responsible for preventing that a {@link ClassFileLocator} is used recursively.
+ * This can happen when a class file transformation causes another class to be loaded. Without avoiding
+ * such circularities, a class loading is aborted by a {@link ClassCircularityError} which causes the
+ * class loading to fail.
+ */
+ interface CircularityLock {
+
+ /**
+ * Attempts to acquire a circularity lock.
+ *
+ * @return {@code true} if the lock was acquired successfully, {@code false} if it is already hold.
+ */
+ boolean acquire();
+
+ /**
+ * Releases the circularity lock if it is currently acquired.
+ */
+ void release();
+
+ /**
+ * An inactive circularity lock which is always acquirable.
+ */
+ enum Inactive implements CircularityLock {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public boolean acquire() {
+ return true;
+ }
+
+ @Override
+ public void release() {
+ /* do nothing */
+ }
+ }
+
+ /**
+ * A default implementation of a circularity lock. Since class loading already synchronizes on a class loader,
+ * it suffices to apply a thread-local lock.
+ */
+ class Default extends ThreadLocal<Boolean> implements CircularityLock {
+
+ /**
+ * Indicates that the circularity lock is not currently acquired.
+ */
+ private static final Boolean NOT_ACQUIRED = null;
+
+ @Override
+ public boolean acquire() {
+ if (get() == NOT_ACQUIRED) {
+ set(true);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void release() {
+ set(NOT_ACQUIRED);
+ }
+ }
+
+ /**
+ * A circularity lock that holds a global monitor and does not permit concurrent access.
+ */
+ @EqualsAndHashCode
+ class Global implements CircularityLock {
+
+ /**
+ * The lock to hold.
+ */
+ private final Lock lock;
+
+ /**
+ * The time to wait for the lock.
+ */
+ private final long time;
+
+ /**
+ * The time's time unit.
+ */
+ private final TimeUnit timeUnit;
+
+ /**
+ * Creates a new global circularity lock that does not wait for a release.
+ */
+ public Global() {
+ this(0, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * Creates a new global circularity lock.
+ *
+ * @param time The time to wait for the lock.
+ * @param timeUnit The time's time unit.
+ */
+ public Global(long time, TimeUnit timeUnit) {
+ lock = new ReentrantLock();
+ this.time = time;
+ this.timeUnit = timeUnit;
+ }
+
+ @Override
+ public boolean acquire() {
+ try {
+ return time == 0
+ ? lock.tryLock()
+ : lock.tryLock(time, timeUnit);
+ } catch (InterruptedException ignored) {
+ return false;
+ }
+ }
+
+ @Override
+ public void release() {
+ lock.unlock();
+ }
+ }
+ }
+
+ /**
+ * A type strategy is responsible for creating a type builder for a type that is being instrumented.
+ */
+ interface TypeStrategy {
+
+ /**
+ * Creates a type builder for a given type.
+ *
+ * @param typeDescription The type being instrumented.
+ * @param byteBuddy The Byte Buddy configuration.
+ * @param classFileLocator The class file locator to use.
+ * @param methodNameTransformer The method name transformer to use.
+ * @return A type builder for the given arguments.
+ */
+ DynamicType.Builder<?> builder(TypeDescription typeDescription,
+ ByteBuddy byteBuddy,
+ ClassFileLocator classFileLocator,
+ MethodNameTransformer methodNameTransformer);
+
+ /**
+ * Default implementations of type strategies.
+ */
+ enum Default implements TypeStrategy {
+
+ /**
+ * A definition handler that performs a rebasing for all types.
+ */
+ REBASE {
+ @Override
+ public DynamicType.Builder<?> builder(TypeDescription typeDescription,
+ ByteBuddy byteBuddy,
+ ClassFileLocator classFileLocator,
+ MethodNameTransformer methodNameTransformer) {
+ return byteBuddy.rebase(typeDescription, classFileLocator, methodNameTransformer);
+ }
+ },
+
+ /**
+ * <p>
+ * A definition handler that performs a redefinition for all types.
+ * </p>
+ * <p>
+ * Note that the default agent builder is configured to apply a self initialization where a static class initializer
+ * is added to the redefined class. This can be disabled by for example using a {@link InitializationStrategy.Minimal} or
+ * {@link InitializationStrategy.NoOp}. Also, consider the constraints implied by {@link ByteBuddy#redefine(TypeDescription, ClassFileLocator)}.
+ * </p>
+ * <p>
+ * For prohibiting any changes on a class file, use {@link AgentBuilder#disableClassFormatChanges()}
+ * </p>
+ */
+ REDEFINE {
+ @Override
+ public DynamicType.Builder<?> builder(TypeDescription typeDescription,
+ ByteBuddy byteBuddy,
+ ClassFileLocator classFileLocator,
+ MethodNameTransformer methodNameTransformer) {
+ return byteBuddy.redefine(typeDescription, classFileLocator);
+ }
+ },
+
+ /**
+ * <p>
+ * A definition handler that performs a redefinition for all types and ignores all methods that were not declared by the instrumented type.
+ * </p>
+ * <p>
+ * Note that the default agent builder is configured to apply a self initialization where a static class initializer
+ * is added to the redefined class. This can be disabled by for example using a {@link InitializationStrategy.Minimal} or
+ * {@link InitializationStrategy.NoOp}. Also, consider the constraints implied by {@link ByteBuddy#redefine(TypeDescription, ClassFileLocator)}.
+ * Using this strategy also configures Byte Buddy to create frozen instrumented types and discards any explicit configuration.
+ * </p>
+ * <p>
+ * For prohibiting any changes on a class file, use {@link AgentBuilder#disableClassFormatChanges()}
+ * </p>
+ */
+ REDEFINE_FROZEN {
+ @Override
+ public DynamicType.Builder<?> builder(TypeDescription typeDescription,
+ ByteBuddy byteBuddy,
+ ClassFileLocator classFileLocator,
+ MethodNameTransformer methodNameTransformer) {
+ return byteBuddy.with(InstrumentedType.Factory.Default.FROZEN)
+ .redefine(typeDescription, classFileLocator)
+ .ignoreAlso(LatentMatcher.ForSelfDeclaredMethod.NOT_DECLARED);
+ }
+ };
+ }
+
+ /**
+ * A type strategy that applies a build {@link EntryPoint}.
+ */
+ @EqualsAndHashCode
+ class ForBuildEntryPoint implements TypeStrategy {
+
+ /**
+ * The entry point to apply.
+ */
+ private final EntryPoint entryPoint;
+
+ /**
+ * Creates a new type strategy for an entry point.
+ *
+ * @param entryPoint The entry point to apply.
+ */
+ public ForBuildEntryPoint(EntryPoint entryPoint) {
+ this.entryPoint = entryPoint;
+ }
+
+ @Override
+ public DynamicType.Builder<?> builder(TypeDescription typeDescription,
+ ByteBuddy byteBuddy,
+ ClassFileLocator classFileLocator,
+ MethodNameTransformer methodNameTransformer) {
+ return entryPoint.transform(typeDescription, byteBuddy, classFileLocator, methodNameTransformer);
+ }
+ }
+ }
+
+ /**
+ * A transformer allows to apply modifications to a {@link net.bytebuddy.dynamic.DynamicType}. Such a modification
+ * is then applied to any instrumented type that was matched by the preceding matcher.
+ */
+ interface Transformer {
+
+ /**
+ * Allows for a transformation of a {@link net.bytebuddy.dynamic.DynamicType.Builder}.
+ *
+ * @param builder The dynamic builder to transform.
+ * @param typeDescription The description of the type currently being instrumented.
+ * @param classLoader The class loader of the instrumented class. Might be {@code null} to represent the bootstrap class loader.
+ * @param module The class's module or {@code null} if the current VM does not support modules.
+ * @return A transformed version of the supplied {@code builder}.
+ */
+ DynamicType.Builder<?> transform(DynamicType.Builder<?> builder,
+ TypeDescription typeDescription,
+ ClassLoader classLoader,
+ JavaModule module);
+
+ /**
+ * A no-op implementation of a {@link net.bytebuddy.agent.builder.AgentBuilder.Transformer} that does
+ * not modify the supplied dynamic type.
+ */
+ enum NoOp implements Transformer {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder,
+ TypeDescription typeDescription,
+ ClassLoader classLoader,
+ JavaModule module) {
+ return builder;
+ }
+ }
+
+ /**
+ * A transformer that applies a build {@link Plugin}.
+ */
+ @EqualsAndHashCode
+ class ForBuildPlugin implements Transformer {
+
+ /**
+ * The plugin to apply.
+ */
+ private final Plugin plugin;
+
+ /**
+ * Creates a new transformer for a build {@link Plugin}.
+ *
+ * @param plugin The plugin to apply.
+ */
+ public ForBuildPlugin(Plugin plugin) {
+ this.plugin = plugin;
+ }
+
+ @Override
+ public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder,
+ TypeDescription typeDescription,
+ ClassLoader classLoader,
+ JavaModule module) {
+ return plugin.apply(builder, typeDescription);
+ }
+ }
+
+ /**
+ * A transformer for applying an {@link Advice} where this advice class might reference types of both the agent's and the user's
+ * class loader. Using this transformer, it is possible to apply advice without including any library dependencies of this advice
+ * class which are then rather looked up from the transformed class's class loader. For this to work, it is required to register
+ * the advice class's class loader manually via the {@code include} methods and to reference the advice class by its fully-qualified
+ * name. The advice class is then never loaded by rather described by a {@link TypePool}.
+ */
+ @EqualsAndHashCode
+ class ForAdvice implements Transformer {
+
+ /**
+ * The advice to use.
+ */
+ private final Advice.WithCustomMapping advice;
+
+ /**
+ * The exception handler to register for the advice.
+ */
+ private final StackManipulation exceptionHandler;
+
+ /**
+ * The assigner to use for the advice.
+ */
+ private final Assigner assigner;
+
+ /**
+ * The class file locator to query for the advice class.
+ */
+ private final ClassFileLocator classFileLocator;
+
+ /**
+ * The pool strategy to use for looking up an advice.
+ */
+ private final PoolStrategy poolStrategy;
+
+ /**
+ * The location strategy to use for class loaders when resolving advice classes.
+ */
+ private final LocationStrategy locationStrategy;
+
+ /**
+ * The advice entries to apply.
+ */
+ private final List<Entry> entries;
+
+ /**
+ * Creates a new advice transformer with a default setup.
+ */
+ public ForAdvice() {
+ this(Advice.withCustomMapping());
+ }
+
+ /**
+ * Creates a new advice transformer which applies the given advice.
+ *
+ * @param advice The configured advice to use.
+ */
+ public ForAdvice(Advice.WithCustomMapping advice) {
+ this(advice,
+ Removal.of(TypeDescription.THROWABLE),
+ Assigner.DEFAULT,
+ ClassFileLocator.NoOp.INSTANCE,
+ PoolStrategy.Default.FAST,
+ LocationStrategy.ForClassLoader.STRONG,
+ Collections.<Entry>emptyList());
+ }
+
+ /**
+ * Creates a new advice transformer.
+ *
+ * @param advice The configured advice to use.
+ * @param exceptionHandler The exception handler to use.
+ * @param assigner The assigner to use.
+ * @param classFileLocator The class file locator to use.
+ * @param poolStrategy The pool strategy to use for looking up an advice.
+ * @param locationStrategy The location strategy to use for class loaders when resolving advice classes.
+ * @param entries The advice entries to apply.
+ */
+ protected ForAdvice(Advice.WithCustomMapping advice,
+ StackManipulation exceptionHandler,
+ Assigner assigner,
+ ClassFileLocator classFileLocator,
+ PoolStrategy poolStrategy,
+ LocationStrategy locationStrategy,
+ List<Entry> entries) {
+ this.advice = advice;
+ this.exceptionHandler = exceptionHandler;
+ this.assigner = assigner;
+ this.classFileLocator = classFileLocator;
+ this.poolStrategy = poolStrategy;
+ this.locationStrategy = locationStrategy;
+ this.entries = entries;
+ }
+
+ @Override
+ public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder,
+ TypeDescription typeDescription,
+ ClassLoader classLoader,
+ JavaModule module) {
+ ClassFileLocator classFileLocator = new ClassFileLocator.Compound(locationStrategy.classFileLocator(classLoader, module), this.classFileLocator);
+ TypePool typePool = poolStrategy.typePool(classFileLocator, classLoader);
+ AsmVisitorWrapper.ForDeclaredMethods asmVisitorWrapper = new AsmVisitorWrapper.ForDeclaredMethods();
+ for (Entry entry : entries) {
+ asmVisitorWrapper = asmVisitorWrapper.method(entry.getMatcher().resolve(typeDescription), entry.resolve(advice, typePool, classFileLocator)
+ .withAssigner(assigner)
+ .withExceptionHandler(exceptionHandler));
+ }
+ return builder.visit(asmVisitorWrapper);
+ }
+
+ /**
+ * Registers a pool strategy for creating a {@link TypePool} that should be used for creating the advice class.
+ *
+ * @param poolStrategy The pool strategy to use.
+ * @return A new instance of this advice transformer that applies the supplied pool strategy.
+ */
+ public ForAdvice with(PoolStrategy poolStrategy) {
+ return new ForAdvice(advice, exceptionHandler, assigner, classFileLocator, poolStrategy, locationStrategy, entries);
+ }
+
+ /**
+ * Registers a location strategy for creating a {@link ClassFileLocator} from the class loader that is supplied during transformation
+ * that should be used for looking up advice-relevant classes.
+ *
+ * @param locationStrategy The location strategy to use.
+ * @return A new instance of this advice transformer that applies the supplied location strategy.
+ */
+ public ForAdvice with(LocationStrategy locationStrategy) {
+ return new ForAdvice(advice, exceptionHandler, assigner, classFileLocator, poolStrategy, locationStrategy, entries);
+ }
+
+ /**
+ * Registers an exception handler for suppressed exceptions to use by the registered advice.
+ *
+ * @param exceptionHandler The exception handler to use.
+ * @return A new instance of this advice transformer that applies the supplied exception handler.
+ * @see Advice#withExceptionHandler(StackManipulation)
+ */
+ public ForAdvice withExceptionHandler(StackManipulation exceptionHandler) {
+ return new ForAdvice(advice, exceptionHandler, assigner, classFileLocator, poolStrategy, locationStrategy, entries);
+ }
+
+ /**
+ * Registers an assigner to be used by the advice class.
+ *
+ * @param assigner The assigner to use.
+ * @return A new instance of this advice transformer that applies the supplied assigner.
+ * @see Advice#withAssigner(Assigner)
+ */
+ public ForAdvice with(Assigner assigner) {
+ return new ForAdvice(advice, exceptionHandler, assigner, classFileLocator, poolStrategy, locationStrategy, entries);
+ }
+
+ /**
+ * Includes the supplied class loaders as a source for looking up an advice class or its dependencies.
+ *
+ * @param classLoader The class loaders to include when looking up classes in their order. Duplicates are filtered.
+ * @return A new instance of this advice transformer that considers the supplied class loaders as a lookup source.
+ */
+ public ForAdvice include(ClassLoader... classLoader) {
+ Set<ClassFileLocator> classFileLocators = new LinkedHashSet<ClassFileLocator>();
+ for (ClassLoader aClassLoader : classLoader) {
+ classFileLocators.add(ClassFileLocator.ForClassLoader.of(aClassLoader));
+ }
+ return include(new ArrayList<ClassFileLocator>(classFileLocators));
+ }
+
+ /**
+ * Includes the supplied class file locators as a source for looking up an advice class or its dependencies.
+ *
+ * @param classFileLocator The class file locators to include when looking up classes in their order. Duplicates are filtered.
+ * @return A new instance of this advice transformer that considers the supplied class file locators as a lookup source.
+ */
+ public ForAdvice include(ClassFileLocator... classFileLocator) {
+ return include(Arrays.asList(classFileLocator));
+ }
+
+ /**
+ * Includes the supplied class file locators as a source for looking up an advice class or its dependencies.
+ *
+ * @param classFileLocators The class file locators to include when looking up classes in their order. Duplicates are filtered.
+ * @return A new instance of this advice transformer that considers the supplied class file locators as a lookup source.
+ */
+ public ForAdvice include(List<? extends ClassFileLocator> classFileLocators) {
+ return new ForAdvice(advice,
+ exceptionHandler,
+ assigner,
+ new ClassFileLocator.Compound(CompoundList.of(classFileLocator, classFileLocators)),
+ poolStrategy,
+ locationStrategy,
+ entries);
+ }
+
+ /**
+ * Applies the given advice class onto all methods that satisfy the supplied matcher.
+ *
+ * @param matcher The matcher to determine what methods the advice should be applied to.
+ * @param name The fully-qualified, binary name of the advice class.
+ * @return A new instance of this advice transformer that applies the given advice to all matched methods of an instrumented type.
+ */
+ public ForAdvice advice(ElementMatcher<? super MethodDescription> matcher, String name) {
+ return advice(new LatentMatcher.Resolved<MethodDescription>(matcher), name);
+ }
+
+ /**
+ * Applies the given advice class onto all methods that satisfy the supplied matcher.
+ *
+ * @param matcher The matcher to determine what methods the advice should be applied to.
+ * @param name The fully-qualified, binary name of the advice class.
+ * @return A new instance of this advice transformer that applies the given advice to all matched methods of an instrumented type.
+ */
+ public ForAdvice advice(LatentMatcher<? super MethodDescription> matcher, String name) {
+ return new ForAdvice(advice,
+ exceptionHandler,
+ assigner,
+ classFileLocator,
+ poolStrategy,
+ locationStrategy,
+ CompoundList.of(entries, new Entry.ForUnifiedAdvice(matcher, name)));
+ }
+
+ /**
+ * Applies the given advice class onto all methods that satisfy the supplied matcher.
+ *
+ * @param matcher The matcher to determine what methods the advice should be applied to.
+ * @param enter The fully-qualified, binary name of the enter advice class.
+ * @param exit The fully-qualified, binary name of the exit advice class.
+ * @return A new instance of this advice transformer that applies the given advice to all matched methods of an instrumented type.
+ */
+ public ForAdvice advice(ElementMatcher<? super MethodDescription> matcher, String enter, String exit) {
+ return advice(new LatentMatcher.Resolved<MethodDescription>(matcher), enter, exit);
+ }
+
+ /**
+ * Applies the given advice class onto all methods that satisfy the supplied matcher.
+ *
+ * @param matcher The matcher to determine what methods the advice should be applied to.
+ * * @param enter The fully-qualified, binary name of the enter advice class.
+ * @param exit The fully-qualified, binary name of the exit advice class.
+ * @return A new instance of this advice transformer that applies the given advice to all matched methods of an instrumented type.
+ */
+ public ForAdvice advice(LatentMatcher<? super MethodDescription> matcher, String enter, String exit) {
+ return new ForAdvice(advice,
+ exceptionHandler,
+ assigner,
+ classFileLocator,
+ poolStrategy,
+ locationStrategy,
+ CompoundList.of(entries, new Entry.ForSplitAdvice(matcher, enter, exit)));
+ }
+
+ /**
+ * An entry for an advice to apply.
+ */
+ @EqualsAndHashCode
+ protected abstract static class Entry {
+
+ /**
+ * The matcher for advised methods.
+ */
+ private final LatentMatcher<? super MethodDescription> matcher;
+
+ /**
+ * Creates a new entry.
+ *
+ * @param matcher The matcher for advised methods.
+ */
+ protected Entry(LatentMatcher<? super MethodDescription> matcher) {
+ this.matcher = matcher;
+ }
+
+ /**
+ * Returns the matcher for advised methods.
+ *
+ * @return The matcher for advised methods.
+ */
+ protected LatentMatcher<? super MethodDescription> getMatcher() {
+ return matcher;
+ }
+
+ /**
+ * Resolves the advice for this entry.
+ *
+ * @param advice The advice configuration.
+ * @param typePool The type pool to use.
+ * @param classFileLocator The class file locator to use.
+ * @return The resolved advice.
+ */
+ protected abstract Advice resolve(Advice.WithCustomMapping advice, TypePool typePool, ClassFileLocator classFileLocator);
+
+ /**
+ * An entry for an advice class where both the (optional) entry and exit advice methods are declared by the same class.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ protected static class ForUnifiedAdvice extends Entry {
+
+ /**
+ * The name of the advice class.
+ */
+ protected final String name;
+
+ /**
+ * Creates a new entry for an advice class where both the (optional) entry and exit advice methods are declared by the same class.
+ *
+ * @param matcher The matcher for advised methods.
+ * @param name The name of the advice class.
+ */
+ protected ForUnifiedAdvice(LatentMatcher<? super MethodDescription> matcher, String name) {
+ super(matcher);
+ this.name = name;
+ }
+
+ @Override
+ protected Advice resolve(Advice.WithCustomMapping advice, TypePool typePool, ClassFileLocator classFileLocator) {
+ return advice.to(typePool.describe(name).resolve(), classFileLocator);
+ }
+ }
+
+ /**
+ * An entry for an advice class where both entry and exit advice methods are declared by the different classes.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ protected static class ForSplitAdvice extends Entry {
+
+ /**
+ * The fully-qualified, binary name of the enter advice class.
+ */
+ private final String enter;
+
+ /**
+ * The fully-qualified, binary name of the exit advice class.
+ */
+ private final String exit;
+
+ /**
+ * Creates a new entry for an advice class with explicit entry and exit advice classes.
+ *
+ * @param matcher The matcher for advised methods.
+ * @param enter The fully-qualified, binary name of the enter advice class.
+ * @param exit The fully-qualified, binary name of the exit advice class.
+ */
+ protected ForSplitAdvice(LatentMatcher<? super MethodDescription> matcher, String enter, String exit) {
+ super(matcher);
+ this.enter = enter;
+ this.exit = exit;
+ }
+
+ @Override
+ protected Advice resolve(Advice.WithCustomMapping advice, TypePool typePool, ClassFileLocator classFileLocator) {
+ return advice.to(typePool.describe(enter).resolve(), typePool.describe(exit).resolve(), classFileLocator);
+ }
+ }
+ }
+ }
+
+ /**
+ * A compound transformer that allows to group several
+ * {@link net.bytebuddy.agent.builder.AgentBuilder.Transformer}s as a single transformer.
+ */
+ @EqualsAndHashCode
+ class Compound implements Transformer {
+
+ /**
+ * The transformers to apply in their application order.
+ */
+ private final List<Transformer> transformers;
+
+ /**
+ * Creates a new compound transformer.
+ *
+ * @param transformer The transformers to apply in their application order.
+ */
+ public Compound(Transformer... transformer) {
+ this(Arrays.asList(transformer));
+ }
+
+ /**
+ * Creates a new compound transformer.
+ *
+ * @param transformers The transformers to apply in their application order.
+ */
+ public Compound(List<? extends Transformer> transformers) {
+ this.transformers = new ArrayList<Transformer>();
+ for (Transformer transformer : transformers) {
+ if (transformer instanceof Compound) {
+ this.transformers.addAll(((Compound) transformer).transformers);
+ } else if (!(transformer instanceof NoOp)) {
+ this.transformers.add(transformer);
+ }
+ }
+ }
+
+ @Override
+ public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder,
+ TypeDescription typeDescription,
+ ClassLoader classLoader,
+ JavaModule module) {
+ for (Transformer transformer : transformers) {
+ builder = transformer.transform(builder, typeDescription, classLoader, module);
+ }
+ return builder;
+ }
+ }
+ }
+
+ /**
+ * A type locator allows to specify how {@link TypeDescription}s are resolved by an {@link net.bytebuddy.agent.builder.AgentBuilder}.
+ */
+ interface PoolStrategy {
+
+ /**
+ * Creates a type pool for a given class file locator.
+ *
+ * @param classFileLocator The class file locator to use.
+ * @param classLoader The class loader for which the class file locator was created.
+ * @return A type pool for the supplied class file locator.
+ */
+ TypePool typePool(ClassFileLocator classFileLocator, ClassLoader classLoader);
+
+ /**
+ * <p>
+ * A default type locator that resolves types only if any property that is not the type's name is requested.
+ * </p>
+ * <p>
+ * The returned type pool uses a {@link net.bytebuddy.pool.TypePool.CacheProvider.Simple} and the
+ * {@link ClassFileLocator} that is provided by the builder's {@link LocationStrategy}.
+ * </p>
+ */
+ enum Default implements PoolStrategy {
+
+ /**
+ * A type locator that parses the code segment of each method for extracting information about parameter
+ * names even if they are not explicitly included in a class file.
+ *
+ * @see net.bytebuddy.pool.TypePool.Default.ReaderMode#EXTENDED
+ */
+ EXTENDED(TypePool.Default.ReaderMode.EXTENDED),
+
+ /**
+ * A type locator that skips the code segment of each method and does therefore not extract information
+ * about parameter names. Parameter names are still included if they are explicitly included in a class file.
+ *
+ * @see net.bytebuddy.pool.TypePool.Default.ReaderMode#FAST
+ */
+ FAST(TypePool.Default.ReaderMode.FAST);
+
+ /**
+ * The reader mode to apply by this type locator.
+ */
+ private final TypePool.Default.ReaderMode readerMode;
+
+ /**
+ * Creates a new type locator.
+ *
+ * @param readerMode The reader mode to apply by this type locator.
+ */
+ Default(TypePool.Default.ReaderMode readerMode) {
+ this.readerMode = readerMode;
+ }
+
+ @Override
+ public TypePool typePool(ClassFileLocator classFileLocator, ClassLoader classLoader) {
+ return new TypePool.Default.WithLazyResolution(TypePool.CacheProvider.Simple.withObjectType(), classFileLocator, readerMode);
+ }
+ }
+
+ /**
+ * <p>
+ * A type locator that resolves all type descriptions eagerly.
+ * </p>
+ * <p>
+ * The returned type pool uses a {@link net.bytebuddy.pool.TypePool.CacheProvider.Simple} and the
+ * {@link ClassFileLocator} that is provided by the builder's {@link LocationStrategy}.
+ * </p>
+ */
+ enum Eager implements PoolStrategy {
+
+ /**
+ * A type locator that parses the code segment of each method for extracting information about parameter
+ * names even if they are not explicitly included in a class file.
+ *
+ * @see net.bytebuddy.pool.TypePool.Default.ReaderMode#EXTENDED
+ */
+ EXTENDED(TypePool.Default.ReaderMode.EXTENDED),
+
+ /**
+ * A type locator that skips the code segment of each method and does therefore not extract information
+ * about parameter names. Parameter names are still included if they are explicitly included in a class file.
+ *
+ * @see net.bytebuddy.pool.TypePool.Default.ReaderMode#FAST
+ */
+ FAST(TypePool.Default.ReaderMode.FAST);
+
+ /**
+ * The reader mode to apply by this type locator.
+ */
+ private final TypePool.Default.ReaderMode readerMode;
+
+ /**
+ * Creates a new type locator.
+ *
+ * @param readerMode The reader mode to apply by this type locator.
+ */
+ Eager(TypePool.Default.ReaderMode readerMode) {
+ this.readerMode = readerMode;
+ }
+
+ @Override
+ public TypePool typePool(ClassFileLocator classFileLocator, ClassLoader classLoader) {
+ return new TypePool.Default(TypePool.CacheProvider.Simple.withObjectType(), classFileLocator, readerMode);
+ }
+ }
+
+ /**
+ * <p>
+ * A type locator that attempts loading a type if it cannot be located by the underlying lazy type pool.
+ * </p>
+ * <p>
+ * The returned type pool uses a {@link net.bytebuddy.pool.TypePool.CacheProvider.Simple} and the
+ * {@link ClassFileLocator} that is provided by the builder's {@link LocationStrategy}. Any types
+ * are loaded via the instrumented type's {@link ClassLoader}.
+ * </p>
+ */
+ enum ClassLoading implements PoolStrategy {
+
+ /**
+ * A type locator that parses the code segment of each method for extracting information about parameter
+ * names even if they are not explicitly included in a class file.
+ *
+ * @see net.bytebuddy.pool.TypePool.Default.ReaderMode#EXTENDED
+ */
+ EXTENDED(TypePool.Default.ReaderMode.EXTENDED),
+
+ /**
+ * A type locator that skips the code segment of each method and does therefore not extract information
+ * about parameter names. Parameter names are still included if they are explicitly included in a class file.
+ *
+ * @see net.bytebuddy.pool.TypePool.Default.ReaderMode#FAST
+ */
+ FAST(TypePool.Default.ReaderMode.FAST);
+
+ /**
+ * The reader mode to apply by this type locator.
+ */
+ private final TypePool.Default.ReaderMode readerMode;
+
+ /**
+ * Creates a new type locator.
+ *
+ * @param readerMode The reader mode to apply by this type locator.
+ */
+ ClassLoading(TypePool.Default.ReaderMode readerMode) {
+ this.readerMode = readerMode;
+ }
+
+ @Override
+ public TypePool typePool(ClassFileLocator classFileLocator, ClassLoader classLoader) {
+ return TypePool.ClassLoading.of(classLoader, new TypePool.Default.WithLazyResolution(TypePool.CacheProvider.Simple.withObjectType(), classFileLocator, readerMode));
+ }
+ }
+
+ /**
+ * <p>
+ * A type locator that uses type pools but allows for the configuration of a custom cache provider by class loader. Note that a
+ * {@link TypePool} can grow in size and that a static reference is kept to this pool by Byte Buddy's registration of a
+ * {@link ClassFileTransformer} what can cause a memory leak if the supplied caches are not cleared on a regular basis. Also note
+ * that a cache provider can be accessed concurrently by multiple {@link ClassLoader}s.
+ * </p>
+ * <p>
+ * All types that are returned by the locator's type pool are resolved lazily.
+ * </p>
+ */
+ @EqualsAndHashCode
+ abstract class WithTypePoolCache implements PoolStrategy {
+
+ /**
+ * The reader mode to use for parsing a class file.
+ */
+ protected final TypePool.Default.ReaderMode readerMode;
+
+ /**
+ * Creates a new type locator that creates {@link TypePool}s but provides a custom {@link net.bytebuddy.pool.TypePool.CacheProvider}.
+ *
+ * @param readerMode The reader mode to use for parsing a class file.
+ */
+ protected WithTypePoolCache(TypePool.Default.ReaderMode readerMode) {
+ this.readerMode = readerMode;
+ }
+
+ @Override
+ public TypePool typePool(ClassFileLocator classFileLocator, ClassLoader classLoader) {
+ return new TypePool.Default.WithLazyResolution(locate(classLoader), classFileLocator, readerMode);
+ }
+
+ /**
+ * Locates a cache provider for a given class loader.
+ *
+ * @param classLoader The class loader for which to locate a cache. This class loader might be {@code null} to represent the bootstrap loader.
+ * @return The cache provider to use.
+ */
+ protected abstract TypePool.CacheProvider locate(ClassLoader classLoader);
+
+ /**
+ * An implementation of a type locator {@link WithTypePoolCache} (note documentation of the linked class) that is based on a
+ * {@link ConcurrentMap}. It is the responsibility of the type locator's user to avoid the type locator from leaking memory.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ public static class Simple extends WithTypePoolCache {
+
+ /**
+ * The concurrent map that is used for storing a cache provider per class loader.
+ */
+ private final ConcurrentMap<? super ClassLoader, TypePool.CacheProvider> cacheProviders;
+
+ /**
+ * Creates a new type locator that caches a cache provider per class loader in a concurrent map. The type
+ * locator uses a fast {@link net.bytebuddy.pool.TypePool.Default.ReaderMode}.
+ *
+ * @param cacheProviders The concurrent map that is used for storing a cache provider per class loader.
+ */
+ public Simple(ConcurrentMap<? super ClassLoader, TypePool.CacheProvider> cacheProviders) {
+ this(TypePool.Default.ReaderMode.FAST, cacheProviders);
+ }
+
+ /**
+ * Creates a new type locator that caches a cache provider per class loader in a concurrent map.
+ *
+ * @param readerMode The reader mode to use for parsing a class file.
+ * @param cacheProviders The concurrent map that is used for storing a cache provider per class loader.
+ */
+ public Simple(TypePool.Default.ReaderMode readerMode, ConcurrentMap<? super ClassLoader, TypePool.CacheProvider> cacheProviders) {
+ super(readerMode);
+ this.cacheProviders = cacheProviders;
+ }
+
+ @Override
+ protected TypePool.CacheProvider locate(ClassLoader classLoader) {
+ classLoader = classLoader == null ? getBootstrapMarkerLoader() : classLoader;
+ TypePool.CacheProvider cacheProvider = cacheProviders.get(classLoader);
+ while (cacheProvider == null) {
+ cacheProvider = TypePool.CacheProvider.Simple.withObjectType();
+ TypePool.CacheProvider previous = cacheProviders.putIfAbsent(classLoader, cacheProvider);
+ if (previous != null) {
+ cacheProvider = previous;
+ }
+ }
+ return cacheProvider;
+ }
+
+ /**
+ * <p>
+ * Returns the class loader to serve as a cache key if a cache provider for the bootstrap class loader is requested.
+ * This class loader is represented by {@code null} in the JVM which is an invalid value for many {@link ConcurrentMap}
+ * implementations.
+ * </p>
+ * <p>
+ * By default, {@link ClassLoader#getSystemClassLoader()} is used as such a key as any resource location for the
+ * bootstrap class loader is performed via the system class loader within Byte Buddy as {@code null} cannot be queried
+ * for resources via method calls such that this does not make a difference.
+ * </p>
+ *
+ * @return A class loader to represent the bootstrap class loader.
+ */
+ protected ClassLoader getBootstrapMarkerLoader() {
+ return ClassLoader.getSystemClassLoader();
+ }
+ }
+ }
+ }
+
+ /**
+ * An initialization strategy which determines the handling of {@link net.bytebuddy.implementation.LoadedTypeInitializer}s
+ * and the loading of auxiliary types. The agent builder does not reuse the {@link TypeResolutionStrategy} as Javaagents cannot access
+ * a loaded class after a transformation such that different initialization strategies become meaningful.
+ */
+ interface InitializationStrategy {
+
+ /**
+ * Creates a new dispatcher for injecting this initialization strategy during a transformation process.
+ *
+ * @return The dispatcher to be used.
+ */
+ Dispatcher dispatcher();
+
+ /**
+ * A dispatcher for changing a class file to adapt a self-initialization strategy.
+ */
+ interface Dispatcher {
+
+ /**
+ * Transforms the instrumented type to implement an appropriate initialization strategy.
+ *
+ * @param builder The builder which should implement the initialization strategy.
+ * @return The given {@code builder} with the initialization strategy applied.
+ */
+ DynamicType.Builder<?> apply(DynamicType.Builder<?> builder);
+
+ /**
+ * Registers a dynamic type for initialization and/or begins the initialization process.
+ *
+ * @param dynamicType The dynamic type that is created.
+ * @param classLoader The class loader of the dynamic type.
+ * @param injectorFactory The injector factory
+ */
+ void register(DynamicType dynamicType, ClassLoader classLoader, InjectorFactory injectorFactory);
+
+ /**
+ * A factory for creating a {@link ClassInjector} only if it is required.
+ */
+ interface InjectorFactory {
+
+ /**
+ * Resolves the class injector for this factory.
+ *
+ * @return The class injector for this factory.
+ */
+ ClassInjector resolve();
+ }
+ }
+
+ /**
+ * A non-initializing initialization strategy.
+ */
+ enum NoOp implements InitializationStrategy, Dispatcher {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Dispatcher dispatcher() {
+ return this;
+ }
+
+ @Override
+ public DynamicType.Builder<?> apply(DynamicType.Builder<?> builder) {
+ return builder;
+ }
+
+ @Override
+ public void register(DynamicType dynamicType, ClassLoader classLoader, InjectorFactory injectorFactory) {
+ /* do nothing */
+ }
+ }
+
+ /**
+ * An initialization strategy that loads auxiliary types before loading the instrumented type. This strategy skips all types
+ * that are a subtype of the instrumented type which would cause a premature loading of the instrumented type and abort
+ * the instrumentation process.
+ */
+ enum Minimal implements InitializationStrategy, Dispatcher {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Dispatcher dispatcher() {
+ return this;
+ }
+
+ @Override
+ public DynamicType.Builder<?> apply(DynamicType.Builder<?> builder) {
+ return builder;
+ }
+
+ @Override
+ public void register(DynamicType dynamicType, ClassLoader classLoader, InjectorFactory injectorFactory) {
+ Map<TypeDescription, byte[]> auxiliaryTypes = dynamicType.getAuxiliaryTypes();
+ Map<TypeDescription, byte[]> independentTypes = new LinkedHashMap<TypeDescription, byte[]>(auxiliaryTypes);
+ for (TypeDescription auxiliaryType : auxiliaryTypes.keySet()) {
+ if (!auxiliaryType.getDeclaredAnnotations().isAnnotationPresent(AuxiliaryType.SignatureRelevant.class)) {
+ independentTypes.remove(auxiliaryType);
+ }
+ }
+ if (!independentTypes.isEmpty()) {
+ ClassInjector classInjector = injectorFactory.resolve();
+ Map<TypeDescription, LoadedTypeInitializer> loadedTypeInitializers = dynamicType.getLoadedTypeInitializers();
+ for (Map.Entry<TypeDescription, Class<?>> entry : classInjector.inject(independentTypes).entrySet()) {
+ loadedTypeInitializers.get(entry.getKey()).onLoad(entry.getValue());
+ }
+ }
+ }
+ }
+
+ /**
+ * An initialization strategy that adds a code block to an instrumented type's type initializer which
+ * then calls a specific class that is responsible for the explicit initialization.
+ */
+ @EqualsAndHashCode
+ abstract class SelfInjection implements InitializationStrategy {
+
+ /**
+ * The nexus accessor to use.
+ */
+ protected final NexusAccessor nexusAccessor;
+
+ /**
+ * Creates a new self-injection strategy.
+ *
+ * @param nexusAccessor The nexus accessor to use.
+ */
+ protected SelfInjection(NexusAccessor nexusAccessor) {
+ this.nexusAccessor = nexusAccessor;
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "DMI_RANDOM_USED_ONLY_ONCE", justification = "Avoiding synchronization without security concerns")
+ public InitializationStrategy.Dispatcher dispatcher() {
+ return dispatcher(new Random().nextInt());
+ }
+
+ /**
+ * Creates a new dispatcher.
+ *
+ * @param identification The identification code to use.
+ * @return An appropriate dispatcher for an initialization strategy.
+ */
+ protected abstract InitializationStrategy.Dispatcher dispatcher(int identification);
+
+ /**
+ * A dispatcher for a self-initialization strategy.
+ */
+ @EqualsAndHashCode
+ protected abstract static class Dispatcher implements InitializationStrategy.Dispatcher {
+
+ /**
+ * The nexus accessor to use.
+ */
+ protected final NexusAccessor nexusAccessor;
+
+ /**
+ * A random identification for the applied self-initialization.
+ */
+ protected final int identification;
+
+ /**
+ * Creates a new dispatcher.
+ *
+ * @param nexusAccessor The nexus accessor to use.
+ * @param identification A random identification for the applied self-initialization.
+ */
+ protected Dispatcher(NexusAccessor nexusAccessor, int identification) {
+ this.nexusAccessor = nexusAccessor;
+ this.identification = identification;
+ }
+
+ @Override
+ public DynamicType.Builder<?> apply(DynamicType.Builder<?> builder) {
+ return builder.initializer(new NexusAccessor.InitializationAppender(identification));
+ }
+
+ /**
+ * A type initializer that injects all auxiliary types of the instrumented type.
+ */
+ @EqualsAndHashCode
+ protected static class InjectingInitializer implements LoadedTypeInitializer {
+
+ /**
+ * The instrumented type.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * The auxiliary types mapped to their class file representation.
+ */
+ private final Map<TypeDescription, byte[]> rawAuxiliaryTypes;
+
+ /**
+ * The instrumented types and auxiliary types mapped to their loaded type initializers.
+ * The instrumented types and auxiliary types mapped to their loaded type initializers.
+ */
+ private final Map<TypeDescription, LoadedTypeInitializer> loadedTypeInitializers;
+
+ /**
+ * The class injector to use.
+ */
+ private final ClassInjector classInjector;
+
+ /**
+ * Creates a new injection initializer.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param rawAuxiliaryTypes The auxiliary types mapped to their class file representation.
+ * @param loadedTypeInitializers The instrumented types and auxiliary types mapped to their loaded type initializers.
+ * @param classInjector The class injector to use.
+ */
+ protected InjectingInitializer(TypeDescription instrumentedType,
+ Map<TypeDescription, byte[]> rawAuxiliaryTypes,
+ Map<TypeDescription, LoadedTypeInitializer> loadedTypeInitializers,
+ ClassInjector classInjector) {
+ this.instrumentedType = instrumentedType;
+ this.rawAuxiliaryTypes = rawAuxiliaryTypes;
+ this.loadedTypeInitializers = loadedTypeInitializers;
+ this.classInjector = classInjector;
+ }
+
+ @Override
+ public void onLoad(Class<?> type) {
+ for (Map.Entry<TypeDescription, Class<?>> auxiliary : classInjector.inject(rawAuxiliaryTypes).entrySet()) {
+ loadedTypeInitializers.get(auxiliary.getKey()).onLoad(auxiliary.getValue());
+ }
+ loadedTypeInitializers.get(instrumentedType).onLoad(type);
+ }
+
+ @Override
+ public boolean isAlive() {
+ return true;
+ }
+ }
+ }
+
+ /**
+ * A form of self-injection where auxiliary types that are annotated by
+ * {@link net.bytebuddy.implementation.auxiliary.AuxiliaryType.SignatureRelevant} of the instrumented type are loaded lazily and
+ * any other auxiliary type is loaded eagerly.
+ */
+ public static class Split extends SelfInjection {
+
+ /**
+ * Creates a new split self-injection strategy that uses a default nexus accessor.
+ */
+ public Split() {
+ this(new NexusAccessor());
+ }
+
+ /**
+ * Creates a new split self-injection strategy that uses the supplied nexus accessor.
+ *
+ * @param nexusAccessor The nexus accessor to use.
+ */
+ public Split(NexusAccessor nexusAccessor) {
+ super(nexusAccessor);
+ }
+
+ @Override
+ protected InitializationStrategy.Dispatcher dispatcher(int identification) {
+ return new Dispatcher(nexusAccessor, identification);
+ }
+
+ /**
+ * A dispatcher for the {@link net.bytebuddy.agent.builder.AgentBuilder.InitializationStrategy.SelfInjection.Split} strategy.
+ */
+ protected static class Dispatcher extends SelfInjection.Dispatcher {
+
+ /**
+ * Creates a new split dispatcher.
+ *
+ * @param nexusAccessor The nexus accessor to use.
+ * @param identification A random identification for the applied self-initialization.
+ */
+ protected Dispatcher(NexusAccessor nexusAccessor, int identification) {
+ super(nexusAccessor, identification);
+ }
+
+ @Override
+ public void register(DynamicType dynamicType, ClassLoader classLoader, InitializationStrategy.Dispatcher.InjectorFactory injectorFactory) {
+ Map<TypeDescription, byte[]> auxiliaryTypes = dynamicType.getAuxiliaryTypes();
+ LoadedTypeInitializer loadedTypeInitializer;
+ if (!auxiliaryTypes.isEmpty()) {
+ TypeDescription instrumentedType = dynamicType.getTypeDescription();
+ ClassInjector classInjector = injectorFactory.resolve();
+ Map<TypeDescription, byte[]> independentTypes = new LinkedHashMap<TypeDescription, byte[]>(auxiliaryTypes);
+ Map<TypeDescription, byte[]> dependentTypes = new LinkedHashMap<TypeDescription, byte[]>(auxiliaryTypes);
+ for (TypeDescription auxiliaryType : auxiliaryTypes.keySet()) {
+ (auxiliaryType.getDeclaredAnnotations().isAnnotationPresent(AuxiliaryType.SignatureRelevant.class)
+ ? dependentTypes
+ : independentTypes).remove(auxiliaryType);
+ }
+ Map<TypeDescription, LoadedTypeInitializer> loadedTypeInitializers = dynamicType.getLoadedTypeInitializers();
+ if (!independentTypes.isEmpty()) {
+ for (Map.Entry<TypeDescription, Class<?>> entry : classInjector.inject(independentTypes).entrySet()) {
+ loadedTypeInitializers.get(entry.getKey()).onLoad(entry.getValue());
+ }
+ }
+ Map<TypeDescription, LoadedTypeInitializer> lazyInitializers = new HashMap<TypeDescription, LoadedTypeInitializer>(loadedTypeInitializers);
+ loadedTypeInitializers.keySet().removeAll(independentTypes.keySet());
+ loadedTypeInitializer = lazyInitializers.size() > 1 // there exist auxiliary types that need lazy loading
+ ? new Dispatcher.InjectingInitializer(instrumentedType, dependentTypes, lazyInitializers, classInjector)
+ : lazyInitializers.get(instrumentedType);
+ } else {
+ loadedTypeInitializer = dynamicType.getLoadedTypeInitializers().get(dynamicType.getTypeDescription());
+ }
+ nexusAccessor.register(dynamicType.getTypeDescription().getName(), classLoader, identification, loadedTypeInitializer);
+ }
+ }
+ }
+
+ /**
+ * A form of self-injection where any auxiliary type is loaded lazily.
+ */
+ public static class Lazy extends SelfInjection {
+
+ /**
+ * Creates a new lazy self-injection strategy that uses a default nexus accessor.
+ */
+ public Lazy() {
+ this(new NexusAccessor());
+ }
+
+ /**
+ * Creates a new lazy self-injection strategy that uses the supplied nexus accessor.
+ *
+ * @param nexusAccessor The nexus accessor to use.
+ */
+ public Lazy(NexusAccessor nexusAccessor) {
+ super(nexusAccessor);
+ }
+
+ @Override
+ protected InitializationStrategy.Dispatcher dispatcher(int identification) {
+ return new Dispatcher(nexusAccessor, identification);
+ }
+
+ /**
+ * A dispatcher for the {@link net.bytebuddy.agent.builder.AgentBuilder.InitializationStrategy.SelfInjection.Lazy} strategy.
+ */
+ protected static class Dispatcher extends SelfInjection.Dispatcher {
+
+ /**
+ * Creates a new lazy dispatcher.
+ *
+ * @param nexusAccessor The nexus accessor to use.
+ * @param identification A random identification for the applied self-initialization.
+ */
+ protected Dispatcher(NexusAccessor nexusAccessor, int identification) {
+ super(nexusAccessor, identification);
+ }
+
+ @Override
+ public void register(DynamicType dynamicType, ClassLoader classLoader, InitializationStrategy.Dispatcher.InjectorFactory injectorFactory) {
+ Map<TypeDescription, byte[]> auxiliaryTypes = dynamicType.getAuxiliaryTypes();
+ LoadedTypeInitializer loadedTypeInitializer = auxiliaryTypes.isEmpty()
+ ? dynamicType.getLoadedTypeInitializers().get(dynamicType.getTypeDescription())
+ : new Dispatcher.InjectingInitializer(dynamicType.getTypeDescription(), auxiliaryTypes, dynamicType.getLoadedTypeInitializers(), injectorFactory.resolve());
+ nexusAccessor.register(dynamicType.getTypeDescription().getName(), classLoader, identification, loadedTypeInitializer);
+ }
+ }
+ }
+
+ /**
+ * A form of self-injection where any auxiliary type is loaded eagerly.
+ */
+ public static class Eager extends SelfInjection {
+
+ /**
+ * Creates a new eager self-injection strategy that uses a default nexus accesor.
+ */
+ public Eager() {
+ this(new NexusAccessor());
+ }
+
+ /**
+ * Creates a new eager self-injection strategy that uses the supplied nexus accessor.
+ *
+ * @param nexusAccessor The nexus accessor to use.
+ */
+ public Eager(NexusAccessor nexusAccessor) {
+ super(nexusAccessor);
+ }
+
+ @Override
+ protected InitializationStrategy.Dispatcher dispatcher(int identification) {
+ return new Dispatcher(nexusAccessor, identification);
+ }
+
+ /**
+ * A dispatcher for the {@link net.bytebuddy.agent.builder.AgentBuilder.InitializationStrategy.SelfInjection.Eager} strategy.
+ */
+ protected static class Dispatcher extends SelfInjection.Dispatcher {
+
+ /**
+ * Creates a new eager dispatcher.
+ *
+ * @param nexusAccessor The nexus accessor to use.
+ * @param identification A random identification for the applied self-initialization.
+ */
+ protected Dispatcher(NexusAccessor nexusAccessor, int identification) {
+ super(nexusAccessor, identification);
+ }
+
+ @Override
+ public void register(DynamicType dynamicType, ClassLoader classLoader, InitializationStrategy.Dispatcher.InjectorFactory injectorFactory) {
+ Map<TypeDescription, byte[]> auxiliaryTypes = dynamicType.getAuxiliaryTypes();
+ Map<TypeDescription, LoadedTypeInitializer> loadedTypeInitializers = dynamicType.getLoadedTypeInitializers();
+ if (!auxiliaryTypes.isEmpty()) {
+ for (Map.Entry<TypeDescription, Class<?>> entry : injectorFactory.resolve().inject(auxiliaryTypes).entrySet()) {
+ loadedTypeInitializers.get(entry.getKey()).onLoad(entry.getValue());
+ }
+ }
+ LoadedTypeInitializer loadedTypeInitializer = loadedTypeInitializers.get(dynamicType.getTypeDescription());
+ nexusAccessor.register(dynamicType.getTypeDescription().getName(), classLoader, identification, loadedTypeInitializer);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * A description strategy is responsible for resolving a {@link TypeDescription} when transforming or retransforming/-defining a type.
+ */
+ interface DescriptionStrategy {
+
+ /**
+ * Indicates if this description strategy makes use of loaded type information and yields a different type description if no loaded type is available.
+ *
+ * @return {@code true} if this description strategy prefers loaded type information when describing a type and only uses a type pool
+ * if loaded type information is not available.
+ */
+ boolean isLoadedFirst();
+
+ /**
+ * Describes the given type.
+ *
+ * @param typeName The binary name of the type to describe.
+ * @param type The type that is being redefined, if a redefinition is applied or {@code null} if no redefined type is available.
+ * @param typePool The type pool to use for locating a type if required.
+ * @param classLoader The type's class loader where {@code null} represents the bootstrap class loader.
+ * @param circularityLock The currently used circularity lock.
+ * @param module The type's module or {@code null} if the current VM does not support modules.
+ * @return An appropriate type description.
+ */
+ TypeDescription apply(String typeName, Class<?> type, TypePool typePool, CircularityLock circularityLock, ClassLoader classLoader, JavaModule module);
+
+ /**
+ * Default implementations of a {@link DescriptionStrategy}.
+ */
+ enum Default implements DescriptionStrategy {
+
+ /**
+ * A description type strategy represents a type as a {@link net.bytebuddy.description.type.TypeDescription.ForLoadedType} if a
+ * retransformation or redefinition is applied on a type. Using a loaded type typically results in better performance as no
+ * I/O is required for resolving type descriptions. However, any interaction with the type is carried out via the Java reflection
+ * API. Using the reflection API triggers eager loading of any type that is part of a method or field signature. If any of these
+ * types are missing from the class path, this eager loading will cause a {@link NoClassDefFoundError}. Some Java code declares
+ * optional dependencies to other classes which are only realized if the optional dependency is present. Such code relies on the
+ * Java reflection API not being used for types using optional dependencies.
+ *
+ * @see FallbackStrategy.Simple#ENABLED
+ * @see FallbackStrategy.ByThrowableType#ofOptionalTypes()
+ */
+ HYBRID(true) {
+ @Override
+ public TypeDescription apply(String typeName,
+ Class<?> type,
+ TypePool typePool,
+ CircularityLock circularityLock,
+ ClassLoader classLoader,
+ JavaModule module) {
+ return type == null
+ ? typePool.describe(typeName).resolve()
+ : new TypeDescription.ForLoadedType(type);
+ }
+ },
+
+ /**
+ * <p>
+ * A description strategy that always describes Java types using a {@link TypePool}. This requires that any type - even if it is already
+ * loaded and a {@link Class} instance is available - is processed as a non-loaded type description. Doing so can cause overhead as processing
+ * loaded types is supported very efficiently by a JVM.
+ * </p>
+ * <p>
+ * Avoiding the usage of loaded types can improve robustness as this approach does not rely on the Java reflection API which triggers eager
+ * validation of this loaded type which can fail an application if optional types are used by any types field or method signatures. Also, it
+ * is possible to guarantee debugging meta data to be available also for retransformed or redefined types if a {@link TypeStrategy} specifies
+ * the extraction of such meta data.
+ * </p>
+ */
+ POOL_ONLY(false) {
+ @Override
+ public TypeDescription apply(String typeName,
+ Class<?> type,
+ TypePool typePool,
+ CircularityLock circularityLock,
+ ClassLoader classLoader,
+ JavaModule module) {
+ return typePool.describe(typeName).resolve();
+ }
+ },
+
+ /**
+ * <p>
+ * A description strategy that always describes Java types using a {@link TypePool} unless a type cannot be resolved by a pool and a loaded
+ * {@link Class} instance is available. Doing so can cause overhead as processing loaded types is supported very efficiently by a JVM.
+ * </p>
+ * <p>
+ * Avoiding the usage of loaded types can improve robustness as this approach does not rely on the Java reflection API which triggers eager
+ * validation of this loaded type which can fail an application if optional types are used by any types field or method signatures. Also, it
+ * is possible to guarantee debugging meta data to be available also for retransformed or redefined types if a {@link TypeStrategy} specifies
+ * the extraction of such meta data.
+ * </p>
+ */
+ POOL_FIRST(false) {
+ @Override
+ public TypeDescription apply(String typeName,
+ Class<?> type,
+ TypePool typePool,
+ CircularityLock circularityLock,
+ ClassLoader classLoader,
+ JavaModule module) {
+ TypePool.Resolution resolution = typePool.describe(typeName);
+ return resolution.isResolved() || type == null
+ ? resolution.resolve()
+ : new TypeDescription.ForLoadedType(type);
+ }
+ };
+
+ /**
+ * Indicates if loaded type information is preferred over using a type pool for describing a type.
+ */
+ private final boolean loadedFirst;
+
+ /**
+ * Indicates if loaded type information is preferred over using a type pool for describing a type.
+ *
+ * @param loadedFirst {@code true} if loaded type information is preferred over using a type pool for describing a type.
+ */
+ Default(boolean loadedFirst) {
+ this.loadedFirst = loadedFirst;
+ }
+
+ /**
+ * Creates a description strategy that uses this strategy but loads any super type. If a super type is not yet loaded,
+ * this causes this super type to never be instrumented. Therefore, this option should only be used if all instrumented
+ * types are guaranteed to be top-level types.
+ *
+ * @return This description strategy where all super types are loaded during the instrumentation.
+ * @see SuperTypeLoading
+ */
+ public DescriptionStrategy withSuperTypeLoading() {
+ return new SuperTypeLoading(this);
+ }
+
+ @Override
+ public boolean isLoadedFirst() {
+ return loadedFirst;
+ }
+
+ /**
+ * Creates a description strategy that uses this strategy but loads any super type asynchronously. Super types are loaded via
+ * another thread supplied by the executor service to enforce the instrumentation of any such super type. It is recommended
+ * to allow the executor service to create new threads without bound as class loading blocks any thread until all super types
+ * were instrumented.
+ *
+ * @param executorService The executor service to use.
+ * @return This description strategy where all super types are loaded asynchronously during the instrumentation.
+ * @see SuperTypeLoading.Asynchronous
+ */
+ public DescriptionStrategy withSuperTypeLoading(ExecutorService executorService) {
+ return new SuperTypeLoading.Asynchronous(this, executorService);
+ }
+ }
+
+ /**
+ * <p>
+ * A description strategy that enforces the loading of any super type of a type description but delegates the actual type description
+ * to another description strategy.
+ * </p>
+ * <p>
+ * <b>Warning</b>: When using this description strategy, a type is not instrumented if any of its subtypes is loaded first.
+ * The instrumentation API does not submit such types to a class file transformer on most VM implementations.
+ * </p>
+ */
+ @EqualsAndHashCode
+ class SuperTypeLoading implements DescriptionStrategy {
+
+ /**
+ * The delegate description strategy.
+ */
+ private final DescriptionStrategy delegate;
+
+ /**
+ * Creates a new description strategy that enforces loading of a super type.
+ *
+ * @param delegate The delegate description strategy.
+ */
+ public SuperTypeLoading(DescriptionStrategy delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public boolean isLoadedFirst() {
+ return delegate.isLoadedFirst();
+ }
+
+ @Override
+ public TypeDescription apply(String typeName,
+ Class<?> type,
+ TypePool typePool,
+ CircularityLock circularityLock,
+ ClassLoader classLoader,
+ JavaModule module) {
+ TypeDescription typeDescription = delegate.apply(typeName, type, typePool, circularityLock, classLoader, module);
+ return typeDescription instanceof TypeDescription.ForLoadedType
+ ? typeDescription
+ : new TypeDescription.SuperTypeLoading(typeDescription, classLoader, new UnlockingClassLoadingDelegate(circularityLock));
+ }
+
+ /**
+ * A class loading delegate that unlocks the circularity lock during class loading.
+ */
+ @EqualsAndHashCode
+ protected static class UnlockingClassLoadingDelegate implements TypeDescription.SuperTypeLoading.ClassLoadingDelegate {
+
+ /**
+ * The circularity lock to unlock.
+ */
+ private final CircularityLock circularityLock;
+
+ /**
+ * Creates an unlocking class loading delegate.
+ *
+ * @param circularityLock The circularity lock to unlock.
+ */
+ protected UnlockingClassLoadingDelegate(CircularityLock circularityLock) {
+ this.circularityLock = circularityLock;
+ }
+
+ @Override
+ public Class<?> load(String name, ClassLoader classLoader) throws ClassNotFoundException {
+ circularityLock.release();
+ try {
+ return Class.forName(name, false, classLoader);
+ } finally {
+ circularityLock.acquire();
+ }
+ }
+ }
+
+ /**
+ * <p>
+ * A description strategy that enforces the loading of any super type of a type description but delegates the actual type description
+ * to another description strategy.
+ * </p>
+ * <p>
+ * <b>Note</b>: This description strategy delegates class loading to another thread in order to enforce the instrumentation of any
+ * unloaded super type. This requires the executor service to supply at least as many threads as the deepest type hierarchy within the
+ * application minus one for the instrumented type as class loading blocks any thread until all of its super types are loaded. These
+ * threads are typically short lived which predestines the use of a {@link Executors#newCachedThreadPool()} without any upper bound
+ * for the maximum number of created threads.
+ * </p>
+ * <p>
+ * <b>Important</b>: This strategy can dead-lock under two circumstances:
+ * </p>
+ * <ul>
+ * <li>
+ * <b>Classes declare circularities</b>: Under normal circumstances, such scenarios result in a {@link ClassCircularityError} but
+ * can result in dead-locks when using this instrumentation strategy.
+ * </li>
+ * <li>
+ * <b>Class loaders declare custom locks</b>: If a class loader locks another lock but itself during class loading, this lock cannot
+ * be released by this strategy.
+ * </li>
+ * </ul>
+ * <p>
+ * For the above reasons, it is not recommended to use this strategy when the target class loader is unknown or if the target application
+ * might contain corrupt class files.
+ * </p>
+ */
+ @EqualsAndHashCode
+ public static class Asynchronous implements DescriptionStrategy {
+
+ /**
+ * The delegate description strategy.
+ */
+ private final DescriptionStrategy delegate;
+
+ /**
+ * The executor service to use for loading super types.
+ */
+ private final ExecutorService executorService;
+
+ /**
+ * Creates a new description strategy that enforces super type loading from another thread.
+ *
+ * @param delegate The delegate description strategy.
+ * @param executorService The executor service to use for loading super types.
+ */
+ public Asynchronous(DescriptionStrategy delegate, ExecutorService executorService) {
+ this.delegate = delegate;
+ this.executorService = executorService;
+ }
+
+ @Override
+ public boolean isLoadedFirst() {
+ return delegate.isLoadedFirst();
+ }
+
+ @Override
+ public TypeDescription apply(String typeName,
+ Class<?> type,
+ TypePool typePool,
+ CircularityLock circularityLock,
+ ClassLoader classLoader,
+ JavaModule module) {
+ TypeDescription typeDescription = delegate.apply(typeName, type, typePool, circularityLock, classLoader, module);
+ return typeDescription instanceof TypeDescription.ForLoadedType
+ ? typeDescription
+ : new TypeDescription.SuperTypeLoading(typeDescription, classLoader, new ThreadSwitchingClassLoadingDelegate(executorService));
+ }
+
+ /**
+ * A class loading delegate that delegates loading of the super type to another thread.
+ */
+ @EqualsAndHashCode
+ protected static class ThreadSwitchingClassLoadingDelegate implements TypeDescription.SuperTypeLoading.ClassLoadingDelegate {
+
+ /**
+ * The executor service to delegate class loading to.
+ */
+ private final ExecutorService executorService;
+
+ /**
+ * Creates a new thread-switching class loading delegate.
+ *
+ * @param executorService The executor service to delegate class loading to.
+ */
+ protected ThreadSwitchingClassLoadingDelegate(ExecutorService executorService) {
+ this.executorService = executorService;
+ }
+
+ @Override
+ public Class<?> load(String name, ClassLoader classLoader) throws ClassNotFoundException {
+ boolean holdsLock = classLoader != null && Thread.holdsLock(classLoader);
+ AtomicBoolean signal = new AtomicBoolean(holdsLock);
+ Future<Class<?>> future = executorService.submit(holdsLock
+ ? new NotifyingClassLoadingAction(name, classLoader, signal)
+ : new SimpleClassLoadingAction(name, classLoader));
+ try {
+ while (holdsLock && signal.get()) {
+ classLoader.wait();
+ }
+ return future.get();
+ } catch (ExecutionException exception) {
+ throw new IllegalStateException("Could not load " + name + " asynchronously", exception.getCause());
+ } catch (Exception exception) {
+ throw new IllegalStateException("Could not load " + name + " asynchronously", exception);
+ }
+ }
+
+ /**
+ * A class loading action that simply loads a type.
+ */
+ @EqualsAndHashCode
+ protected static class SimpleClassLoadingAction implements Callable<Class<?>> {
+
+ /**
+ * The loaded type's name.
+ */
+ private final String name;
+
+ /**
+ * The type's class loader or {@code null} if the type is loaded by the bootstrap loader.
+ */
+ private final ClassLoader classLoader;
+
+ /**
+ * Creates a simple class loading action.
+ *
+ * @param name The loaded type's name.
+ * @param classLoader The type's class loader or {@code null} if the type is loaded by the bootstrap loader.
+ */
+ protected SimpleClassLoadingAction(String name, ClassLoader classLoader) {
+ this.name = name;
+ this.classLoader = classLoader;
+ }
+
+ @Override
+ public Class<?> call() throws ClassNotFoundException {
+ return Class.forName(name, false, classLoader);
+ }
+ }
+
+ /**
+ * A class loading action that notifies the class loader's lock after the type was loaded.
+ */
+ protected static class NotifyingClassLoadingAction implements Callable<Class<?>> {
+
+ /**
+ * The loaded type's name.
+ */
+ private final String name;
+
+ /**
+ * The type's class loader or {@code null} if the type is loaded by the bootstrap loader.
+ */
+ private final ClassLoader classLoader;
+
+ /**
+ * The signal that indicates the completion of the class loading with {@code false}.
+ */
+ private final AtomicBoolean signal;
+
+ /**
+ * Creates a notifying class loading action.
+ *
+ * @param name The loaded type's name.
+ * @param classLoader The type's class loader or {@code null} if the type is loaded by the bootstrap loader.
+ * @param signal The signal that indicates the completion of the class loading with {@code false}.
+ */
+ protected NotifyingClassLoadingAction(String name, ClassLoader classLoader, AtomicBoolean signal) {
+ this.name = name;
+ this.classLoader = classLoader;
+ this.signal = signal;
+ }
+
+ @Override
+ public Class<?> call() throws ClassNotFoundException {
+ synchronized (classLoader) {
+ try {
+ return Class.forName(name, false, classLoader);
+ } finally {
+ signal.set(false);
+ classLoader.notifyAll();
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * A strategy for creating a {@link ClassFileLocator} when instrumenting a type.
+ */
+ interface LocationStrategy {
+
+ /**
+ * Creates a class file locator for a given class loader and module combination.
+ *
+ * @param classLoader The class loader that is loading an instrumented type. Might be {@code null} to represent the bootstrap class loader.
+ * @param module The type's module or {@code null} if Java modules are not supported on the current VM.
+ * @return The class file locator to use.
+ */
+ ClassFileLocator classFileLocator(ClassLoader classLoader, JavaModule module);
+
+ /**
+ * A location strategy that never locates any byte code.
+ */
+ enum NoOp implements LocationStrategy {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public ClassFileLocator classFileLocator(ClassLoader classLoader, JavaModule module) {
+ return ClassFileLocator.NoOp.INSTANCE;
+ }
+ }
+
+ /**
+ * A location strategy that locates class files by querying an instrumented type's {@link ClassLoader}.
+ */
+ enum ForClassLoader implements LocationStrategy {
+
+ /**
+ * A location strategy that keeps a strong reference to the class loader the created class file locator represents.
+ */
+ STRONG {
+ @Override
+ public ClassFileLocator classFileLocator(ClassLoader classLoader, JavaModule module) {
+ return ClassFileLocator.ForClassLoader.of(classLoader);
+ }
+ },
+
+ /**
+ * A location strategy that keeps a weak reference to the class loader the created class file locator represents.
+ * As a consequence, any returned class file locator stops working once the represented class loader is garbage collected.
+ */
+ WEAK {
+ @Override
+ public ClassFileLocator classFileLocator(ClassLoader classLoader, JavaModule module) {
+ return ClassFileLocator.ForClassLoader.WeaklyReferenced.of(classLoader);
+ }
+ };
+
+ /**
+ * Adds additional location strategies as fallbacks to this location strategy.
+ *
+ * @param classFileLocator The class file locators to query if this location strategy cannot locate a class file.
+ * @return A compound location strategy that first applies this location strategy and then queries the supplied class file locators.
+ */
+ public LocationStrategy withFallbackTo(ClassFileLocator... classFileLocator) {
+ return withFallbackTo(Arrays.asList(classFileLocator));
+ }
+
+ /**
+ * Adds additional location strategies as fallbacks to this location strategy.
+ *
+ * @param classFileLocators The class file locators to query if this location strategy cannot locate a class file.
+ * @return A compound location strategy that first applies this location strategy and then queries the supplied class file locators.
+ */
+ public LocationStrategy withFallbackTo(Collection<? extends ClassFileLocator> classFileLocators) {
+ List<LocationStrategy> locationStrategies = new ArrayList<LocationStrategy>(classFileLocators.size());
+ for (ClassFileLocator classFileLocator : classFileLocators) {
+ locationStrategies.add(new Simple(classFileLocator));
+ }
+ return withFallbackTo(locationStrategies);
+ }
+
+ /**
+ * Adds additional location strategies as fallbacks to this location strategy.
+ *
+ * @param locationStrategy The fallback location strategies to use.
+ * @return A compound location strategy that first applies this location strategy and then the supplied fallback location strategies
+ * in the supplied order.
+ */
+ public LocationStrategy withFallbackTo(LocationStrategy... locationStrategy) {
+ return withFallbackTo(Arrays.asList(locationStrategy));
+ }
+
+ /**
+ * Adds additional location strategies as fallbacks to this location strategy.
+ *
+ * @param locationStrategies The fallback location strategies to use.
+ * @return A compound location strategy that first applies this location strategy and then the supplied fallback location strategies
+ * in the supplied order.
+ */
+ public LocationStrategy withFallbackTo(List<? extends LocationStrategy> locationStrategies) {
+ List<LocationStrategy> allLocationStrategies = new ArrayList<LocationStrategy>(locationStrategies.size() + 1);
+ allLocationStrategies.add(this);
+ allLocationStrategies.addAll(locationStrategies);
+ return new Compound(allLocationStrategies);
+ }
+ }
+
+ /**
+ * A simple location strategy that queries a given class file locator.
+ */
+ @EqualsAndHashCode
+ class Simple implements LocationStrategy {
+
+ /**
+ * The class file locator to query.
+ */
+ private final ClassFileLocator classFileLocator;
+
+ /**
+ * A simple location strategy that queries a given class file locator.
+ *
+ * @param classFileLocator The class file locator to query.
+ */
+ public Simple(ClassFileLocator classFileLocator) {
+ this.classFileLocator = classFileLocator;
+ }
+
+ @Override
+ public ClassFileLocator classFileLocator(ClassLoader classLoader, JavaModule module) {
+ return classFileLocator;
+ }
+ }
+
+ /**
+ * A compound location strategy that applies a list of location strategies.
+ */
+ @EqualsAndHashCode
+ class Compound implements LocationStrategy {
+
+ /**
+ * The location strategies in their application order.
+ */
+ private final List<LocationStrategy> locationStrategies;
+
+ /**
+ * Creates a new compound location strategy.
+ *
+ * @param locationStrategy The location strategies in their application order.
+ */
+ public Compound(LocationStrategy... locationStrategy) {
+ this(Arrays.asList(locationStrategy));
+ }
+
+ /**
+ * Creates a new compound location strategy.
+ *
+ * @param locationStrategies The location strategies in their application order.
+ */
+ public Compound(List<? extends LocationStrategy> locationStrategies) {
+ this.locationStrategies = new ArrayList<LocationStrategy>();
+ for (LocationStrategy locationStrategy : locationStrategies) {
+ if (locationStrategy instanceof Compound) {
+ this.locationStrategies.addAll(((Compound) locationStrategy).locationStrategies);
+ } else if (!(locationStrategy instanceof NoOp)) {
+ this.locationStrategies.add(locationStrategy);
+ }
+ }
+ }
+
+ @Override
+ public ClassFileLocator classFileLocator(ClassLoader classLoader, JavaModule module) {
+ List<ClassFileLocator> classFileLocators = new ArrayList<ClassFileLocator>(locationStrategies.size());
+ for (LocationStrategy locationStrategy : locationStrategies) {
+ classFileLocators.add(locationStrategy.classFileLocator(classLoader, module));
+ }
+ return new ClassFileLocator.Compound(classFileLocators);
+ }
+ }
+ }
+
+ /**
+ * A fallback strategy allows to reattempt a transformation or a consideration for redefinition/retransformation in case an exception
+ * occurs. Doing so, it is possible to use a {@link TypePool} rather than using a loaded type description backed by a {@link Class}.
+ * Loaded types can raise exceptions and errors if a {@link ClassLoader} cannot resolve all types that this class references. Using
+ * a type pool, such errors can be avoided as type descriptions can be resolved lazily, avoiding such errors.
+ */
+ interface FallbackStrategy {
+
+ /**
+ * Returns {@code true} if the supplied type and throwable combination should result in a reattempt where the
+ * loaded type is not used for querying information.
+ *
+ * @param type The loaded type that was queried during the transformation attempt.
+ * @param throwable The error or exception that was caused during the transformation.
+ * @return {@code true} if the supplied type and throwable combination should
+ */
+ boolean isFallback(Class<?> type, Throwable throwable);
+
+ /**
+ * A simple fallback strategy that either always reattempts a transformation or never does so.
+ */
+ enum Simple implements FallbackStrategy {
+
+ /**
+ * An enabled fallback strategy that always attempts a new trial.
+ */
+ ENABLED(true),
+
+ /**
+ * A disabled fallback strategy that never attempts a new trial.
+ */
+ DISABLED(false);
+
+ /**
+ * {@code true} if this fallback strategy is enabled.
+ */
+ private final boolean enabled;
+
+ /**
+ * Creates a new default fallback strategy.
+ *
+ * @param enabled {@code true} if this fallback strategy is enabled.
+ */
+ Simple(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ @Override
+ public boolean isFallback(Class<?> type, Throwable throwable) {
+ return enabled;
+ }
+ }
+
+ /**
+ * A fallback strategy that discriminates by the type of the {@link Throwable} that triggered a request.
+ */
+ @EqualsAndHashCode
+ class ByThrowableType implements FallbackStrategy {
+
+ /**
+ * A set of throwable types that should trigger a fallback attempt.
+ */
+ private final Set<? extends Class<? extends Throwable>> types;
+
+ /**
+ * Creates a new throwable type-discriminating fallback strategy.
+ *
+ * @param type The throwable types that should trigger a fallback.
+ */
+ @SuppressWarnings("unchecked") // In absence of @SafeVarargs for Java 6
+ public ByThrowableType(Class<? extends Throwable>... type) {
+ this(new HashSet<Class<? extends Throwable>>(Arrays.asList(type)));
+ }
+
+ /**
+ * Creates a new throwable type-discriminating fallback strategy.
+ *
+ * @param types The throwable types that should trigger a fallback.
+ */
+ public ByThrowableType(Set<? extends Class<? extends Throwable>> types) {
+ this.types = types;
+ }
+
+ /**
+ * Creates a fallback strategy that attempts a fallback if an error indicating a type error is the reason for requesting a reattempt.
+ *
+ * @return A fallback strategy that triggers a reattempt if a {@link LinkageError} or a {@link TypeNotPresentException} is raised.
+ */
+ @SuppressWarnings("unchecked") // In absence of @SafeVarargs for Java 6
+ public static FallbackStrategy ofOptionalTypes() {
+ return new ByThrowableType(LinkageError.class, TypeNotPresentException.class);
+ }
+
+ @Override
+ public boolean isFallback(Class<?> type, Throwable throwable) {
+ for (Class<? extends Throwable> aType : types) {
+ if (aType.isInstance(throwable)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+ }
+
+ /**
+ * A listener that is notified during the installation and the resetting of a class file transformer.
+ */
+ interface InstallationListener {
+
+ /**
+ * Indicates that an exception is handled.
+ */
+ Throwable SUPPRESS_ERROR = null;
+
+ /**
+ * Invoked prior to the installation of a class file transformer.
+ *
+ * @param instrumentation The instrumentation on which the class file transformer is installed.
+ * @param classFileTransformer The class file transformer that is being installed.
+ */
+ void onBeforeInstall(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer);
+
+ /**
+ * Invoked upon the successful installation of a class file transformer. This method is only invoked if no error occurred during the
+ * installation or if such an error was handled by {@link InstallationListener#onError(Instrumentation, ResettableClassFileTransformer, Throwable)}.
+ *
+ * @param instrumentation The instrumentation on which the class file transformer is installed.
+ * @param classFileTransformer The class file transformer that is being installed.
+ */
+ void onInstall(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer);
+
+ /**
+ * Invoked if an installation causes an error. The listener has an opportunity to handle the error. This method is invoked prior to
+ * {@link InstallationListener#onInstall(Instrumentation, ResettableClassFileTransformer)}.
+ *
+ * @param instrumentation The instrumentation on which the class file transformer is installed.
+ * @param classFileTransformer The class file transformer that is being installed.
+ * @param throwable The throwable that causes the error.
+ * @return The error to propagate or {@code null} if the error is handled. Any subsequent listeners are not called if the exception is handled.
+ */
+ Throwable onError(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer, Throwable throwable);
+
+ /**
+ * Invoked if an installation is reset.
+ *
+ * @param instrumentation The instrumentation on which the class file transformer is installed.
+ * @param classFileTransformer The class file transformer that is being installed.
+ */
+ void onReset(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer);
+
+ /**
+ * A non-operational listener that does not do anything.
+ */
+ enum NoOp implements InstallationListener {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public void onBeforeInstall(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) {
+ /* do nothing */
+ }
+
+ @Override
+ public void onInstall(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) {
+ /* do nothing */
+ }
+
+ @Override
+ public Throwable onError(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer, Throwable throwable) {
+ return throwable;
+ }
+
+ @Override
+ public void onReset(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) {
+ /* do nothing */
+ }
+ }
+
+ /**
+ * A listener that suppresses any installation error.
+ */
+ enum ErrorSuppressing implements InstallationListener {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public void onBeforeInstall(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) {
+ /* do nothing */
+ }
+
+ @Override
+ public void onInstall(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) {
+ /* do nothing */
+ }
+
+ @Override
+ public Throwable onError(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer, Throwable throwable) {
+ return SUPPRESS_ERROR;
+ }
+
+ @Override
+ public void onReset(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) {
+ /* do nothing */
+ }
+ }
+
+ /**
+ * An adapter implementation for an installation listener that serves as a convenience.
+ */
+ abstract class Adapter implements InstallationListener {
+
+ @Override
+ public void onBeforeInstall(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) {
+ /* do nothing */
+ }
+
+ @Override
+ public void onInstall(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) {
+ /* do nothing */
+ }
+
+ @Override
+ public Throwable onError(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer, Throwable throwable) {
+ return throwable;
+ }
+
+ @Override
+ public void onReset(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) {
+ /* do nothing */
+ }
+ }
+
+ /**
+ * This installation listener prints the status of any installation to a {@link PrintStream}.
+ */
+ @EqualsAndHashCode
+ class StreamWriting implements InstallationListener {
+
+ /**
+ * The prefix prepended to any message written.
+ */
+ protected static final String PREFIX = "[Byte Buddy]";
+
+ /**
+ * The print stream to write to.
+ */
+ private final PrintStream printStream;
+
+ /**
+ * Creates a new stream writing installation listener.
+ *
+ * @param printStream The print stream to write to.
+ */
+ public StreamWriting(PrintStream printStream) {
+ this.printStream = printStream;
+ }
+
+ /**
+ * Creates a stream writing installation listener that prints to {@link System#out}.
+ *
+ * @return An installation listener that prints to {@link System#out}.
+ */
+ public static InstallationListener toSystemOut() {
+ return new StreamWriting(System.out);
+ }
+
+ /**
+ * Creates a stream writing installation listener that prints to {@link System#err}.
+ *
+ * @return An installation listener that prints to {@link System#err}.
+ */
+ public static InstallationListener toSystemErr() {
+ return new StreamWriting(System.err);
+ }
+
+ @Override
+ public void onBeforeInstall(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) {
+ printStream.printf(PREFIX + " BEFORE_INSTALL %s on %s%n", classFileTransformer, instrumentation);
+ }
+
+ @Override
+ public void onInstall(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) {
+ printStream.printf(PREFIX + " INSTALL %s on %s%n", classFileTransformer, instrumentation);
+ }
+
+ @Override
+ public Throwable onError(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer, Throwable throwable) {
+ synchronized (printStream) {
+ printStream.printf(PREFIX + " ERROR %s on %s%n", classFileTransformer, instrumentation);
+ throwable.printStackTrace(printStream);
+ }
+ return throwable;
+ }
+
+ @Override
+ public void onReset(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) {
+ printStream.printf(PREFIX + " RESET %s on %s%n", classFileTransformer, instrumentation);
+ }
+ }
+
+ /**
+ * A compound installation listener.
+ */
+ @EqualsAndHashCode
+ class Compound implements InstallationListener {
+
+ /**
+ * The installation listeners to notify.
+ */
+ private final List<InstallationListener> installationListeners;
+
+ /**
+ * Creates a new compound listener.
+ *
+ * @param installationListener The installation listeners to notify.
+ */
+ public Compound(InstallationListener... installationListener) {
+ this(Arrays.asList(installationListener));
+ }
+
+ /**
+ * Creates a new compound listener.
+ *
+ * @param installationListeners The installation listeners to notify.
+ */
+ public Compound(List<? extends InstallationListener> installationListeners) {
+ this.installationListeners = new ArrayList<InstallationListener>();
+ for (InstallationListener installationListener : installationListeners) {
+ if (installationListener instanceof Compound) {
+ this.installationListeners.addAll(((Compound) installationListener).installationListeners);
+ } else if (!(installationListener instanceof NoOp)) {
+ this.installationListeners.add(installationListener);
+ }
+ }
+ }
+
+ @Override
+ public void onBeforeInstall(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) {
+ for (InstallationListener installationListener : installationListeners) {
+ installationListener.onBeforeInstall(instrumentation, classFileTransformer);
+ }
+ }
+
+ @Override
+ public void onInstall(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) {
+ for (InstallationListener installationListener : installationListeners) {
+ installationListener.onInstall(instrumentation, classFileTransformer);
+ }
+ }
+
+ @Override
+ public Throwable onError(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer, Throwable throwable) {
+ for (InstallationListener installationListener : installationListeners) {
+ if (throwable == SUPPRESS_ERROR) {
+ return SUPPRESS_ERROR;
+ }
+ throwable = installationListener.onError(instrumentation, classFileTransformer, throwable);
+ }
+ return throwable;
+ }
+
+ @Override
+ public void onReset(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) {
+ for (InstallationListener installationListener : installationListeners) {
+ installationListener.onReset(instrumentation, classFileTransformer);
+ }
+ }
+ }
+ }
+
+ /**
+ * <p>
+ * A redefinition strategy regulates how already loaded classes are modified by a built agent.
+ * </p>
+ * <p>
+ * <b>Important</b>: Most JVMs do not support changes of a class's structure after a class was already
+ * loaded. Therefore, it is typically required that this class file transformer was built while enabling
+ * {@link AgentBuilder#disableClassFormatChanges()}.
+ * </p>
+ */
+ enum RedefinitionStrategy {
+
+ /**
+ * Disables redefinition such that already loaded classes are not affected by the agent.
+ */
+ DISABLED(false, false) {
+ @Override
+ public void apply(Instrumentation instrumentation,
+ AgentBuilder.Listener listener,
+ CircularityLock circularityLock,
+ PoolStrategy poolStrategy,
+ LocationStrategy locationStrategy,
+ DiscoveryStrategy discoveryStrategy,
+ BatchAllocator redefinitionBatchAllocator,
+ Listener redefinitionListener,
+ LambdaInstrumentationStrategy lambdaInstrumentationStrategy,
+ DescriptionStrategy descriptionStrategy,
+ FallbackStrategy fallbackStrategy,
+ RawMatcher typeMatcher,
+ RawMatcher ignoredTypeMatcher) {
+ /* do nothing */
+ }
+
+ @Override
+ protected void check(Instrumentation instrumentation) {
+ throw new IllegalStateException("Cannot apply redefinition on disabled strategy");
+ }
+
+ @Override
+ protected Collector make() {
+ throw new IllegalStateException("A disabled redefinition strategy cannot create a collector");
+ }
+ },
+
+ /**
+ * <p>
+ * Applies a <b>redefinition</b> to all classes that are already loaded and that would have been transformed if
+ * the built agent was registered before they were loaded. The created {@link ClassFileTransformer} is <b>not</b>
+ * registered for applying retransformations.
+ * </p>
+ * <p>
+ * Using this strategy, a redefinition is applied as a single transformation request. This means that a single illegal
+ * redefinition of a class causes the entire redefinition attempt to fail.
+ * </p>
+ * <p>
+ * <b>Note</b>: When applying a redefinition, it is normally required to use a {@link TypeStrategy} that applies
+ * a redefinition instead of rebasing classes such as {@link TypeStrategy.Default#REDEFINE}. Also, consider
+ * the constrains given by this type strategy.
+ * </p>
+ */
+ REDEFINITION(true, false) {
+ @Override
+ protected void check(Instrumentation instrumentation) {
+ if (!instrumentation.isRedefineClassesSupported()) {
+ throw new IllegalStateException("Cannot apply redefinition on " + instrumentation);
+ }
+ }
+
+ @Override
+ protected Collector make() {
+ return new Collector.ForRedefinition();
+ }
+ },
+
+ /**
+ * <p>
+ * Applies a <b>retransformation</b> to all classes that are already loaded and that would have been transformed if
+ * the built agent was registered before they were loaded. The created {@link ClassFileTransformer} is registered
+ * for applying retransformations.
+ * </p>
+ * <p>
+ * Using this strategy, a retransformation is applied as a single transformation request. This means that a single illegal
+ * retransformation of a class causes the entire retransformation attempt to fail.
+ * </p>
+ * <p>
+ * <b>Note</b>: When applying a redefinition, it is normally required to use a {@link TypeStrategy} that applies
+ * a redefinition instead of rebasing classes such as {@link TypeStrategy.Default#REDEFINE}. Also, consider
+ * the constrains given by this type strategy.
+ * </p>
+ */
+ RETRANSFORMATION(true, true) {
+ @Override
+ protected void check(Instrumentation instrumentation) {
+ if (!instrumentation.isRetransformClassesSupported()) {
+ throw new IllegalStateException("Cannot apply redefinition on " + instrumentation);
+ }
+ }
+
+ @Override
+ protected Collector make() {
+ return new Collector.ForRetransformation();
+ }
+ };
+
+ /**
+ * Indicates that this redefinition strategy is enabled.
+ */
+ private final boolean enabled;
+
+ /**
+ * {@code true} if this strategy applies retransformation.
+ */
+ private final boolean retransforming;
+
+ /**
+ * Creates a new redefinition strategy.
+ *
+ * @param enabled {@code true} if this strategy is enabled.
+ * @param retransforming {@code true} if this strategy applies retransformation.
+ */
+ RedefinitionStrategy(boolean enabled, boolean retransforming) {
+ this.enabled = enabled;
+ this.retransforming = retransforming;
+ }
+
+ /**
+ * Indicates if this strategy requires a class file transformer to be registered with a hint to apply the
+ * transformer for retransformation.
+ *
+ * @return {@code true} if a class file transformer must be registered with a hint for retransformation.
+ */
+ protected boolean isRetransforming() {
+ return retransforming;
+ }
+
+ /**
+ * Checks if this strategy can be applied to the supplied instrumentation instance.
+ *
+ * @param instrumentation The instrumentation instance to validate.
+ */
+ protected abstract void check(Instrumentation instrumentation);
+
+ /**
+ * Indicates that this redefinition strategy applies a modification of already loaded classes.
+ *
+ * @return {@code true} if this redefinition strategy applies a modification of already loaded classes.
+ */
+ protected boolean isEnabled() {
+ return enabled;
+ }
+
+ /**
+ * Creates a collector instance that is responsible for collecting loaded classes for potential retransformation.
+ *
+ * @return A new collector for collecting already loaded classes for transformation.
+ */
+ protected abstract Collector make();
+
+ /**
+ * Applies this redefinition strategy by submitting all loaded types to redefiniton. If this redefinition strategy is disabled,
+ * this method is non-operational.
+ *
+ * @param instrumentation The instrumentation instance to use.
+ * @param listener The listener to notify on transformations.
+ * @param circularityLock The circularity lock to use.
+ * @param poolStrategy The type locator to use.
+ * @param locationStrategy The location strategy to use.
+ * @param redefinitionDiscoveryStrategy The discovery strategy for loaded types to be redefined.
+ * @param redefinitionBatchAllocator The batch allocator for the redefinition strategy to apply.
+ * @param redefinitionListener The redefinition listener for the redefinition strategy to apply.
+ * @param lambdaInstrumentationStrategy A strategy to determine of the {@code LambdaMetafactory} should be instrumented to allow for the
+ * instrumentation of classes that represent lambda expressions.
+ * @param descriptionStrategy The description strategy for resolving type descriptions for types.
+ * @param fallbackStrategy The fallback strategy to apply.
+ * @param typeMatcher Identifies types that should be instrumented.
+ * @param ignoredTypeMatcher Identifies types that should not be instrumented.
+ */
+ public void apply(Instrumentation instrumentation,
+ AgentBuilder.Listener listener,
+ CircularityLock circularityLock,
+ PoolStrategy poolStrategy,
+ LocationStrategy locationStrategy,
+ DiscoveryStrategy redefinitionDiscoveryStrategy,
+ BatchAllocator redefinitionBatchAllocator,
+ Listener redefinitionListener,
+ LambdaInstrumentationStrategy lambdaInstrumentationStrategy,
+ DescriptionStrategy descriptionStrategy,
+ FallbackStrategy fallbackStrategy,
+ RawMatcher typeMatcher,
+ RawMatcher ignoredTypeMatcher) {
+ check(instrumentation);
+ int batch = RedefinitionStrategy.BatchAllocator.FIRST_BATCH;
+ for (Iterable<Class<?>> types : redefinitionDiscoveryStrategy.resolve(instrumentation)) {
+ RedefinitionStrategy.Collector collector = make();
+ for (Class<?> type : types) {
+ if (type.isArray() || !lambdaInstrumentationStrategy.isInstrumented(type)) {
+ continue;
+ }
+ JavaModule module = JavaModule.ofType(type);
+ try {
+ TypePool typePool = poolStrategy.typePool(locationStrategy.classFileLocator(type.getClassLoader(), module), type.getClassLoader());
+ try {
+ collector.consider(typeMatcher,
+ ignoredTypeMatcher,
+ listener,
+ descriptionStrategy.apply(TypeDescription.ForLoadedType.getName(type), type, typePool, circularityLock, type.getClassLoader(), module),
+ type,
+ type,
+ module,
+ !instrumentation.isModifiableClass(type));
+ } catch (Throwable throwable) {
+ if (descriptionStrategy.isLoadedFirst() && fallbackStrategy.isFallback(type, throwable)) {
+ collector.consider(typeMatcher,
+ ignoredTypeMatcher,
+ listener,
+ typePool.describe(TypeDescription.ForLoadedType.getName(type)).resolve(),
+ type,
+ module);
+ } else {
+ throw throwable;
+ }
+ }
+ } catch (Throwable throwable) {
+ try {
+ try {
+ listener.onError(TypeDescription.ForLoadedType.getName(type), type.getClassLoader(), module, AgentBuilder.Listener.LOADED, throwable);
+ } finally {
+ listener.onComplete(TypeDescription.ForLoadedType.getName(type), type.getClassLoader(), module, AgentBuilder.Listener.LOADED);
+ }
+ } catch (Throwable ignored) {
+ // Ignore exceptions that are thrown by listeners to mimic the behavior of a transformation.
+ }
+ }
+ }
+ batch = collector.apply(instrumentation, circularityLock, locationStrategy, listener, redefinitionBatchAllocator, redefinitionListener, batch);
+ }
+ }
+
+ /**
+ * A batch allocator which is responsible for applying a redefinition in a batches. A class redefinition or
+ * retransformation can be a time-consuming operation rendering a JVM non-responsive. In combination with a
+ * a {@link RedefinitionStrategy.Listener}, it is also possible to apply pauses between batches to distribute
+ * the load of a retransformation over time.
+ */
+ public interface BatchAllocator {
+
+ /**
+ * The index of the first batch.
+ */
+ int FIRST_BATCH = 0;
+
+ /**
+ * Splits a list of types to be retransformed into seperate batches.
+ *
+ * @param types A list of types which should be retransformed.
+ * @return An iterable of retransformations within a batch.
+ */
+ Iterable<? extends List<Class<?>>> batch(List<Class<?>> types);
+
+ /**
+ * A batch allocator that includes all types in a single batch.
+ */
+ enum ForTotal implements BatchAllocator {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Iterable<? extends List<Class<?>>> batch(List<Class<?>> types) {
+ return types.isEmpty()
+ ? Collections.<List<Class<?>>>emptySet()
+ : Collections.singleton(types);
+ }
+ }
+
+ /**
+ * A batch allocator that creates chunks with a fixed size as batch jobs.
+ */
+ @EqualsAndHashCode
+ class ForFixedSize implements BatchAllocator {
+
+ /**
+ * The size of each chunk.
+ */
+ private final int size;
+
+ /**
+ * Creates a new batch allocator that creates fixed-sized chunks.
+ *
+ * @param size The size of each chunk.
+ */
+ protected ForFixedSize(int size) {
+ this.size = size;
+ }
+
+ /**
+ * Creates a new batch allocator that creates chunks of a fixed size.
+ *
+ * @param size The size of each chunk or {@code 0} if the batch should be included in a single chunk.
+ * @return An appropriate batch allocator.
+ */
+ public static BatchAllocator ofSize(int size) {
+ if (size > 0) {
+ return new ForFixedSize(size);
+ } else if (size == 0) {
+ return ForTotal.INSTANCE;
+ } else {
+ throw new IllegalArgumentException("Cannot define a batch with a negative size: " + size);
+ }
+ }
+
+ @Override
+ public Iterable<? extends List<Class<?>>> batch(List<Class<?>> types) {
+ List<List<Class<?>>> batches = new ArrayList<List<Class<?>>>();
+ for (int index = 0; index < types.size(); index += size) {
+ batches.add(new ArrayList<Class<?>>(types.subList(index, Math.min(types.size(), index + size))));
+ }
+ return batches;
+ }
+ }
+
+ /**
+ * A batch allocator that groups all batches by discriminating types using a type matcher.
+ */
+ @EqualsAndHashCode
+ class ForMatchedGrouping implements BatchAllocator {
+
+ /**
+ * The type matchers to apply.
+ */
+ private final Collection<? extends ElementMatcher<? super TypeDescription>> matchers;
+
+ /**
+ * Creates a new batch allocator that groups all batches by discriminating types using a type matcher. All batches
+ * are applied in their application order with any unmatched type being included in the last batch.
+ *
+ * @param matcher The type matchers to apply in their application order.
+ */
+ @SuppressWarnings("unchecked") // In absence of @SafeVarargs for Java 6
+ public ForMatchedGrouping(ElementMatcher<? super TypeDescription>... matcher) {
+ this(new LinkedHashSet<ElementMatcher<? super TypeDescription>>(Arrays.asList(matcher)));
+ }
+
+ /**
+ * Creates a new batch allocator that groups all batches by discriminating types using a type matcher. All batches
+ * are applied in their application order with any unmatched type being included in the last batch.
+ *
+ * @param matchers The type matchers to apply in their application order.
+ */
+ public ForMatchedGrouping(Collection<? extends ElementMatcher<? super TypeDescription>> matchers) {
+ this.matchers = matchers;
+ }
+
+ /**
+ * Assures that any group is at least of a given size. If a group is smaller than a given size, it is merged with its types
+ * are merged with its subsequent group(s) as long as such groups exist.
+ *
+ * @param threshold The minimum threshold for any batch.
+ * @return An appropriate batch allocator.
+ */
+ public BatchAllocator withMinimum(int threshold) {
+ return Slicing.withMinimum(threshold, this);
+ }
+
+ /**
+ * Assures that any group is at least of a given size. If a group is bigger than a given size, it is split into two several
+ * batches.
+ *
+ * @param threshold The maximum threshold for any batch.
+ * @return An appropriate batch allocator.
+ */
+ public BatchAllocator withMaximum(int threshold) {
+ return Slicing.withMaximum(threshold, this);
+ }
+
+ /**
+ * Assures that any group is within a size range described by the supplied minimum and maximum. Groups are split and merged
+ * according to the supplied thresholds. The last group contains might be smaller than the supplied minimum.
+ *
+ * @param minimum The minimum threshold for any batch.
+ * @param maximum The maximum threshold for any batch.
+ * @return An appropriate batch allocator.
+ */
+ public BatchAllocator withinRange(int minimum, int maximum) {
+ return Slicing.withinRange(minimum, maximum, this);
+ }
+
+ @Override
+ public Iterable<? extends List<Class<?>>> batch(List<Class<?>> types) {
+ Map<ElementMatcher<? super TypeDescription>, List<Class<?>>> matched = new LinkedHashMap<ElementMatcher<? super TypeDescription>, List<Class<?>>>();
+ List<Class<?>> unmatched = new ArrayList<Class<?>>();
+ for (ElementMatcher<? super TypeDescription> matcher : matchers) {
+ matched.put(matcher, new ArrayList<Class<?>>());
+ }
+ typeLoop:
+ for (Class<?> type : types) {
+ for (ElementMatcher<? super TypeDescription> matcher : matchers) {
+ if (matcher.matches(new TypeDescription.ForLoadedType(type))) {
+ matched.get(matcher).add(type);
+ continue typeLoop;
+ }
+ }
+ unmatched.add(type);
+ }
+ List<List<Class<?>>> batches = new ArrayList<List<Class<?>>>(matchers.size() + 1);
+ for (List<Class<?>> batch : matched.values()) {
+ if (!batch.isEmpty()) {
+ batches.add(batch);
+ }
+ }
+ if (!unmatched.isEmpty()) {
+ batches.add(unmatched);
+ }
+ return batches;
+ }
+ }
+
+ /**
+ * A slicing batch allocator that assures that any batch is within a certain size range.
+ */
+ @EqualsAndHashCode
+ class Slicing implements BatchAllocator {
+
+ /**
+ * The minimum size of each slice.
+ */
+ private final int minimum;
+
+ /**
+ * The maximum size of each slice.
+ */
+ private final int maximum;
+
+ /**
+ * The delegate batch allocator.
+ */
+ private final BatchAllocator batchAllocator;
+
+ /**
+ * Creates a new slicing batch allocator.
+ *
+ * @param minimum The minimum size of each slice.
+ * @param maximum The maximum size of each slice.
+ * @param batchAllocator The delegate batch allocator.
+ */
+ protected Slicing(int minimum, int maximum, BatchAllocator batchAllocator) {
+ this.minimum = minimum;
+ this.maximum = maximum;
+ this.batchAllocator = batchAllocator;
+ }
+
+ /**
+ * Creates a new slicing batch allocator.
+ *
+ * @param minimum The minimum size of each slice.
+ * @param batchAllocator The delegate batch allocator.
+ * @return An appropriate slicing batch allocator.
+ */
+ public static BatchAllocator withMinimum(int minimum, BatchAllocator batchAllocator) {
+ return withinRange(minimum, Integer.MAX_VALUE, batchAllocator);
+ }
+
+ /**
+ * Creates a new slicing batch allocator.
+ *
+ * @param maximum The maximum size of each slice.
+ * @param batchAllocator The delegate batch allocator.
+ * @return An appropriate slicing batch allocator.
+ */
+ public static BatchAllocator withMaximum(int maximum, BatchAllocator batchAllocator) {
+ return withinRange(1, maximum, batchAllocator);
+ }
+
+ /**
+ * Creates a new slicing batch allocator.
+ *
+ * @param minimum The minimum size of each slice.
+ * @param maximum The maximum size of each slice.
+ * @param batchAllocator The delegate batch allocator.
+ * @return An appropriate slicing batch allocator.
+ */
+ public static BatchAllocator withinRange(int minimum, int maximum, BatchAllocator batchAllocator) {
+ if (minimum <= 0) {
+ throw new IllegalArgumentException("Minimum must be a positive number: " + minimum);
+ } else if (minimum > maximum) {
+ throw new IllegalArgumentException("Minimum must not be bigger than maximum: " + minimum + " >" + maximum);
+ }
+ return new Slicing(minimum, maximum, batchAllocator);
+ }
+
+ @Override
+ public Iterable<? extends List<Class<?>>> batch(List<Class<?>> types) {
+ return new SlicingIterable(minimum, maximum, batchAllocator.batch(types));
+ }
+
+ /**
+ * An iterable that slices batches into parts of a minimum and maximum size.
+ */
+ protected static class SlicingIterable implements Iterable<List<Class<?>>> {
+
+ /**
+ * The minimum size of any slice.
+ */
+ private final int minimum;
+
+ /**
+ * The maximum size of any slice.
+ */
+ private final int maximum;
+
+ /**
+ * The delegate iterable.
+ */
+ private final Iterable<? extends List<Class<?>>> iterable;
+
+ /**
+ * Creates a new slicing iterable.
+ *
+ * @param minimum The minimum size of any slice.
+ * @param maximum The maximum size of any slice.
+ * @param iterable The delegate iterable.
+ */
+ protected SlicingIterable(int minimum, int maximum, Iterable<? extends List<Class<?>>> iterable) {
+ this.minimum = minimum;
+ this.maximum = maximum;
+ this.iterable = iterable;
+ }
+
+ @Override
+ public Iterator<List<Class<?>>> iterator() {
+ return new SlicingIterator(minimum, maximum, iterable.iterator());
+ }
+
+ /**
+ * An iterator that slices batches into parts of a minimum and maximum size.
+ */
+ protected static class SlicingIterator implements Iterator<List<Class<?>>> {
+
+ /**
+ * The minimum size of any slice.
+ */
+ private final int minimum;
+
+ /**
+ * The maximum size of any slice.
+ */
+ private final int maximum;
+
+ /**
+ * The delegate iterator.
+ */
+ private final Iterator<? extends List<Class<?>>> iterator;
+
+ /**
+ * A buffer containing all types that surpassed the maximum.
+ */
+ private List<Class<?>> buffer;
+
+ /**
+ * Creates a new slicing iterator.
+ *
+ * @param minimum The minimum size of any slice.
+ * @param maximum The maximum size of any slice.
+ * @param iterator The delegate iterator.
+ */
+ protected SlicingIterator(int minimum, int maximum, Iterator<? extends List<Class<?>>> iterator) {
+ this.minimum = minimum;
+ this.maximum = maximum;
+ this.iterator = iterator;
+ buffer = Collections.emptyList();
+ }
+
+ @Override
+ public boolean hasNext() {
+ return !buffer.isEmpty() || iterator.hasNext();
+ }
+
+ @Override
+ public List<Class<?>> next() {
+ if (buffer.isEmpty()) {
+ buffer = iterator.next();
+ }
+ while (buffer.size() < minimum && iterator.hasNext()) {
+ buffer.addAll(iterator.next());
+ }
+ if (buffer.size() > maximum) {
+ try {
+ return buffer.subList(0, maximum);
+ } finally {
+ buffer = buffer.subList(maximum, buffer.size());
+ }
+ } else {
+ try {
+ return buffer;
+ } finally {
+ buffer = Collections.emptyList();
+ }
+ }
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("remove");
+ }
+ }
+ }
+ }
+
+ /**
+ * A partitioning batch allocator that splits types for redefinition into a fixed amount of parts.
+ */
+ @EqualsAndHashCode
+ class Partitioning implements BatchAllocator {
+
+ /**
+ * The amount of batches to generate.
+ */
+ private final int parts;
+
+ /**
+ * Creates a new batch allocator that splits types for redefinition into a fixed amount of parts.
+ *
+ * @param parts The amount of parts to create.
+ */
+ protected Partitioning(int parts) {
+ this.parts = parts;
+ }
+
+ /**
+ * Creates a part-splitting batch allocator.
+ *
+ * @param parts The amount of parts to create.
+ * @return A batch allocator that splits the redefined types into a fixed amount of batches.
+ */
+ public static BatchAllocator of(int parts) {
+ if (parts < 1) {
+ throw new IllegalArgumentException("A batch size must be positive: " + parts);
+ }
+ return new Partitioning(parts);
+ }
+
+ @Override
+ public Iterable<? extends List<Class<?>>> batch(List<Class<?>> types) {
+ if (types.isEmpty()) {
+ return Collections.emptyList();
+ } else {
+ List<List<Class<?>>> batches = new ArrayList<List<Class<?>>>();
+ int size = types.size() / parts, reminder = types.size() % parts;
+ for (int index = reminder; index < types.size(); index += size) {
+ batches.add(new ArrayList<Class<?>>(types.subList(index, index + size)));
+ }
+ if (batches.isEmpty()) {
+ return Collections.singletonList(types);
+ } else {
+ batches.get(0).addAll(0, types.subList(0, reminder));
+ return batches;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * A listener to be applied during a redefinition.
+ */
+ public interface Listener {
+
+ /**
+ * Invoked before applying a batch.
+ *
+ * @param index A running index of the batch starting at {@code 0}.
+ * @param batch The types included in this batch.
+ * @param types All types included in the redefinition.
+ */
+ void onBatch(int index, List<Class<?>> batch, List<Class<?>> types);
+
+ /**
+ * Invoked upon an error during a batch. This method is not invoked if the failure handler handled this error.
+ *
+ * @param index A running index of the batch starting at {@code 0}.
+ * @param batch The types included in this batch.
+ * @param throwable The throwable that caused this invocation.
+ * @param types All types included in the redefinition.
+ * @return A set of classes which should be attempted to be redefined. Typically, this should be a subset of the classes
+ * contained in {@code batch} but not all classes.
+ */
+ Iterable<? extends List<Class<?>>> onError(int index, List<Class<?>> batch, Throwable throwable, List<Class<?>> types);
+
+ /**
+ * Invoked upon completion of all batches.
+ *
+ * @param amount The total amount of batches that were executed.
+ * @param types All types included in the redefinition.
+ * @param failures A mapping of batch types to their unhandled failures.
+ */
+ void onComplete(int amount, List<Class<?>> types, Map<List<Class<?>>, Throwable> failures);
+
+ /**
+ * A non-operational listener.
+ */
+ enum NoOp implements Listener {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public void onBatch(int index, List<Class<?>> batch, List<Class<?>> types) {
+ /* do nothing */
+ }
+
+ @Override
+ public Iterable<? extends List<Class<?>>> onError(int index, List<Class<?>> batch, Throwable throwable, List<Class<?>> types) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public void onComplete(int amount, List<Class<?>> types, Map<List<Class<?>>, Throwable> failures) {
+ /* do nothing */
+ }
+ }
+
+ /**
+ * A listener that invokes {@link Thread#yield()} prior to every batch but the first batch.
+ */
+ enum Yielding implements Listener {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public void onBatch(int index, List<Class<?>> batch, List<Class<?>> types) {
+ if (index > 0) {
+ Thread.yield();
+ }
+ }
+
+ @Override
+ public Iterable<? extends List<Class<?>>> onError(int index, List<Class<?>> batch, Throwable throwable, List<Class<?>> types) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public void onComplete(int amount, List<Class<?>> types, Map<List<Class<?>>, Throwable> failures) {
+ /* do nothing */
+ }
+ }
+
+ /**
+ * A listener that halts a retransformation process upon an exception.
+ */
+ enum ErrorEscalating implements Listener {
+
+ /**
+ * A listener that fails the retransformation upon the first failed retransformation of a batch.
+ */
+ FAIL_FAST {
+ @Override
+ public Iterable<? extends List<Class<?>>> onError(int index, List<Class<?>> batch, Throwable throwable, List<Class<?>> types) {
+ throw new IllegalStateException("Could not transform any of " + batch, throwable);
+ }
+
+ @Override
+ public void onComplete(int amount, List<Class<?>> types, Map<List<Class<?>>, Throwable> failures) {
+ /* do nothing */
+ }
+ },
+
+ /**
+ * A listener that fails the retransformation after all batches were executed if any error occured.
+ */
+ FAIL_LAST {
+ @Override
+ public Iterable<? extends List<Class<?>>> onError(int index, List<Class<?>> batch, Throwable throwable, List<Class<?>> types) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public void onComplete(int amount, List<Class<?>> types, Map<List<Class<?>>, Throwable> failures) {
+ if (!failures.isEmpty()) {
+ throw new IllegalStateException("Could not transform any of " + failures);
+ }
+ }
+ };
+
+ @Override
+ public void onBatch(int index, List<Class<?>> batch, List<Class<?>> types) {
+ /* do nothing */
+ }
+ }
+
+ /**
+ * A listener adapter that offers non-operational implementations of all listener methods.
+ */
+ @EqualsAndHashCode
+ abstract class Adapter implements Listener {
+
+ @Override
+ public void onBatch(int index, List<Class<?>> batch, List<Class<?>> types) {
+ /* do nothing */
+ }
+
+ @Override
+ public Iterable<? extends List<Class<?>>> onError(int index, List<Class<?>> batch, Throwable throwable, List<Class<?>> types) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public void onComplete(int amount, List<Class<?>> types, Map<List<Class<?>>, Throwable> failures) {
+ /* do nothing */
+ }
+ }
+
+ /**
+ * <p>
+ * A batch reallocator allows to split up a failed retransformation into additional batches which are reenqueed to the
+ * current retransformation process. To do so, any batch with at least to classes is rerouted through a {@link BatchAllocator}
+ * which is responsible for regrouping the classes that failed to be retransformed into new batches.
+ * </p>
+ * <p>
+ * <b>Important</b>: To avoid endless looping over classes that cannot be successfully retransformed, the supplied batch
+ * allocator must not resubmit batches that previously failed as an identical outcome is likely.
+ * </p>
+ */
+ @EqualsAndHashCode(callSuper = false)
+ class BatchReallocator extends Adapter {
+
+ /**
+ * The batch allocator to use for reallocating failed batches.
+ */
+ private final BatchAllocator batchAllocator;
+
+ /**
+ * Creates a new batch reallocator.
+ *
+ * @param batchAllocator The batch allocator to use for reallocating failed batches.
+ */
+ public BatchReallocator(BatchAllocator batchAllocator) {
+ this.batchAllocator = batchAllocator;
+ }
+
+ /**
+ * Creates a batch allocator that splits any batch into two parts and resubmits these parts as two batches.
+ *
+ * @return A batch reallocating batch listener that splits failed batches into two parts for resubmission.
+ */
+ public static Listener splitting() {
+ return new BatchReallocator(new BatchAllocator.Partitioning(2));
+ }
+
+ @Override
+ public Iterable<? extends List<Class<?>>> onError(int index, List<Class<?>> batch, Throwable throwable, List<Class<?>> types) {
+ return batch.size() < 2
+ ? Collections.<List<Class<?>>>emptyList()
+ : batchAllocator.batch(batch);
+ }
+ }
+
+ /**
+ * A listener that invokes {@link Thread#sleep(long)} prior to every batch but the first batch.
+ */
+ @EqualsAndHashCode(callSuper = false)
+ class Pausing extends Adapter {
+
+ /**
+ * The time to sleep in milliseconds between every two batches.
+ */
+ private final long value;
+
+ /**
+ * Creates a new pausing listener.
+ *
+ * @param value The time to sleep in milliseconds between every two batches.
+ */
+ protected Pausing(long value) {
+ this.value = value;
+ }
+
+ /**
+ * Creates a listener that pauses for the specified amount of time. If the specified value is {@code 0}, a
+ * non-operational listener is returned.
+ *
+ * @param value The amount of time to pause between redefinition batches.
+ * @param timeUnit The time unit of {@code value}.
+ * @return An appropriate listener.
+ */
+ public static Listener of(long value, TimeUnit timeUnit) {
+ if (value > 0L) {
+ return new Pausing(timeUnit.toMillis(value));
+ } else if (value == 0L) {
+ return NoOp.INSTANCE;
+ } else {
+ throw new IllegalArgumentException("Cannot sleep for a non-positive amount of time: " + value);
+ }
+ }
+
+ @Override
+ public void onBatch(int index, List<Class<?>> batch, List<Class<?>> types) {
+ if (index > 0) {
+ try {
+ Thread.sleep(value);
+ } catch (InterruptedException exception) {
+ throw new RuntimeException("Sleep was interrupted", exception);
+ }
+ }
+ }
+ }
+
+ /**
+ * A listener that writes events to a {@link PrintStream}.
+ */
+ @EqualsAndHashCode
+ class StreamWriting implements Listener {
+
+ /**
+ * The print stream to write any events to.
+ */
+ private final PrintStream printStream;
+
+ /**
+ * Creates a new stream writing listener.
+ *
+ * @param printStream The print stream to write any events to.
+ */
+ public StreamWriting(PrintStream printStream) {
+ this.printStream = printStream;
+ }
+
+ /**
+ * Writes the stream result to {@link System#out}.
+ *
+ * @return An appropriate listener.
+ */
+ public static Listener toSystemOut() {
+ return new StreamWriting(System.out);
+ }
+
+ /**
+ * Writes the stream result to {@link System#err}.
+ *
+ * @return An appropriate listener.
+ */
+ public static Listener toSystemError() {
+ return new StreamWriting(System.err);
+ }
+
+ @Override
+ public void onBatch(int index, List<Class<?>> batch, List<Class<?>> types) {
+ printStream.printf(AgentBuilder.Listener.StreamWriting.PREFIX + " REDEFINE BATCH #%d [%d of %d type(s)]%n", index, batch.size(), types.size());
+ }
+
+ @Override
+ public Iterable<? extends List<Class<?>>> onError(int index, List<Class<?>> batch, Throwable throwable, List<Class<?>> types) {
+ synchronized (printStream) {
+ printStream.printf(AgentBuilder.Listener.StreamWriting.PREFIX + " REDEFINE ERROR #%d [%d of %d type(s)]%n", index, batch.size(), types.size());
+ throwable.printStackTrace(printStream);
+ }
+ return Collections.emptyList();
+ }
+
+ @Override
+ public void onComplete(int amount, List<Class<?>> types, Map<List<Class<?>>, Throwable> failures) {
+ printStream.printf(AgentBuilder.Listener.StreamWriting.PREFIX + " REDEFINE COMPLETE #%d batch(es) containing %d types [%d failed batch(es)]%n", amount, types.size(), failures.size());
+ }
+ }
+
+ /**
+ * A compound listener that delegates events to several listeners.
+ */
+ @EqualsAndHashCode
+ class Compound implements Listener {
+
+ /**
+ * The listeners to invoke.
+ */
+ private final List<Listener> listeners;
+
+ /**
+ * Creates a new compound listener.
+ *
+ * @param listener The listeners to invoke.
+ */
+ public Compound(Listener... listener) {
+ this(Arrays.asList(listener));
+ }
+
+ /**
+ * Creates a new compound listener.
+ *
+ * @param listeners The listeners to invoke.
+ */
+ public Compound(List<? extends Listener> listeners) {
+ this.listeners = new ArrayList<Listener>();
+ for (Listener listener : listeners) {
+ if (listener instanceof Compound) {
+ this.listeners.addAll(((Compound) listener).listeners);
+ } else if (!(listener instanceof NoOp)) {
+ this.listeners.add(listener);
+ }
+ }
+ }
+
+ @Override
+ public void onBatch(int index, List<Class<?>> batch, List<Class<?>> types) {
+ for (Listener listener : listeners) {
+ listener.onBatch(index, batch, types);
+ }
+ }
+
+ @Override
+ public Iterable<? extends List<Class<?>>> onError(int index, List<Class<?>> batch, Throwable throwable, List<Class<?>> types) {
+ List<Iterable<? extends List<Class<?>>>> reattempts = new ArrayList<Iterable<? extends List<Class<?>>>>();
+ for (Listener listener : listeners) {
+ reattempts.add(listener.onError(index, batch, throwable, types));
+ }
+ return new CompoundIterable(reattempts);
+ }
+
+ @Override
+ public void onComplete(int amount, List<Class<?>> types, Map<List<Class<?>>, Throwable> failures) {
+ for (Listener listener : listeners) {
+ listener.onComplete(amount, types, failures);
+ }
+ }
+
+ /**
+ * A compound iterable.
+ */
+ @EqualsAndHashCode
+ protected static class CompoundIterable implements Iterable<List<Class<?>>> {
+
+ /**
+ * The iterables to consider.
+ */
+ private final List<Iterable<? extends List<Class<?>>>> iterables;
+
+ /**
+ * Creates a compound iterable.
+ *
+ * @param iterables The iterables to consider.
+ */
+ protected CompoundIterable(List<Iterable<? extends List<Class<?>>>> iterables) {
+ this.iterables = iterables;
+ }
+
+ @Override
+ public Iterator<List<Class<?>>> iterator() {
+ return new CompoundIterator(new ArrayList<Iterable<? extends List<Class<?>>>>(iterables));
+ }
+
+ /**
+ * A compound iterator that combines several iteratables.
+ */
+ protected static class CompoundIterator implements Iterator<List<Class<?>>> {
+
+ /**
+ * The current iterator or {@code null} if no such iterator is defined.
+ */
+ private Iterator<? extends List<Class<?>>> current;
+
+ /**
+ * A backlog of iterables to still consider.
+ */
+ private final List<Iterable<? extends List<Class<?>>>> backlog;
+
+ /**
+ * Creates a compount iterator.
+ *
+ * @param iterables The iterables to consider.
+ */
+ protected CompoundIterator(List<Iterable<? extends List<Class<?>>>> iterables) {
+ backlog = iterables;
+ forward();
+ }
+
+ @Override
+ public boolean hasNext() {
+ return current != null && current.hasNext();
+ }
+
+ @Override
+ public List<Class<?>> next() {
+ try {
+ if (current != null) {
+ return current.next();
+ } else {
+ throw new NoSuchElementException();
+ }
+ } finally {
+ forward();
+ }
+ }
+
+ /**
+ * Forwards the iterator to the next relevant iterable.
+ */
+ private void forward() {
+ while ((current == null || !current.hasNext()) && !backlog.isEmpty()) {
+ current = backlog.remove(0).iterator();
+ }
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("remove");
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * A strategy for discovering types to redefine.
+ */
+ public interface DiscoveryStrategy {
+
+ /**
+ * Resolves an iterable of types to retransform. Types might be loaded during a previous retransformation which might require
+ * multiple passes for a retransformation.
+ *
+ * @param instrumentation The instrumentation instance used for the redefinition.
+ * @return An iterable of types to consider for retransformation.
+ */
+ Iterable<Iterable<Class<?>>> resolve(Instrumentation instrumentation);
+
+ /**
+ * A discovery strategy that considers all loaded types supplied by {@link Instrumentation#getAllLoadedClasses()}.
+ */
+ enum SinglePass implements DiscoveryStrategy {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Iterable<Iterable<Class<?>>> resolve(Instrumentation instrumentation) {
+ return Collections.<Iterable<Class<?>>>singleton(Arrays.<Class<?>>asList(instrumentation.getAllLoadedClasses()));
+ }
+ }
+
+ /**
+ * A discovery strategy that considers all loaded types supplied by {@link Instrumentation#getAllLoadedClasses()}. For each reiteration,
+ * this strategy checks if additional types were loaded after the previously supplied types. Doing so, types that were loaded during
+ * instrumentations can be retransformed as such types are not passed to any class file transformer.
+ */
+ enum Reiterating implements DiscoveryStrategy {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Iterable<Iterable<Class<?>>> resolve(Instrumentation instrumentation) {
+ return new ReiteratingIterable(instrumentation);
+ }
+
+ /**
+ * An iterable that returns any loaded types and checks if any additional types were loaded during the last instrumentation.
+ */
+ @EqualsAndHashCode
+ protected static class ReiteratingIterable implements Iterable<Iterable<Class<?>>> {
+
+ /**
+ * The instrumentation instance to use.
+ */
+ private final Instrumentation instrumentation;
+
+ /**
+ * Creates a new reiterating iterable.
+ *
+ * @param instrumentation The instrumentation instance to use.
+ */
+ protected ReiteratingIterable(Instrumentation instrumentation) {
+ this.instrumentation = instrumentation;
+ }
+
+ @Override
+ public Iterator<Iterable<Class<?>>> iterator() {
+ return new ReiteratingIterator(instrumentation);
+ }
+ }
+
+ /**
+ * A reiterating iterator that considers types that were loaded during an instrumentation.
+ */
+ protected static class ReiteratingIterator implements Iterator<Iterable<Class<?>>> {
+
+ /**
+ * The instrumentation instance to use.
+ */
+ private final Instrumentation instrumentation;
+
+ /**
+ * A set containing all previously discovered types.
+ */
+ private final Set<Class<?>> processed;
+
+ /**
+ * The current list of types or {@code null} if the current list of types is not prepared.
+ */
+ private List<Class<?>> types;
+
+ /**
+ * Creates a new reiterating iterator.
+ *
+ * @param instrumentation The instrumentation instance to use.
+ */
+ protected ReiteratingIterator(Instrumentation instrumentation) {
+ this.instrumentation = instrumentation;
+ processed = new HashSet<Class<?>>();
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (types == null) {
+ types = new ArrayList<Class<?>>();
+ for (Class<?> type : instrumentation.getAllLoadedClasses()) {
+ if (processed.add(type)) {
+ types.add(type);
+ }
+ }
+ }
+ return !types.isEmpty();
+ }
+
+ @Override
+ public Iterable<Class<?>> next() {
+ if (hasNext()) {
+ try {
+ return types;
+ } finally {
+ types = null;
+ }
+ } else {
+ throw new NoSuchElementException();
+ }
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("remove");
+ }
+ }
+ }
+
+ /**
+ * An explicit discovery strategy that only attempts the redefinition of specific types.
+ */
+ @EqualsAndHashCode
+ class Explicit implements DiscoveryStrategy {
+
+ /**
+ * The types to redefine.
+ */
+ private final Set<Class<?>> types;
+
+ /**
+ * Creates a new explicit discovery strategy.
+ *
+ * @param type The types to redefine.
+ */
+ public Explicit(Class<?>... type) {
+ this(new LinkedHashSet<Class<?>>(Arrays.asList(type)));
+ }
+
+ /**
+ * Creates a new explicit discovery strategy.
+ *
+ * @param types The types to redefine.
+ */
+ public Explicit(Set<Class<?>> types) {
+ this.types = types;
+ }
+
+ @Override
+ public Iterable<Iterable<Class<?>>> resolve(Instrumentation instrumentation) {
+ return Collections.<Iterable<Class<?>>>singleton(types);
+ }
+ }
+ }
+
+ /**
+ * A resubmission scheduler is responsible for scheduling a job that is resubmitting unloaded types that failed during retransformation.
+ */
+ public interface ResubmissionScheduler {
+
+ /**
+ * Checks if this scheduler is currently available.
+ *
+ * @return {@code true} if this scheduler is alive.
+ */
+ boolean isAlive();
+
+ /**
+ * Schedules a resubmission job for regular application.
+ *
+ * @param job The job to schedule.
+ * @return A cancelable that is canceled upon resetting the corresponding class file transformer.
+ */
+ Cancelable schedule(Runnable job);
+
+ /**
+ * A cancelable allows to discontinue a resubmission job.
+ */
+ interface Cancelable {
+
+ /**
+ * Cancels this resubmission job.
+ */
+ void cancel();
+
+ /**
+ * A non-operational cancelable.
+ */
+ enum NoOp implements Cancelable {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public void cancel() {
+ /* do nothing */
+ }
+ }
+
+ /**
+ * A cancelable for a {@link Future}.
+ */
+ @EqualsAndHashCode
+ class ForFuture implements Cancelable {
+
+ /**
+ * The future to cancel upon cancelation of this instance.
+ */
+ private final Future<?> future;
+
+ /**
+ * Creates a cancelable for a future.
+ *
+ * @param future The future to cancel upon cancelation of this instance.
+ */
+ public ForFuture(Future<?> future) {
+ this.future = future;
+ }
+
+ @Override
+ public void cancel() {
+ future.cancel(true);
+ }
+ }
+ }
+
+ /**
+ * A resubmission scheduler that does not apply any scheduling.
+ */
+ enum NoOp implements ResubmissionScheduler {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public boolean isAlive() {
+ return false;
+ }
+
+ @Override
+ public Cancelable schedule(Runnable job) {
+ return Cancelable.NoOp.INSTANCE;
+ }
+ }
+
+ /**
+ * A resubmission scheduler that schedules jobs at a fixed rate.
+ */
+ @EqualsAndHashCode
+ class AtFixedRate implements ResubmissionScheduler {
+
+ /**
+ * The executor service to schedule to.
+ */
+ private final ScheduledExecutorService scheduledExecutorService;
+
+ /**
+ * The time interval between schedulings.
+ */
+ private final long time;
+
+ /**
+ * The time's time unit.
+ */
+ private final TimeUnit timeUnit;
+
+ /**
+ * Creates a new resubmission scheduler which schedules executions at a fixed rate.
+ *
+ * @param scheduledExecutorService The executor service to schedule to.
+ * @param time The time interval between schedulings.
+ * @param timeUnit The time's time unit.
+ */
+ public AtFixedRate(ScheduledExecutorService scheduledExecutorService, long time, TimeUnit timeUnit) {
+ this.scheduledExecutorService = scheduledExecutorService;
+ this.time = time;
+ this.timeUnit = timeUnit;
+ }
+
+ @Override
+ public boolean isAlive() {
+ return !scheduledExecutorService.isShutdown();
+ }
+
+ @Override
+ public Cancelable schedule(Runnable job) {
+ return new Cancelable.ForFuture(scheduledExecutorService.scheduleAtFixedRate(job, time, time, timeUnit));
+ }
+ }
+
+ /**
+ * A resubmission scheduler that schedules jobs with a fixed delay.
+ */
+ @EqualsAndHashCode
+ class WithFixedDelay implements ResubmissionScheduler {
+
+ /**
+ * The executor service to schedule to.
+ */
+ private final ScheduledExecutorService scheduledExecutorService;
+
+ /**
+ * The time interval to pause between completed jobs.
+ */
+ private final long time;
+
+ /**
+ * The time's time unit.
+ */
+ private final TimeUnit timeUnit;
+
+ /**
+ * Creates a new resubmission scheduler with a fixed delay between job executions.
+ *
+ * @param scheduledExecutorService The executor service to schedule to.
+ * @param time The time interval to pause between completed jobs.
+ * @param timeUnit The time's time unit.
+ */
+ public WithFixedDelay(ScheduledExecutorService scheduledExecutorService, long time, TimeUnit timeUnit) {
+ this.scheduledExecutorService = scheduledExecutorService;
+ this.time = time;
+ this.timeUnit = timeUnit;
+ }
+
+ @Override
+ public boolean isAlive() {
+ return !scheduledExecutorService.isShutdown();
+ }
+
+ @Override
+ public Cancelable schedule(Runnable job) {
+ return new Cancelable.ForFuture(scheduledExecutorService.scheduleWithFixedDelay(job, time, time, timeUnit));
+ }
+ }
+ }
+
+ /**
+ * A resubmission strategy is responsible for enabling resubmission of types that failed to resubmit.
+ */
+ protected interface ResubmissionStrategy {
+
+ /**
+ * Invoked upon installation of an agent builder.
+ *
+ * @param instrumentation The instrumentation instance to use.
+ * @param locationStrategy The location strategy to use.
+ * @param listener The listener to use.
+ * @param installationListener The installation listener to use.
+ * @param circularityLock The circularity lock to use.
+ * @param matcher The matcher to apply for analyzing if a type is to be resubmitted.
+ * @param redefinitionStrategy The redefinition strategy to use.
+ * @param redefinitionBatchAllocator The batch allocator to use.
+ * @param redefinitionBatchListener The batch listener to notify.
+ * @return A potentially modified listener to apply.
+ */
+ Installation apply(Instrumentation instrumentation,
+ LocationStrategy locationStrategy,
+ AgentBuilder.Listener listener,
+ InstallationListener installationListener,
+ CircularityLock circularityLock,
+ RawMatcher matcher,
+ RedefinitionStrategy redefinitionStrategy,
+ RedefinitionStrategy.BatchAllocator redefinitionBatchAllocator,
+ RedefinitionStrategy.Listener redefinitionBatchListener);
+
+ /**
+ * A disabled resubmission strategy.
+ */
+ enum Disabled implements ResubmissionStrategy {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Installation apply(Instrumentation instrumentation,
+ LocationStrategy locationStrategy,
+ AgentBuilder.Listener listener,
+ InstallationListener installationListener,
+ CircularityLock circularityLock,
+ RawMatcher matcher,
+ RedefinitionStrategy redefinitionStrategy,
+ BatchAllocator redefinitionBatchAllocator,
+ Listener redefinitionBatchListener) {
+ return new Installation(listener, installationListener);
+ }
+ }
+
+ /**
+ * An enabled resubmission strategy.
+ */
+ @EqualsAndHashCode
+ class Enabled implements ResubmissionStrategy {
+
+ /**
+ * A scheduler that is responsible for resubmission of types.
+ */
+ private final ResubmissionScheduler resubmissionScheduler;
+
+ /**
+ * The matcher for filtering error causes.
+ */
+ private final ElementMatcher<? super Throwable> matcher;
+
+ /**
+ * Creates a new enabled resubmission strategy.
+ *
+ * @param resubmissionScheduler A scheduler that is responsible for resubmission of types.
+ * @param matcher The matcher for filtering error causes.
+ */
+ protected Enabled(ResubmissionScheduler resubmissionScheduler, ElementMatcher<? super Throwable> matcher) {
+ this.resubmissionScheduler = resubmissionScheduler;
+ this.matcher = matcher;
+ }
+
+ @Override
+ public Installation apply(Instrumentation instrumentation,
+ LocationStrategy locationStrategy,
+ AgentBuilder.Listener listener,
+ InstallationListener installationListener,
+ CircularityLock circularityLock,
+ RawMatcher matcher,
+ RedefinitionStrategy redefinitionStrategy,
+ RedefinitionStrategy.BatchAllocator redefinitionBatchAllocator,
+ RedefinitionStrategy.Listener redefinitionBatchListener) {
+ if (redefinitionStrategy.isEnabled() && resubmissionScheduler.isAlive()) {
+ ConcurrentMap<StorageKey, Set<String>> types = new ConcurrentHashMap<StorageKey, Set<String>>();
+ return new Installation(new AgentBuilder.Listener.Compound(new ResubmissionListener(this.matcher, types), listener),
+ new InstallationListener.Compound(new ResubmissionInstallationListener(resubmissionScheduler,
+ instrumentation,
+ locationStrategy,
+ listener,
+ circularityLock,
+ matcher,
+ redefinitionStrategy,
+ redefinitionBatchAllocator,
+ redefinitionBatchListener,
+ types), installationListener));
+ } else {
+ return new Installation(listener, installationListener);
+ }
+ }
+
+ /**
+ * A listener that registers types for resubmission that failed during transformations.
+ */
+ protected static class ResubmissionListener extends AgentBuilder.Listener.Adapter {
+
+ /**
+ * The matcher for filtering error causes.
+ */
+ private final ElementMatcher<? super Throwable> matcher;
+
+ /**
+ * A map of class loaders to their types to resubmit.
+ */
+ private final ConcurrentMap<StorageKey, Set<String>> types;
+
+ /**
+ * @param matcher The matcher for filtering error causes.
+ * @param types A map of class loaders to their types to resubmit.
+ */
+ protected ResubmissionListener(ElementMatcher<? super Throwable> matcher, ConcurrentMap<StorageKey, Set<String>> types) {
+ this.matcher = matcher;
+ this.types = types;
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "GC_UNRELATED_TYPES", justification = "Use of unrelated key is inteded for avoiding unnecessary weak reference")
+ public void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable) {
+ if (!loaded && matcher.matches(throwable)) {
+ Set<String> types = this.types.get(new LookupKey(classLoader));
+ if (types == null) {
+ types = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
+ Set<String> previous = this.types.putIfAbsent(new StorageKey(classLoader), types);
+ if (previous != null) {
+ types = previous;
+ }
+ }
+ types.add(typeName);
+ }
+ }
+ }
+
+ /**
+ * A job that resubmits any matched type that previously failed during transformation.
+ */
+ protected static class ResubmissionInstallationListener extends AgentBuilder.InstallationListener.Adapter implements Runnable {
+
+ /**
+ * The resubmission scheduler to use.
+ */
+ private final ResubmissionScheduler resubmissionScheduler;
+
+ /**
+ * The instrumentation instance to use.
+ */
+ private final Instrumentation instrumentation;
+
+ /**
+ * The location strategy to use.
+ */
+ private final LocationStrategy locationStrategy;
+
+ /**
+ * The listener to use.
+ */
+ private final AgentBuilder.Listener listener;
+
+ /**
+ * The circularity lock to use.
+ */
+ private final CircularityLock circularityLock;
+
+ /**
+ * The matcher to apply for analyzing if a type is to be resubmitted.
+ */
+ private final RawMatcher matcher;
+
+ /**
+ * The redefinition strategy to use.
+ */
+ private final RedefinitionStrategy redefinitionStrategy;
+
+ /**
+ * The batch allocator to use.
+ */
+ private final BatchAllocator redefinitionBatchAllocator;
+
+ /**
+ * The batch listener to notify.
+ */
+ private final Listener redefinitionBatchListener;
+
+ /**
+ * A map of class loaders to their types to resubmit.
+ */
+ private final ConcurrentMap<StorageKey, Set<String>> types;
+
+ /**
+ * This scheduler's cancelable or {@code null} if no cancelable was registered.
+ */
+ private volatile ResubmissionScheduler.Cancelable cancelable;
+
+ /**
+ * Creates a new resubmission job.
+ *
+ * @param resubmissionScheduler The resubmission scheduler to use.
+ * @param instrumentation The instrumentation instance to use.
+ * @param locationStrategy The location strategy to use.
+ * @param listener The listener to use.
+ * @param circularityLock The circularity lock to use.
+ * @param matcher The matcher to apply for analyzing if a type is to be resubmitted.
+ * @param redefinitionStrategy The redefinition strategy to use.
+ * @param redefinitionBatchAllocator The batch allocator to use.
+ * @param redefinitionBatchListener The batch listener to notify.
+ * @param types A map of class loaders to their types to resubmit.
+ */
+ protected ResubmissionInstallationListener(ResubmissionScheduler resubmissionScheduler,
+ Instrumentation instrumentation,
+ LocationStrategy locationStrategy,
+ AgentBuilder.Listener listener,
+ CircularityLock circularityLock,
+ RawMatcher matcher,
+ RedefinitionStrategy redefinitionStrategy,
+ BatchAllocator redefinitionBatchAllocator,
+ Listener redefinitionBatchListener,
+ ConcurrentMap<StorageKey, Set<String>> types) {
+ this.resubmissionScheduler = resubmissionScheduler;
+ this.instrumentation = instrumentation;
+ this.locationStrategy = locationStrategy;
+ this.listener = listener;
+ this.circularityLock = circularityLock;
+ this.matcher = matcher;
+ this.redefinitionStrategy = redefinitionStrategy;
+ this.redefinitionBatchAllocator = redefinitionBatchAllocator;
+ this.redefinitionBatchListener = redefinitionBatchListener;
+ this.types = types;
+ }
+
+
+ @Override
+ public void onInstall(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) {
+ cancelable = resubmissionScheduler.schedule(this);
+ }
+
+ @Override
+ public void onReset(Instrumentation instrumentation, ResettableClassFileTransformer classFileTransformer) {
+ ResubmissionScheduler.Cancelable cancelable = this.cancelable;
+ if (cancelable != null) {
+ cancelable.cancel();
+ }
+ }
+
+ @Override
+ public void run() {
+ boolean release = circularityLock.acquire();
+ try {
+ Iterator<Map.Entry<StorageKey, Set<String>>> entries = types.entrySet().iterator();
+ List<Class<?>> types = new ArrayList<Class<?>>();
+ while (!Thread.interrupted() && entries.hasNext()) {
+ Map.Entry<StorageKey, Set<String>> entry = entries.next();
+ ClassLoader classLoader = entry.getKey().get();
+ if (classLoader != null || entry.getKey().isBootstrapLoader()) {
+ Iterator<String> iterator = entry.getValue().iterator();
+ while (iterator.hasNext()) {
+ try {
+ Class<?> type = Class.forName(iterator.next(), false, classLoader);
+ try {
+ if (instrumentation.isModifiableClass(type) && matcher.matches(new TypeDescription.ForLoadedType(type),
+ type.getClassLoader(),
+ JavaModule.ofType(type),
+ type,
+ type.getProtectionDomain())) {
+ types.add(type);
+ }
+ } catch (Throwable throwable) {
+ try {
+ listener.onError(TypeDescription.ForLoadedType.getName(type),
+ type.getClassLoader(),
+ JavaModule.ofType(type),
+ AgentBuilder.Listener.LOADED,
+ throwable);
+ } finally {
+ listener.onComplete(TypeDescription.ForLoadedType.getName(type),
+ type.getClassLoader(),
+ JavaModule.ofType(type),
+ AgentBuilder.Listener.LOADED);
+ }
+ }
+ } catch (Throwable ignored) {
+ /* do nothing */
+ } finally {
+ iterator.remove();
+ }
+ }
+ } else {
+ entries.remove();
+ }
+ }
+ if (!types.isEmpty()) {
+ RedefinitionStrategy.Collector collector = redefinitionStrategy.make();
+ collector.include(types);
+ collector.apply(instrumentation,
+ circularityLock,
+ locationStrategy,
+ listener,
+ redefinitionBatchAllocator,
+ redefinitionBatchListener,
+ BatchAllocator.FIRST_BATCH);
+ }
+ } finally {
+ if (release) {
+ circularityLock.release();
+ }
+ }
+ }
+ }
+
+ /**
+ * A key for a class loader that can only be used for looking up a preexisting value but avoids reference management.
+ */
+ protected static class LookupKey {
+
+ /**
+ * The represented class loader.
+ */
+ private final ClassLoader classLoader;
+
+ /**
+ * The represented class loader's hash code or {@code 0} if this entry represents the bootstrap class loader.
+ */
+ private final int hashCode;
+
+ /**
+ * Creates a new lookup key.
+ *
+ * @param classLoader The represented class loader.
+ */
+ protected LookupKey(ClassLoader classLoader) {
+ this.classLoader = classLoader;
+ hashCode = System.identityHashCode(classLoader);
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "EQ_CHECK_FOR_OPERAND_NOT_COMPATIBLE_WITH_THIS", justification = "Cross-comparison is intended")
+ public boolean equals(Object object) {
+ if (this == object) {
+ return true;
+ } else if (object instanceof LookupKey) {
+ return classLoader == ((LookupKey) object).classLoader;
+ } else if (object instanceof StorageKey) {
+ StorageKey storageKey = (StorageKey) object;
+ return hashCode == storageKey.hashCode && classLoader == storageKey.get();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+ }
+
+ /**
+ * A key for a class loader that only weakly references the class loader.
+ */
+ protected static class StorageKey extends WeakReference<ClassLoader> {
+
+ /**
+ * The represented class loader's hash code or {@code 0} if this entry represents the bootstrap class loader.
+ */
+ private final int hashCode;
+
+ /**
+ * Creates a new storage key.
+ *
+ * @param classLoader The represented class loader or {@code null} for the bootstrap class loader.
+ */
+ protected StorageKey(ClassLoader classLoader) {
+ super(classLoader);
+ hashCode = System.identityHashCode(classLoader);
+ }
+
+ /**
+ * Checks if this reference represents the bootstrap class loader.
+ *
+ * @return {@code true} if this entry represents the bootstrap class loader.
+ */
+ protected boolean isBootstrapLoader() {
+ return hashCode == 0;
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "EQ_CHECK_FOR_OPERAND_NOT_COMPATIBLE_WITH_THIS", justification = "Cross-comparison is intended")
+ public boolean equals(Object object) {
+ if (this == object) {
+ return true;
+ } else if (object instanceof LookupKey) {
+ LookupKey lookupKey = (LookupKey) object;
+ return hashCode == lookupKey.hashCode && get() == lookupKey.classLoader;
+ } else if (object instanceof StorageKey) {
+ StorageKey storageKey = (StorageKey) object;
+ return hashCode == storageKey.hashCode && get() == storageKey.get();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+ }
+ }
+
+ /**
+ * Represents an installation of a resubmission strategy.
+ */
+ @EqualsAndHashCode
+ class Installation {
+
+ /**
+ * The listener to apply.
+ */
+ private final AgentBuilder.Listener listener;
+
+ /**
+ * The installation listener to apply.
+ */
+ private final InstallationListener installationListener;
+
+ /**
+ * Creates a new installation.
+ *
+ * @param listener The listener to apply.
+ * @param installationListener The installation listener to apply.
+ */
+ protected Installation(AgentBuilder.Listener listener, InstallationListener installationListener) {
+ this.listener = listener;
+ this.installationListener = installationListener;
+ }
+
+ /**
+ * Returns the listener to apply.
+ *
+ * @return The listener to apply.
+ */
+ protected AgentBuilder.Listener getListener() {
+ return listener;
+ }
+
+ /**
+ * Returns the installation listener to apply.
+ *
+ * @return The installation listener to apply.
+ */
+ protected InstallationListener getInstallationListener() {
+ return installationListener;
+ }
+ }
+ }
+
+ /**
+ * A collector is responsible for collecting classes that are to be considered for modification.
+ */
+ protected abstract static class Collector {
+
+ /**
+ * A representation for a non-available loaded type.
+ */
+ private static final Class<?> NO_LOADED_TYPE = null;
+
+ /**
+ * All types that were collected for redefinition.
+ */
+ protected final List<Class<?>> types;
+
+ /**
+ * Creates a new collector.
+ */
+ protected Collector() {
+ types = new ArrayList<Class<?>>();
+ }
+
+ /**
+ * Does consider the retransformation or redefinition of a loaded type without a loaded type representation.
+ *
+ * @param typeMatcher The type matcher to apply.
+ * @param ignoredTypeMatcher The ignored type matcher to apply.
+ * @param listener The listener to apply during the consideration.
+ * @param typeDescription The type description of the type being considered.
+ * @param type The loaded type being considered.
+ * @param module The type's Java module or {@code null} if the current VM does not support modules.
+ */
+ protected void consider(RawMatcher typeMatcher,
+ RawMatcher ignoredTypeMatcher,
+ AgentBuilder.Listener listener,
+ TypeDescription typeDescription,
+ Class<?> type,
+ JavaModule module) {
+ consider(typeMatcher, ignoredTypeMatcher, listener, typeDescription, type, NO_LOADED_TYPE, module, false);
+ }
+
+ /**
+ * Does consider the retransformation or redefinition of a loaded type.
+ *
+ * @param typeMatcher A type matcher to apply.
+ * @param ignoredTypeMatcher The ignored type matcher to apply.
+ * @param listener The listener to apply during the consideration.
+ * @param typeDescription The type description of the type being considered.
+ * @param type The loaded type being considered.
+ * @param classBeingRedefined The loaded type being considered or {@code null} if it should be considered non-available.
+ * @param module The type's Java module or {@code null} if the current VM does not support modules.
+ * @param unmodifiable {@code true} if the current type should be considered unmodifiable.
+ */
+ protected void consider(RawMatcher typeMatcher,
+ RawMatcher ignoredTypeMatcher,
+ AgentBuilder.Listener listener,
+ TypeDescription typeDescription,
+ Class<?> type,
+ Class<?> classBeingRedefined,
+ JavaModule module,
+ boolean unmodifiable) {
+ if (unmodifiable
+ || ignoredTypeMatcher.matches(typeDescription, type.getClassLoader(), module, classBeingRedefined, type.getProtectionDomain())
+ || !typeMatcher.matches(typeDescription, type.getClassLoader(), module, classBeingRedefined, type.getProtectionDomain())
+ || !types.add(type)) {
+ try {
+ try {
+ listener.onIgnored(typeDescription, type.getClassLoader(), module, classBeingRedefined != null);
+ } finally {
+ listener.onComplete(typeDescription.getName(), type.getClassLoader(), module, classBeingRedefined != null);
+ }
+ } catch (Throwable ignored) {
+ // Ignore exceptions that are thrown by listeners to mimic the behavior of a transformation.
+ }
+ }
+ }
+
+ /**
+ * Includes all the supplied types in this collector.
+ *
+ * @param types The types to include.
+ */
+ protected void include(List<Class<?>> types) {
+ this.types.addAll(types);
+ }
+
+ /**
+ * Applies all types that this collector collected.
+ *
+ * @param instrumentation The instrumentation instance to apply changes to.
+ * @param circularityLock The circularity lock to use.
+ * @param locationStrategy The location strategy to use.
+ * @param listener The listener to use.
+ * @param redefinitionBatchAllocator The redefinition batch allocator to use.
+ * @param redefinitionListener The redefinition listener to use.
+ * @param batch The next batch's index.
+ * @return The next batch's index after this application.
+ */
+ protected int apply(Instrumentation instrumentation,
+ CircularityLock circularityLock,
+ LocationStrategy locationStrategy,
+ AgentBuilder.Listener listener,
+ BatchAllocator redefinitionBatchAllocator,
+ Listener redefinitionListener,
+ int batch) {
+ Map<List<Class<?>>, Throwable> failures = new HashMap<List<Class<?>>, Throwable>();
+ PrependableIterator prepanedableIterator = new PrependableIterator(redefinitionBatchAllocator.batch(this.types));
+ while (prepanedableIterator.hasNext()) {
+ List<Class<?>> types = prepanedableIterator.next();
+ redefinitionListener.onBatch(batch, types, this.types);
+ try {
+ doApply(instrumentation, circularityLock, types, locationStrategy, listener);
+ } catch (Throwable throwable) {
+ prepanedableIterator.prepend(redefinitionListener.onError(batch, types, throwable, this.types));
+ failures.put(types, throwable);
+ }
+ batch += 1;
+ }
+ redefinitionListener.onComplete(batch, types, failures);
+ return batch;
+ }
+
+ /**
+ * Applies this collector.
+ *
+ * @param instrumentation The instrumentation instance to apply the transformation for.
+ * @param circularityLock The circularity lock to use.
+ * @param types The types of the current patch to transform.
+ * @param locationStrategy The location strategy to use.
+ * @param listener the listener to notify.
+ * @throws UnmodifiableClassException If a class is not modifiable.
+ * @throws ClassNotFoundException If a class could not be found.
+ */
+ protected abstract void doApply(Instrumentation instrumentation,
+ CircularityLock circularityLock,
+ List<Class<?>> types,
+ LocationStrategy locationStrategy,
+ AgentBuilder.Listener listener) throws UnmodifiableClassException, ClassNotFoundException;
+
+ /**
+ * An iterator that allows prepending of iterables to be applied previous to another iterator.
+ */
+ protected static class PrependableIterator implements Iterator<List<Class<?>>> {
+
+ /**
+ * The current iterator.
+ */
+ private Iterator<? extends List<Class<?>>> current;
+
+ /**
+ * The backlog of iterators to apply.
+ */
+ private final Deque<Iterator<? extends List<Class<?>>>> backlog;
+
+ /**
+ * Creates a new prependable iterator.
+ *
+ * @param origin The original iterable to begin with.
+ */
+ protected PrependableIterator(Iterable<? extends List<Class<?>>> origin) {
+ current = origin.iterator();
+ backlog = new ArrayDeque<Iterator<? extends List<Class<?>>>>();
+ }
+
+ /**
+ * Prepends an iterable to the backlog.
+ *
+ * @param iterable The iterable to prepend.
+ */
+ public void prepend(Iterable<? extends List<Class<?>>> iterable) {
+ if (current.hasNext()) {
+ backlog.addLast(current);
+ }
+ current = iterable.iterator();
+ }
+
+ @Override
+ public boolean hasNext() {
+ return current.hasNext();
+ }
+
+ @Override
+ public List<Class<?>> next() {
+ try {
+ return current.next();
+ } finally {
+ while (!backlog.isEmpty() && !current.hasNext()) {
+ current = backlog.removeLast();
+ }
+ }
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("remove");
+ }
+ }
+
+ /**
+ * A collector that applies a <b>redefinition</b> of already loaded classes.
+ */
+ protected static class ForRedefinition extends Collector {
+
+ @Override
+ protected void doApply(Instrumentation instrumentation,
+ CircularityLock circularityLock,
+ List<Class<?>> types,
+ LocationStrategy locationStrategy,
+ AgentBuilder.Listener listener) throws UnmodifiableClassException, ClassNotFoundException {
+ List<ClassDefinition> classDefinitions = new ArrayList<ClassDefinition>(types.size());
+ for (Class<?> type : types) {
+ try {
+ try {
+ classDefinitions.add(new ClassDefinition(type, locationStrategy.classFileLocator(type.getClassLoader(), JavaModule.ofType(type))
+ .locate(TypeDescription.ForLoadedType.getName(type))
+ .resolve()));
+ } catch (Throwable throwable) {
+ JavaModule module = JavaModule.ofType(type);
+ try {
+ listener.onError(TypeDescription.ForLoadedType.getName(type), type.getClassLoader(), module, AgentBuilder.Listener.LOADED, throwable);
+ } finally {
+ listener.onComplete(TypeDescription.ForLoadedType.getName(type), type.getClassLoader(), module, AgentBuilder.Listener.LOADED);
+ }
+ }
+ } catch (Throwable ignored) {
+ // Ignore exceptions that are thrown by listeners to mimic the behavior of a transformation.
+ }
+ }
+ if (!classDefinitions.isEmpty()) {
+ circularityLock.release();
+ try {
+ instrumentation.redefineClasses(classDefinitions.toArray(new ClassDefinition[classDefinitions.size()]));
+ } finally {
+ circularityLock.acquire();
+ }
+ }
+ }
+ }
+
+ /**
+ * A collector that applies a <b>retransformation</b> of already loaded classes.
+ */
+ protected static class ForRetransformation extends Collector {
+
+ @Override
+ protected void doApply(Instrumentation instrumentation,
+ CircularityLock circularityLock,
+ List<Class<?>> types,
+ LocationStrategy locationStrategy,
+ AgentBuilder.Listener listener) throws UnmodifiableClassException {
+ if (!types.isEmpty()) {
+ circularityLock.release();
+ try {
+ instrumentation.retransformClasses(types.toArray(new Class<?>[types.size()]));
+ } finally {
+ circularityLock.acquire();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Implements the instrumentation of the {@code LambdaMetafactory} if this feature is enabled.
+ */
+ enum LambdaInstrumentationStrategy {
+
+ /**
+ * A strategy that enables instrumentation of the {@code LambdaMetafactory} if such a factory exists on the current VM.
+ * Classes representing lambda expressions that are created by Byte Buddy are fully compatible to those created by
+ * the JVM and can be serialized or deserialized to one another. The classes do however show a few differences:
+ * <ul>
+ * <li>Byte Buddy's classes are public with a public executing transformer. Doing so, it is not necessary to instantiate a
+ * non-capturing lambda expression by reflection. This is done because Byte Buddy is not necessarily capable
+ * of using reflection due to an active security manager.</li>
+ * <li>Byte Buddy's classes are not marked as synthetic as an agent builder does not instrument synthetic classes
+ * by default.</li>
+ * </ul>
+ */
+ ENABLED {
+ @Override
+ protected void apply(ByteBuddy byteBuddy,
+ Instrumentation instrumentation,
+ ClassFileTransformer classFileTransformer) {
+ if (LambdaFactory.register(classFileTransformer, new LambdaInstanceFactory(byteBuddy))) {
+ Class<?> lambdaMetaFactory;
+ try {
+ lambdaMetaFactory = Class.forName("java.lang.invoke.LambdaMetafactory");
+ } catch (ClassNotFoundException ignored) {
+ return;
+ }
+ byteBuddy.with(Implementation.Context.Disabled.Factory.INSTANCE)
+ .redefine(lambdaMetaFactory)
+ .visit(new AsmVisitorWrapper.ForDeclaredMethods()
+ .method(named("metafactory"), MetaFactoryRedirection.INSTANCE)
+ .method(named("altMetafactory"), AlternativeMetaFactoryRedirection.INSTANCE))
+ .make()
+ .load(lambdaMetaFactory.getClassLoader(), ClassReloadingStrategy.of(instrumentation));
+ }
+ }
+
+ @Override
+ protected boolean isInstrumented(Class<?> type) {
+ return true;
+ }
+ },
+
+ /**
+ * A strategy that does not instrument the {@code LambdaMetafactory}.
+ */
+ DISABLED {
+ @Override
+ protected void apply(ByteBuddy byteBuddy,
+ Instrumentation instrumentation,
+ ClassFileTransformer classFileTransformer) {
+ /* do nothing */
+ }
+
+ @Override
+ protected boolean isInstrumented(Class<?> type) {
+ return type == null || !type.getName().contains("/");
+ }
+ };
+
+ /**
+ * The name of the current VM's {@code Unsafe} class that is visible to the bootstrap loader.
+ */
+ private static final String UNSAFE_CLASS = ClassFileVersion.ofThisVm(ClassFileVersion.JAVA_V6).isAtLeast(ClassFileVersion.JAVA_V9)
+ ? "jdk/internal/misc/Unsafe"
+ : "sun/misc/Unsafe";
+
+ /**
+ * Indicates that an original implementation can be ignored when redefining a method.
+ */
+ protected static final MethodVisitor IGNORE_ORIGINAL = null;
+
+ /**
+ * Releases the supplied class file transformer when it was built with {@link AgentBuilder#with(LambdaInstrumentationStrategy)} enabled.
+ * Subsequently, the class file transformer is no longer applied when a class that represents a lambda expression is created.
+ *
+ * @param classFileTransformer The class file transformer to release.
+ * @param instrumentation The instrumentation instance that is used to potentially rollback the instrumentation of the {@code LambdaMetafactory}.
+ */
+ public static void release(ClassFileTransformer classFileTransformer, Instrumentation instrumentation) {
+ if (LambdaFactory.release(classFileTransformer)) {
+ try {
+ ClassReloadingStrategy.of(instrumentation).reset(Class.forName("java.lang.invoke.LambdaMetafactory"));
+ } catch (Exception exception) {
+ throw new IllegalStateException("Could not release lambda transformer", exception);
+ }
+ }
+ }
+
+ /**
+ * Returns an enabled lambda instrumentation strategy for {@code true}.
+ *
+ * @param enabled If lambda instrumentation should be enabled.
+ * @return {@code true} if the returned strategy should be enabled.
+ */
+ public static LambdaInstrumentationStrategy of(boolean enabled) {
+ return enabled
+ ? ENABLED
+ : DISABLED;
+ }
+
+ /**
+ * Applies a transformation to lambda instances if applicable.
+ *
+ * @param byteBuddy The Byte Buddy instance to use.
+ * @param instrumentation The instrumentation instance for applying a redefinition.
+ * @param classFileTransformer The class file transformer to apply.
+ */
+ protected abstract void apply(ByteBuddy byteBuddy, Instrumentation instrumentation, ClassFileTransformer classFileTransformer);
+
+ /**
+ * Indicates if this strategy enables instrumentation of the {@code LambdaMetafactory}.
+ *
+ * @return {@code true} if this strategy is enabled.
+ */
+ public boolean isEnabled() {
+ return this == ENABLED;
+ }
+
+ /**
+ * Validates if the supplied class is instrumented. For lambda types (which are loaded by anonymous class loader), this method
+ * should return false if lambda instrumentation is disabled.
+ *
+ * @param type The redefined type or {@code null} if no such type exists.
+ * @return {@code true} if the supplied type should be instrumented according to this strategy.
+ */
+ protected abstract boolean isInstrumented(Class<?> type);
+
+ /**
+ * A factory that creates instances that represent lambda expressions.
+ */
+ @EqualsAndHashCode
+ protected static class LambdaInstanceFactory {
+
+ /**
+ * The name of a factory for a lambda expression.
+ */
+ private static final String LAMBDA_FACTORY = "get$Lambda";
+
+ /**
+ * A prefix for a field that represents a property of a lambda expression.
+ */
+ private static final String FIELD_PREFIX = "arg$";
+
+ /**
+ * The infix to use for naming classes that represent lambda expression. The additional prefix
+ * is necessary because the subsequent counter is not sufficient to keep names unique compared
+ * to the original factory.
+ */
+ private static final String LAMBDA_TYPE_INFIX = "$$Lambda$ByteBuddy$";
+
+ /**
+ * A type-safe constant to express that a class is not already loaded when applying a class file transformer.
+ */
+ private static final Class<?> NOT_PREVIOUSLY_DEFINED = null;
+
+ /**
+ * A counter for naming lambda expressions randomly.
+ */
+ private static final AtomicInteger LAMBDA_NAME_COUNTER = new AtomicInteger();
+
+ /**
+ * The Byte Buddy instance to use for creating lambda objects.
+ */
+ private final ByteBuddy byteBuddy;
+
+ /**
+ * Creates a new lambda instance factory.
+ *
+ * @param byteBuddy The Byte Buddy instance to use for creating lambda objects.
+ */
+ protected LambdaInstanceFactory(ByteBuddy byteBuddy) {
+ this.byteBuddy = byteBuddy;
+ }
+
+ /**
+ * Applies this lambda meta factory.
+ *
+ * @param targetTypeLookup A lookup context representing the creating class of this lambda expression.
+ * @param lambdaMethodName The name of the lambda expression's represented method.
+ * @param factoryMethodType The type of the lambda expression's represented method.
+ * @param lambdaMethodType The type of the lambda expression's factory method.
+ * @param targetMethodHandle A handle representing the target of the lambda expression's method.
+ * @param specializedLambdaMethodType A specialization of the type of the lambda expression's represented method.
+ * @param serializable {@code true} if the lambda expression should be serializable.
+ * @param markerInterfaces A list of interfaces for the lambda expression to represent.
+ * @param additionalBridges A list of additional bridge methods to be implemented by the lambda expression.
+ * @param classFileTransformers A collection of class file transformers to apply when creating the class.
+ * @return A binary representation of the transformed class file.
+ */
+ public byte[] make(Object targetTypeLookup,
+ String lambdaMethodName,
+ Object factoryMethodType,
+ Object lambdaMethodType,
+ Object targetMethodHandle,
+ Object specializedLambdaMethodType,
+ boolean serializable,
+ List<Class<?>> markerInterfaces,
+ List<?> additionalBridges,
+ Collection<? extends ClassFileTransformer> classFileTransformers) {
+ JavaConstant.MethodType factoryMethod = JavaConstant.MethodType.ofLoaded(factoryMethodType);
+ JavaConstant.MethodType lambdaMethod = JavaConstant.MethodType.ofLoaded(lambdaMethodType);
+ JavaConstant.MethodHandle targetMethod = JavaConstant.MethodHandle.ofLoaded(targetMethodHandle, targetTypeLookup);
+ JavaConstant.MethodType specializedLambdaMethod = JavaConstant.MethodType.ofLoaded(specializedLambdaMethodType);
+ Class<?> targetType = JavaConstant.MethodHandle.lookupType(targetTypeLookup);
+ String lambdaClassName = targetType.getName() + LAMBDA_TYPE_INFIX + LAMBDA_NAME_COUNTER.incrementAndGet();
+ DynamicType.Builder<?> builder = byteBuddy
+ .subclass(factoryMethod.getReturnType(), ConstructorStrategy.Default.NO_CONSTRUCTORS)
+ .modifiers(TypeManifestation.FINAL, Visibility.PUBLIC)
+ .implement(markerInterfaces)
+ .name(lambdaClassName)
+ .defineConstructor(Visibility.PUBLIC)
+ .withParameters(factoryMethod.getParameterTypes())
+ .intercept(ConstructorImplementation.INSTANCE)
+ .method(named(lambdaMethodName)
+ .and(takesArguments(lambdaMethod.getParameterTypes()))
+ .and(returns(lambdaMethod.getReturnType())))
+ .intercept(new LambdaMethodImplementation(targetMethod, specializedLambdaMethod));
+ int index = 0;
+ for (TypeDescription capturedType : factoryMethod.getParameterTypes()) {
+ builder = builder.defineField(FIELD_PREFIX + ++index, capturedType, Visibility.PRIVATE, FieldManifestation.FINAL);
+ }
+ if (!factoryMethod.getParameterTypes().isEmpty()) {
+ builder = builder.defineMethod(LAMBDA_FACTORY, factoryMethod.getReturnType(), Visibility.PRIVATE, Ownership.STATIC)
+ .withParameters(factoryMethod.getParameterTypes())
+ .intercept(FactoryImplementation.INSTANCE);
+ }
+ if (serializable) {
+ if (!markerInterfaces.contains(Serializable.class)) {
+ builder = builder.implement(Serializable.class);
+ }
+ builder = builder.defineMethod("writeReplace", Object.class, Visibility.PRIVATE)
+ .intercept(new SerializationImplementation(new TypeDescription.ForLoadedType(targetType),
+ factoryMethod.getReturnType(),
+ lambdaMethodName,
+ lambdaMethod,
+ targetMethod,
+ JavaConstant.MethodType.ofLoaded(specializedLambdaMethodType)));
+ } else if (factoryMethod.getReturnType().isAssignableTo(Serializable.class)) {
+ builder = builder.defineMethod("readObject", void.class, Visibility.PRIVATE)
+ .withParameters(ObjectInputStream.class)
+ .throwing(NotSerializableException.class)
+ .intercept(ExceptionMethod.throwing(NotSerializableException.class, "Non-serializable lambda"))
+ .defineMethod("writeObject", void.class, Visibility.PRIVATE)
+ .withParameters(ObjectOutputStream.class)
+ .throwing(NotSerializableException.class)
+ .intercept(ExceptionMethod.throwing(NotSerializableException.class, "Non-serializable lambda"));
+ }
+ for (Object additionalBridgeType : additionalBridges) {
+ JavaConstant.MethodType additionalBridge = JavaConstant.MethodType.ofLoaded(additionalBridgeType);
+ builder = builder.defineMethod(lambdaMethodName, additionalBridge.getReturnType(), MethodManifestation.BRIDGE, Visibility.PUBLIC)
+ .withParameters(additionalBridge.getParameterTypes())
+ .intercept(new BridgeMethodImplementation(lambdaMethodName, lambdaMethod));
+ }
+ byte[] classFile = builder.make().getBytes();
+ for (ClassFileTransformer classFileTransformer : classFileTransformers) {
+ try {
+ byte[] transformedClassFile = classFileTransformer.transform(targetType.getClassLoader(),
+ lambdaClassName.replace('.', '/'),
+ NOT_PREVIOUSLY_DEFINED,
+ targetType.getProtectionDomain(),
+ classFile);
+ classFile = transformedClassFile == null
+ ? classFile
+ : transformedClassFile;
+ } catch (Throwable ignored) {
+ /* do nothing */
+ }
+ }
+ return classFile;
+ }
+
+ /**
+ * Implements a lambda class's executing transformer.
+ */
+ @SuppressFBWarnings(value = "SE_BAD_FIELD", justification = "An enumeration does not serialize fields")
+ protected enum ConstructorImplementation implements Implementation {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ /**
+ * A reference to the {@link Object} class's default executing transformer.
+ */
+ private final MethodDescription.InDefinedShape objectConstructor;
+
+ /**
+ * Creates a new executing transformer implementation.
+ */
+ ConstructorImplementation() {
+ objectConstructor = TypeDescription.OBJECT.getDeclaredMethods().filter(isConstructor()).getOnly();
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return new Appender(implementationTarget.getInstrumentedType().getDeclaredFields());
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ /**
+ * An appender to implement the executing transformer.
+ */
+ @EqualsAndHashCode
+ protected static class Appender implements ByteCodeAppender {
+
+ /**
+ * The fields that are declared by the instrumented type.
+ */
+ private final List<FieldDescription.InDefinedShape> declaredFields;
+
+ /**
+ * Creates a new appender.
+ *
+ * @param declaredFields The fields that are declared by the instrumented type.
+ */
+ protected Appender(List<FieldDescription.InDefinedShape> declaredFields) {
+ this.declaredFields = declaredFields;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ List<StackManipulation> fieldAssignments = new ArrayList<StackManipulation>(declaredFields.size() * 3);
+ for (ParameterDescription parameterDescription : instrumentedMethod.getParameters()) {
+ fieldAssignments.add(MethodVariableAccess.loadThis());
+ fieldAssignments.add(MethodVariableAccess.load(parameterDescription));
+ fieldAssignments.add(FieldAccess.forField(declaredFields.get(parameterDescription.getIndex())).write());
+ }
+ return new Size(new StackManipulation.Compound(
+ MethodVariableAccess.loadThis(),
+ MethodInvocation.invoke(INSTANCE.objectConstructor),
+ new StackManipulation.Compound(fieldAssignments),
+ MethodReturn.VOID
+ ).apply(methodVisitor, implementationContext).getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+ }
+ }
+
+ /**
+ * An implementation of a instance factory for a lambda expression's class.
+ */
+ protected enum FactoryImplementation implements Implementation {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return new Appender(implementationTarget.getInstrumentedType());
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ /**
+ * An appender for a lambda expression factory.
+ */
+ @EqualsAndHashCode
+ protected static class Appender implements ByteCodeAppender {
+
+ /**
+ * The instrumented type.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * Creates a new appender.
+ *
+ * @param instrumentedType The instrumented type.
+ */
+ protected Appender(TypeDescription instrumentedType) {
+ this.instrumentedType = instrumentedType;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ return new Size(new StackManipulation.Compound(
+ TypeCreation.of(instrumentedType),
+ Duplication.SINGLE,
+ MethodVariableAccess.allArgumentsOf(instrumentedMethod),
+ MethodInvocation.invoke(instrumentedType.getDeclaredMethods().filter(isConstructor()).getOnly()),
+ MethodReturn.REFERENCE
+ ).apply(methodVisitor, implementationContext).getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+ }
+ }
+
+ /**
+ * Implements a lambda expression's functional method.
+ */
+ @EqualsAndHashCode
+ protected static class LambdaMethodImplementation implements Implementation {
+
+ /**
+ * The handle of the target method of the lambda expression.
+ */
+ private final JavaConstant.MethodHandle targetMethod;
+
+ /**
+ * The specialized type of the lambda method.
+ */
+ private final JavaConstant.MethodType specializedLambdaMethod;
+
+ /**
+ * Creates a implementation of a lambda expression's functional method.
+ *
+ * @param targetMethod The target method of the lambda expression.
+ * @param specializedLambdaMethod The specialized type of the lambda method.
+ */
+ protected LambdaMethodImplementation(JavaConstant.MethodHandle targetMethod, JavaConstant.MethodType specializedLambdaMethod) {
+ this.targetMethod = targetMethod;
+ this.specializedLambdaMethod = specializedLambdaMethod;
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return new Appender(targetMethod.getOwnerType()
+ .getDeclaredMethods()
+ .filter(hasMethodName(targetMethod.getName())
+ .and(returns(targetMethod.getReturnType()))
+ .and(takesArguments(targetMethod.getParameterTypes())))
+ .getOnly(),
+ specializedLambdaMethod,
+ implementationTarget.getInstrumentedType().getDeclaredFields());
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ /**
+ * An appender for a lambda expression's functional method.
+ */
+ @EqualsAndHashCode
+ protected static class Appender implements ByteCodeAppender {
+
+ /**
+ * The target method of the lambda expression.
+ */
+ private final MethodDescription targetMethod;
+
+ /**
+ * The specialized type of the lambda method.
+ */
+ private final JavaConstant.MethodType specializedLambdaMethod;
+
+ /**
+ * The instrumented type's declared fields.
+ */
+ private final List<FieldDescription.InDefinedShape> declaredFields;
+
+ /**
+ * Creates an appender of a lambda expression's functional method.
+ *
+ * @param targetMethod The target method of the lambda expression.
+ * @param specializedLambdaMethod The specialized type of the lambda method.
+ * @param declaredFields The instrumented type's declared fields.
+ */
+ protected Appender(MethodDescription targetMethod,
+ JavaConstant.MethodType specializedLambdaMethod,
+ List<FieldDescription.InDefinedShape> declaredFields) {
+ this.targetMethod = targetMethod;
+ this.specializedLambdaMethod = specializedLambdaMethod;
+ this.declaredFields = declaredFields;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ StackManipulation preparation = targetMethod.isConstructor()
+ ? new StackManipulation.Compound(TypeCreation.of(targetMethod.getDeclaringType().asErasure()), Duplication.SINGLE)
+ : StackManipulation.Trivial.INSTANCE;
+ List<StackManipulation> fieldAccess = new ArrayList<StackManipulation>(declaredFields.size() * 2 + 1);
+ for (FieldDescription.InDefinedShape fieldDescription : declaredFields) {
+ fieldAccess.add(MethodVariableAccess.loadThis());
+ fieldAccess.add(FieldAccess.forField(fieldDescription).read());
+ }
+ List<StackManipulation> parameterAccess = new ArrayList<StackManipulation>(instrumentedMethod.getParameters().size() * 2);
+ for (ParameterDescription parameterDescription : instrumentedMethod.getParameters()) {
+ parameterAccess.add(MethodVariableAccess.load(parameterDescription));
+ parameterAccess.add(Assigner.DEFAULT.assign(parameterDescription.getType(),
+ specializedLambdaMethod.getParameterTypes().get(parameterDescription.getIndex()).asGenericType(),
+ Assigner.Typing.DYNAMIC));
+ }
+ return new Size(new StackManipulation.Compound(
+ preparation,
+ new StackManipulation.Compound(fieldAccess),
+ new StackManipulation.Compound(parameterAccess),
+ MethodInvocation.invoke(targetMethod),
+ Assigner.DEFAULT.assign(targetMethod.isConstructor()
+ ? targetMethod.getDeclaringType().asGenericType()
+ : targetMethod.getReturnType(),
+ specializedLambdaMethod.getReturnType().asGenericType(),
+ Assigner.Typing.DYNAMIC),
+ MethodReturn.of(specializedLambdaMethod.getReturnType())
+ ).apply(methodVisitor, implementationContext).getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+ }
+ }
+
+ /**
+ * Implements the {@code writeReplace} method for serializable lambda expressions.
+ */
+ @EqualsAndHashCode
+ protected static class SerializationImplementation implements Implementation {
+
+ /**
+ * The lambda expression's declaring type.
+ */
+ private final TypeDescription targetType;
+
+ /**
+ * The lambda expression's functional type.
+ */
+ private final TypeDescription lambdaType;
+
+ /**
+ * The lambda expression's functional method name.
+ */
+ private final String lambdaMethodName;
+
+ /**
+ * The method type of the lambda expression's functional method.
+ */
+ private final JavaConstant.MethodType lambdaMethod;
+
+ /**
+ * A handle that references the lambda expressions invocation target.
+ */
+ private final JavaConstant.MethodHandle targetMethod;
+
+ /**
+ * The specialized method type of the lambda expression's functional method.
+ */
+ private final JavaConstant.MethodType specializedMethod;
+
+ /**
+ * Creates a new implementation for a serializable's lambda expression's {@code writeReplace} method.
+ *
+ * @param targetType The lambda expression's declaring type.
+ * @param lambdaType The lambda expression's functional type.
+ * @param lambdaMethodName The lambda expression's functional method name.
+ * @param lambdaMethod The method type of the lambda expression's functional method.
+ * @param targetMethod A handle that references the lambda expressions invocation target.
+ * @param specializedMethod The specialized method type of the lambda expression's functional method.
+ */
+ protected SerializationImplementation(TypeDescription targetType,
+ TypeDescription lambdaType,
+ String lambdaMethodName,
+ JavaConstant.MethodType lambdaMethod,
+ JavaConstant.MethodHandle targetMethod,
+ JavaConstant.MethodType specializedMethod) {
+ this.targetType = targetType;
+ this.lambdaType = lambdaType;
+ this.lambdaMethodName = lambdaMethodName;
+ this.lambdaMethod = lambdaMethod;
+ this.targetMethod = targetMethod;
+ this.specializedMethod = specializedMethod;
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ TypeDescription serializedLambda;
+ try {
+ serializedLambda = new TypeDescription.ForLoadedType(Class.forName("java.lang.invoke.SerializedLambda"));
+ } catch (ClassNotFoundException exception) {
+ throw new IllegalStateException("Cannot find class for lambda serialization", exception);
+ }
+ List<StackManipulation> lambdaArguments = new ArrayList<StackManipulation>(implementationTarget.getInstrumentedType().getDeclaredFields().size());
+ for (FieldDescription.InDefinedShape fieldDescription : implementationTarget.getInstrumentedType().getDeclaredFields()) {
+ lambdaArguments.add(new StackManipulation.Compound(MethodVariableAccess.loadThis(),
+ FieldAccess.forField(fieldDescription).read(),
+ Assigner.DEFAULT.assign(fieldDescription.getType(), TypeDescription.Generic.OBJECT, Assigner.Typing.STATIC)));
+ }
+ return new ByteCodeAppender.Simple(new StackManipulation.Compound(
+ TypeCreation.of(serializedLambda),
+ Duplication.SINGLE,
+ ClassConstant.of(targetType),
+ new TextConstant(lambdaType.getInternalName()),
+ new TextConstant(lambdaMethodName),
+ new TextConstant(lambdaMethod.getDescriptor()),
+ IntegerConstant.forValue(targetMethod.getHandleType().getIdentifier()),
+ new TextConstant(targetMethod.getOwnerType().getInternalName()),
+ new TextConstant(targetMethod.getName()),
+ new TextConstant(targetMethod.getDescriptor()),
+ new TextConstant(specializedMethod.getDescriptor()),
+ ArrayFactory.forType(TypeDescription.Generic.OBJECT).withValues(lambdaArguments),
+ MethodInvocation.invoke(serializedLambda.getDeclaredMethods().filter(isConstructor()).getOnly()),
+ MethodReturn.REFERENCE
+ ));
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+ }
+
+ /**
+ * Implements an explicit bridge method for a lambda expression.
+ */
+ @EqualsAndHashCode
+ protected static class BridgeMethodImplementation implements Implementation {
+
+ /**
+ * The name of the lambda expression's functional method.
+ */
+ private final String lambdaMethodName;
+
+ /**
+ * The actual type of the lambda expression's functional method.
+ */
+ private final JavaConstant.MethodType lambdaMethod;
+
+ /**
+ * Creates a new bridge method implementation for a lambda expression.
+ *
+ * @param lambdaMethodName The name of the lambda expression's functional method.
+ * @param lambdaMethod The actual type of the lambda expression's functional method.
+ */
+ protected BridgeMethodImplementation(String lambdaMethodName, JavaConstant.MethodType lambdaMethod) {
+ this.lambdaMethodName = lambdaMethodName;
+ this.lambdaMethod = lambdaMethod;
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return new Appender(implementationTarget.invokeSuper(new MethodDescription.SignatureToken(lambdaMethodName,
+ lambdaMethod.getReturnType(),
+ lambdaMethod.getParameterTypes())));
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ /**
+ * An appender for implementing a bridge method for a lambda expression.
+ */
+ @EqualsAndHashCode
+ protected static class Appender implements ByteCodeAppender {
+
+ /**
+ * The invocation of the bridge's target method.
+ */
+ private final SpecialMethodInvocation bridgeTargetInvocation;
+
+ /**
+ * Creates a new appender for invoking a lambda expression's bridge method target.
+ *
+ * @param bridgeTargetInvocation The invocation of the bridge's target method.
+ */
+ protected Appender(SpecialMethodInvocation bridgeTargetInvocation) {
+ this.bridgeTargetInvocation = bridgeTargetInvocation;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ return new Compound(new Simple(
+ MethodVariableAccess.allArgumentsOf(instrumentedMethod)
+ .asBridgeOf(bridgeTargetInvocation.getMethodDescription())
+ .prependThisReference(),
+ bridgeTargetInvocation,
+ bridgeTargetInvocation.getMethodDescription().getReturnType().asErasure().isAssignableTo(instrumentedMethod.getReturnType().asErasure())
+ ? StackManipulation.Trivial.INSTANCE
+ : TypeCasting.to(instrumentedMethod.getReceiverType()),
+ MethodReturn.of(instrumentedMethod.getReturnType())
+
+ )).apply(methodVisitor, implementationContext, instrumentedMethod);
+ }
+ }
+ }
+ }
+
+ /**
+ * Implements the regular lambda meta factory. The implementation represents the following code:
+ * <blockquote><pre>
+ * public static CallSite metafactory(MethodHandles.Lookup caller,
+ * String invokedName,
+ * MethodType invokedType,
+ * MethodType samMethodType,
+ * MethodHandle implMethod,
+ * MethodType instantiatedMethodType) throws Exception {
+ * Unsafe unsafe = Unsafe.getUnsafe();
+ * {@code Class<?>} lambdaClass = unsafe.defineAnonymousClass(caller.lookupClass(),
+ * (byte[]) ClassLoader.getSystemClassLoader().loadClass("net.bytebuddy.agent.builder.LambdaFactory").getDeclaredMethod("make",
+ * Object.class,
+ * String.class,
+ * Object.class,
+ * Object.class,
+ * Object.class,
+ * Object.class,
+ * boolean.class,
+ * List.class,
+ * List.class).invoke(null,
+ * caller,
+ * invokedName,
+ * invokedType,
+ * samMethodType,
+ * implMethod,
+ * instantiatedMethodType,
+ * false,
+ * Collections.emptyList(),
+ * Collections.emptyList()),
+ * null);
+ * unsafe.ensureClassInitialized(lambdaClass);
+ * return invokedType.parameterCount() == 0
+ * ? new ConstantCallSite(MethodHandles.constant(invokedType.returnType(), lambdaClass.getDeclaredConstructors()[0].newInstance()))
+ * : new ConstantCallSite(MethodHandles.Lookup.IMPL_LOOKUP.findStatic(lambdaClass, "get$Lambda", invokedType));
+ * </pre></blockquote>
+ */
+ protected enum MetaFactoryRedirection implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public MethodVisitor wrap(TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ TypePool typePool,
+ int writerFlags,
+ int readerFlags) {
+ methodVisitor.visitCode();
+ methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, UNSAFE_CLASS, "getUnsafe", "()L" + UNSAFE_CLASS + ";", false);
+ methodVisitor.visitVarInsn(Opcodes.ASTORE, 6);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 6);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "lookupClass", "()Ljava/lang/Class;", false);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/ClassLoader", "getSystemClassLoader", "()Ljava/lang/ClassLoader;", false);
+ methodVisitor.visitLdcInsn("net.bytebuddy.agent.builder.LambdaFactory");
+ methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/ClassLoader", "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;", false);
+ methodVisitor.visitLdcInsn("make");
+ methodVisitor.visitIntInsn(Opcodes.BIPUSH, 9);
+ methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Class");
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitInsn(Opcodes.ICONST_0);
+ methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;"));
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitInsn(Opcodes.ICONST_1);
+ methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/String;"));
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitInsn(Opcodes.ICONST_2);
+ methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;"));
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitInsn(Opcodes.ICONST_3);
+ methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;"));
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitInsn(Opcodes.ICONST_4);
+ methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;"));
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitInsn(Opcodes.ICONST_5);
+ methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;"));
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitIntInsn(Opcodes.BIPUSH, 6);
+ methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Boolean", "TYPE", "Ljava/lang/Class;");
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitIntInsn(Opcodes.BIPUSH, 7);
+ methodVisitor.visitLdcInsn(Type.getType("Ljava/util/List;"));
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitIntInsn(Opcodes.BIPUSH, 8);
+ methodVisitor.visitLdcInsn(Type.getType("Ljava/util/List;"));
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getDeclaredMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", false);
+ methodVisitor.visitInsn(Opcodes.ACONST_NULL);
+ methodVisitor.visitIntInsn(Opcodes.BIPUSH, 9);
+ methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitInsn(Opcodes.ICONST_0);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitInsn(Opcodes.ICONST_1);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitInsn(Opcodes.ICONST_2);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 2);
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitInsn(Opcodes.ICONST_3);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 3);
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitInsn(Opcodes.ICONST_4);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 4);
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitInsn(Opcodes.ICONST_5);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 5);
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitIntInsn(Opcodes.BIPUSH, 6);
+ methodVisitor.visitInsn(Opcodes.ICONST_0);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitIntInsn(Opcodes.BIPUSH, 7);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Collections", "emptyList", "()Ljava/util/List;", false);
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitIntInsn(Opcodes.BIPUSH, 8);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Collections", "emptyList", "()Ljava/util/List;", false);
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", false);
+ methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, "[B");
+ methodVisitor.visitInsn(Opcodes.ACONST_NULL);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, UNSAFE_CLASS, "defineAnonymousClass", "(Ljava/lang/Class;[B[Ljava/lang/Object;)Ljava/lang/Class;", false);
+ methodVisitor.visitVarInsn(Opcodes.ASTORE, 7);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 6);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 7);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, UNSAFE_CLASS, "ensureClassInitialized", "(Ljava/lang/Class;)V", false);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 2);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodType", "parameterCount", "()I", false);
+ Label conditionalDefault = new Label();
+ methodVisitor.visitJumpInsn(Opcodes.IFNE, conditionalDefault);
+ methodVisitor.visitTypeInsn(Opcodes.NEW, "java/lang/invoke/ConstantCallSite");
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 2);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodType", "returnType", "()Ljava/lang/Class;", false);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 7);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getDeclaredConstructors", "()[Ljava/lang/reflect/Constructor;", false);
+ methodVisitor.visitInsn(Opcodes.ICONST_0);
+ methodVisitor.visitInsn(Opcodes.AALOAD);
+ methodVisitor.visitInsn(Opcodes.ICONST_0);
+ methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
+ methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/reflect/Constructor", "newInstance", "([Ljava/lang/Object;)Ljava/lang/Object;", false);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/invoke/MethodHandles", "constant", "(Ljava/lang/Class;Ljava/lang/Object;)Ljava/lang/invoke/MethodHandle;", false);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/invoke/ConstantCallSite", "<init>", "(Ljava/lang/invoke/MethodHandle;)V", false);
+ Label conditionalAlternative = new Label();
+ methodVisitor.visitJumpInsn(Opcodes.GOTO, conditionalAlternative);
+ methodVisitor.visitLabel(conditionalDefault);
+ methodVisitor.visitFrame(Opcodes.F_APPEND, 2, new Object[]{UNSAFE_CLASS, "java/lang/Class"}, 0, null);
+ methodVisitor.visitTypeInsn(Opcodes.NEW, "java/lang/invoke/ConstantCallSite");
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/invoke/MethodHandles$Lookup", "IMPL_LOOKUP", "Ljava/lang/invoke/MethodHandles$Lookup;");
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 7);
+ methodVisitor.visitLdcInsn("get$Lambda");
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 2);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "findStatic", "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;", false);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/invoke/ConstantCallSite", "<init>", "(Ljava/lang/invoke/MethodHandle;)V", false);
+ methodVisitor.visitLabel(conditionalAlternative);
+ methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/invoke/CallSite"});
+ methodVisitor.visitInsn(Opcodes.ARETURN);
+ methodVisitor.visitMaxs(8, 8);
+ methodVisitor.visitEnd();
+ return IGNORE_ORIGINAL;
+ }
+ }
+
+ /**
+ * Implements the alternative lambda meta factory. The implementation represents the following code:
+ * <blockquote><pre>
+ * public static CallSite altMetafactory(MethodHandles.Lookup caller,
+ * String invokedName,
+ * MethodType invokedType,
+ * Object... args) throws Exception {
+ * int flags = (Integer) args[3];
+ * int argIndex = 4;
+ * {@code Class<?>[]} markerInterface;
+ * if ((flags {@code &} FLAG_MARKERS) != 0) {
+ * int markerCount = (Integer) args[argIndex++];
+ * markerInterface = new {@code Class<?>}[markerCount];
+ * System.arraycopy(args, argIndex, markerInterface, 0, markerCount);
+ * argIndex += markerCount;
+ * } else {
+ * markerInterface = new {@code Class<?>}[0];
+ * }
+ * MethodType[] additionalBridge;
+ * if ((flags {@code &} FLAG_BRIDGES) != 0) {
+ * int bridgeCount = (Integer) args[argIndex++];
+ * additionalBridge = new MethodType[bridgeCount];
+ * System.arraycopy(args, argIndex, additionalBridge, 0, bridgeCount);
+ * // argIndex += bridgeCount;
+ * } else {
+ * additionalBridge = new MethodType[0];
+ * }
+ * Unsafe unsafe = Unsafe.getUnsafe();
+ * {@code Class<?>} lambdaClass = unsafe.defineAnonymousClass(caller.lookupClass(),
+ * (byte[]) ClassLoader.getSystemClassLoader().loadClass("net.bytebuddy.agent.builder.LambdaFactory").getDeclaredMethod("make",
+ * Object.class,
+ * String.class,
+ * Object.class,
+ * Object.class,
+ * Object.class,
+ * Object.class,
+ * boolean.class,
+ * List.class,
+ * List.class).invoke(null,
+ * caller,
+ * invokedName,
+ * invokedType,
+ * args[0],
+ * args[1],
+ * args[2],
+ * (flags {@code &} FLAG_SERIALIZABLE) != 0,
+ * Arrays.asList(markerInterface),
+ * Arrays.asList(additionalBridge)),
+ * null);
+ * unsafe.ensureClassInitialized(lambdaClass);
+ * return invokedType.parameterCount() == 0
+ * ? new ConstantCallSite(MethodHandles.constant(invokedType.returnType(), lambdaClass.getDeclaredConstructors()[0].newInstance()))
+ * : new ConstantCallSite(MethodHandles.Lookup.IMPL_LOOKUP.findStatic(lambdaClass, "get$Lambda", invokedType));
+ * }
+ * </pre></blockquote>
+ */
+ protected enum AlternativeMetaFactoryRedirection implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public MethodVisitor wrap(TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ TypePool typePool,
+ int writerFlags,
+ int readerFlags) {
+ methodVisitor.visitCode();
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 3);
+ methodVisitor.visitInsn(Opcodes.ICONST_3);
+ methodVisitor.visitInsn(Opcodes.AALOAD);
+ methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Integer");
+ methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false);
+ methodVisitor.visitVarInsn(Opcodes.ISTORE, 4);
+ methodVisitor.visitInsn(Opcodes.ICONST_4);
+ methodVisitor.visitVarInsn(Opcodes.ISTORE, 5);
+ methodVisitor.visitVarInsn(Opcodes.ILOAD, 4);
+ methodVisitor.visitInsn(Opcodes.ICONST_2);
+ methodVisitor.visitInsn(Opcodes.IAND);
+ Label markerInterfaceLoop = new Label();
+ methodVisitor.visitJumpInsn(Opcodes.IFEQ, markerInterfaceLoop);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 3);
+ methodVisitor.visitVarInsn(Opcodes.ILOAD, 5);
+ methodVisitor.visitIincInsn(5, 1);
+ methodVisitor.visitInsn(Opcodes.AALOAD);
+ methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Integer");
+ methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false);
+ methodVisitor.visitVarInsn(Opcodes.ISTORE, 7);
+ methodVisitor.visitVarInsn(Opcodes.ILOAD, 7);
+ methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Class");
+ methodVisitor.visitVarInsn(Opcodes.ASTORE, 6);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 3);
+ methodVisitor.visitVarInsn(Opcodes.ILOAD, 5);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 6);
+ methodVisitor.visitInsn(Opcodes.ICONST_0);
+ methodVisitor.visitVarInsn(Opcodes.ILOAD, 7);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "arraycopy", "(Ljava/lang/Object;ILjava/lang/Object;II)V", false);
+ methodVisitor.visitVarInsn(Opcodes.ILOAD, 5);
+ methodVisitor.visitVarInsn(Opcodes.ILOAD, 7);
+ methodVisitor.visitInsn(Opcodes.IADD);
+ methodVisitor.visitVarInsn(Opcodes.ISTORE, 5);
+ Label markerInterfaceExit = new Label();
+ methodVisitor.visitJumpInsn(Opcodes.GOTO, markerInterfaceExit);
+ methodVisitor.visitLabel(markerInterfaceLoop);
+ methodVisitor.visitFrame(Opcodes.F_APPEND, 2, new Object[]{Opcodes.INTEGER, Opcodes.INTEGER}, 0, null);
+ methodVisitor.visitInsn(Opcodes.ICONST_0);
+ methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Class");
+ methodVisitor.visitVarInsn(Opcodes.ASTORE, 6);
+ methodVisitor.visitLabel(markerInterfaceExit);
+ methodVisitor.visitFrame(Opcodes.F_APPEND, 1, new Object[]{"[Ljava/lang/Class;"}, 0, null);
+ methodVisitor.visitVarInsn(Opcodes.ILOAD, 4);
+ methodVisitor.visitInsn(Opcodes.ICONST_4);
+ methodVisitor.visitInsn(Opcodes.IAND);
+ Label additionalBridgesLoop = new Label();
+ methodVisitor.visitJumpInsn(Opcodes.IFEQ, additionalBridgesLoop);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 3);
+ methodVisitor.visitVarInsn(Opcodes.ILOAD, 5);
+ methodVisitor.visitIincInsn(5, 1);
+ methodVisitor.visitInsn(Opcodes.AALOAD);
+ methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Integer");
+ methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false);
+ methodVisitor.visitVarInsn(Opcodes.ISTORE, 8);
+ methodVisitor.visitVarInsn(Opcodes.ILOAD, 8);
+ methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/invoke/MethodType");
+ methodVisitor.visitVarInsn(Opcodes.ASTORE, 7);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 3);
+ methodVisitor.visitVarInsn(Opcodes.ILOAD, 5);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 7);
+ methodVisitor.visitInsn(Opcodes.ICONST_0);
+ methodVisitor.visitVarInsn(Opcodes.ILOAD, 8);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "arraycopy", "(Ljava/lang/Object;ILjava/lang/Object;II)V", false);
+ Label additionalBridgesExit = new Label();
+ methodVisitor.visitJumpInsn(Opcodes.GOTO, additionalBridgesExit);
+ methodVisitor.visitLabel(additionalBridgesLoop);
+ methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ methodVisitor.visitInsn(Opcodes.ICONST_0);
+ methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/invoke/MethodType");
+ methodVisitor.visitVarInsn(Opcodes.ASTORE, 7);
+ methodVisitor.visitLabel(additionalBridgesExit);
+ methodVisitor.visitFrame(Opcodes.F_APPEND, 1, new Object[]{"[Ljava/lang/invoke/MethodType;"}, 0, null);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, UNSAFE_CLASS, "getUnsafe", "()L" + UNSAFE_CLASS + ";", false);
+ methodVisitor.visitVarInsn(Opcodes.ASTORE, 8);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 8);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "lookupClass", "()Ljava/lang/Class;", false);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/ClassLoader", "getSystemClassLoader", "()Ljava/lang/ClassLoader;", false);
+ methodVisitor.visitLdcInsn("net.bytebuddy.agent.builder.LambdaFactory");
+ methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/ClassLoader", "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;", false);
+ methodVisitor.visitLdcInsn("make");
+ methodVisitor.visitIntInsn(Opcodes.BIPUSH, 9);
+ methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Class");
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitInsn(Opcodes.ICONST_0);
+ methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;"));
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitInsn(Opcodes.ICONST_1);
+ methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/String;"));
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitInsn(Opcodes.ICONST_2);
+ methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;"));
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitInsn(Opcodes.ICONST_3);
+ methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;"));
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitInsn(Opcodes.ICONST_4);
+ methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;"));
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitInsn(Opcodes.ICONST_5);
+ methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/Object;"));
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitIntInsn(Opcodes.BIPUSH, 6);
+ methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Boolean", "TYPE", "Ljava/lang/Class;");
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitIntInsn(Opcodes.BIPUSH, 7);
+ methodVisitor.visitLdcInsn(Type.getType("Ljava/util/List;"));
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitIntInsn(Opcodes.BIPUSH, 8);
+ methodVisitor.visitLdcInsn(Type.getType("Ljava/util/List;"));
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getDeclaredMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", false);
+ methodVisitor.visitInsn(Opcodes.ACONST_NULL);
+ methodVisitor.visitIntInsn(Opcodes.BIPUSH, 9);
+ methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitInsn(Opcodes.ICONST_0);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitInsn(Opcodes.ICONST_1);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitInsn(Opcodes.ICONST_2);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 2);
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitInsn(Opcodes.ICONST_3);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 3);
+ methodVisitor.visitInsn(Opcodes.ICONST_0);
+ methodVisitor.visitInsn(Opcodes.AALOAD);
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitInsn(Opcodes.ICONST_4);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 3);
+ methodVisitor.visitInsn(Opcodes.ICONST_1);
+ methodVisitor.visitInsn(Opcodes.AALOAD);
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitInsn(Opcodes.ICONST_5);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 3);
+ methodVisitor.visitInsn(Opcodes.ICONST_2);
+ methodVisitor.visitInsn(Opcodes.AALOAD);
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitIntInsn(Opcodes.BIPUSH, 6);
+ methodVisitor.visitVarInsn(Opcodes.ILOAD, 4);
+ methodVisitor.visitInsn(Opcodes.ICONST_1);
+ methodVisitor.visitInsn(Opcodes.IAND);
+ Label callSiteConditional = new Label();
+ methodVisitor.visitJumpInsn(Opcodes.IFEQ, callSiteConditional);
+ methodVisitor.visitInsn(Opcodes.ICONST_1);
+ Label callSiteAlternative = new Label();
+ methodVisitor.visitJumpInsn(Opcodes.GOTO, callSiteAlternative);
+ methodVisitor.visitLabel(callSiteConditional);
+ methodVisitor.visitFrame(Opcodes.F_FULL, 9, new Object[]{"java/lang/invoke/MethodHandles$Lookup", "java/lang/String", "java/lang/invoke/MethodType", "[Ljava/lang/Object;", Opcodes.INTEGER, Opcodes.INTEGER, "[Ljava/lang/Class;", "[Ljava/lang/invoke/MethodType;", UNSAFE_CLASS}, 7, new Object[]{UNSAFE_CLASS, "java/lang/Class", "java/lang/reflect/Method", Opcodes.NULL, "[Ljava/lang/Object;", "[Ljava/lang/Object;", Opcodes.INTEGER});
+ methodVisitor.visitInsn(Opcodes.ICONST_0);
+ methodVisitor.visitLabel(callSiteAlternative);
+ methodVisitor.visitFrame(Opcodes.F_FULL, 9, new Object[]{"java/lang/invoke/MethodHandles$Lookup", "java/lang/String", "java/lang/invoke/MethodType", "[Ljava/lang/Object;", Opcodes.INTEGER, Opcodes.INTEGER, "[Ljava/lang/Class;", "[Ljava/lang/invoke/MethodType;", UNSAFE_CLASS}, 8, new Object[]{UNSAFE_CLASS, "java/lang/Class", "java/lang/reflect/Method", Opcodes.NULL, "[Ljava/lang/Object;", "[Ljava/lang/Object;", Opcodes.INTEGER, Opcodes.INTEGER});
+ methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitIntInsn(Opcodes.BIPUSH, 7);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 6);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "asList", "([Ljava/lang/Object;)Ljava/util/List;", false);
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitIntInsn(Opcodes.BIPUSH, 8);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 7);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "asList", "([Ljava/lang/Object;)Ljava/util/List;", false);
+ methodVisitor.visitInsn(Opcodes.AASTORE);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", false);
+ methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, "[B");
+ methodVisitor.visitInsn(Opcodes.ACONST_NULL);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, UNSAFE_CLASS, "defineAnonymousClass", "(Ljava/lang/Class;[B[Ljava/lang/Object;)Ljava/lang/Class;", false);
+ methodVisitor.visitVarInsn(Opcodes.ASTORE, 9);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 8);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 9);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, UNSAFE_CLASS, "ensureClassInitialized", "(Ljava/lang/Class;)V", false);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 2);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodType", "parameterCount", "()I", false);
+ Label callSiteJump = new Label();
+ methodVisitor.visitJumpInsn(Opcodes.IFNE, callSiteJump);
+ methodVisitor.visitTypeInsn(Opcodes.NEW, "java/lang/invoke/ConstantCallSite");
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 2);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodType", "returnType", "()Ljava/lang/Class;", false);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 9);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getDeclaredConstructors", "()[Ljava/lang/reflect/Constructor;", false);
+ methodVisitor.visitInsn(Opcodes.ICONST_0);
+ methodVisitor.visitInsn(Opcodes.AALOAD);
+ methodVisitor.visitInsn(Opcodes.ICONST_0);
+ methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
+ methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/reflect/Constructor", "newInstance", "([Ljava/lang/Object;)Ljava/lang/Object;", false);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/invoke/MethodHandles", "constant", "(Ljava/lang/Class;Ljava/lang/Object;)Ljava/lang/invoke/MethodHandle;", false);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/invoke/ConstantCallSite", "<init>", "(Ljava/lang/invoke/MethodHandle;)V", false);
+ Label callSiteExit = new Label();
+ methodVisitor.visitJumpInsn(Opcodes.GOTO, callSiteExit);
+ methodVisitor.visitLabel(callSiteJump);
+ methodVisitor.visitFrame(Opcodes.F_APPEND, 1, new Object[]{"java/lang/Class"}, 0, null);
+ methodVisitor.visitTypeInsn(Opcodes.NEW, "java/lang/invoke/ConstantCallSite");
+ methodVisitor.visitInsn(Opcodes.DUP);
+ methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/invoke/MethodHandles$Lookup", "IMPL_LOOKUP", "Ljava/lang/invoke/MethodHandles$Lookup;");
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 9);
+ methodVisitor.visitLdcInsn("get$Lambda");
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 2);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "findStatic", "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;", false);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/invoke/ConstantCallSite", "<init>", "(Ljava/lang/invoke/MethodHandle;)V", false);
+ methodVisitor.visitLabel(callSiteExit);
+ methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/invoke/CallSite"});
+ methodVisitor.visitInsn(Opcodes.ARETURN);
+ methodVisitor.visitMaxs(9, 10);
+ methodVisitor.visitEnd();
+ return IGNORE_ORIGINAL;
+ }
+ }
+ }
+
+ /**
+ * <p>
+ * The default implementation of an {@link net.bytebuddy.agent.builder.AgentBuilder}.
+ * </p>
+ * <p>
+ * By default, Byte Buddy ignores any types loaded by the bootstrap class loader and
+ * any synthetic type. Self-injection and rebasing is enabled. In order to avoid class format changes, set
+ * {@link AgentBuilder#disableBootstrapInjection()}). All types are parsed without their debugging information ({@link PoolStrategy.Default#FAST}).
+ * </p>
+ */
+ @EqualsAndHashCode
+ class Default implements AgentBuilder {
+
+ /**
+ * The name of the Byte Buddy {@code net.bytebuddy.agent.Installer} class.
+ */
+ private static final String INSTALLER_TYPE = "net.bytebuddy.agent.Installer";
+
+ /**
+ * The name of the {@code net.bytebuddy.agent.Installer} getter for reading an installed {@link Instrumentation}.
+ */
+ private static final String INSTRUMENTATION_GETTER = "getInstrumentation";
+
+ /**
+ * Indicator for access to a static member via reflection to make the code more readable.
+ */
+ private static final Object STATIC_MEMBER = null;
+
+ /**
+ * The value that is to be returned from a {@link java.lang.instrument.ClassFileTransformer} to indicate
+ * that no class file transformation is to be applied.
+ */
+ private static final byte[] NO_TRANSFORMATION = null;
+
+ /**
+ * Indicates that a loaded type should be considered as non-available.
+ */
+ private static final Class<?> NO_LOADED_TYPE = null;
+
+ /**
+ * The default circularity lock that assures that no agent created by any agent builder within this
+ * class loader causes a class loading circularity.
+ */
+ private static final CircularityLock DEFAULT_LOCK = new CircularityLock.Default();
+
+ /**
+ * The {@link net.bytebuddy.ByteBuddy} instance to be used.
+ */
+ protected final ByteBuddy byteBuddy;
+
+ /**
+ * The listener to notify on transformations.
+ */
+ protected final Listener listener;
+
+ /**
+ * The circularity lock to use.
+ */
+ protected final CircularityLock circularityLock;
+
+ /**
+ * The type locator to use.
+ */
+ protected final PoolStrategy poolStrategy;
+
+ /**
+ * The definition handler to use.
+ */
+ protected final TypeStrategy typeStrategy;
+
+ /**
+ * The location strategy to use.
+ */
+ protected final LocationStrategy locationStrategy;
+
+ /**
+ * The native method strategy to use.
+ */
+ protected final NativeMethodStrategy nativeMethodStrategy;
+
+ /**
+ * The initialization strategy to use for creating classes.
+ */
+ protected final InitializationStrategy initializationStrategy;
+
+ /**
+ * The redefinition strategy to apply.
+ */
+ protected final RedefinitionStrategy redefinitionStrategy;
+
+ /**
+ * The discovery strategy for loaded types to be redefined.
+ */
+ protected final RedefinitionStrategy.DiscoveryStrategy redefinitionDiscoveryStrategy;
+
+ /**
+ * The batch allocator for the redefinition strategy to apply.
+ */
+ protected final RedefinitionStrategy.BatchAllocator redefinitionBatchAllocator;
+
+ /**
+ * The redefinition listener for the redefinition strategy to apply.
+ */
+ protected final RedefinitionStrategy.Listener redefinitionListener;
+
+ /**
+ * The resubmission strategy to apply.
+ */
+ protected final RedefinitionStrategy.ResubmissionStrategy redefinitionResubmissionStrategy;
+
+ /**
+ * The injection strategy for injecting classes into the bootstrap class loader.
+ */
+ protected final BootstrapInjectionStrategy bootstrapInjectionStrategy;
+
+ /**
+ * A strategy to determine of the {@code LambdaMetafactory} should be instrumented to allow for the instrumentation
+ * of classes that represent lambda expressions.
+ */
+ protected final LambdaInstrumentationStrategy lambdaInstrumentationStrategy;
+
+ /**
+ * The description strategy for resolving type descriptions for types.
+ */
+ protected final DescriptionStrategy descriptionStrategy;
+
+ /**
+ * The fallback strategy to apply.
+ */
+ protected final FallbackStrategy fallbackStrategy;
+
+ /**
+ * The installation listener to notify.
+ */
+ protected final InstallationListener installationListener;
+
+ /**
+ * Identifies types that should not be instrumented.
+ */
+ protected final RawMatcher ignoredTypeMatcher;
+
+ /**
+ * The transformation object for handling type transformations.
+ */
+ protected final Transformation transformation;
+
+ /**
+ * Creates a new default agent builder that uses a default {@link net.bytebuddy.ByteBuddy} instance for creating classes.
+ */
+ public Default() {
+ this(new ByteBuddy());
+ }
+
+ /**
+ * Creates a new agent builder with default settings. By default, Byte Buddy ignores any types loaded by the bootstrap class loader, any
+ * type within a {@code net.bytebuddy} package and any synthetic type. Self-injection and rebasing is enabled. In order to avoid class format
+ * changes, set {@link AgentBuilder#disableBootstrapInjection()}). All types are parsed without their debugging information
+ * ({@link PoolStrategy.Default#FAST}).
+ *
+ * @param byteBuddy The Byte Buddy instance to be used.
+ */
+ public Default(ByteBuddy byteBuddy) {
+ this(byteBuddy,
+ Listener.NoOp.INSTANCE,
+ DEFAULT_LOCK,
+ PoolStrategy.Default.FAST,
+ TypeStrategy.Default.REBASE,
+ LocationStrategy.ForClassLoader.STRONG,
+ NativeMethodStrategy.Disabled.INSTANCE,
+ new InitializationStrategy.SelfInjection.Split(),
+ RedefinitionStrategy.DISABLED,
+ RedefinitionStrategy.DiscoveryStrategy.SinglePass.INSTANCE,
+ RedefinitionStrategy.BatchAllocator.ForTotal.INSTANCE,
+ RedefinitionStrategy.Listener.NoOp.INSTANCE,
+ RedefinitionStrategy.ResubmissionStrategy.Disabled.INSTANCE,
+ BootstrapInjectionStrategy.Disabled.INSTANCE,
+ LambdaInstrumentationStrategy.DISABLED,
+ DescriptionStrategy.Default.HYBRID,
+ FallbackStrategy.ByThrowableType.ofOptionalTypes(),
+ InstallationListener.NoOp.INSTANCE,
+ new RawMatcher.Disjunction(
+ new RawMatcher.ForElementMatchers(any(), isBootstrapClassLoader()),
+ new RawMatcher.ForElementMatchers(nameStartsWith("net.bytebuddy.").or(nameStartsWith("sun.reflect.")).<TypeDescription>or(isSynthetic()))),
+ Transformation.Ignored.INSTANCE);
+ }
+
+ /**
+ * Creates a new default agent builder.
+ *
+ * @param byteBuddy The Byte Buddy instance to be used.
+ * @param listener The listener to notify on transformations.
+ * @param circularityLock The circularity lock to use.
+ * @param poolStrategy The type locator to use.
+ * @param typeStrategy The definition handler to use.
+ * @param locationStrategy The location strategy to use.
+ * @param nativeMethodStrategy The native method strategy to apply.
+ * @param initializationStrategy The initialization strategy to use for transformed types.
+ * @param redefinitionStrategy The redefinition strategy to apply.
+ * @param redefinitionDiscoveryStrategy The discovery strategy for loaded types to be redefined.
+ * @param redefinitionBatchAllocator The batch allocator for the redefinition strategy to apply.
+ * @param redefinitionListener The redefinition listener for the redefinition strategy to apply.
+ * @param redefinitionResubmissionStrategy The resubmission strategy to apply.
+ * @param bootstrapInjectionStrategy The injection strategy for injecting classes into the bootstrap class loader.
+ * @param lambdaInstrumentationStrategy A strategy to determine of the {@code LambdaMetafactory} should be instrumented to allow for the
+ * instrumentation of classes that represent lambda expressions.
+ * @param descriptionStrategy The description strategy for resolving type descriptions for types.
+ * @param fallbackStrategy The fallback strategy to apply.
+ * @param installationListener The installation listener to notify.
+ * @param ignoredTypeMatcher Identifies types that should not be instrumented.
+ * @param transformation The transformation object for handling type transformations.
+ */
+ protected Default(ByteBuddy byteBuddy,
+ Listener listener,
+ CircularityLock circularityLock,
+ PoolStrategy poolStrategy,
+ TypeStrategy typeStrategy,
+ LocationStrategy locationStrategy,
+ NativeMethodStrategy nativeMethodStrategy,
+ InitializationStrategy initializationStrategy,
+ RedefinitionStrategy redefinitionStrategy,
+ RedefinitionStrategy.DiscoveryStrategy redefinitionDiscoveryStrategy,
+ RedefinitionStrategy.BatchAllocator redefinitionBatchAllocator,
+ RedefinitionStrategy.Listener redefinitionListener,
+ RedefinitionStrategy.ResubmissionStrategy redefinitionResubmissionStrategy,
+ BootstrapInjectionStrategy bootstrapInjectionStrategy,
+ LambdaInstrumentationStrategy lambdaInstrumentationStrategy,
+ DescriptionStrategy descriptionStrategy,
+ FallbackStrategy fallbackStrategy,
+ InstallationListener installationListener,
+ RawMatcher ignoredTypeMatcher,
+ Transformation transformation) {
+ this.byteBuddy = byteBuddy;
+ this.listener = listener;
+ this.circularityLock = circularityLock;
+ this.poolStrategy = poolStrategy;
+ this.typeStrategy = typeStrategy;
+ this.locationStrategy = locationStrategy;
+ this.nativeMethodStrategy = nativeMethodStrategy;
+ this.initializationStrategy = initializationStrategy;
+ this.redefinitionStrategy = redefinitionStrategy;
+ this.redefinitionDiscoveryStrategy = redefinitionDiscoveryStrategy;
+ this.redefinitionBatchAllocator = redefinitionBatchAllocator;
+ this.redefinitionListener = redefinitionListener;
+ this.redefinitionResubmissionStrategy = redefinitionResubmissionStrategy;
+ this.bootstrapInjectionStrategy = bootstrapInjectionStrategy;
+ this.lambdaInstrumentationStrategy = lambdaInstrumentationStrategy;
+ this.descriptionStrategy = descriptionStrategy;
+ this.fallbackStrategy = fallbackStrategy;
+ this.installationListener = installationListener;
+ this.ignoredTypeMatcher = ignoredTypeMatcher;
+ this.transformation = transformation;
+ }
+
+ /**
+ * Creates an {@link AgentBuilder} that realizes the provided build plugins. As {@link EntryPoint}, {@link EntryPoint.Default#REBASE} is implied.
+ *
+ * @param plugin The build plugins to apply as a Java agent.
+ * @return An appropriate agent builder.
+ */
+ public static AgentBuilder of(Plugin... plugin) {
+ return of(Arrays.asList(plugin));
+ }
+
+ /**
+ * Creates an {@link AgentBuilder} that realizes the provided build plugins. As {@link EntryPoint}, {@link EntryPoint.Default#REBASE} is implied.
+ *
+ * @param plugins The build plugins to apply as a Java agent.
+ * @return An appropriate agent builder.
+ */
+ public static AgentBuilder of(List<? extends Plugin> plugins) {
+ return of(EntryPoint.Default.REBASE, plugins);
+ }
+
+ /**
+ * Creates an {@link AgentBuilder} that realizes the provided build plugins.
+ *
+ * @param entryPoint The build entry point to use.
+ * @param plugin The build plugins to apply as a Java agent.
+ * @return An appropriate agent builder.
+ */
+ public static AgentBuilder of(EntryPoint entryPoint, Plugin... plugin) {
+ return of(entryPoint, Arrays.asList(plugin));
+ }
+
+ /**
+ * Creates an {@link AgentBuilder} that realizes the provided build plugins.
+ *
+ * @param entryPoint The build entry point to use.
+ * @param plugins The build plugins to apply as a Java agent.
+ * @return An appropriate agent builder.
+ */
+ public static AgentBuilder of(EntryPoint entryPoint, List<? extends Plugin> plugins) {
+ AgentBuilder agentBuilder = new AgentBuilder.Default(entryPoint.getByteBuddy()).with(new TypeStrategy.ForBuildEntryPoint(entryPoint));
+ for (Plugin plugin : plugins) {
+ agentBuilder = agentBuilder.type(plugin).transform(new Transformer.ForBuildPlugin(plugin));
+ }
+ return agentBuilder;
+ }
+
+ @Override
+ public AgentBuilder with(ByteBuddy byteBuddy) {
+ return new Default(byteBuddy,
+ listener,
+ circularityLock,
+ poolStrategy,
+ typeStrategy,
+ locationStrategy,
+ nativeMethodStrategy,
+ initializationStrategy,
+ redefinitionStrategy,
+ redefinitionDiscoveryStrategy,
+ redefinitionBatchAllocator,
+ redefinitionListener,
+ redefinitionResubmissionStrategy,
+ bootstrapInjectionStrategy,
+ lambdaInstrumentationStrategy,
+ descriptionStrategy,
+ fallbackStrategy,
+ installationListener,
+ ignoredTypeMatcher,
+ transformation);
+ }
+
+ @Override
+ public AgentBuilder with(Listener listener) {
+ return new Default(byteBuddy,
+ new Listener.Compound(this.listener, listener),
+ circularityLock,
+ poolStrategy,
+ typeStrategy,
+ locationStrategy,
+ nativeMethodStrategy,
+ initializationStrategy,
+ redefinitionStrategy,
+ redefinitionDiscoveryStrategy,
+ redefinitionBatchAllocator,
+ redefinitionListener,
+ redefinitionResubmissionStrategy,
+ bootstrapInjectionStrategy,
+ lambdaInstrumentationStrategy,
+ descriptionStrategy,
+ fallbackStrategy,
+ installationListener,
+ ignoredTypeMatcher,
+ transformation);
+ }
+
+ @Override
+ public AgentBuilder with(CircularityLock circularityLock) {
+ return new Default(byteBuddy,
+ listener,
+ circularityLock,
+ poolStrategy,
+ typeStrategy,
+ locationStrategy,
+ nativeMethodStrategy,
+ initializationStrategy,
+ redefinitionStrategy,
+ redefinitionDiscoveryStrategy,
+ redefinitionBatchAllocator,
+ redefinitionListener,
+ redefinitionResubmissionStrategy,
+ bootstrapInjectionStrategy,
+ lambdaInstrumentationStrategy,
+ descriptionStrategy,
+ fallbackStrategy,
+ installationListener,
+ ignoredTypeMatcher,
+ transformation);
+ }
+
+ @Override
+ public AgentBuilder with(TypeStrategy typeStrategy) {
+ return new Default(byteBuddy,
+ listener,
+ circularityLock,
+ poolStrategy,
+ typeStrategy,
+ locationStrategy,
+ nativeMethodStrategy,
+ initializationStrategy,
+ redefinitionStrategy,
+ redefinitionDiscoveryStrategy,
+ redefinitionBatchAllocator,
+ redefinitionListener,
+ redefinitionResubmissionStrategy,
+ bootstrapInjectionStrategy,
+ lambdaInstrumentationStrategy,
+ descriptionStrategy,
+ fallbackStrategy,
+ installationListener,
+ ignoredTypeMatcher,
+ transformation);
+ }
+
+ @Override
+ public AgentBuilder with(PoolStrategy poolStrategy) {
+ return new Default(byteBuddy,
+ listener,
+ circularityLock,
+ poolStrategy,
+ typeStrategy,
+ locationStrategy,
+ nativeMethodStrategy,
+ initializationStrategy,
+ redefinitionStrategy,
+ redefinitionDiscoveryStrategy,
+ redefinitionBatchAllocator,
+ redefinitionListener,
+ redefinitionResubmissionStrategy,
+ bootstrapInjectionStrategy,
+ lambdaInstrumentationStrategy,
+ descriptionStrategy,
+ fallbackStrategy,
+ installationListener,
+ ignoredTypeMatcher,
+ transformation);
+ }
+
+ @Override
+ public AgentBuilder with(LocationStrategy locationStrategy) {
+ return new Default(byteBuddy,
+ listener,
+ circularityLock,
+ poolStrategy,
+ typeStrategy,
+ locationStrategy,
+ nativeMethodStrategy,
+ initializationStrategy,
+ redefinitionStrategy,
+ redefinitionDiscoveryStrategy,
+ redefinitionBatchAllocator,
+ redefinitionListener,
+ redefinitionResubmissionStrategy,
+ bootstrapInjectionStrategy,
+ lambdaInstrumentationStrategy,
+ descriptionStrategy,
+ fallbackStrategy,
+ installationListener,
+ ignoredTypeMatcher,
+ transformation);
+ }
+
+ @Override
+ public AgentBuilder enableNativeMethodPrefix(String prefix) {
+ return new Default(byteBuddy,
+ listener,
+ circularityLock,
+ poolStrategy,
+ typeStrategy,
+ locationStrategy,
+ NativeMethodStrategy.ForPrefix.of(prefix),
+ initializationStrategy,
+ redefinitionStrategy,
+ redefinitionDiscoveryStrategy,
+ redefinitionBatchAllocator,
+ redefinitionListener,
+ redefinitionResubmissionStrategy,
+ bootstrapInjectionStrategy,
+ lambdaInstrumentationStrategy,
+ descriptionStrategy,
+ fallbackStrategy,
+ installationListener,
+ ignoredTypeMatcher,
+ transformation);
+ }
+
+ @Override
+ public AgentBuilder disableNativeMethodPrefix() {
+ return new Default(byteBuddy,
+ listener,
+ circularityLock,
+ poolStrategy,
+ typeStrategy,
+ locationStrategy,
+ NativeMethodStrategy.Disabled.INSTANCE,
+ initializationStrategy,
+ redefinitionStrategy,
+ redefinitionDiscoveryStrategy,
+ redefinitionBatchAllocator,
+ redefinitionListener,
+ redefinitionResubmissionStrategy,
+ bootstrapInjectionStrategy,
+ lambdaInstrumentationStrategy,
+ descriptionStrategy,
+ fallbackStrategy,
+ installationListener,
+ ignoredTypeMatcher,
+ transformation);
+ }
+
+ @Override
+ public RedefinitionListenable.WithoutBatchStrategy with(RedefinitionStrategy redefinitionStrategy) {
+ return new Redefining(byteBuddy,
+ listener,
+ circularityLock,
+ poolStrategy,
+ typeStrategy,
+ locationStrategy,
+ nativeMethodStrategy,
+ initializationStrategy,
+ redefinitionStrategy,
+ RedefinitionStrategy.DiscoveryStrategy.SinglePass.INSTANCE,
+ RedefinitionStrategy.BatchAllocator.ForTotal.INSTANCE,
+ RedefinitionStrategy.Listener.NoOp.INSTANCE,
+ RedefinitionStrategy.ResubmissionStrategy.Disabled.INSTANCE,
+ bootstrapInjectionStrategy,
+ lambdaInstrumentationStrategy,
+ descriptionStrategy,
+ fallbackStrategy,
+ installationListener,
+ ignoredTypeMatcher,
+ transformation);
+ }
+
+ @Override
+ public AgentBuilder with(InitializationStrategy initializationStrategy) {
+ return new Default(byteBuddy,
+ listener,
+ circularityLock,
+ poolStrategy,
+ typeStrategy,
+ locationStrategy,
+ nativeMethodStrategy,
+ initializationStrategy,
+ redefinitionStrategy,
+ redefinitionDiscoveryStrategy,
+ redefinitionBatchAllocator,
+ redefinitionListener,
+ redefinitionResubmissionStrategy,
+ bootstrapInjectionStrategy,
+ lambdaInstrumentationStrategy,
+ descriptionStrategy,
+ fallbackStrategy,
+ installationListener,
+ ignoredTypeMatcher,
+ transformation);
+ }
+
+ @Override
+ public AgentBuilder with(LambdaInstrumentationStrategy lambdaInstrumentationStrategy) {
+ return new Default(byteBuddy,
+ listener,
+ circularityLock,
+ poolStrategy,
+ typeStrategy,
+ locationStrategy,
+ nativeMethodStrategy,
+ initializationStrategy,
+ redefinitionStrategy,
+ redefinitionDiscoveryStrategy,
+ redefinitionBatchAllocator,
+ redefinitionListener,
+ redefinitionResubmissionStrategy,
+ bootstrapInjectionStrategy,
+ lambdaInstrumentationStrategy,
+ descriptionStrategy,
+ fallbackStrategy,
+ installationListener,
+ ignoredTypeMatcher,
+ transformation);
+ }
+
+ @Override
+ public AgentBuilder with(DescriptionStrategy descriptionStrategy) {
+ return new Default(byteBuddy,
+ listener,
+ circularityLock,
+ poolStrategy,
+ typeStrategy,
+ locationStrategy,
+ nativeMethodStrategy,
+ initializationStrategy,
+ redefinitionStrategy,
+ redefinitionDiscoveryStrategy,
+ redefinitionBatchAllocator,
+ redefinitionListener,
+ redefinitionResubmissionStrategy,
+ bootstrapInjectionStrategy,
+ lambdaInstrumentationStrategy,
+ descriptionStrategy,
+ fallbackStrategy,
+ installationListener,
+ ignoredTypeMatcher,
+ transformation);
+ }
+
+ @Override
+ public AgentBuilder with(FallbackStrategy fallbackStrategy) {
+ return new Default(byteBuddy,
+ listener,
+ circularityLock,
+ poolStrategy,
+ typeStrategy,
+ locationStrategy,
+ nativeMethodStrategy,
+ initializationStrategy,
+ redefinitionStrategy,
+ redefinitionDiscoveryStrategy,
+ redefinitionBatchAllocator,
+ redefinitionListener,
+ redefinitionResubmissionStrategy,
+ bootstrapInjectionStrategy,
+ lambdaInstrumentationStrategy,
+ descriptionStrategy,
+ fallbackStrategy,
+ installationListener,
+ ignoredTypeMatcher,
+ transformation);
+ }
+
+ @Override
+ public AgentBuilder with(InstallationListener installationListener) {
+ return new Default(byteBuddy,
+ listener,
+ circularityLock,
+ poolStrategy,
+ typeStrategy,
+ locationStrategy,
+ nativeMethodStrategy,
+ initializationStrategy,
+ redefinitionStrategy,
+ redefinitionDiscoveryStrategy,
+ redefinitionBatchAllocator,
+ redefinitionListener,
+ redefinitionResubmissionStrategy,
+ bootstrapInjectionStrategy,
+ lambdaInstrumentationStrategy,
+ descriptionStrategy,
+ fallbackStrategy,
+ new InstallationListener.Compound(this.installationListener, installationListener),
+ ignoredTypeMatcher,
+ transformation);
+ }
+
+ @Override
+ public AgentBuilder enableBootstrapInjection(Instrumentation instrumentation, File folder) {
+ return new Default(byteBuddy,
+ listener,
+ circularityLock,
+ poolStrategy,
+ typeStrategy,
+ locationStrategy,
+ nativeMethodStrategy,
+ initializationStrategy,
+ redefinitionStrategy,
+ redefinitionDiscoveryStrategy,
+ redefinitionBatchAllocator,
+ redefinitionListener,
+ redefinitionResubmissionStrategy,
+ new BootstrapInjectionStrategy.Enabled(folder, instrumentation),
+ lambdaInstrumentationStrategy,
+ descriptionStrategy,
+ fallbackStrategy,
+ installationListener,
+ ignoredTypeMatcher,
+ transformation);
+ }
+
+ @Override
+ public AgentBuilder enableUnsafeBootstrapInjection() {
+ return new Default(byteBuddy,
+ listener,
+ circularityLock,
+ poolStrategy,
+ typeStrategy,
+ locationStrategy,
+ nativeMethodStrategy,
+ initializationStrategy,
+ redefinitionStrategy,
+ redefinitionDiscoveryStrategy,
+ redefinitionBatchAllocator,
+ redefinitionListener,
+ redefinitionResubmissionStrategy,
+ BootstrapInjectionStrategy.Unsafe.INSTANCE,
+ lambdaInstrumentationStrategy,
+ descriptionStrategy,
+ fallbackStrategy,
+ installationListener,
+ ignoredTypeMatcher,
+ transformation);
+ }
+
+ @Override
+ public AgentBuilder disableBootstrapInjection() {
+ return new Default(byteBuddy,
+ listener,
+ circularityLock,
+ poolStrategy,
+ typeStrategy,
+ locationStrategy,
+ nativeMethodStrategy,
+ initializationStrategy,
+ redefinitionStrategy,
+ redefinitionDiscoveryStrategy,
+ redefinitionBatchAllocator,
+ redefinitionListener,
+ redefinitionResubmissionStrategy,
+ BootstrapInjectionStrategy.Disabled.INSTANCE,
+ lambdaInstrumentationStrategy,
+ descriptionStrategy,
+ fallbackStrategy,
+ installationListener,
+ ignoredTypeMatcher,
+ transformation);
+ }
+
+ @Override
+ public AgentBuilder disableClassFormatChanges() {
+ return new Default(byteBuddy.with(Implementation.Context.Disabled.Factory.INSTANCE),
+ listener,
+ circularityLock,
+ poolStrategy,
+ TypeStrategy.Default.REDEFINE_FROZEN,
+ locationStrategy,
+ NativeMethodStrategy.Disabled.INSTANCE,
+ InitializationStrategy.NoOp.INSTANCE,
+ redefinitionStrategy,
+ redefinitionDiscoveryStrategy,
+ redefinitionBatchAllocator,
+ redefinitionListener,
+ redefinitionResubmissionStrategy,
+ bootstrapInjectionStrategy,
+ lambdaInstrumentationStrategy,
+ descriptionStrategy,
+ fallbackStrategy,
+ installationListener,
+ ignoredTypeMatcher,
+ transformation);
+ }
+
+ @Override
+ public AgentBuilder assureReadEdgeTo(Instrumentation instrumentation, Class<?>... type) {
+ return JavaModule.isSupported()
+ ? with(Listener.ModuleReadEdgeCompleting.of(instrumentation, false, type))
+ : this;
+ }
+
+ @Override
+ public AgentBuilder assureReadEdgeTo(Instrumentation instrumentation, JavaModule... module) {
+ return assureReadEdgeTo(instrumentation, Arrays.asList(module));
+ }
+
+ @Override
+ public AgentBuilder assureReadEdgeTo(Instrumentation instrumentation, Collection<? extends JavaModule> modules) {
+ return with(new Listener.ModuleReadEdgeCompleting(instrumentation, false, new HashSet<JavaModule>(modules)));
+ }
+
+ @Override
+ public AgentBuilder assureReadEdgeFromAndTo(Instrumentation instrumentation, Class<?>... type) {
+ return JavaModule.isSupported()
+ ? with(Listener.ModuleReadEdgeCompleting.of(instrumentation, true, type))
+ : this;
+ }
+
+ @Override
+ public AgentBuilder assureReadEdgeFromAndTo(Instrumentation instrumentation, JavaModule... module) {
+ return assureReadEdgeFromAndTo(instrumentation, Arrays.asList(module));
+ }
+
+ @Override
+ public AgentBuilder assureReadEdgeFromAndTo(Instrumentation instrumentation, Collection<? extends JavaModule> modules) {
+ return with(new Listener.ModuleReadEdgeCompleting(instrumentation, true, new HashSet<JavaModule>(modules)));
+ }
+
+ @Override
+ public Identified.Narrowable type(RawMatcher matcher) {
+ return new Transforming(matcher, Transformer.NoOp.INSTANCE, false);
+ }
+
+ @Override
+ public Identified.Narrowable type(ElementMatcher<? super TypeDescription> typeMatcher) {
+ return type(typeMatcher, any());
+ }
+
+ @Override
+ public Identified.Narrowable type(ElementMatcher<? super TypeDescription> typeMatcher, ElementMatcher<? super ClassLoader> classLoaderMatcher) {
+ return type(typeMatcher, classLoaderMatcher, any());
+ }
+
+ @Override
+ public Identified.Narrowable type(ElementMatcher<? super TypeDescription> typeMatcher,
+ ElementMatcher<? super ClassLoader> classLoaderMatcher,
+ ElementMatcher<? super JavaModule> moduleMatcher) {
+ return type(new RawMatcher.ForElementMatchers(typeMatcher, classLoaderMatcher, not(supportsModules()).or(moduleMatcher)));
+ }
+
+ @Override
+ public Ignored ignore(ElementMatcher<? super TypeDescription> typeMatcher) {
+ return ignore(typeMatcher, any());
+ }
+
+ @Override
+ public Ignored ignore(ElementMatcher<? super TypeDescription> typeMatcher, ElementMatcher<? super ClassLoader> classLoaderMatcher) {
+ return ignore(typeMatcher, classLoaderMatcher, any());
+ }
+
+ @Override
+ public Ignored ignore(ElementMatcher<? super TypeDescription> typeMatcher,
+ ElementMatcher<? super ClassLoader> classLoaderMatcher,
+ ElementMatcher<? super JavaModule> moduleMatcher) {
+ return ignore(new RawMatcher.ForElementMatchers(typeMatcher, classLoaderMatcher, not(supportsModules()).or(moduleMatcher)));
+ }
+
+ @Override
+ public Ignored ignore(RawMatcher rawMatcher) {
+ return new Ignoring(rawMatcher);
+ }
+
+ @Override
+ public ResettableClassFileTransformer makeRaw() {
+ return makeRaw(listener, InstallationListener.NoOp.INSTANCE);
+ }
+
+ /**
+ * Creates a new class file transformer with a given listener.
+ *
+ * @param listener The listener to supply.
+ * @param installationListener The installation listener to notify.
+ * @return The resettable class file transformer to use.
+ */
+ private ResettableClassFileTransformer makeRaw(Listener listener, InstallationListener installationListener) {
+ return ExecutingTransformer.FACTORY.make(byteBuddy,
+ listener,
+ poolStrategy,
+ typeStrategy,
+ locationStrategy,
+ nativeMethodStrategy,
+ initializationStrategy,
+ bootstrapInjectionStrategy,
+ lambdaInstrumentationStrategy,
+ descriptionStrategy,
+ fallbackStrategy,
+ installationListener,
+ ignoredTypeMatcher,
+ transformation,
+ circularityLock);
+ }
+
+ @Override
+ public ResettableClassFileTransformer installOn(Instrumentation instrumentation) {
+ if (!circularityLock.acquire()) {
+ throw new IllegalStateException("Could not acquire the circularity lock upon installation.");
+ }
+ try {
+ RedefinitionStrategy.ResubmissionStrategy.Installation installation = redefinitionResubmissionStrategy.apply(instrumentation,
+ locationStrategy,
+ listener,
+ installationListener,
+ circularityLock,
+ new RawMatcher.Conjunction(new RawMatcher.Inversion(ignoredTypeMatcher), transformation),
+ redefinitionStrategy,
+ redefinitionBatchAllocator,
+ redefinitionListener);
+ ResettableClassFileTransformer classFileTransformer = makeRaw(installation.getListener(), installation.getInstallationListener());
+ installation.getInstallationListener().onBeforeInstall(instrumentation, classFileTransformer);
+ try {
+ instrumentation.addTransformer(classFileTransformer, redefinitionStrategy.isRetransforming());
+ if (nativeMethodStrategy.isEnabled(instrumentation)) {
+ instrumentation.setNativeMethodPrefix(classFileTransformer, nativeMethodStrategy.getPrefix());
+ }
+ lambdaInstrumentationStrategy.apply(byteBuddy, instrumentation, classFileTransformer);
+ if (redefinitionStrategy.isEnabled()) {
+ redefinitionStrategy.apply(instrumentation,
+ installation.getListener(),
+ circularityLock,
+ poolStrategy,
+ locationStrategy,
+ redefinitionDiscoveryStrategy,
+ redefinitionBatchAllocator,
+ redefinitionListener,
+ lambdaInstrumentationStrategy,
+ descriptionStrategy,
+ fallbackStrategy,
+ transformation,
+ ignoredTypeMatcher);
+ }
+ } catch (Throwable throwable) {
+ throwable = installation.getInstallationListener().onError(instrumentation, classFileTransformer, throwable);
+ if (throwable != null) {
+ instrumentation.removeTransformer(classFileTransformer);
+ throw new IllegalStateException("Could not install class file transformer", throwable);
+ }
+ }
+ installation.getInstallationListener().onInstall(instrumentation, classFileTransformer);
+ return classFileTransformer;
+ } finally {
+ circularityLock.release();
+ }
+ }
+
+ @Override
+ public ResettableClassFileTransformer installOnByteBuddyAgent() {
+ try {
+ return installOn((Instrumentation) ClassLoader.getSystemClassLoader()
+ .loadClass(INSTALLER_TYPE)
+ .getMethod(INSTRUMENTATION_GETTER)
+ .invoke(STATIC_MEMBER));
+ } catch (RuntimeException exception) {
+ throw exception;
+ } catch (Exception exception) {
+ throw new IllegalStateException("The Byte Buddy agent is not installed or not accessible", exception);
+ }
+ }
+
+ /**
+ * An injection strategy for injecting classes into the bootstrap class loader.
+ */
+ protected interface BootstrapInjectionStrategy {
+
+ /**
+ * Creates an injector for the bootstrap class loader.
+ *
+ * @param protectionDomain The protection domain to be used.
+ * @return A class injector for the bootstrap class loader.
+ */
+ ClassInjector make(ProtectionDomain protectionDomain);
+
+ /**
+ * A disabled bootstrap injection strategy.
+ */
+ enum Disabled implements BootstrapInjectionStrategy {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public ClassInjector make(ProtectionDomain protectionDomain) {
+ throw new IllegalStateException("Injecting classes into the bootstrap class loader was not enabled");
+ }
+ }
+
+ /**
+ * A bootstrap injection strategy relying on {@code sun.misc.Unsafe}.
+ */
+ enum Unsafe implements BootstrapInjectionStrategy {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public ClassInjector make(ProtectionDomain protectionDomain) {
+ return new ClassInjector.UsingUnsafe(ClassLoadingStrategy.BOOTSTRAP_LOADER, protectionDomain);
+ }
+
+ @Override
+ public String toString() {
+ return "AgentBuilder.Default.BootstrapInjectionStrategy.Unsafe." + name();
+ }
+ }
+
+ /**
+ * An enabled bootstrap injection strategy.
+ */
+ @EqualsAndHashCode
+ class Enabled implements BootstrapInjectionStrategy {
+
+ /**
+ * The folder in which jar files are to be saved.
+ */
+ private final File folder;
+
+ /**
+ * The instrumentation to use for appending jar files.
+ */
+ private final Instrumentation instrumentation;
+
+ /**
+ * Creates a new enabled bootstrap class loader injection strategy.
+ *
+ * @param folder The folder in which jar files are to be saved.
+ * @param instrumentation The instrumentation to use for appending jar files.
+ */
+ public Enabled(File folder, Instrumentation instrumentation) {
+ this.folder = folder;
+ this.instrumentation = instrumentation;
+ }
+
+ @Override
+ public ClassInjector make(ProtectionDomain protectionDomain) {
+ return ClassInjector.UsingInstrumentation.of(folder,
+ ClassInjector.UsingInstrumentation.Target.BOOTSTRAP,
+ instrumentation);
+ }
+ }
+ }
+
+ /**
+ * A strategy for determining if a native method name prefix should be used when rebasing methods.
+ */
+ protected interface NativeMethodStrategy {
+
+ /**
+ * Determines if this strategy enables name prefixing for native methods.
+ *
+ * @param instrumentation The instrumentation used.
+ * @return {@code true} if this strategy indicates that a native method prefix should be used.
+ */
+ boolean isEnabled(Instrumentation instrumentation);
+
+ /**
+ * Resolves the method name transformer for this strategy.
+ *
+ * @return A method name transformer for this strategy.
+ */
+ MethodNameTransformer resolve();
+
+ /**
+ * Returns the method prefix if the strategy is enabled. This method must only be called if this strategy enables prefixing.
+ *
+ * @return The method prefix.
+ */
+ String getPrefix();
+
+ /**
+ * A native method strategy that suffixes method names with a random suffix and disables native method rebasement.
+ */
+ enum Disabled implements NativeMethodStrategy {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public MethodNameTransformer resolve() {
+ return MethodNameTransformer.Suffixing.withRandomSuffix();
+ }
+
+ @Override
+ public boolean isEnabled(Instrumentation instrumentation) {
+ return false;
+ }
+
+ @Override
+ public String getPrefix() {
+ throw new IllegalStateException("A disabled native method strategy does not define a method name prefix");
+ }
+ }
+
+ /**
+ * A native method strategy that prefixes method names with a fixed value for supporting rebasing of native methods.
+ */
+ @EqualsAndHashCode
+ class ForPrefix implements NativeMethodStrategy {
+
+ /**
+ * The method name prefix.
+ */
+ private final String prefix;
+
+ /**
+ * Creates a new name prefixing native method strategy.
+ *
+ * @param prefix The method name prefix.
+ */
+ protected ForPrefix(String prefix) {
+ this.prefix = prefix;
+ }
+
+ /**
+ * Creates a new native method strategy for prefixing method names.
+ *
+ * @param prefix The method name prefix.
+ * @return An appropriate native method strategy.
+ */
+ protected static NativeMethodStrategy of(String prefix) {
+ if (prefix.length() == 0) {
+ throw new IllegalArgumentException("A method name prefix must not be the empty string");
+ }
+ return new ForPrefix(prefix);
+ }
+
+ @Override
+ public MethodNameTransformer resolve() {
+ return new MethodNameTransformer.Prefixing(prefix);
+ }
+
+ @Override
+ public boolean isEnabled(Instrumentation instrumentation) {
+ if (!instrumentation.isNativeMethodPrefixSupported()) {
+ throw new IllegalArgumentException("A prefix for native methods is not supported: " + instrumentation);
+ }
+ return true;
+ }
+
+ @Override
+ public String getPrefix() {
+ return prefix;
+ }
+ }
+ }
+
+ /**
+ * A transformation serves as a handler for modifying a class.
+ */
+ protected interface Transformation extends RawMatcher {
+
+ /**
+ * Resolves an attempted transformation to a specific transformation.
+ *
+ * @param typeDescription A description of the type that is to be transformed.
+ * @param classLoader The class loader of the type being transformed.
+ * @param module The transformed type's module or {@code null} if the current VM does not support modules.
+ * @param classBeingRedefined In case of a type redefinition, the loaded type being transformed or {@code null} if that is not the case.
+ * @param loaded {@code true} if the instrumented type is loaded.
+ * @param protectionDomain The protection domain of the type being transformed.
+ * @param typePool The type pool to apply during type creation.
+ * @return A resolution for the given type.
+ */
+ Resolution resolve(TypeDescription typeDescription,
+ ClassLoader classLoader,
+ JavaModule module,
+ Class<?> classBeingRedefined,
+ boolean loaded,
+ ProtectionDomain protectionDomain,
+ TypePool typePool);
+
+ /**
+ * A resolution to a transformation.
+ */
+ interface Resolution {
+
+ /**
+ * Returns the sort of this resolution.
+ *
+ * @return The sort of this resolution.
+ */
+ Sort getSort();
+
+ /**
+ * Resolves this resolution as a decorator of the supplied resolution.
+ *
+ * @param resolution The resolution for which this resolution should serve as a decorator.
+ * @return A resolution where this resolution is applied as a decorator if this resolution is alive.
+ */
+ Resolution asDecoratorOf(Resolution resolution);
+
+ /**
+ * Resolves this resolution as a decorator of the supplied resolution.
+ *
+ * @param resolution The resolution for which this resolution should serve as a decorator.
+ * @return A resolution where this resolution is applied as a decorator if this resolution is alive.
+ */
+ Resolution prepend(Decoratable resolution);
+
+ /**
+ * Transforms a type or returns {@code null} if a type is not to be transformed.
+ *
+ * @param initializationStrategy The initialization strategy to use.
+ * @param classFileLocator The class file locator to use.
+ * @param typeStrategy The definition handler to use.
+ * @param byteBuddy The Byte Buddy instance to use.
+ * @param methodNameTransformer The method name transformer to be used.
+ * @param bootstrapInjectionStrategy The bootstrap injection strategy to be used.
+ * @param accessControlContext The access control context to be used.
+ * @param listener The listener to be invoked to inform about an applied or non-applied transformation.
+ * @return The class file of the transformed class or {@code null} if no transformation is attempted.
+ */
+ byte[] apply(InitializationStrategy initializationStrategy,
+ ClassFileLocator classFileLocator,
+ TypeStrategy typeStrategy,
+ ByteBuddy byteBuddy,
+ NativeMethodStrategy methodNameTransformer,
+ BootstrapInjectionStrategy bootstrapInjectionStrategy,
+ AccessControlContext accessControlContext,
+ Listener listener);
+
+ /**
+ * Describes a specific sort of a {@link Resolution}.
+ */
+ enum Sort {
+
+ /**
+ * A terminal resolution. After discovering such a resolution, no further transformers are considered.
+ */
+ TERMINAL(true),
+
+ /**
+ * A resolution that can serve as a decorator for another resolution. After discovering such a resolution
+ * further transformations are considered where the represented resolution is prepended if applicable.
+ */
+ DECORATOR(true),
+
+ /**
+ * A non-resolved resolution.
+ */
+ UNDEFINED(false);
+
+ /**
+ * Indicates if this sort represents an active resolution.
+ */
+ private final boolean alive;
+
+ /**
+ * Creates a new resolution sort.
+ *
+ * @param alive Indicates if this sort represents an active resolution.
+ */
+ Sort(boolean alive) {
+ this.alive = alive;
+ }
+
+ /**
+ * Returns {@code true} if this resolution is alive.
+ *
+ * @return {@code true} if this resolution is alive.
+ */
+ protected boolean isAlive() {
+ return alive;
+ }
+ }
+
+ /**
+ * A resolution that can be decorated by a transformer.
+ */
+ interface Decoratable extends Resolution {
+
+ /**
+ * Appends the supplied transformer to this resolution.
+ *
+ * @param transformer The transformer to append to the transformer that is represented bz this instance.
+ * @return A new resolution with the supplied transformer appended to this transformer.
+ */
+ Resolution append(Transformer transformer);
+ }
+
+ /**
+ * A canonical implementation of a non-resolved resolution.
+ */
+ @EqualsAndHashCode
+ class Unresolved implements Resolution {
+
+ /**
+ * The type that is not transformed.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * The unresolved type's class loader.
+ */
+ private final ClassLoader classLoader;
+
+ /**
+ * The non-transformed type's module or {@code null} if the current VM does not support modules.
+ */
+ private final JavaModule module;
+
+ /**
+ * {@code true} if the type is already loaded.
+ */
+ private final boolean loaded;
+
+ /**
+ * Creates a new unresolved resolution.
+ *
+ * @param typeDescription The type that is not transformed.
+ * @param classLoader The unresolved type's class loader.
+ * @param module The non-transformed type's module or {@code null} if the current VM does not support modules.
+ * @param loaded {@code true} if the type is already loaded.
+ */
+ protected Unresolved(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded) {
+ this.typeDescription = typeDescription;
+ this.classLoader = classLoader;
+ this.module = module;
+ this.loaded = loaded;
+ }
+
+ @Override
+ public Sort getSort() {
+ return Sort.UNDEFINED;
+ }
+
+ @Override
+ public Resolution asDecoratorOf(Resolution resolution) {
+ return resolution;
+ }
+
+ @Override
+ public Resolution prepend(Decoratable resolution) {
+ return resolution;
+ }
+
+ @Override
+ public byte[] apply(InitializationStrategy initializationStrategy,
+ ClassFileLocator classFileLocator,
+ TypeStrategy typeStrategy,
+ ByteBuddy byteBuddy,
+ NativeMethodStrategy methodNameTransformer,
+ BootstrapInjectionStrategy bootstrapInjectionStrategy,
+ AccessControlContext accessControlContext,
+ Listener listener) {
+ listener.onIgnored(typeDescription, classLoader, module, loaded);
+ return NO_TRANSFORMATION;
+ }
+ }
+ }
+
+ /**
+ * A transformation that does not attempt to transform any type.
+ */
+ enum Ignored implements Transformation {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public boolean matches(TypeDescription typeDescription,
+ ClassLoader classLoader,
+ JavaModule module,
+ Class<?> classBeingRedefined,
+ ProtectionDomain protectionDomain) {
+ return false;
+ }
+
+ @Override
+ public Resolution resolve(TypeDescription typeDescription,
+ ClassLoader classLoader,
+ JavaModule module,
+ Class<?> classBeingRedefined,
+ boolean loaded,
+ ProtectionDomain protectionDomain,
+ TypePool typePool) {
+ return new Resolution.Unresolved(typeDescription, classLoader, module, loaded);
+ }
+ }
+
+ /**
+ * A simple, active transformation.
+ */
+ @EqualsAndHashCode
+ class Simple implements Transformation {
+
+ /**
+ * The raw matcher that is represented by this transformation.
+ */
+ private final RawMatcher rawMatcher;
+
+ /**
+ * The transformer that is represented by this transformation.
+ */
+ private final Transformer transformer;
+
+ /**
+ * {@code true} if this transformer serves as a decorator.
+ */
+ private final boolean decorator;
+
+ /**
+ * Creates a new transformation.
+ *
+ * @param rawMatcher The raw matcher that is represented by this transformation.
+ * @param transformer The transformer that is represented by this transformation.
+ * @param decorator {@code true} if this transformer serves as a decorator.
+ */
+ protected Simple(RawMatcher rawMatcher, Transformer transformer, boolean decorator) {
+ this.rawMatcher = rawMatcher;
+ this.transformer = transformer;
+ this.decorator = decorator;
+ }
+
+ @Override
+ public boolean matches(TypeDescription typeDescription,
+ ClassLoader classLoader,
+ JavaModule module,
+ Class<?> classBeingRedefined,
+ ProtectionDomain protectionDomain) {
+ return rawMatcher.matches(typeDescription, classLoader, module, classBeingRedefined, protectionDomain);
+ }
+
+ @Override
+ public Transformation.Resolution resolve(TypeDescription typeDescription,
+ ClassLoader classLoader,
+ JavaModule module,
+ Class<?> classBeingRedefined,
+ boolean loaded,
+ ProtectionDomain protectionDomain,
+ TypePool typePool) {
+ return matches(typeDescription, classLoader, module, classBeingRedefined, protectionDomain)
+ ? new Resolution(typeDescription, classLoader, module, protectionDomain, loaded, typePool, transformer, decorator)
+ : new Transformation.Resolution.Unresolved(typeDescription, classLoader, module, loaded);
+ }
+
+ /**
+ * A resolution that performs a type transformation.
+ */
+ @EqualsAndHashCode
+ protected static class Resolution implements Transformation.Resolution.Decoratable {
+
+ /**
+ * A description of the transformed type.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * The class loader of the transformed type.
+ */
+ private final ClassLoader classLoader;
+
+ /**
+ * The transformed type's module or {@code null} if the current VM does not support modules.
+ */
+ private final JavaModule module;
+
+ /**
+ * The protection domain of the transformed type.
+ */
+ private final ProtectionDomain protectionDomain;
+
+ /**
+ * {@code true} if the transformed type is already loaded.
+ */
+ private final boolean loaded;
+
+ /**
+ * The type pool to apply during type creation.
+ */
+ private final TypePool typePool;
+
+ /**
+ * The transformer to be applied.
+ */
+ private final Transformer transformer;
+
+ /**
+ * {@code true} if this transformer serves as a decorator.
+ */
+ private final boolean decorator;
+
+ /**
+ * Creates a new active transformation.
+ *
+ * @param typeDescription A description of the transformed type.
+ * @param classLoader The class loader of the transformed type.
+ * @param module The transformed type's module or {@code null} if the current VM does not support modules.
+ * @param protectionDomain The protection domain of the transformed type.
+ * @param loaded {@code true} if the transformed type is already loaded.
+ * @param typePool The type pool to apply during type creation.
+ * @param transformer The transformer to be applied.
+ * @param decorator {@code true} if this transformer serves as a decorator.
+ */
+ protected Resolution(TypeDescription typeDescription,
+ ClassLoader classLoader,
+ JavaModule module,
+ ProtectionDomain protectionDomain,
+ boolean loaded,
+ TypePool typePool,
+ Transformer transformer,
+ boolean decorator) {
+ this.typeDescription = typeDescription;
+ this.classLoader = classLoader;
+ this.module = module;
+ this.protectionDomain = protectionDomain;
+ this.loaded = loaded;
+ this.typePool = typePool;
+ this.transformer = transformer;
+ this.decorator = decorator;
+ }
+
+ @Override
+ public Sort getSort() {
+ return decorator
+ ? Sort.DECORATOR
+ : Sort.TERMINAL;
+ }
+
+ @Override
+ public Transformation.Resolution asDecoratorOf(Transformation.Resolution resolution) {
+ return resolution.prepend(this);
+ }
+
+ @Override
+ public Transformation.Resolution prepend(Decoratable resolution) {
+ return resolution.append(transformer);
+ }
+
+ @Override
+ public Transformation.Resolution append(Transformer transformer) {
+ return new Resolution(typeDescription,
+ classLoader,
+ module,
+ protectionDomain,
+ loaded,
+ typePool,
+ new Transformer.Compound(this.transformer, transformer),
+ decorator);
+ }
+
+ @Override
+ public byte[] apply(InitializationStrategy initializationStrategy,
+ ClassFileLocator classFileLocator,
+ TypeStrategy typeStrategy,
+ ByteBuddy byteBuddy,
+ NativeMethodStrategy methodNameTransformer,
+ BootstrapInjectionStrategy bootstrapInjectionStrategy,
+ AccessControlContext accessControlContext,
+ Listener listener) {
+ InitializationStrategy.Dispatcher dispatcher = initializationStrategy.dispatcher();
+ DynamicType.Unloaded<?> dynamicType = dispatcher.apply(transformer.transform(typeStrategy.builder(typeDescription,
+ byteBuddy,
+ classFileLocator,
+ methodNameTransformer.resolve()), typeDescription, classLoader, module)).make(TypeResolutionStrategy.Disabled.INSTANCE, typePool);
+ dispatcher.register(dynamicType, classLoader, new BootstrapClassLoaderCapableInjectorFactory(bootstrapInjectionStrategy,
+ classLoader,
+ protectionDomain));
+ listener.onTransformation(typeDescription, classLoader, module, loaded, dynamicType);
+ return dynamicType.getBytes();
+ }
+
+ /**
+ * An injector factory that resolves to a bootstrap class loader injection if this is necessary and enabled.
+ */
+ @EqualsAndHashCode
+ protected static class BootstrapClassLoaderCapableInjectorFactory implements InitializationStrategy.Dispatcher.InjectorFactory {
+
+ /**
+ * The bootstrap injection strategy being used.
+ */
+ private final BootstrapInjectionStrategy bootstrapInjectionStrategy;
+
+ /**
+ * The class loader for which to create an injection factory.
+ */
+ private final ClassLoader classLoader;
+
+ /**
+ * The protection domain of the created classes.
+ */
+ private final ProtectionDomain protectionDomain;
+
+ /**
+ * Creates a new bootstrap class loader capable injector factory.
+ *
+ * @param bootstrapInjectionStrategy The bootstrap injection strategy being used.
+ * @param classLoader The class loader for which to create an injection factory.
+ * @param protectionDomain The protection domain of the created classes.
+ */
+ protected BootstrapClassLoaderCapableInjectorFactory(BootstrapInjectionStrategy bootstrapInjectionStrategy,
+ ClassLoader classLoader,
+ ProtectionDomain protectionDomain) {
+ this.bootstrapInjectionStrategy = bootstrapInjectionStrategy;
+ this.classLoader = classLoader;
+ this.protectionDomain = protectionDomain;
+ }
+
+ @Override
+ public ClassInjector resolve() {
+ return classLoader == null
+ ? bootstrapInjectionStrategy.make(protectionDomain)
+ : new ClassInjector.UsingReflection(classLoader, protectionDomain);
+ }
+ }
+ }
+ }
+
+ /**
+ * A compound transformation that applied several transformation in the given order and applies the first active transformation.
+ */
+ @EqualsAndHashCode
+ class Compound implements Transformation {
+
+ /**
+ * The list of transformations to apply in their application order.
+ */
+ private final List<Transformation> transformations;
+
+ /**
+ * Creates a new compound transformation.
+ *
+ * @param transformation An array of transformations to apply in their application order.
+ */
+ protected Compound(Transformation... transformation) {
+ this(Arrays.asList(transformation));
+ }
+
+ /**
+ * Creates a new compound transformation.
+ *
+ * @param transformations A list of transformations to apply in their application order.
+ */
+ protected Compound(List<? extends Transformation> transformations) {
+ this.transformations = new ArrayList<Transformation>();
+ for (Transformation transformation : transformations) {
+ if (transformation instanceof Compound) {
+ this.transformations.addAll(((Compound) transformation).transformations);
+ } else if (!(transformation instanceof Ignored)) {
+ this.transformations.add(transformation);
+ }
+ }
+ }
+
+ @Override
+ public boolean matches(TypeDescription typeDescription,
+ ClassLoader classLoader,
+ JavaModule module,
+ Class<?> classBeingRedefined,
+ ProtectionDomain protectionDomain) {
+ for (Transformation transformation : transformations) {
+ if (transformation.matches(typeDescription, classLoader, module, classBeingRedefined, protectionDomain)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public Resolution resolve(TypeDescription typeDescription,
+ ClassLoader classLoader,
+ JavaModule module,
+ Class<?> classBeingRedefined,
+ boolean loaded,
+ ProtectionDomain protectionDomain,
+ TypePool typePool) {
+ Resolution current = new Resolution.Unresolved(typeDescription, classLoader, module, classBeingRedefined != null);
+ for (Transformation transformation : transformations) {
+ Resolution resolution = transformation.resolve(typeDescription,
+ classLoader,
+ module,
+ classBeingRedefined,
+ loaded,
+ protectionDomain,
+ typePool);
+ switch (resolution.getSort()) {
+ case TERMINAL:
+ return current.asDecoratorOf(resolution);
+ case DECORATOR:
+ current = current.asDecoratorOf(resolution);
+ break;
+ case UNDEFINED:
+ break;
+ default:
+ throw new IllegalStateException("Unexpected resolution type: " + resolution.getSort());
+ }
+ }
+ return current;
+ }
+ }
+ }
+
+ /**
+ * A {@link java.lang.instrument.ClassFileTransformer} that implements the enclosing agent builder's
+ * configuration.
+ */
+ protected static class ExecutingTransformer extends ResettableClassFileTransformer.AbstractBase {
+
+ /**
+ * A factory for creating a {@link ClassFileTransformer} that supports the features of the current VM.
+ */
+ protected static final Factory FACTORY = AccessController.doPrivileged(Factory.CreationAction.INSTANCE);
+
+ /**
+ * The Byte Buddy instance to be used.
+ */
+ private final ByteBuddy byteBuddy;
+
+ /**
+ * The type locator to use.
+ */
+ private final PoolStrategy poolStrategy;
+
+ /**
+ * The definition handler to use.
+ */
+ private final TypeStrategy typeStrategy;
+
+ /**
+ * The listener to notify on transformations.
+ */
+ private final Listener listener;
+
+ /**
+ * The native method strategy to apply.
+ */
+ private final NativeMethodStrategy nativeMethodStrategy;
+
+ /**
+ * The initialization strategy to use for transformed types.
+ */
+ private final InitializationStrategy initializationStrategy;
+
+ /**
+ * The injection strategy for injecting classes into the bootstrap class loader.
+ */
+ private final BootstrapInjectionStrategy bootstrapInjectionStrategy;
+
+ /**
+ * The lambda instrumentation strategy to use.
+ */
+ private final LambdaInstrumentationStrategy lambdaInstrumentationStrategy;
+
+ /**
+ * The description strategy for resolving type descriptions for types.
+ */
+ private final DescriptionStrategy descriptionStrategy;
+
+ /**
+ * The location strategy to use.
+ */
+ private final LocationStrategy locationStrategy;
+
+ /**
+ * The fallback strategy to use.
+ */
+ private final FallbackStrategy fallbackStrategy;
+
+ /**
+ * The installation listener to notify.
+ */
+ private final InstallationListener installationListener;
+
+ /**
+ * Identifies types that should not be instrumented.
+ */
+ private final RawMatcher ignoredTypeMatcher;
+
+ /**
+ * The transformation object for handling type transformations.
+ */
+ private final Transformation transformation;
+
+ /**
+ * A lock that prevents circular class transformations.
+ */
+ private final CircularityLock circularityLock;
+
+ /**
+ * The access control context to use for loading classes.
+ */
+ private final AccessControlContext accessControlContext;
+
+ /**
+ * Creates a new class file transformer.
+ *
+ * @param byteBuddy The Byte Buddy instance to be used.
+ * @param listener The listener to notify on transformations.
+ * @param poolStrategy The type locator to use.
+ * @param typeStrategy The definition handler to use.
+ * @param locationStrategy The location strategy to use.
+ * @param nativeMethodStrategy The native method strategy to apply.
+ * @param initializationStrategy The initialization strategy to use for transformed types.
+ * @param bootstrapInjectionStrategy The injection strategy for injecting classes into the bootstrap class loader.
+ * @param lambdaInstrumentationStrategy The lambda instrumentation strategy to use.
+ * @param descriptionStrategy The description strategy for resolving type descriptions for types.
+ * @param fallbackStrategy The fallback strategy to use.
+ * @param installationListener The installation listener to notify.
+ * @param ignoredTypeMatcher Identifies types that should not be instrumented.
+ * @param transformation The transformation object for handling type transformations.
+ * @param circularityLock The circularity lock to use.
+ */
+ public ExecutingTransformer(ByteBuddy byteBuddy,
+ Listener listener,
+ PoolStrategy poolStrategy,
+ TypeStrategy typeStrategy,
+ LocationStrategy locationStrategy,
+ NativeMethodStrategy nativeMethodStrategy,
+ InitializationStrategy initializationStrategy,
+ BootstrapInjectionStrategy bootstrapInjectionStrategy,
+ LambdaInstrumentationStrategy lambdaInstrumentationStrategy,
+ DescriptionStrategy descriptionStrategy,
+ FallbackStrategy fallbackStrategy,
+ InstallationListener installationListener,
+ RawMatcher ignoredTypeMatcher,
+ Transformation transformation,
+ CircularityLock circularityLock) {
+ this.byteBuddy = byteBuddy;
+ this.typeStrategy = typeStrategy;
+ this.poolStrategy = poolStrategy;
+ this.locationStrategy = locationStrategy;
+ this.listener = listener;
+ this.nativeMethodStrategy = nativeMethodStrategy;
+ this.initializationStrategy = initializationStrategy;
+ this.bootstrapInjectionStrategy = bootstrapInjectionStrategy;
+ this.lambdaInstrumentationStrategy = lambdaInstrumentationStrategy;
+ this.descriptionStrategy = descriptionStrategy;
+ this.fallbackStrategy = fallbackStrategy;
+ this.installationListener = installationListener;
+ this.ignoredTypeMatcher = ignoredTypeMatcher;
+ this.transformation = transformation;
+ this.circularityLock = circularityLock;
+ accessControlContext = AccessController.getContext();
+ }
+
+ @Override
+ public byte[] transform(ClassLoader classLoader,
+ String internalTypeName,
+ Class<?> classBeingRedefined,
+ ProtectionDomain protectionDomain,
+ byte[] binaryRepresentation) {
+ if (circularityLock.acquire()) {
+ try {
+ return AccessController.doPrivileged(new LegacyVmDispatcher(classLoader,
+ internalTypeName,
+ classBeingRedefined,
+ protectionDomain,
+ binaryRepresentation), accessControlContext);
+ } finally {
+ circularityLock.release();
+ }
+ } else {
+ return NO_TRANSFORMATION;
+ }
+ }
+
+ /**
+ * Applies a transformation for a class that was captured by this {@link ClassFileTransformer}. Invoking this method
+ * allows to process module information which is available since Java 9.
+ *
+ * @param rawModule The instrumented class's Java {@code java.lang.Module}.
+ * @param classLoader The type's class loader or {@code null} if the type is loaded by the bootstrap loader.
+ * @param internalTypeName The internal name of the instrumented class.
+ * @param classBeingRedefined The loaded {@link Class} being redefined or {@code null} if no such class exists.
+ * @param protectionDomain The instrumented type's protection domain.
+ * @param binaryRepresentation The class file of the instrumented class in its current state.
+ * @return The transformed class file or an empty byte array if this transformer does not apply an instrumentation.
+ */
+ protected byte[] transform(Object rawModule,
+ ClassLoader classLoader,
+ String internalTypeName,
+ Class<?> classBeingRedefined,
+ ProtectionDomain protectionDomain,
+ byte[] binaryRepresentation) {
+ if (circularityLock.acquire()) {
+ try {
+ return AccessController.doPrivileged(new Java9CapableVmDispatcher(rawModule,
+ classLoader,
+ internalTypeName,
+ classBeingRedefined,
+ protectionDomain,
+ binaryRepresentation), accessControlContext);
+ } finally {
+ circularityLock.release();
+ }
+ } else {
+ return NO_TRANSFORMATION;
+ }
+ }
+
+ /**
+ * Applies a transformation for a class that was captured by this {@link ClassFileTransformer}.
+ *
+ * @param module The instrumented class's Java module in its wrapped form or {@code null} if the current VM does not support modules.
+ * @param classLoader The instrumented class's class loader.
+ * @param internalTypeName The internal name of the instrumented class.
+ * @param classBeingRedefined The loaded {@link Class} being redefined or {@code null} if no such class exists.
+ * @param protectionDomain The instrumented type's protection domain.
+ * @param binaryRepresentation The class file of the instrumented class in its current state.
+ * @return The transformed class file or an empty byte array if this transformer does not apply an instrumentation.
+ */
+ private byte[] transform(JavaModule module,
+ ClassLoader classLoader,
+ String internalTypeName,
+ Class<?> classBeingRedefined,
+ ProtectionDomain protectionDomain,
+ byte[] binaryRepresentation) {
+ if (internalTypeName == null || !lambdaInstrumentationStrategy.isInstrumented(classBeingRedefined)) {
+ return NO_TRANSFORMATION;
+ }
+ String typeName = internalTypeName.replace('/', '.');
+ try {
+ listener.onDiscovery(typeName, classLoader, module, classBeingRedefined != null);
+ ClassFileLocator classFileLocator = ClassFileLocator.Simple.of(typeName,
+ binaryRepresentation,
+ locationStrategy.classFileLocator(classLoader, module));
+ TypePool typePool = poolStrategy.typePool(classFileLocator, classLoader);
+ try {
+ return doTransform(module, classLoader, typeName, classBeingRedefined, classBeingRedefined != null, protectionDomain, typePool, classFileLocator);
+ } catch (Throwable throwable) {
+ if (classBeingRedefined != null && descriptionStrategy.isLoadedFirst() && fallbackStrategy.isFallback(classBeingRedefined, throwable)) {
+ return doTransform(module, classLoader, typeName, NO_LOADED_TYPE, Listener.LOADED, protectionDomain, typePool, classFileLocator);
+ } else {
+ throw throwable;
+ }
+ }
+ } catch (Throwable throwable) {
+ listener.onError(typeName, classLoader, module, classBeingRedefined != null, throwable);
+ return NO_TRANSFORMATION;
+ } finally {
+ listener.onComplete(typeName, classLoader, module, classBeingRedefined != null);
+ }
+ }
+
+ /**
+ * Applies a transformation for a class that was captured by this {@link ClassFileTransformer}.
+ *
+ * @param module The instrumented class's Java module in its wrapped form or {@code null} if the current VM does not support modules.
+ * @param classLoader The instrumented class's class loader.
+ * @param typeName The binary name of the instrumented class.
+ * @param classBeingRedefined The loaded {@link Class} being redefined or {@code null} if no such class exists.
+ * @param loaded {@code true} if the instrumented type is loaded.
+ * @param protectionDomain The instrumented type's protection domain.
+ * @param typePool The type pool to use.
+ * @param classFileLocator The class file locator to use.
+ * @return The transformed class file or an empty byte array if this transformer does not apply an instrumentation.
+ */
+ private byte[] doTransform(JavaModule module,
+ ClassLoader classLoader,
+ String typeName,
+ Class<?> classBeingRedefined,
+ boolean loaded,
+ ProtectionDomain protectionDomain,
+ TypePool typePool,
+ ClassFileLocator classFileLocator) {
+ return resolve(module, classLoader, typeName, classBeingRedefined, loaded, protectionDomain, typePool).apply(initializationStrategy,
+ classFileLocator,
+ typeStrategy,
+ byteBuddy,
+ nativeMethodStrategy,
+ bootstrapInjectionStrategy,
+ accessControlContext,
+ listener);
+ }
+
+
+ /**
+ * Resolves the transformation and assures it is not ignored.
+ *
+ * @param module The instrumented class's Java module in its wrapped form or {@code null} if the current VM does not support modules.
+ * @param classLoader The instrumented class's class loader.
+ * @param typeName The binary name of the instrumented class.
+ * @param classBeingRedefined The loaded {@link Class} being redefined or {@code null} if no such class exists.
+ * @param loaded {@code true} if the instrumented type is loaded.
+ * @param protectionDomain The instrumented type's protection domain.
+ * @param typePool The type pool to use.
+ * @return The resolution for the transformation.
+ */
+ private Transformation.Resolution resolve(JavaModule module,
+ ClassLoader classLoader,
+ String typeName,
+ Class<?> classBeingRedefined,
+ boolean loaded,
+ ProtectionDomain protectionDomain,
+ TypePool typePool) {
+ TypeDescription typeDescription = descriptionStrategy.apply(typeName, classBeingRedefined, typePool, circularityLock, classLoader, module);
+ return ignoredTypeMatcher.matches(typeDescription, classLoader, module, classBeingRedefined, protectionDomain)
+ ? new Transformation.Resolution.Unresolved(typeDescription, classLoader, module, loaded)
+ : transformation.resolve(typeDescription, classLoader, module, classBeingRedefined, loaded, protectionDomain, typePool);
+ }
+
+ @Override
+ public synchronized boolean reset(Instrumentation instrumentation,
+ RedefinitionStrategy redefinitionStrategy,
+ RedefinitionStrategy.DiscoveryStrategy redefinitionDiscoveryStrategy,
+ RedefinitionStrategy.BatchAllocator redefinitionBatchAllocator,
+ RedefinitionStrategy.Listener redefinitionListener) {
+ if (instrumentation.removeTransformer(this)) {
+ redefinitionStrategy.apply(instrumentation,
+ Listener.NoOp.INSTANCE,
+ CircularityLock.Inactive.INSTANCE,
+ poolStrategy,
+ locationStrategy,
+ redefinitionDiscoveryStrategy,
+ redefinitionBatchAllocator,
+ redefinitionListener,
+ lambdaInstrumentationStrategy,
+ descriptionStrategy,
+ fallbackStrategy,
+ transformation,
+ ignoredTypeMatcher);
+ installationListener.onReset(instrumentation, this);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /* does not implement hashCode and equals in order to align with identity treatment of the JVM */
+
+ /**
+ * A factory for creating a {@link ClassFileTransformer} for the current VM.
+ */
+ protected interface Factory {
+
+ /**
+ * Creates a new class file transformer for the current VM.
+ *
+ * @param byteBuddy The Byte Buddy instance to be used.
+ * @param listener The listener to notify on transformations.
+ * @param poolStrategy The type locator to use.
+ * @param typeStrategy The definition handler to use.
+ * @param locationStrategy The location strategy to use.
+ * @param nativeMethodStrategy The native method strategy to apply.
+ * @param initializationStrategy The initialization strategy to use for transformed types.
+ * @param bootstrapInjectionStrategy The injection strategy for injecting classes into the bootstrap class loader.
+ * @param lambdaInstrumentationStrategy The lambda instrumentation strategy to use.
+ * @param descriptionStrategy The description strategy for resolving type descriptions for types.
+ * @param fallbackStrategy The fallback strategy to use.
+ * @param installationListener The installation listener to notify.
+ * @param ignoredTypeMatcher Identifies types that should not be instrumented.
+ * @param transformation The transformation object for handling type transformations.
+ * @param circularityLock The circularity lock to use.
+ * @return A class file transformer for the current VM that supports the API of the current VM.
+ */
+ ResettableClassFileTransformer make(ByteBuddy byteBuddy,
+ Listener listener,
+ PoolStrategy poolStrategy,
+ TypeStrategy typeStrategy,
+ LocationStrategy locationStrategy,
+ NativeMethodStrategy nativeMethodStrategy,
+ InitializationStrategy initializationStrategy,
+ BootstrapInjectionStrategy bootstrapInjectionStrategy,
+ LambdaInstrumentationStrategy lambdaInstrumentationStrategy,
+ DescriptionStrategy descriptionStrategy,
+ FallbackStrategy fallbackStrategy,
+ InstallationListener installationListener,
+ RawMatcher ignoredTypeMatcher,
+ Transformation transformation,
+ CircularityLock circularityLock);
+
+ /**
+ * An action to create an implementation of {@link ExecutingTransformer} that support Java 9 modules.
+ */
+ enum CreationAction implements PrivilegedAction<Factory> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback")
+ public Factory run() {
+ try {
+ return new Factory.ForJava9CapableVm(new ByteBuddy()
+ .subclass(ExecutingTransformer.class)
+ .name(ExecutingTransformer.class.getName() + "$ByteBuddy$ModuleSupport")
+ .method(named("transform").and(takesArgument(0, JavaType.MODULE.load())))
+ .intercept(MethodCall.invoke(ExecutingTransformer.class.getMethod("transform",
+ Object.class,
+ ClassLoader.class,
+ String.class,
+ Class.class,
+ ProtectionDomain.class,
+ byte[].class)).onSuper().withAllArguments())
+ .make()
+ .load(ExecutingTransformer.class.getClassLoader(),
+ ClassLoadingStrategy.Default.WRAPPER_PERSISTENT.with(ExecutingTransformer.class.getProtectionDomain()))
+ .getLoaded()
+ .getDeclaredConstructor(ByteBuddy.class,
+ Listener.class,
+ PoolStrategy.class,
+ TypeStrategy.class,
+ LocationStrategy.class,
+ NativeMethodStrategy.class,
+ InitializationStrategy.class,
+ BootstrapInjectionStrategy.class,
+ LambdaInstrumentationStrategy.class,
+ DescriptionStrategy.class,
+ FallbackStrategy.class,
+ InstallationListener.class,
+ RawMatcher.class,
+ Transformation.class,
+ CircularityLock.class));
+ } catch (Exception ignored) {
+ return Factory.ForLegacyVm.INSTANCE;
+ }
+ }
+ }
+
+ /**
+ * A factory for a class file transformer on a JVM that supports the {@code java.lang.Module} API to override
+ * the newly added method of the {@link ClassFileTransformer} to capture an instrumented class's module.
+ */
+ @EqualsAndHashCode
+ class ForJava9CapableVm implements Factory {
+
+ /**
+ * A constructor for creating a {@link ClassFileTransformer} that overrides the newly added method for extracting
+ * the {@code java.lang.Module} of an instrumented class.
+ */
+ private final Constructor<? extends ResettableClassFileTransformer> executingTransformer;
+
+ /**
+ * Creates a class file transformer factory for a Java 9 capable VM.
+ *
+ * @param executingTransformer A constructor for creating a {@link ClassFileTransformer} that overrides the newly added
+ * method for extracting the {@code java.lang.Module} of an instrumented class.
+ */
+ protected ForJava9CapableVm(Constructor<? extends ResettableClassFileTransformer> executingTransformer) {
+ this.executingTransformer = executingTransformer;
+ }
+
+ @Override
+ public ResettableClassFileTransformer make(ByteBuddy byteBuddy,
+ Listener listener,
+ PoolStrategy poolStrategy,
+ TypeStrategy typeStrategy,
+ LocationStrategy locationStrategy,
+ NativeMethodStrategy nativeMethodStrategy,
+ InitializationStrategy initializationStrategy,
+ BootstrapInjectionStrategy bootstrapInjectionStrategy,
+ LambdaInstrumentationStrategy lambdaInstrumentationStrategy,
+ DescriptionStrategy descriptionStrategy,
+ FallbackStrategy fallbackStrategy,
+ InstallationListener installationListener,
+ RawMatcher ignoredTypeMatcher,
+ Transformation transformation,
+ CircularityLock circularityLock) {
+ try {
+ return executingTransformer.newInstance(byteBuddy,
+ listener,
+ poolStrategy,
+ typeStrategy,
+ locationStrategy,
+ nativeMethodStrategy,
+ initializationStrategy,
+ bootstrapInjectionStrategy,
+ lambdaInstrumentationStrategy,
+ descriptionStrategy,
+ fallbackStrategy,
+ installationListener,
+ ignoredTypeMatcher,
+ transformation,
+ circularityLock);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access " + executingTransformer, exception);
+ } catch (InstantiationException exception) {
+ throw new IllegalStateException("Cannot instantiate " + executingTransformer.getDeclaringClass(), exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Cannot invoke " + executingTransformer, exception.getCause());
+ }
+ }
+ }
+
+ /**
+ * A factory for a {@link ClassFileTransformer} on a VM that does not support the {@code java.lang.Module} API.
+ */
+ enum ForLegacyVm implements Factory {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public ResettableClassFileTransformer make(ByteBuddy byteBuddy,
+ Listener listener,
+ PoolStrategy poolStrategy,
+ TypeStrategy typeStrategy,
+ LocationStrategy locationStrategy,
+ NativeMethodStrategy nativeMethodStrategy,
+ InitializationStrategy initializationStrategy,
+ BootstrapInjectionStrategy bootstrapInjectionStrategy,
+ LambdaInstrumentationStrategy lambdaInstrumentationStrategy,
+ DescriptionStrategy descriptionStrategy,
+ FallbackStrategy fallbackStrategy,
+ InstallationListener installationListener,
+ RawMatcher ignoredTypeMatcher,
+ Transformation transformation,
+ CircularityLock circularityLock) {
+ return new ExecutingTransformer(byteBuddy,
+ listener,
+ poolStrategy,
+ typeStrategy,
+ locationStrategy,
+ nativeMethodStrategy,
+ initializationStrategy,
+ bootstrapInjectionStrategy,
+ lambdaInstrumentationStrategy,
+ descriptionStrategy,
+ fallbackStrategy,
+ installationListener,
+ ignoredTypeMatcher,
+ transformation,
+ circularityLock);
+ }
+ }
+ }
+
+ /**
+ * A privileged action for transforming a class on a JVM prior to Java 9.
+ */
+ protected class LegacyVmDispatcher implements PrivilegedAction<byte[]> {
+
+ /**
+ * The type's class loader or {@code null} if the bootstrap class loader is represented.
+ */
+ private final ClassLoader classLoader;
+
+ /**
+ * The type's internal name or {@code null} if no such name exists.
+ */
+ private final String internalTypeName;
+
+ /**
+ * The class being redefined or {@code null} if no such class exists.
+ */
+ private final Class<?> classBeingRedefined;
+
+ /**
+ * The type's protection domain.
+ */
+ private final ProtectionDomain protectionDomain;
+
+ /**
+ * The type's binary representation.
+ */
+ private final byte[] binaryRepresentation;
+
+ /**
+ * Creates a new type transformation dispatcher.
+ *
+ * @param classLoader The type's class loader or {@code null} if the bootstrap class loader is represented.
+ * @param internalTypeName The type's internal name or {@code null} if no such name exists.
+ * @param classBeingRedefined The class being redefined or {@code null} if no such class exists.
+ * @param protectionDomain The type's protection domain.
+ * @param binaryRepresentation The type's binary representation.
+ */
+ protected LegacyVmDispatcher(ClassLoader classLoader,
+ String internalTypeName,
+ Class<?> classBeingRedefined,
+ ProtectionDomain protectionDomain,
+ byte[] binaryRepresentation) {
+ this.classLoader = classLoader;
+ this.internalTypeName = internalTypeName;
+ this.classBeingRedefined = classBeingRedefined;
+ this.protectionDomain = protectionDomain;
+ this.binaryRepresentation = binaryRepresentation;
+ }
+
+ @Override
+ public byte[] run() {
+ return transform(JavaModule.UNSUPPORTED,
+ classLoader,
+ internalTypeName,
+ classBeingRedefined,
+ protectionDomain,
+ binaryRepresentation);
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private ExecutingTransformer getOuter() {
+ return ExecutingTransformer.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object object) {
+ if (this == object) return true;
+ if (object == null || getClass() != object.getClass()) return false;
+ LegacyVmDispatcher that = (LegacyVmDispatcher) object;
+ return (classLoader != null ? classLoader.equals(that.classLoader) : that.classLoader == null)
+ && (internalTypeName != null ? internalTypeName.equals(that.internalTypeName) : that.internalTypeName == null)
+ && (classBeingRedefined != null ? classBeingRedefined.equals(that.classBeingRedefined) : that.classBeingRedefined == null)
+ && protectionDomain.equals(that.protectionDomain)
+ && ExecutingTransformer.this.equals(that.getOuter())
+ && Arrays.equals(binaryRepresentation, that.binaryRepresentation);
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ int result = classLoader != null ? classLoader.hashCode() : 0;
+ result = 31 * result + (internalTypeName != null ? internalTypeName.hashCode() : 0);
+ result = 31 * result + (classBeingRedefined != null ? classBeingRedefined.hashCode() : 0);
+ result = 31 * result + protectionDomain.hashCode();
+ result = 31 * result + ExecutingTransformer.this.hashCode();
+ result = 31 * result + Arrays.hashCode(binaryRepresentation);
+ return result;
+ }
+ }
+
+ /**
+ * A privileged action for transforming a class on a JVM that supports modules.
+ */
+ protected class Java9CapableVmDispatcher implements PrivilegedAction<byte[]> {
+
+ /**
+ * The type's {@code java.lang.Module}.
+ */
+ private final Object rawModule;
+
+ /**
+ * The type's class loader or {@code null} if the type is loaded by the bootstrap loader.
+ */
+ private final ClassLoader classLoader;
+
+ /**
+ * The type's internal name or {@code null} if no such name exists.
+ */
+ private final String internalTypeName;
+
+ /**
+ * The class being redefined or {@code null} if no such class exists.
+ */
+ private final Class<?> classBeingRedefined;
+
+ /**
+ * The type's protection domain.
+ */
+ private final ProtectionDomain protectionDomain;
+
+ /**
+ * The type's binary representation.
+ */
+ private final byte[] binaryRepresentation;
+
+
+ /**
+ * Creates a new legacy dispatcher.
+ *
+ * @param rawModule The type's {@code java.lang.Module}.
+ * @param classLoader The type's class loader or {@code null} if the type is loaded by the bootstrap loader.
+ * @param internalTypeName The type's internal name or {@code null} if no such name exists.
+ * @param classBeingRedefined The class being redefined or {@code null} if no such class exists.
+ * @param protectionDomain The type's protection domain.
+ * @param binaryRepresentation The type's binary representation.
+ */
+ protected Java9CapableVmDispatcher(Object rawModule,
+ ClassLoader classLoader,
+ String internalTypeName,
+ Class<?> classBeingRedefined,
+ ProtectionDomain protectionDomain,
+ byte[] binaryRepresentation) {
+ this.rawModule = rawModule;
+ this.classLoader = classLoader;
+ this.internalTypeName = internalTypeName;
+ this.classBeingRedefined = classBeingRedefined;
+ this.protectionDomain = protectionDomain;
+ this.binaryRepresentation = binaryRepresentation;
+ }
+
+ @Override
+ public byte[] run() {
+ return transform(JavaModule.of(rawModule),
+ classLoader,
+ internalTypeName,
+ classBeingRedefined,
+ protectionDomain,
+ binaryRepresentation);
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private ExecutingTransformer getOuter() {
+ return ExecutingTransformer.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object object) {
+ if (this == object) return true;
+ if (object == null || getClass() != object.getClass()) return false;
+ Java9CapableVmDispatcher that = (Java9CapableVmDispatcher) object;
+ return rawModule.equals(that.rawModule)
+ && (classLoader != null ? classLoader.equals(that.classLoader) : that.classLoader == null)
+ && (internalTypeName != null ? internalTypeName.equals(that.internalTypeName) : that.internalTypeName == null)
+ && (classBeingRedefined != null ? classBeingRedefined.equals(that.classBeingRedefined) : that.classBeingRedefined == null)
+ && protectionDomain.equals(that.protectionDomain)
+ && ExecutingTransformer.this.equals(that.getOuter())
+ && Arrays.equals(binaryRepresentation, that.binaryRepresentation);
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ int result = rawModule.hashCode();
+ result = 31 * result + (classLoader != null ? classLoader.hashCode() : 0);
+ result = 31 * result + (internalTypeName != null ? internalTypeName.hashCode() : 0);
+ result = 31 * result + (classBeingRedefined != null ? classBeingRedefined.hashCode() : 0);
+ result = 31 * result + protectionDomain.hashCode();
+ result = 31 * result + ExecutingTransformer.this.hashCode();
+ result = 31 * result + Arrays.hashCode(binaryRepresentation);
+ return result;
+ }
+ }
+ }
+
+ /**
+ * An abstract implementation of an agent builder that delegates all invocation to another instance.
+ *
+ * @param <T> The type that is produced by chaining a matcher.
+ */
+ protected abstract class Delegator<T extends Matchable<T>> extends Matchable.AbstractBase<T> implements AgentBuilder {
+
+ /**
+ * Materializes the currently described {@link net.bytebuddy.agent.builder.AgentBuilder}.
+ *
+ * @return An agent builder that represents the currently described entry of this instance.
+ */
+ protected abstract AgentBuilder materialize();
+
+ @Override
+ public AgentBuilder with(ByteBuddy byteBuddy) {
+ return materialize().with(byteBuddy);
+ }
+
+ @Override
+ public AgentBuilder with(Listener listener) {
+ return materialize().with(listener);
+ }
+
+ @Override
+ public AgentBuilder with(CircularityLock circularityLock) {
+ return materialize().with(circularityLock);
+ }
+
+ @Override
+ public AgentBuilder with(TypeStrategy typeStrategy) {
+ return materialize().with(typeStrategy);
+ }
+
+ @Override
+ public AgentBuilder with(PoolStrategy poolStrategy) {
+ return materialize().with(poolStrategy);
+ }
+
+ @Override
+ public AgentBuilder with(LocationStrategy locationStrategy) {
+ return materialize().with(locationStrategy);
+ }
+
+ @Override
+ public AgentBuilder with(InitializationStrategy initializationStrategy) {
+ return materialize().with(initializationStrategy);
+ }
+
+ @Override
+ public RedefinitionListenable.WithoutBatchStrategy with(RedefinitionStrategy redefinitionStrategy) {
+ return materialize().with(redefinitionStrategy);
+ }
+
+ @Override
+ public AgentBuilder with(LambdaInstrumentationStrategy lambdaInstrumentationStrategy) {
+ return materialize().with(lambdaInstrumentationStrategy);
+ }
+
+ @Override
+ public AgentBuilder with(DescriptionStrategy descriptionStrategy) {
+ return materialize().with(descriptionStrategy);
+ }
+
+ @Override
+ public AgentBuilder with(FallbackStrategy fallbackStrategy) {
+ return materialize().with(fallbackStrategy);
+ }
+
+ @Override
+ public AgentBuilder with(InstallationListener installationListener) {
+ return materialize().with(installationListener);
+ }
+
+ @Override
+ public AgentBuilder enableBootstrapInjection(Instrumentation instrumentation, File folder) {
+ return materialize().enableBootstrapInjection(instrumentation, folder);
+ }
+
+ @Override
+ public AgentBuilder enableUnsafeBootstrapInjection() {
+ return materialize().enableUnsafeBootstrapInjection();
+ }
+
+ @Override
+ public AgentBuilder disableBootstrapInjection() {
+ return materialize().disableBootstrapInjection();
+ }
+
+ @Override
+ public AgentBuilder enableNativeMethodPrefix(String prefix) {
+ return materialize().enableNativeMethodPrefix(prefix);
+ }
+
+ @Override
+ public AgentBuilder disableNativeMethodPrefix() {
+ return materialize().disableNativeMethodPrefix();
+ }
+
+ @Override
+ public AgentBuilder disableClassFormatChanges() {
+ return materialize().disableClassFormatChanges();
+ }
+
+ @Override
+ public AgentBuilder assureReadEdgeTo(Instrumentation instrumentation, Class<?>... type) {
+ return materialize().assureReadEdgeTo(instrumentation, type);
+ }
+
+ @Override
+ public AgentBuilder assureReadEdgeTo(Instrumentation instrumentation, JavaModule... module) {
+ return materialize().assureReadEdgeTo(instrumentation, module);
+ }
+
+ @Override
+ public AgentBuilder assureReadEdgeTo(Instrumentation instrumentation, Collection<? extends JavaModule> modules) {
+ return materialize().assureReadEdgeTo(instrumentation, modules);
+ }
+
+ @Override
+ public AgentBuilder assureReadEdgeFromAndTo(Instrumentation instrumentation, Class<?>... type) {
+ return materialize().assureReadEdgeFromAndTo(instrumentation, type);
+ }
+
+ @Override
+ public AgentBuilder assureReadEdgeFromAndTo(Instrumentation instrumentation, JavaModule... module) {
+ return materialize().assureReadEdgeFromAndTo(instrumentation, module);
+ }
+
+ @Override
+ public AgentBuilder assureReadEdgeFromAndTo(Instrumentation instrumentation, Collection<? extends JavaModule> modules) {
+ return materialize().assureReadEdgeFromAndTo(instrumentation, modules);
+ }
+
+ @Override
+ public Identified.Narrowable type(ElementMatcher<? super TypeDescription> typeMatcher) {
+ return materialize().type(typeMatcher);
+ }
+
+ @Override
+ public Identified.Narrowable type(ElementMatcher<? super TypeDescription> typeMatcher, ElementMatcher<? super ClassLoader> classLoaderMatcher) {
+ return materialize().type(typeMatcher, classLoaderMatcher);
+ }
+
+ @Override
+ public Identified.Narrowable type(ElementMatcher<? super TypeDescription> typeMatcher,
+ ElementMatcher<? super ClassLoader> classLoaderMatcher,
+ ElementMatcher<? super JavaModule> moduleMatcher) {
+ return materialize().type(typeMatcher, classLoaderMatcher, moduleMatcher);
+ }
+
+
+ @Override
+ public Identified.Narrowable type(RawMatcher matcher) {
+ return materialize().type(matcher);
+ }
+
+ @Override
+ public Ignored ignore(ElementMatcher<? super TypeDescription> ignoredTypes) {
+ return materialize().ignore(ignoredTypes);
+ }
+
+ @Override
+ public Ignored ignore(ElementMatcher<? super TypeDescription> ignoredTypes, ElementMatcher<? super ClassLoader> ignoredClassLoaders) {
+ return materialize().ignore(ignoredTypes, ignoredClassLoaders);
+ }
+
+ @Override
+ public Ignored ignore(ElementMatcher<? super TypeDescription> typeMatcher,
+ ElementMatcher<? super ClassLoader> classLoaderMatcher,
+ ElementMatcher<? super JavaModule> moduleMatcher) {
+ return materialize().ignore(typeMatcher, classLoaderMatcher, moduleMatcher);
+ }
+
+ @Override
+ public Ignored ignore(RawMatcher rawMatcher) {
+ return materialize().ignore(rawMatcher);
+ }
+
+ @Override
+ public ClassFileTransformer makeRaw() {
+ return materialize().makeRaw();
+ }
+
+ @Override
+ public ResettableClassFileTransformer installOn(Instrumentation instrumentation) {
+ return materialize().installOn(instrumentation);
+ }
+
+ @Override
+ public ResettableClassFileTransformer installOnByteBuddyAgent() {
+ return materialize().installOnByteBuddyAgent();
+ }
+ }
+
+ /**
+ * A delegator transformer for further precising what types to ignore.
+ */
+ protected class Ignoring extends Delegator<Ignored> implements Ignored {
+
+ /**
+ * A matcher for identifying types that should not be instrumented.
+ */
+ private final RawMatcher rawMatcher;
+
+ /**
+ * Creates a new agent builder for further specifying what types to ignore.
+ *
+ * @param rawMatcher A matcher for identifying types that should not be instrumented.
+ */
+ protected Ignoring(RawMatcher rawMatcher) {
+ this.rawMatcher = rawMatcher;
+ }
+
+ @Override
+ protected AgentBuilder materialize() {
+ return new Default(byteBuddy,
+ listener,
+ circularityLock,
+ poolStrategy,
+ typeStrategy,
+ locationStrategy,
+ nativeMethodStrategy,
+ initializationStrategy,
+ redefinitionStrategy,
+ redefinitionDiscoveryStrategy,
+ redefinitionBatchAllocator,
+ redefinitionListener,
+ redefinitionResubmissionStrategy,
+ bootstrapInjectionStrategy,
+ lambdaInstrumentationStrategy,
+ descriptionStrategy,
+ fallbackStrategy,
+ installationListener,
+ rawMatcher,
+ transformation);
+ }
+
+ @Override
+ public Ignored and(RawMatcher rawMatcher) {
+ return new Ignoring(new RawMatcher.Conjunction(this.rawMatcher, rawMatcher));
+ }
+
+ @Override
+ public Ignored or(RawMatcher rawMatcher) {
+ return new Ignoring(new RawMatcher.Disjunction(this.rawMatcher, rawMatcher));
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private Default getOuter() {
+ return Default.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && rawMatcher.equals(((Ignoring) other).rawMatcher)
+ && Default.this.equals(((Ignoring) other).getOuter());
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ int result = rawMatcher.hashCode();
+ result = 31 * result + Default.this.hashCode();
+ return result;
+ }
+ }
+
+ /**
+ * An implementation of a default agent builder that allows for refinement of the redefinition strategy.
+ */
+ protected static class Redefining extends Default implements RedefinitionListenable.WithoutBatchStrategy {
+
+ /**
+ * Creates a new default agent builder that allows for refinement of the redefinition strategy.
+ *
+ * @param byteBuddy The Byte Buddy instance to be used.
+ * @param listener The listener to notify on transformations.
+ * @param circularityLock The circularity lock to use.
+ * @param poolStrategy The type locator to use.
+ * @param typeStrategy The definition handler to use.
+ * @param locationStrategy The location strategy to use.
+ * @param nativeMethodStrategy The native method strategy to apply.
+ * @param initializationStrategy The initialization strategy to use for transformed types.
+ * @param redefinitionStrategy The redefinition strategy to apply.
+ * @param redefinitionDiscoveryStrategy The discovery strategy for loaded types to be redefined.
+ * @param redefinitionBatchAllocator The batch allocator for the redefinition strategy to apply.
+ * @param redefinitionListener The redefinition listener for the redefinition strategy to apply.
+ * @param redefinitionResubmissionStrategy The resubmission strategy to apply.
+ * @param bootstrapInjectionStrategy The injection strategy for injecting classes into the bootstrap class loader.
+ * @param lambdaInstrumentationStrategy A strategy to determine of the {@code LambdaMetafactory} should be instrumented to allow for the
+ * instrumentation of classes that represent lambda expressions.
+ * @param descriptionStrategy The description strategy for resolving type descriptions for types.
+ * @param fallbackStrategy The fallback strategy to apply.
+ * @param installationListener The installation listener to notify.
+ * @param ignoredTypeMatcher Identifies types that should not be instrumented.
+ * @param transformation The transformation object for handling type transformations.
+ */
+ protected Redefining(ByteBuddy byteBuddy,
+ Listener listener,
+ CircularityLock circularityLock,
+ PoolStrategy poolStrategy,
+ TypeStrategy typeStrategy,
+ LocationStrategy locationStrategy,
+ NativeMethodStrategy nativeMethodStrategy,
+ InitializationStrategy initializationStrategy,
+ RedefinitionStrategy redefinitionStrategy,
+ RedefinitionStrategy.DiscoveryStrategy redefinitionDiscoveryStrategy,
+ RedefinitionStrategy.BatchAllocator redefinitionBatchAllocator,
+ RedefinitionStrategy.Listener redefinitionListener,
+ RedefinitionStrategy.ResubmissionStrategy redefinitionResubmissionStrategy,
+ BootstrapInjectionStrategy bootstrapInjectionStrategy,
+ LambdaInstrumentationStrategy lambdaInstrumentationStrategy,
+ DescriptionStrategy descriptionStrategy,
+ FallbackStrategy fallbackStrategy,
+ InstallationListener installationListener,
+ RawMatcher ignoredTypeMatcher,
+ Transformation transformation) {
+ super(byteBuddy,
+ listener,
+ circularityLock,
+ poolStrategy,
+ typeStrategy,
+ locationStrategy,
+ nativeMethodStrategy,
+ initializationStrategy,
+ redefinitionStrategy,
+ redefinitionDiscoveryStrategy,
+ redefinitionBatchAllocator,
+ redefinitionListener,
+ redefinitionResubmissionStrategy,
+ bootstrapInjectionStrategy,
+ lambdaInstrumentationStrategy,
+ descriptionStrategy,
+ fallbackStrategy,
+ installationListener,
+ ignoredTypeMatcher,
+ transformation);
+ }
+
+ @Override
+ public WithImplicitDiscoveryStrategy with(RedefinitionStrategy.BatchAllocator redefinitionBatchAllocator) {
+
+ if (!redefinitionStrategy.isEnabled()) {
+ throw new IllegalStateException("Cannot set redefinition batch allocator when redefinition is disabled");
+ }
+ return new Redefining(byteBuddy,
+ listener,
+ circularityLock,
+ poolStrategy,
+ typeStrategy,
+ locationStrategy,
+ nativeMethodStrategy,
+ initializationStrategy,
+ redefinitionStrategy,
+ redefinitionDiscoveryStrategy,
+ redefinitionBatchAllocator,
+ redefinitionListener,
+ redefinitionResubmissionStrategy,
+ bootstrapInjectionStrategy,
+ lambdaInstrumentationStrategy,
+ descriptionStrategy,
+ fallbackStrategy,
+ installationListener,
+ ignoredTypeMatcher,
+ transformation);
+ }
+
+ @Override
+ public RedefinitionListenable redefineOnly(Class<?>... type) {
+ return with(new RedefinitionStrategy.DiscoveryStrategy.Explicit(type));
+ }
+
+ @Override
+ public RedefinitionListenable with(RedefinitionStrategy.DiscoveryStrategy redefinitionDiscoveryStrategy) {
+ if (!redefinitionStrategy.isEnabled()) {
+ throw new IllegalStateException("Cannot set redefinition discovery strategy when redefinition is disabled");
+ }
+ return new Redefining(byteBuddy,
+ listener,
+ circularityLock,
+ poolStrategy,
+ typeStrategy,
+ locationStrategy,
+ nativeMethodStrategy,
+ initializationStrategy,
+ redefinitionStrategy,
+ redefinitionDiscoveryStrategy,
+ redefinitionBatchAllocator,
+ redefinitionListener,
+ redefinitionResubmissionStrategy,
+ bootstrapInjectionStrategy,
+ lambdaInstrumentationStrategy,
+ descriptionStrategy,
+ fallbackStrategy,
+ installationListener,
+ ignoredTypeMatcher,
+ transformation);
+ }
+
+ @Override
+ public RedefinitionListenable with(RedefinitionStrategy.Listener redefinitionListener) {
+ if (!redefinitionStrategy.isEnabled()) {
+ throw new IllegalStateException("Cannot set redefinition listener when redefinition is disabled");
+ }
+ return new Redefining(byteBuddy,
+ listener,
+ circularityLock,
+ poolStrategy,
+ typeStrategy,
+ locationStrategy,
+ nativeMethodStrategy,
+ initializationStrategy,
+ redefinitionStrategy,
+ redefinitionDiscoveryStrategy,
+ redefinitionBatchAllocator,
+ new RedefinitionStrategy.Listener.Compound(this.redefinitionListener, redefinitionListener),
+ redefinitionResubmissionStrategy,
+ bootstrapInjectionStrategy,
+ lambdaInstrumentationStrategy,
+ descriptionStrategy,
+ fallbackStrategy,
+ installationListener,
+ ignoredTypeMatcher,
+ transformation);
+ }
+
+ @Override
+ public AgentBuilder withResubmission(RedefinitionStrategy.ResubmissionScheduler resubmissionScheduler) {
+ return withResubmission(resubmissionScheduler, any());
+ }
+
+ @Override
+ public AgentBuilder withResubmission(RedefinitionStrategy.ResubmissionScheduler resubmissionScheduler, ElementMatcher<? super Throwable> matcher) {
+ if (!redefinitionStrategy.isEnabled()) {
+ throw new IllegalStateException("Cannot enable redefinition resubmission when redefinition is disabled");
+ }
+ return new Redefining(byteBuddy,
+ listener,
+ circularityLock,
+ poolStrategy,
+ typeStrategy,
+ locationStrategy,
+ nativeMethodStrategy,
+ initializationStrategy,
+ redefinitionStrategy,
+ redefinitionDiscoveryStrategy,
+ redefinitionBatchAllocator,
+ redefinitionListener,
+ new RedefinitionStrategy.ResubmissionStrategy.Enabled(resubmissionScheduler, matcher),
+ bootstrapInjectionStrategy,
+ lambdaInstrumentationStrategy,
+ descriptionStrategy,
+ fallbackStrategy,
+ installationListener,
+ ignoredTypeMatcher,
+ transformation);
+ }
+ }
+
+ /**
+ * A helper class that describes a {@link net.bytebuddy.agent.builder.AgentBuilder.Default} after supplying
+ * a {@link net.bytebuddy.agent.builder.AgentBuilder.RawMatcher} such that one or several
+ * {@link net.bytebuddy.agent.builder.AgentBuilder.Transformer}s can be supplied.
+ */
+ protected class Transforming extends Delegator<Identified.Narrowable> implements Identified.Extendable, Identified.Narrowable {
+
+ /**
+ * The supplied raw matcher.
+ */
+ private final RawMatcher rawMatcher;
+
+ /**
+ * The supplied transformer.
+ */
+ private final Transformer transformer;
+
+ /**
+ * {@code true} if this transformer serves as a decorator.
+ */
+ private final boolean decorator;
+
+ /**
+ * Creates a new matched default agent builder.
+ *
+ * @param rawMatcher The supplied raw matcher.
+ * @param transformer The supplied transformer.
+ * @param decorator {@code true} if this transformer serves as a decorator.
+ */
+ protected Transforming(RawMatcher rawMatcher, Transformer transformer, boolean decorator) {
+ this.rawMatcher = rawMatcher;
+ this.transformer = transformer;
+ this.decorator = decorator;
+ }
+
+ @Override
+ protected AgentBuilder materialize() {
+ return new Default(byteBuddy,
+ listener,
+ circularityLock,
+ poolStrategy,
+ typeStrategy,
+ locationStrategy,
+ nativeMethodStrategy,
+ initializationStrategy,
+ redefinitionStrategy,
+ redefinitionDiscoveryStrategy,
+ redefinitionBatchAllocator,
+ redefinitionListener,
+ redefinitionResubmissionStrategy,
+ bootstrapInjectionStrategy,
+ lambdaInstrumentationStrategy,
+ descriptionStrategy,
+ fallbackStrategy,
+ installationListener,
+ ignoredTypeMatcher,
+ new Transformation.Compound(new Transformation.Simple(rawMatcher, transformer, decorator), transformation));
+ }
+
+ @Override
+ public Identified.Extendable transform(Transformer transformer) {
+ return new Transforming(rawMatcher, new Transformer.Compound(this.transformer, transformer), decorator);
+ }
+
+ @Override
+ public AgentBuilder asDecorator() {
+ return new Transforming(rawMatcher, transformer, true);
+ }
+
+ @Override
+ public Narrowable and(RawMatcher rawMatcher) {
+ return new Transforming(new RawMatcher.Conjunction(this.rawMatcher, rawMatcher), transformer, decorator);
+ }
+
+ @Override
+ public Narrowable or(RawMatcher rawMatcher) {
+ return new Transforming(new RawMatcher.Disjunction(this.rawMatcher, rawMatcher), transformer, decorator);
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private Default getOuter() {
+ return Default.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && decorator == ((Transforming) other).decorator
+ && rawMatcher.equals(((Transforming) other).rawMatcher)
+ && transformer.equals(((Transforming) other).transformer)
+ && Default.this.equals(((Transforming) other).getOuter());
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ int result = rawMatcher.hashCode();
+ result = 31 * result + (decorator ? 1 : 0);
+ result = 31 * result + transformer.hashCode();
+ result = 31 * result + Default.this.hashCode();
+ return result;
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/agent/builder/LambdaFactory.java b/byte-buddy-dep/src/main/java/net/bytebuddy/agent/builder/LambdaFactory.java
new file mode 100644
index 0000000..53ba895
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/agent/builder/LambdaFactory.java
@@ -0,0 +1,215 @@
+package net.bytebuddy.agent.builder;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.loading.ClassInjector;
+
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * This class serves as a dispatcher for creating lambda expression objects when Byte Buddy is configured to instrument the
+ * {@code java.lang.invoke.LambdaMetafactory}. For this purpose, this class is injected into the class path to serve as a VM-global
+ * singleton and for becoming reachable from the JVM's meta factory. This class keeps a reference to all registered transformers which need
+ * to be explicitly deregistered in order to avoid a memory leak.
+ */
+ at EqualsAndHashCode
+public class LambdaFactory {
+
+ /**
+ * The name of the field to access.
+ */
+ private static final String FIELD_NAME = "CLASS_FILE_TRANSFORMERS";
+
+ /**
+ * A mapping of all registered class file transformers and their lambda factories, linked in their application order.
+ * This field <b>must not</b> be accessed directly but only by reading this class from the system class loader.
+ */
+ @SuppressFBWarnings(value = "MS_MUTABLE_COLLECTION_PKGPROTECT", justification = "The field must be accessible by different class loader instances")
+ public static final Map<ClassFileTransformer, LambdaFactory> CLASS_FILE_TRANSFORMERS = new ConcurrentHashMap<ClassFileTransformer, LambdaFactory>();
+
+ /**
+ * The target instance that is a factory for creating lambdas.
+ */
+ private final Object target;
+
+ /**
+ * The dispatcher method to invoke for creating a new lambda instance.
+ */
+ private final Method dispatcher;
+
+ /**
+ * Creates a new lambda factory.
+ *
+ * @param target The target instance that is a factory for creating lambdas.
+ * @param dispatcher The dispatcher method to invoke for creating a new lambda instance.
+ */
+ public LambdaFactory(Object target, Method dispatcher) {
+ this.target = target;
+ this.dispatcher = dispatcher;
+ }
+
+ /**
+ * Registers a class file transformer together with a factory for creating a lambda expression. It is possible to call this method independently
+ * of the class loader's context as the supplied injector makes sure that the manipulated collection is the one that is held by the system class
+ * loader.
+ *
+ * @param classFileTransformer The class file transformer to register.
+ * @param classFileFactory The lambda class file factory to use. This factory must define a visible instance method with the signature
+ * {@code byte[] make(Object, String, Object, Object, Object, Object, boolean, List, List, Collection}. The arguments provided
+ * are the invokedynamic call site's lookup object, the lambda method's name, the factory method's type, the lambda method's
+ * type, the target method's handle, the specialized method type of the lambda expression, a boolean to indicate
+ * serializability, a list of marker interfaces, a list of additional bridges and a collection of class file transformers to
+ * apply.
+ * @return {@code true} if this is the first registered transformer. This indicates that the {@code LambdaMetafactory} must be instrumented to delegate
+ * to this alternative factory.
+ */
+ @SuppressWarnings("all")
+ public static boolean register(ClassFileTransformer classFileTransformer, Object classFileFactory) {
+ try {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(LambdaFactory.class);
+ Class<?> lambdaFactory = ClassInjector.UsingReflection.ofSystemClassLoader()
+ .inject(Collections.singletonMap(typeDescription, ClassFileLocator.ForClassLoader.read(LambdaFactory.class).resolve()))
+ .get(typeDescription);
+ @SuppressWarnings("unchecked")
+ Map<ClassFileTransformer, Object> classFileTransformers = (Map<ClassFileTransformer, Object>) lambdaFactory
+ .getField(FIELD_NAME)
+ .get(null);
+ synchronized (classFileTransformers) {
+ try {
+ return classFileTransformers.isEmpty();
+ } finally {
+ classFileTransformers.put(classFileTransformer, lambdaFactory
+ .getConstructor(Object.class, Method.class)
+ .newInstance(classFileFactory, classFileFactory.getClass().getMethod("make",
+ Object.class,
+ String.class,
+ Object.class,
+ Object.class,
+ Object.class,
+ Object.class,
+ boolean.class,
+ List.class,
+ List.class,
+ Collection.class)));
+ }
+ }
+ } catch (RuntimeException exception) {
+ throw exception;
+ } catch (Exception exception) {
+ throw new IllegalStateException("Could not register class file transformer", exception);
+ }
+ }
+
+ /**
+ * Releases a class file transformer.
+ *
+ * @param classFileTransformer The class file transformer to release.
+ * @return {@code true} if the removed transformer was the last class file transformer registered. This indicates that the {@code LambdaMetafactory} must
+ * be instrumented to no longer delegate to this alternative factory.
+ */
+ @SuppressWarnings("all")
+ public static boolean release(ClassFileTransformer classFileTransformer) {
+ try {
+ @SuppressWarnings("unchecked")
+ Map<ClassFileTransformer, ?> classFileTransformers = (Map<ClassFileTransformer, ?>) ClassLoader.getSystemClassLoader()
+ .loadClass(LambdaFactory.class.getName())
+ .getField(FIELD_NAME)
+ .get(null);
+ synchronized (classFileTransformers) {
+ return classFileTransformers.remove(classFileTransformer) != null && classFileTransformers.isEmpty();
+ }
+ } catch (RuntimeException exception) {
+ throw exception;
+ } catch (Exception exception) {
+ throw new IllegalStateException("Could not release class file transformer", exception);
+ }
+ }
+
+ /**
+ * Applies this lambda meta factory.
+ *
+ * @param caller A lookup context representing the creating class of this lambda expression.
+ * @param invokedName The name of the lambda expression's represented method.
+ * @param invokedType The type of the lambda expression's factory method.
+ * @param samMethodType The type of the lambda expression's represented method.
+ * @param implMethod A handle representing the target of the lambda expression's method.
+ * @param instantiatedMethodType A specialization of the type of the lambda expression's represented method.
+ * @param serializable {@code true} if the lambda expression should be serializable.
+ * @param markerInterfaces A list of interfaces for the lambda expression to represent.
+ * @param additionalBridges A list of additional bridge methods to be implemented by the lambda expression.
+ * @param classFileTransformers A collection of class file transformers to apply when creating the class.
+ * @return A binary representation of the transformed class file.
+ */
+ private byte[] invoke(Object caller,
+ String invokedName,
+ Object invokedType,
+ Object samMethodType,
+ Object implMethod,
+ Object instantiatedMethodType,
+ boolean serializable,
+ List<Class<?>> markerInterfaces,
+ List<?> additionalBridges,
+ Collection<ClassFileTransformer> classFileTransformers) {
+
+ try {
+ return (byte[]) dispatcher.invoke(target,
+ caller,
+ invokedName,
+ invokedType,
+ samMethodType,
+ implMethod,
+ instantiatedMethodType,
+ serializable,
+ markerInterfaces,
+ additionalBridges,
+ classFileTransformers);
+ } catch (RuntimeException exception) {
+ throw exception;
+ } catch (Exception exception) {
+ throw new IllegalStateException("Cannot create class for lambda expression", exception);
+ }
+ }
+
+ /**
+ * Dispatches the creation of a new class representing a class file.
+ *
+ * @param caller A lookup context representing the creating class of this lambda expression.
+ * @param invokedName The name of the lambda expression's represented method.
+ * @param invokedType The type of the lambda expression's factory method.
+ * @param samMethodType The type of the lambda expression's represented method.
+ * @param implMethod A handle representing the target of the lambda expression's method.
+ * @param instantiatedMethodType A specialization of the type of the lambda expression's represented method.
+ * @param serializable {@code true} if the lambda expression should be serializable.
+ * @param markerInterfaces A list of interfaces for the lambda expression to represent.
+ * @param additionalBridges A list of additional bridge methods to be implemented by the lambda expression.
+ * @return A binary representation of the transformed class file.
+ */
+ public static byte[] make(Object caller,
+ String invokedName,
+ Object invokedType,
+ Object samMethodType,
+ Object implMethod,
+ Object instantiatedMethodType,
+ boolean serializable,
+ List<Class<?>> markerInterfaces,
+ List<?> additionalBridges) {
+ return CLASS_FILE_TRANSFORMERS.values().iterator().next().invoke(caller,
+ invokedName,
+ invokedType,
+ samMethodType,
+ implMethod,
+ instantiatedMethodType,
+ serializable,
+ markerInterfaces,
+ additionalBridges,
+ CLASS_FILE_TRANSFORMERS.keySet());
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/agent/builder/ResettableClassFileTransformer.java b/byte-buddy-dep/src/main/java/net/bytebuddy/agent/builder/ResettableClassFileTransformer.java
new file mode 100644
index 0000000..12a1ea0
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/agent/builder/ResettableClassFileTransformer.java
@@ -0,0 +1,268 @@
+package net.bytebuddy.agent.builder;
+
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.Instrumentation;
+
+/**
+ * A class file transformer that can reset its transformation.
+ */
+public interface ResettableClassFileTransformer extends ClassFileTransformer {
+
+ /**
+ * <p>
+ * Deregisters this class file transformer and redefines any transformed class to its state without this
+ * class file transformer applied, if the supplied redefinition strategy is enabled. If it is not enabled,
+ * only the {@link net.bytebuddy.agent.builder.AgentBuilder.InstallationListener} is informed about the
+ * resetting without undoing any code changes.
+ * </p>
+ * <p>
+ * <b>Note</b>: A reset class file transformer should not be reinstalled. Instead, the {@link AgentBuilder}
+ * which built the transformer should be asked to install a new transformer.
+ * </p>
+ * <p>
+ * <b>Important</b>: Most JVMs do not support changes of a class's structure after a class was already
+ * loaded. Therefore, it is typically required that this class file transformer was built while enabling
+ * {@link AgentBuilder#disableClassFormatChanges()}.
+ * </p>
+ *
+ * @param instrumentation The instrumentation instance from which to deregister the transformer.
+ * @param redefinitionStrategy The redefinition to apply.
+ * @return {@code true} if a reset was applied and this transformer was not previously removed.
+ */
+ boolean reset(Instrumentation instrumentation, AgentBuilder.RedefinitionStrategy redefinitionStrategy);
+
+ /**
+ * <p>
+ * Deregisters this class file transformer and redefines any transformed class to its state without this
+ * class file transformer applied, if the supplied redefinition strategy is enabled. If it is not enabled,
+ * only the {@link net.bytebuddy.agent.builder.AgentBuilder.InstallationListener} is informed about the
+ * resetting without undoing any code changes.
+ * </p>
+ * <p>
+ * <b>Note</b>: A reset class file transformer should not be reinstalled. Instead, the {@link AgentBuilder}
+ * which built the transformer should be asked to install a new transformer.
+ * </p>
+ * <p>
+ * <b>Important</b>: Most JVMs do not support changes of a class's structure after a class was already
+ * loaded. Therefore, it is typically required that this class file transformer was built while enabling
+ * {@link AgentBuilder#disableClassFormatChanges()}.
+ * </p>
+ *
+ * @param instrumentation The instrumentation instance from which to deregister the transformer.
+ * @param redefinitionStrategy The redefinition to apply.
+ * @param redefinitionBatchAllocator The batch allocator to use.
+ * @return {@code true} if a reset was applied and this transformer was not previously removed.
+ */
+ boolean reset(Instrumentation instrumentation,
+ AgentBuilder.RedefinitionStrategy redefinitionStrategy,
+ AgentBuilder.RedefinitionStrategy.BatchAllocator redefinitionBatchAllocator);
+
+ /**
+ * <p>
+ * Deregisters this class file transformer and redefines any transformed class to its state without this
+ * class file transformer applied, if the supplied redefinition strategy is enabled. If it is not enabled,
+ * only the {@link net.bytebuddy.agent.builder.AgentBuilder.InstallationListener} is informed about the
+ * resetting without undoing any code changes.
+ * </p>
+ * <p>
+ * <b>Note</b>: A reset class file transformer should not be reinstalled. Instead, the {@link AgentBuilder}
+ * which built the transformer should be asked to install a new transformer.
+ * </p>
+ * <p>
+ * <b>Important</b>: Most JVMs do not support changes of a class's structure after a class was already
+ * loaded. Therefore, it is typically required that this class file transformer was built while enabling
+ * {@link AgentBuilder#disableClassFormatChanges()}.
+ * </p>
+ *
+ * @param instrumentation The instrumentation instance from which to deregister the transformer.
+ * @param redefinitionStrategy The redefinition to apply.
+ * @param redefinitionDiscoveryStrategy The discovery strategy for the types to reset.
+ * @return {@code true} if a reset was applied and this transformer was not previously removed.
+ */
+ boolean reset(Instrumentation instrumentation,
+ AgentBuilder.RedefinitionStrategy redefinitionStrategy,
+ AgentBuilder.RedefinitionStrategy.DiscoveryStrategy redefinitionDiscoveryStrategy);
+
+ /**
+ * <p>
+ * Deregisters this class file transformer and redefines any transformed class to its state without this
+ * class file transformer applied, if the supplied redefinition strategy is enabled. If it is not enabled,
+ * only the {@link net.bytebuddy.agent.builder.AgentBuilder.InstallationListener} is informed about the
+ * resetting without undoing any code changes.
+ * </p>
+ * <p>
+ * <b>Note</b>: A reset class file transformer should not be reinstalled. Instead, the {@link AgentBuilder}
+ * which built the transformer should be asked to install a new transformer.
+ * </p>
+ * <p>
+ * <b>Important</b>: Most JVMs do not support changes of a class's structure after a class was already
+ * loaded. Therefore, it is typically required that this class file transformer was built while enabling
+ * {@link AgentBuilder#disableClassFormatChanges()}.
+ * </p>
+ *
+ * @param instrumentation The instrumentation instance from which to deregister the transformer.
+ * @param redefinitionStrategy The redefinition to apply.
+ * @param redefinitionDiscoveryStrategy The discovery strategy for the types to reset.
+ * @param redefinitionBatchAllocator The batch allocator to use.
+ * @return {@code true} if a reset was applied and this transformer was not previously removed.
+ */
+ boolean reset(Instrumentation instrumentation,
+ AgentBuilder.RedefinitionStrategy redefinitionStrategy,
+ AgentBuilder.RedefinitionStrategy.BatchAllocator redefinitionBatchAllocator,
+ AgentBuilder.RedefinitionStrategy.DiscoveryStrategy redefinitionDiscoveryStrategy);
+
+ /**
+ * <p>
+ * Deregisters this class file transformer and redefines any transformed class to its state without this
+ * class file transformer applied, if the supplied redefinition strategy is enabled. If it is not enabled,
+ * only the {@link net.bytebuddy.agent.builder.AgentBuilder.InstallationListener} is informed about the
+ * resetting without undoing any code changes.
+ * </p>
+ * <p>
+ * <b>Note</b>: A reset class file transformer should not be reinstalled. Instead, the {@link AgentBuilder}
+ * which built the transformer should be asked to install a new transformer.
+ * </p>
+ * <p>
+ * <b>Important</b>: Most JVMs do not support changes of a class's structure after a class was already
+ * loaded. Therefore, it is typically required that this class file transformer was built while enabling
+ * {@link AgentBuilder#disableClassFormatChanges()}.
+ * </p>
+ *
+ * @param instrumentation The instrumentation instance from which to deregister the transformer.
+ * @param redefinitionStrategy The redefinition to apply.
+ * @param redefinitionDiscoveryStrategy The discovery strategy for the types to reset.
+ * @param redefinitionListener The redefinition listener to apply.
+ * @return {@code true} if a reset was applied and this transformer was not previously removed.
+ */
+ boolean reset(Instrumentation instrumentation,
+ AgentBuilder.RedefinitionStrategy redefinitionStrategy,
+ AgentBuilder.RedefinitionStrategy.DiscoveryStrategy redefinitionDiscoveryStrategy,
+ AgentBuilder.RedefinitionStrategy.Listener redefinitionListener);
+
+ /**
+ * <p>
+ * Deregisters this class file transformer and redefines any transformed class to its state without this
+ * class file transformer applied, if the supplied redefinition strategy is enabled. If it is not enabled,
+ * only the {@link net.bytebuddy.agent.builder.AgentBuilder.InstallationListener} is informed about the
+ * resetting without undoing any code changes.
+ * </p>
+ * <p>
+ * <b>Note</b>: A reset class file transformer should not be reinstalled. Instead, the {@link AgentBuilder}
+ * which built the transformer should be asked to install a new transformer.
+ * </p>
+ * <p>
+ * <b>Important</b>: Most JVMs do not support changes of a class's structure after a class was already
+ * loaded. Therefore, it is typically required that this class file transformer was built while enabling
+ * {@link AgentBuilder#disableClassFormatChanges()}.
+ * </p>
+ *
+ * @param instrumentation The instrumentation instance from which to deregister the transformer.
+ * @param redefinitionStrategy The redefinition to apply.
+ * @param redefinitionBatchAllocator The batch allocator to use.
+ * @param redefinitionListener The redefinition listener to apply.
+ * @return {@code true} if a reset was applied and this transformer was not previously removed.
+ */
+ boolean reset(Instrumentation instrumentation,
+ AgentBuilder.RedefinitionStrategy redefinitionStrategy,
+ AgentBuilder.RedefinitionStrategy.BatchAllocator redefinitionBatchAllocator,
+ AgentBuilder.RedefinitionStrategy.Listener redefinitionListener);
+
+ /**
+ * <p>
+ * Deregisters this class file transformer and redefines any transformed class to its state without this
+ * class file transformer applied, if the supplied redefinition strategy is enabled. If it is not enabled,
+ * only the {@link net.bytebuddy.agent.builder.AgentBuilder.InstallationListener} is informed about the
+ * resetting without undoing any code changes.
+ * </p>
+ * <p>
+ * <b>Note</b>: A reset class file transformer should not be reinstalled. Instead, the {@link AgentBuilder}
+ * which built the transformer should be asked to install a new transformer.
+ * </p>
+ * <p>
+ * <b>Important</b>: Most JVMs do not support changes of a class's structure after a class was already
+ * loaded. Therefore, it is typically required that this class file transformer was built while enabling
+ * {@link AgentBuilder#disableClassFormatChanges()}.
+ * </p>
+ *
+ * @param instrumentation The instrumentation instance from which to deregister the transformer.
+ * @param redefinitionStrategy The redefinition to apply.
+ * @param redefinitionDiscoveryStrategy The discovery strategy for the types to reset.
+ * @param redefinitionBatchAllocator The batch allocator to use.
+ * @param redefinitionListener The redefinition listener to apply.
+ * @return {@code true} if a reset was applied and this transformer was not previously removed.
+ */
+ boolean reset(Instrumentation instrumentation,
+ AgentBuilder.RedefinitionStrategy redefinitionStrategy,
+ AgentBuilder.RedefinitionStrategy.DiscoveryStrategy redefinitionDiscoveryStrategy,
+ AgentBuilder.RedefinitionStrategy.BatchAllocator redefinitionBatchAllocator,
+ AgentBuilder.RedefinitionStrategy.Listener redefinitionListener);
+
+ /**
+ * An abstract base implementation of a {@link ResettableClassFileTransformer}.
+ */
+ abstract class AbstractBase implements ResettableClassFileTransformer {
+
+ @Override
+ public boolean reset(Instrumentation instrumentation, AgentBuilder.RedefinitionStrategy redefinitionStrategy) {
+ return reset(instrumentation,
+ redefinitionStrategy,
+ AgentBuilder.RedefinitionStrategy.BatchAllocator.ForTotal.INSTANCE);
+ }
+
+ @Override
+ public boolean reset(Instrumentation instrumentation,
+ AgentBuilder.RedefinitionStrategy redefinitionStrategy,
+ AgentBuilder.RedefinitionStrategy.BatchAllocator redefinitionBatchAllocator) {
+ return reset(instrumentation,
+ redefinitionStrategy,
+ redefinitionBatchAllocator,
+ AgentBuilder.RedefinitionStrategy.Listener.NoOp.INSTANCE);
+ }
+
+ @Override
+ public boolean reset(Instrumentation instrumentation,
+ AgentBuilder.RedefinitionStrategy redefinitionStrategy,
+ AgentBuilder.RedefinitionStrategy.DiscoveryStrategy redefinitionDiscoveryStrategy) {
+ return reset(instrumentation,
+ redefinitionStrategy,
+ redefinitionDiscoveryStrategy,
+ AgentBuilder.RedefinitionStrategy.Listener.NoOp.INSTANCE);
+ }
+
+ @Override
+ public boolean reset(Instrumentation instrumentation,
+ AgentBuilder.RedefinitionStrategy redefinitionStrategy,
+ AgentBuilder.RedefinitionStrategy.BatchAllocator redefinitionBatchAllocator,
+ AgentBuilder.RedefinitionStrategy.DiscoveryStrategy redefinitionDiscoveryStrategy) {
+ return reset(instrumentation,
+ redefinitionStrategy,
+ redefinitionDiscoveryStrategy,
+ redefinitionBatchAllocator,
+ AgentBuilder.RedefinitionStrategy.Listener.NoOp.INSTANCE);
+ }
+
+ @Override
+ public boolean reset(Instrumentation instrumentation,
+ AgentBuilder.RedefinitionStrategy redefinitionStrategy,
+ AgentBuilder.RedefinitionStrategy.DiscoveryStrategy redefinitionDiscoveryStrategy,
+ AgentBuilder.RedefinitionStrategy.Listener redefinitionListener) {
+ return reset(instrumentation,
+ redefinitionStrategy,
+ redefinitionDiscoveryStrategy,
+ AgentBuilder.RedefinitionStrategy.BatchAllocator.ForTotal.INSTANCE,
+ redefinitionListener);
+ }
+
+ @Override
+ public boolean reset(Instrumentation instrumentation,
+ AgentBuilder.RedefinitionStrategy redefinitionStrategy,
+ AgentBuilder.RedefinitionStrategy.BatchAllocator redefinitionBatchAllocator,
+ AgentBuilder.RedefinitionStrategy.Listener redefinitionListener) {
+ return reset(instrumentation,
+ redefinitionStrategy,
+ AgentBuilder.RedefinitionStrategy.DiscoveryStrategy.SinglePass.INSTANCE,
+ redefinitionBatchAllocator,
+ redefinitionListener);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/agent/builder/package-info.java b/byte-buddy-dep/src/main/java/net/bytebuddy/agent/builder/package-info.java
new file mode 100644
index 0000000..48b6864
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/agent/builder/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * An agent builder is used to easily implement load-time class-transformations using a Java agent. The API
+ * builds on Java's {@link java.lang.instrument.ClassFileTransformer} and {@link java.lang.instrument.Instrumentation}
+ * but offers higher-level APIs in order to allow for the implementation of very readable transformations using
+ * {@link net.bytebuddy.ByteBuddy}.
+ */
+package net.bytebuddy.agent.builder;
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/asm/Advice.java b/byte-buddy-dep/src/main/java/net/bytebuddy/asm/Advice.java
new file mode 100644
index 0000000..d21e117
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/asm/Advice.java
@@ -0,0 +1,8706 @@
+package net.bytebuddy.asm;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.enumeration.EnumerationDescription;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.TargetType;
+import net.bytebuddy.dynamic.scaffold.FieldLocator;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.SuperMethodCall;
+import net.bytebuddy.implementation.bytecode.*;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.collection.ArrayAccess;
+import net.bytebuddy.implementation.bytecode.collection.ArrayFactory;
+import net.bytebuddy.implementation.bytecode.constant.*;
+import net.bytebuddy.implementation.bytecode.member.FieldAccess;
+import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
+import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.pool.TypePool;
+import net.bytebuddy.utility.CompoundList;
+import net.bytebuddy.utility.JavaType;
+import net.bytebuddy.utility.visitor.ExceptionTableSensitiveMethodVisitor;
+import net.bytebuddy.utility.visitor.LineNumberPrependingMethodVisitor;
+import net.bytebuddy.utility.visitor.StackAwareMethodVisitor;
+import org.objectweb.asm.*;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.lang.annotation.*;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.*;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+
+/**
+ * <p>
+ * Advice wrappers copy the code of blueprint methods to be executed before and/or after a matched method. To achieve this, a {@code static}
+ * method of a class is annotated by {@link OnMethodEnter} and/or {@link OnMethodExit} and provided to an instance of this class.
+ * </p>
+ * <p>
+ * A method that is annotated with {@link OnMethodEnter} can annotate its parameters with {@link Argument} where field access to this parameter
+ * is substituted with access to the specified argument of the instrumented method. Alternatively, a parameter can be annotated by {@link This}
+ * where the {@code this} reference of the instrumented method is read when the parameter is accessed. This mechanism can also be used to assign a
+ * new value to the {@code this} reference of an instrumented method. If no annotation is used on a parameter, it is assigned the {@code n}-th
+ * parameter of the instrumented method for the {@code n}-th parameter of the advice method. All parameters must declare the exact same type as
+ * the parameters of the instrumented type or the method's declaring type for the {@link This} reference respectively if they are not marked as
+ * <i>read-only</i>. In the latter case, it suffices that a parameter type is a super type of the corresponding type of the instrumented method.
+ * </p>
+ * <p>
+ * A method that is annotated with {@link OnMethodExit} can equally annotate its parameters with {@link Argument} and {@link This}. Additionally,
+ * it can annotate a parameter with {@link Return} to receive the original method's return value. By reassigning the return value, it is possible
+ * to replace the returned value. If an instrumented method does not return a value, this annotation must not be used. If a method returns
+ * exceptionally, the parameter is set to its default value, i.e. to {@code 0} for primitive types and to {@code null} for reference types. The
+ * parameter's type must be equal to the instrumented method's return type if it is not set to <i>read-only</i> where it suffices to declare the
+ * parameter type to be of any super type to the instrumented method's return type. An exception can be read by annotating a parameter of type
+ * {@link Throwable} annotated with {@link Thrown} which is assigned the thrown {@link Throwable} or {@code null} if a method returns normally.
+ * Doing so, it is possible to exchange a thrown exception with any checked or unchecked exception.Finally, if a method annotated with
+ * {@link OnMethodEnter} exists and this method returns a value, this value can be accessed by a parameter annotated with {@link Enter}.
+ * This parameter must declare the same type as type being returned by the method annotated with {@link OnMethodEnter}. If the parameter is marked
+ * to be <i>read-only</i>, it suffices that the annotated parameter is of a super type of the return type of the method annotated by
+ * {@link OnMethodEnter}. If no such method exists or this method returns {@code void}, no such parameter must be declared. Any return value
+ * of a method that is annotated by {@link OnMethodExit} is discarded.
+ * </p>
+ * <p>
+ * If any advice method throws an exception, the method is terminated prematurely. If the method annotated by {@link OnMethodEnter} throws an exception,
+ * the method annotated by {@link OnMethodExit} method is not invoked. If the instrumented method throws an exception, the method that is annotated by
+ * {@link OnMethodExit} is only invoked if the {@link OnMethodExit#onThrowable()} property is set to {@code true} what is the default. If this property
+ * is set to {@code false}, the {@link Thrown} annotation must not be used on any parameter.
+ * </p>
+ * <p>
+ * Byte Buddy does not assert the visibility of any types that are referenced within an inlined advice method. It is the responsibility of
+ * the user of this class to assure that all types referenced within the advice methods are visible to the instrumented class. Failing to
+ * do so results in a {@link IllegalAccessError} at the instrumented class's runtime.
+ * </p>
+ * <p>
+ * Advice can be used either as a {@link AsmVisitorWrapper} where any declared methods of the currently instrumented type are enhanced without
+ * replacing an existing implementation. Alternatively, advice can function as an {@link Implementation} where, by default, the original super
+ * or default method of the instrumented method is invoked. If this is not possible or undesired, the delegate implementation can be changed
+ * by specifying a wrapped implementation explicitly by {@link Advice#wrap(Implementation)}.
+ * </p>
+ * <p>
+ * When using an advice class as a visitor wrapper, native or abstract methods which are silently skipped when advice matches such a method.
+ * </p>
+ * <p>
+ * <b>Important</b>: Since Java 6, class files contain <i>stack map frames</i> embedded into a method's byte code. When advice methods are compiled
+ * with a class file version less then Java 6 but are used for a class file that was compiled to Java 6 or newer, these stack map frames must be
+ * computed by ASM by using the {@link ClassWriter#COMPUTE_FRAMES} option. If the advice methods do not contain any branching instructions, this is
+ * not required. No action is required if the advice methods are at least compiled with Java 6 but are used on classes older than Java 6. This
+ * limitation only applies to advice methods that are inlined. Also, it is the responsibility of this class's user to assure that the advice method
+ * does not contain byte code constructs that are not supported by the class containing the instrumented method. In particular, pre Java-5
+ * try-finally blocks cannot be inlined into classes with newer byte code levels as the <i>jsr</i> instruction was deprecated. Also, classes prior
+ * to Java 7 do not support the <i>invokedynamic</i> command which must not be contained by an advice method if the instrumented method targets an
+ * older class file format version.
+ * </p>
+ * <p>
+ * <b>Note</b>: For the purpose of inlining, Java 5 and Java 6 byte code can be seen as the best candidate for advice methods. These versions do
+ * no longer allow subroutines, neither do they already allow invokedynamic instructions or method handles. This way, Java 5 and Java 6 byte
+ * code is compatible to both older and newer versions. One exception for backwards-incompatible byte code is the possibility to load type references
+ * from the constant pool onto the operand stack. These instructions can however easily be transformerd for classes compiled to Java 4 and older
+ * by registering a {@link TypeConstantAdjustment} <b>before</b> the advice visitor.
+ * </p>
+ * <p>
+ * <b>Note</b>: It is not possible to trigger break points in inlined advice methods as the debugging information of the inlined advice is not
+ * preserved. It is not possible in Java to reference more than one source file per class what makes translating such debugging information
+ * impossible. It is however possible to set break points in advice methods when invoking the original advice target. This allows debugging
+ * of advice code within unit tests that invoke the advice method without instrumentation. As a conequence of not transferring debugging information,
+ * the names of the parameters of an advice method do not matter when inlining, neither does any meta information on the advice method's body
+ * such as annotations or parameter modifiers.
+ * </p>
+ * <p>
+ * <b>Note</b>: The behavior of this component is undefined if it is supplied with invalid byte code what might result in runtime exceptions.
+ * </p>
+ * <p>
+ * <b>Note</b>: When using advice from a Java agent with an {@link net.bytebuddy.agent.builder.AgentBuilder}, it often makes sense to not include
+ * any library-specific code in the agent's jar file. For being able to locate the advice code in the context of the library dependencies, Byte
+ * Buddy offers an {@link net.bytebuddy.agent.builder.AgentBuilder.Transformer.ForAdvice} implementation that allows registering the agent's
+ * class file locators for assembly of the advice class's description at runtime and with respect to the specific user dependencies.
+ * </p>
+ *
+ * @see OnMethodEnter
+ * @see OnMethodExit
+ */
+ at EqualsAndHashCode
+public class Advice implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper, Implementation {
+
+ /**
+ * Indicates that no class reader is available to an adice method.
+ */
+ private static final ClassReader UNDEFINED = null;
+
+ /**
+ * A reference to the {@link OnMethodEnter#inline()} method.
+ */
+ private static final MethodDescription.InDefinedShape INLINE_ENTER;
+
+ /**
+ * A reference to the {@link OnMethodEnter#suppress()} method.
+ */
+ private static final MethodDescription.InDefinedShape SUPPRESS_ENTER;
+
+ /**
+ * A reference to the {@link OnMethodEnter#prependLineNumber()} method.
+ */
+ private static final MethodDescription.InDefinedShape PREPEND_LINE_NUMBER;
+
+ /**
+ * A reference to the {@link OnMethodEnter#skipOn()} method.
+ */
+ private static final MethodDescription.InDefinedShape SKIP_ON;
+
+ /**
+ * A reference to the {@link OnMethodExit#inline()} method.
+ */
+ private static final MethodDescription.InDefinedShape INLINE_EXIT;
+
+ /**
+ * A reference to the {@link OnMethodExit#suppress()} method.
+ */
+ private static final MethodDescription.InDefinedShape SUPPRESS_EXIT;
+
+ /**
+ * A reference to the {@link OnMethodExit#onThrowable()} method.
+ */
+ private static final MethodDescription.InDefinedShape ON_THROWABLE;
+
+ /*
+ * Extracts the annotation values for the enter and exit advice annotations.
+ */
+ static {
+ MethodList<MethodDescription.InDefinedShape> enter = new TypeDescription.ForLoadedType(OnMethodEnter.class).getDeclaredMethods();
+ INLINE_ENTER = enter.filter(named("inline")).getOnly();
+ SUPPRESS_ENTER = enter.filter(named("suppress")).getOnly();
+ SKIP_ON = enter.filter(named("skipOn")).getOnly();
+ PREPEND_LINE_NUMBER = enter.filter(named("prependLineNumber")).getOnly();
+ MethodList<MethodDescription.InDefinedShape> exit = new TypeDescription.ForLoadedType(OnMethodExit.class).getDeclaredMethods();
+ INLINE_EXIT = exit.filter(named("inline")).getOnly();
+ SUPPRESS_EXIT = exit.filter(named("suppress")).getOnly();
+ ON_THROWABLE = exit.filter(named("onThrowable")).getOnly();
+ }
+
+ /**
+ * The dispatcher for instrumenting the instrumented method upon entering.
+ */
+ private final Dispatcher.Resolved.ForMethodEnter methodEnter;
+
+ /**
+ * The dispatcher for instrumenting the instrumented method upon exiting.
+ */
+ private final Dispatcher.Resolved.ForMethodExit methodExit;
+
+ /**
+ * The assigner to use.
+ */
+ private final Assigner assigner;
+
+ /**
+ * The stack manipulation to apply within a suppression handler.
+ */
+ private final StackManipulation exceptionHandler;
+
+ /**
+ * The delegate implementation to apply if this advice is used as an instrumentation.
+ */
+ private final Implementation delegate;
+
+ /**
+ * Creates a new advice.
+ *
+ * @param methodEnter The dispatcher for instrumenting the instrumented method upon entering.
+ * @param methodExit The dispatcher for instrumenting the instrumented method upon exiting.
+ */
+ protected Advice(Dispatcher.Resolved.ForMethodEnter methodEnter, Dispatcher.Resolved.ForMethodExit methodExit) {
+ this(methodEnter, methodExit, Assigner.DEFAULT, Removal.of(TypeDescription.THROWABLE), SuperMethodCall.INSTANCE);
+ }
+
+ /**
+ * Creates a new advice.
+ *
+ * @param methodEnter The dispatcher for instrumenting the instrumented method upon entering.
+ * @param methodExit The dispatcher for instrumenting the instrumented method upon exiting.
+ * @param assigner The assigner to use.
+ * @param exceptionHandler The stack manipulation to apply within a suppression handler.
+ * @param delegate The delegate implementation to apply if this advice is used as an instrumentation.
+ */
+ private Advice(Dispatcher.Resolved.ForMethodEnter methodEnter,
+ Dispatcher.Resolved.ForMethodExit methodExit,
+ Assigner assigner,
+ StackManipulation exceptionHandler,
+ Implementation delegate) {
+ this.methodEnter = methodEnter;
+ this.methodExit = methodExit;
+ this.assigner = assigner;
+ this.exceptionHandler = exceptionHandler;
+ this.delegate = delegate;
+ }
+
+ /**
+ * Implements advice where every matched method is advised by the given type's advisory methods. The advices binary representation is
+ * accessed by querying the class loader of the supplied class for a class file.
+ *
+ * @param advice The type declaring the advice.
+ * @return A method visitor wrapper representing the supplied advice.
+ */
+ public static Advice to(Class<?> advice) {
+ return to(advice, ClassFileLocator.ForClassLoader.of(advice.getClassLoader()));
+ }
+
+ /**
+ * Implements advice where every matched method is advised by the given type's advisory methods.
+ *
+ * @param advice The type declaring the advice.
+ * @param classFileLocator The class file locator for locating the advisory class's class file.
+ * @return A method visitor wrapper representing the supplied advice.
+ */
+ public static Advice to(Class<?> advice, ClassFileLocator classFileLocator) {
+ return to(new TypeDescription.ForLoadedType(advice), classFileLocator);
+ }
+
+ /**
+ * Implements advice where every matched method is advised by the given type's advisory methods. Using this method, a non-operational
+ * class file locator is specified for the advice target. This implies that only advice targets with the <i>inline</i> target set
+ * to {@code false} are resolvable by the returned instance.
+ *
+ * @param advice The type declaring the advice.
+ * @return A method visitor wrapper representing the supplied advice.
+ */
+ public static Advice to(TypeDescription advice) {
+ return to(advice, ClassFileLocator.NoOp.INSTANCE);
+ }
+
+ /**
+ * Implements advice where every matched method is advised by the given type's advisory methods.
+ *
+ * @param advice A description of the type declaring the advice.
+ * @param classFileLocator The class file locator for locating the advisory class's class file.
+ * @return A method visitor wrapper representing the supplied advice.
+ */
+ public static Advice to(TypeDescription advice, ClassFileLocator classFileLocator) {
+ return to(advice, classFileLocator, Collections.<OffsetMapping.Factory<?>>emptyList());
+ }
+
+ /**
+ * Creates a new advice.
+ *
+ * @param advice A description of the type declaring the advice.
+ * @param classFileLocator The class file locator for locating the advisory class's class file.
+ * @param userFactories A list of custom factories for user generated offset mappings.
+ * @return A method visitor wrapper representing the supplied advice.
+ */
+ protected static Advice to(TypeDescription advice, ClassFileLocator classFileLocator, List<? extends OffsetMapping.Factory<?>> userFactories) {
+ Dispatcher.Unresolved methodEnter = Dispatcher.Inactive.INSTANCE, methodExit = Dispatcher.Inactive.INSTANCE;
+ for (MethodDescription.InDefinedShape methodDescription : advice.getDeclaredMethods()) {
+ methodEnter = locate(OnMethodEnter.class, INLINE_ENTER, methodEnter, methodDescription);
+ methodExit = locate(OnMethodExit.class, INLINE_EXIT, methodExit, methodDescription);
+ }
+ if (!methodEnter.isAlive() && !methodExit.isAlive()) {
+ throw new IllegalArgumentException("No advice defined by " + advice);
+ }
+ try {
+ ClassReader classReader = methodEnter.isBinary() || methodExit.isBinary()
+ ? new ClassReader(classFileLocator.locate(advice.getName()).resolve())
+ : UNDEFINED;
+ Dispatcher.Resolved.ForMethodEnter resolved = methodEnter.asMethodEnter(userFactories, classReader);
+ return new Advice(resolved, methodExit.asMethodExitTo(userFactories, classReader, resolved));
+ } catch (IOException exception) {
+ throw new IllegalStateException("Error reading class file of " + advice, exception);
+ }
+ }
+
+ /**
+ * Implements advice where every matched method is advised by the given type's advisory methods. The advices binary representation is
+ * accessed by querying the class loader of the supplied class for a class file.
+ *
+ * @param enterAdvice The type declaring the enter advice.
+ * @param exitAdvice The type declaring the exit advice.
+ * @return A method visitor wrapper representing the supplied advice.
+ */
+ public static Advice to(Class<?> enterAdvice, Class<?> exitAdvice) {
+ ClassLoader enterLoader = enterAdvice.getClassLoader(), exitLoader = exitAdvice.getClassLoader();
+ return to(enterAdvice, exitAdvice, enterLoader == exitLoader
+ ? ClassFileLocator.ForClassLoader.of(enterLoader)
+ : new ClassFileLocator.Compound(ClassFileLocator.ForClassLoader.of(enterLoader), ClassFileLocator.ForClassLoader.of(exitLoader)));
+ }
+
+ /**
+ * Implements advice where every matched method is advised by the given type's advisory methods.
+ *
+ * @param enterAdvice The type declaring the enter advice.
+ * @param exitAdvice The type declaring the exit advice.
+ * @param classFileLocator The class file locator for locating the advisory class's class file.
+ * @return A method visitor wrapper representing the supplied advice.
+ */
+ public static Advice to(Class<?> enterAdvice, Class<?> exitAdvice, ClassFileLocator classFileLocator) {
+ return to(new TypeDescription.ForLoadedType(enterAdvice), new TypeDescription.ForLoadedType(exitAdvice), classFileLocator);
+ }
+
+ /**
+ * Implements advice where every matched method is advised by the given type's advisory methods. Using this method, a non-operational
+ * class file locator is specified for the advice target. This implies that only advice targets with the <i>inline</i> target set
+ * to {@code false} are resolvable by the returned instance.
+ *
+ * @param enterAdvice The type declaring the enter advice.
+ * @param exitAdvice The type declaring the exit advice.
+ * @return A method visitor wrapper representing the supplied advice.
+ */
+ public static Advice to(TypeDescription enterAdvice, TypeDescription exitAdvice) {
+ return to(enterAdvice, exitAdvice, ClassFileLocator.NoOp.INSTANCE);
+ }
+
+ /**
+ * Implements advice where every matched method is advised by the given type's advisory methods.
+ *
+ * @param enterAdvice The type declaring the enter advice.
+ * @param exitAdvice The type declaring the exit advice.
+ * @param classFileLocator The class file locator for locating the advisory class's class file.
+ * @return A method visitor wrapper representing the supplied advice.
+ */
+ public static Advice to(TypeDescription enterAdvice, TypeDescription exitAdvice, ClassFileLocator classFileLocator) {
+ return to(enterAdvice, exitAdvice, classFileLocator, Collections.<OffsetMapping.Factory<?>>emptyList());
+ }
+
+ /**
+ * Creates a new advice.
+ *
+ * @param enterAdvice The type declaring the enter advice.
+ * @param exitAdvice The type declaring the exit advice.
+ * @param classFileLocator The class file locator for locating the advisory class's class file.
+ * @param userFactories A list of custom factories for user generated offset mappings.
+ * @return A method visitor wrapper representing the supplied advice.
+ */
+ protected static Advice to(TypeDescription enterAdvice,
+ TypeDescription exitAdvice,
+ ClassFileLocator classFileLocator,
+ List<? extends OffsetMapping.Factory<?>> userFactories) {
+ Dispatcher.Unresolved methodEnter = Dispatcher.Inactive.INSTANCE, methodExit = Dispatcher.Inactive.INSTANCE;
+ for (MethodDescription.InDefinedShape methodDescription : enterAdvice.getDeclaredMethods()) {
+ methodEnter = locate(OnMethodEnter.class, INLINE_ENTER, methodEnter, methodDescription);
+ }
+ if (!methodEnter.isAlive()) {
+ throw new IllegalArgumentException("No enter advice defined by " + enterAdvice);
+ }
+ for (MethodDescription.InDefinedShape methodDescription : exitAdvice.getDeclaredMethods()) {
+ methodExit = locate(OnMethodExit.class, INLINE_EXIT, methodExit, methodDescription);
+ }
+ if (!methodExit.isAlive()) {
+ throw new IllegalArgumentException("No enter advice defined by " + exitAdvice);
+ }
+ try {
+ Dispatcher.Resolved.ForMethodEnter resolved = methodEnter.asMethodEnter(userFactories, methodEnter.isBinary()
+ ? new ClassReader(classFileLocator.locate(enterAdvice.getName()).resolve())
+ : UNDEFINED);
+ return new Advice(resolved, methodExit.asMethodExitTo(userFactories, methodExit.isBinary()
+ ? new ClassReader(classFileLocator.locate(exitAdvice.getName()).resolve())
+ : UNDEFINED, resolved));
+ } catch (IOException exception) {
+ throw new IllegalStateException("Error reading class file of " + enterAdvice + " or " + exitAdvice, exception);
+ }
+ }
+
+ /**
+ * Locates a dispatcher for the method if available.
+ *
+ * @param type The annotation type that indicates a given form of advice that is currently resolved.
+ * @param property An annotation property that indicates if the advice method should be inlined.
+ * @param dispatcher Any previous dispatcher that was discovered or {@code null} if no such dispatcher was yet found.
+ * @param methodDescription The method description that is considered as an advice method.
+ * @return A resolved dispatcher or {@code null} if no dispatcher was resolved.
+ */
+ private static Dispatcher.Unresolved locate(Class<? extends Annotation> type,
+ MethodDescription.InDefinedShape property,
+ Dispatcher.Unresolved dispatcher,
+ MethodDescription.InDefinedShape methodDescription) {
+ AnnotationDescription annotation = methodDescription.getDeclaredAnnotations().ofType(type);
+ if (annotation == null) {
+ return dispatcher;
+ } else if (dispatcher.isAlive()) {
+ throw new IllegalStateException("Duplicate advice for " + dispatcher + " and " + methodDescription);
+ } else if (!methodDescription.isStatic()) {
+ throw new IllegalStateException("Advice for " + methodDescription + " is not static");
+ } else {
+ return annotation.getValue(property).resolve(Boolean.class)
+ ? new Dispatcher.Inlining(methodDescription)
+ : new Dispatcher.Delegating(methodDescription);
+ }
+ }
+
+ /**
+ * Allows for the configuration of custom annotations that are then bound to a dynamically computed, constant value.
+ *
+ * @return A builder for an {@link Advice} instrumentation with custom values.
+ * @see OffsetMapping.Factory
+ */
+ public static WithCustomMapping withCustomMapping() {
+ return new WithCustomMapping();
+ }
+
+ /**
+ * Returns an ASM visitor wrapper that matches the given matcher and applies this advice to the matched methods.
+ *
+ * @param matcher The matcher identifying methods to apply the advice to.
+ * @return A suitable ASM visitor wrapper with the <i>compute frames</i> option enabled.
+ */
+ public AsmVisitorWrapper.ForDeclaredMethods on(ElementMatcher<? super MethodDescription> matcher) {
+ return new AsmVisitorWrapper.ForDeclaredMethods().method(matcher, this);
+ }
+
+ @Override
+ public MethodVisitor wrap(TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ TypePool typePool,
+ int writerFlags,
+ int readerFlags) {
+ return instrumentedMethod.isAbstract() || instrumentedMethod.isNative()
+ ? methodVisitor
+ : doWrap(instrumentedType, instrumentedMethod, methodVisitor, implementationContext, writerFlags, readerFlags);
+ }
+
+ /**
+ * Wraps the method visitor to implement this advice.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param instrumentedMethod The instrumented method.
+ * @param methodVisitor The method visitor to write to.
+ * @param implementationContext The implementation context to use.
+ * @param writerFlags The ASM writer flags to use.
+ * @param readerFlags The ASM reader flags to use.
+ * @return A method visitor that applies this advice.
+ */
+ protected MethodVisitor doWrap(TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ int writerFlags,
+ int readerFlags) {
+ methodVisitor = methodEnter.isPrependLineNumber()
+ ? new LineNumberPrependingMethodVisitor(methodVisitor)
+ : methodVisitor;
+ if (!methodExit.isAlive()) {
+ return new AdviceVisitor.WithoutExitAdvice(methodVisitor,
+ implementationContext,
+ assigner,
+ exceptionHandler,
+ instrumentedType,
+ instrumentedMethod,
+ methodEnter,
+ writerFlags,
+ readerFlags);
+ } else if (methodExit.getThrowable().represents(NoExceptionHandler.class)) {
+ return new AdviceVisitor.WithExitAdvice.WithoutExceptionHandling(methodVisitor,
+ implementationContext,
+ assigner,
+ exceptionHandler,
+ instrumentedType,
+ instrumentedMethod,
+ methodEnter,
+ methodExit,
+ writerFlags,
+ readerFlags);
+ } else if (instrumentedMethod.isConstructor()) {
+ throw new IllegalStateException("Cannot catch exception during constructor call for " + instrumentedMethod);
+ } else {
+ return new AdviceVisitor.WithExitAdvice.WithExceptionHandling(methodVisitor,
+ implementationContext,
+ assigner,
+ exceptionHandler,
+ instrumentedType,
+ instrumentedMethod,
+ methodEnter,
+ methodExit,
+ writerFlags,
+ readerFlags,
+ methodExit.getThrowable());
+ }
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return delegate.prepare(instrumentedType);
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return new Appender(this, implementationTarget, delegate.appender(implementationTarget));
+ }
+
+ /**
+ * Configures this advice to use the specified assigner. Any previous or default assigner is replaced.
+ *
+ * @param assigner The assigner to use,
+ * @return A version of this advice that uses the specified assigner.
+ */
+ public Advice withAssigner(Assigner assigner) {
+ return new Advice(methodEnter, methodExit, assigner, exceptionHandler, delegate);
+ }
+
+ /**
+ * Configures this advice to call {@link Throwable#printStackTrace()} upon a suppressed exception.
+ *
+ * @return A version of this advice that prints any suppressed exception.
+ */
+ public Advice withExceptionPrinting() {
+ try {
+ return withExceptionHandler(MethodInvocation.invoke(new MethodDescription.ForLoadedMethod(Throwable.class.getMethod("printStackTrace"))));
+ } catch (NoSuchMethodException exception) {
+ throw new IllegalStateException("Cannot locate Throwable::printStackTrace");
+ }
+ }
+
+ /**
+ * Configures this advice to execute the given stack manipulation upon a suppressed exception. The stack manipulation is executed with a
+ * {@link Throwable} instance on the operand stack. The stack must be empty upon completing the exception handler.
+ *
+ * @param exceptionHandler The exception handler to apply.
+ * @return A version of this advice that applies the supplied exception handler.
+ */
+ public Advice withExceptionHandler(StackManipulation exceptionHandler) {
+ return new Advice(methodEnter, methodExit, assigner, exceptionHandler, delegate);
+ }
+
+ /**
+ * Wraps the supplied implementation to have this advice applied around it.
+ *
+ * @param implementation The implementation to wrap.
+ * @return An implementation that applies the supplied implementation and wraps it with this advice.
+ */
+ public Implementation wrap(Implementation implementation) {
+ return new Advice(methodEnter, methodExit, assigner, exceptionHandler, implementation);
+ }
+
+ /**
+ * Represents an offset mapping for an advice method to an alternative offset.
+ */
+ public interface OffsetMapping {
+
+ /**
+ * Resolves an offset mapping to a given target offset.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param instrumentedMethod The instrumented method for which the mapping is to be resolved.
+ * @param assigner The assigner to use.
+ * @param context The context in which the offset mapping is applied.
+ * @return A suitable target mapping.
+ */
+ Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Context context);
+
+ /**
+ * A context for applying an {@link OffsetMapping}.
+ */
+ interface Context {
+
+ /**
+ * Returns {@code true} if the advice is applied on a fully initialized instance, i.e. describes if the {@code this}
+ * instance is available or still uninitialized during calling the advice.
+ *
+ * @return {@code true} if the advice is applied onto a fully initialized method.
+ */
+ boolean isInitialized();
+
+ /**
+ * Returns the padding before writing additional values that this context applies.
+ *
+ * @return The required padding for this context.
+ */
+ int getPadding();
+
+ /**
+ * A context for an offset mapping describing a method entry.
+ */
+ enum ForMethodEntry implements Context {
+
+ /**
+ * Describes a context for a method entry that is not a constructor.
+ */
+ INITIALIZED(true),
+
+ /**
+ * Describes a context for a method entry that is a constructor.
+ */
+ NON_INITIALIZED(false);
+
+ /**
+ * Resolves an appropriate method entry context for the supplied instrumented method.
+ *
+ * @param instrumentedMethod The instrumented method.
+ * @return An appropriate context.
+ */
+ protected static Context of(MethodDescription instrumentedMethod) {
+ return instrumentedMethod.isConstructor()
+ ? NON_INITIALIZED
+ : INITIALIZED;
+ }
+
+ /**
+ * {@code true} if the method is no constructor, i.e. is invoked for an initialized instance upon entry.
+ */
+ private final boolean initialized;
+
+ /**
+ * Creates a new context for a method entry.
+ *
+ * @param initialized {@code true} if the method is no constructor, i.e. is invoked for an initialized instance upon entry.
+ */
+ ForMethodEntry(boolean initialized) {
+ this.initialized = initialized;
+ }
+
+ @Override
+ public boolean isInitialized() {
+ return initialized;
+ }
+
+ @Override
+ public int getPadding() {
+ return StackSize.ZERO.getSize();
+ }
+ }
+
+ /**
+ * A context for an offset mapping describing a method exit.
+ */
+ enum ForMethodExit implements Context {
+
+ /**
+ * A method exit with a zero sized padding.
+ */
+ ZERO(StackSize.ZERO),
+
+ /**
+ * A method exit with a single slot padding.
+ */
+ SINGLE(StackSize.SINGLE),
+
+ /**
+ * A method exit with a double slot padding.
+ */
+ DOUBLE(StackSize.DOUBLE);
+
+ /**
+ * The padding implied by this method exit.
+ */
+ private final StackSize stackSize;
+
+ /**
+ * Creates a new context for a method exit.
+ *
+ * @param stackSize The padding implied by this method exit.
+ */
+ ForMethodExit(StackSize stackSize) {
+ this.stackSize = stackSize;
+ }
+
+ /**
+ * Resolves an appropriate method exit context for the supplied entry method type.
+ *
+ * @param typeDescription The type that is returned by the enter method.
+ * @return An appropriate context for the supplied entry method type.
+ */
+ protected static Context of(TypeDefinition typeDescription) {
+ switch (typeDescription.getStackSize()) {
+ case ZERO:
+ return ZERO;
+ case SINGLE:
+ return SINGLE;
+ case DOUBLE:
+ return DOUBLE;
+ default:
+ throw new IllegalStateException("Unknown stack size: " + typeDescription);
+ }
+ }
+
+ @Override
+ public boolean isInitialized() {
+ return true;
+ }
+
+ @Override
+ public int getPadding() {
+ return stackSize.getSize();
+ }
+ }
+ }
+
+ /**
+ * A target offset of an offset mapping.
+ */
+ interface Target {
+
+ /**
+ * Resolves a read instruction.
+ *
+ * @return A stack manipulation that represents a reading of an advice parameter.
+ */
+ StackManipulation resolveRead();
+
+ /**
+ * Resolves a write instruction.
+ *
+ * @return A stack manipulation that represents a writing to an advice parameter.
+ */
+ StackManipulation resolveWrite();
+
+ /**
+ * Resolves an increment instruction.
+ *
+ * @param value The incrementation value.
+ * @return A stack manipulation that represents a writing to an advice parameter.
+ */
+ StackManipulation resolveIncrement(int value);
+
+ /**
+ * A target for an offset mapping that represents a non-operational value. All writes are discarded and a value's
+ * default value is returned upon every read.
+ */
+ @EqualsAndHashCode
+ abstract class ForDefaultValue implements Target {
+
+ /**
+ * The represented type.
+ */
+ protected final TypeDefinition typeDefinition;
+
+ /**
+ * A stack manipulation to apply after a read instruction.
+ */
+ protected final StackManipulation readAssignment;
+
+ /**
+ * Creates a new target for a default value.
+ *
+ * @param typeDefinition The represented type.
+ * @param readAssignment A stack manipulation to apply after a read instruction.
+ */
+ protected ForDefaultValue(TypeDefinition typeDefinition, StackManipulation readAssignment) {
+ this.typeDefinition = typeDefinition;
+ this.readAssignment = readAssignment;
+ }
+
+ @Override
+ public StackManipulation resolveRead() {
+ return new StackManipulation.Compound(DefaultValue.of(typeDefinition), readAssignment);
+ }
+
+ /**
+ * A read-only target for a default value.
+ */
+ protected static class ReadOnly extends ForDefaultValue {
+
+ /**
+ * Creates a new writable target for a default value.
+ *
+ * @param typeDefinition The represented type.
+ */
+ protected ReadOnly(TypeDefinition typeDefinition) {
+ this(typeDefinition, StackManipulation.Trivial.INSTANCE);
+ }
+
+ /**
+ * Creates a new -writable target for a default value.
+ *
+ * @param typeDefinition The represented type.
+ * @param readAssignment A stack manipulation to apply after a read instruction.
+ */
+ protected ReadOnly(TypeDefinition typeDefinition, StackManipulation readAssignment) {
+ super(typeDefinition, readAssignment);
+ }
+
+ @Override
+ public StackManipulation resolveWrite() {
+ throw new IllegalStateException("Cannot write to read-only default value");
+ }
+
+ @Override
+ public StackManipulation resolveIncrement(int value) {
+ throw new IllegalStateException("Cannot write to read-only default value");
+ }
+ }
+
+ /**
+ * A read-write target for a default value.
+ */
+ protected static class ReadWrite extends ForDefaultValue {
+
+ /**
+ * Creates a new read-only target for a default value.
+ *
+ * @param typeDefinition The represented type.
+ */
+ protected ReadWrite(TypeDefinition typeDefinition) {
+ this(typeDefinition, StackManipulation.Trivial.INSTANCE);
+ }
+
+ /**
+ * Creates a new read-only target for a default value.
+ *
+ * @param typeDefinition The represented type.
+ * @param readAssignment A stack manipulation to apply after a read instruction.
+ */
+ protected ReadWrite(TypeDefinition typeDefinition, StackManipulation readAssignment) {
+ super(typeDefinition, readAssignment);
+ }
+
+ @Override
+ public StackManipulation resolveWrite() {
+ return Removal.of(typeDefinition);
+ }
+
+ @Override
+ public StackManipulation resolveIncrement(int value) {
+ return StackManipulation.Trivial.INSTANCE;
+ }
+ }
+ }
+
+ /**
+ * A target for an offset mapping that represents a local variable.
+ */
+ @EqualsAndHashCode
+ abstract class ForVariable implements Target {
+
+ /**
+ * The represented type.
+ */
+ protected final TypeDefinition typeDefinition;
+
+ /**
+ * The value's offset.
+ */
+ protected final int offset;
+
+ /**
+ * An assignment to execute upon reading a value.
+ */
+ protected final StackManipulation readAssignment;
+
+ /**
+ * Creates a new target for a local variable mapping.
+ *
+ * @param typeDefinition The represented type.
+ * @param offset The value's offset.
+ * @param readAssignment An assignment to execute upon reading a value.
+ */
+ protected ForVariable(TypeDefinition typeDefinition, int offset, StackManipulation readAssignment) {
+ this.typeDefinition = typeDefinition;
+ this.offset = offset;
+ this.readAssignment = readAssignment;
+ }
+
+ @Override
+ public StackManipulation resolveRead() {
+ return new StackManipulation.Compound(MethodVariableAccess.of(typeDefinition).loadFrom(offset), readAssignment);
+ }
+
+ /**
+ * A target for a read-only mapping of a local variable.
+ */
+ protected static class ReadOnly extends ForVariable {
+
+ /**
+ * Creates a read-only mapping for a local variable.
+ *
+ * @param parameterDescription The mapped parameter.
+ * @param readAssignment An assignment to execute upon reading a value.
+ */
+ protected ReadOnly(ParameterDescription parameterDescription, StackManipulation readAssignment) {
+ this(parameterDescription.getType(), parameterDescription.getOffset(), readAssignment);
+ }
+
+ /**
+ * Creates a read-only mapping for a local variable.
+ *
+ * @param typeDefinition The represented type.
+ * @param offset The value's offset.
+ * @param readAssignment An assignment to execute upon reading a value.
+ */
+ protected ReadOnly(TypeDefinition typeDefinition, int offset, StackManipulation readAssignment) {
+ super(typeDefinition, offset, readAssignment);
+ }
+
+ @Override
+ public StackManipulation resolveWrite() {
+ throw new IllegalStateException("Cannot write to read-only parameter " + typeDefinition + " at " + offset);
+ }
+
+ @Override
+ public StackManipulation resolveIncrement(int value) {
+ throw new IllegalStateException("Cannot write to read-only variable " + typeDefinition + " at " + offset);
+ }
+ }
+
+ /**
+ * A target for a writable mapping of a local variable.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ protected static class ReadWrite extends ForVariable {
+
+ /**
+ * A stack manipulation to apply upon a write to the variable.
+ */
+ private final StackManipulation writeAssignment;
+
+ /**
+ * Creates a new target mapping for a writable local variable.
+ *
+ * @param parameterDescription The mapped parameter.
+ * @param readAssignment An assignment to execute upon reading a value.
+ * @param writeAssignment A stack manipulation to apply upon a write to the variable.
+ */
+ protected ReadWrite(ParameterDescription parameterDescription, StackManipulation readAssignment, StackManipulation writeAssignment) {
+ this(parameterDescription.getType(), parameterDescription.getOffset(), readAssignment, writeAssignment);
+ }
+
+ /**
+ * Creates a new target mapping for a writable local variable.
+ *
+ * @param typeDefinition The represented type.
+ * @param offset The value's offset.
+ * @param readAssignment An assignment to execute upon reading a value.
+ * @param writeAssignment A stack manipulation to apply upon a write to the variable.
+ */
+ protected ReadWrite(TypeDefinition typeDefinition, int offset, StackManipulation readAssignment, StackManipulation writeAssignment) {
+ super(typeDefinition, offset, readAssignment);
+ this.writeAssignment = writeAssignment;
+ }
+
+ @Override
+ public StackManipulation resolveWrite() {
+ return new StackManipulation.Compound(writeAssignment, MethodVariableAccess.of(typeDefinition).storeAt(offset));
+ }
+
+ @Override
+ public StackManipulation resolveIncrement(int value) {
+ return typeDefinition.represents(int.class)
+ ? MethodVariableAccess.of(typeDefinition).increment(offset, value)
+ : new StackManipulation.Compound(resolveRead(), IntegerConstant.forValue(1), Addition.INTEGER, resolveWrite());
+ }
+ }
+ }
+
+ /**
+ * A target mapping for an array of all local variables.
+ */
+ @EqualsAndHashCode
+ abstract class ForArray implements Target {
+
+ /**
+ * The compound target type.
+ */
+ protected final TypeDescription.Generic target;
+
+ /**
+ * The stack manipulations to apply upon reading a variable array.
+ */
+ protected final List<? extends StackManipulation> valueReads;
+
+ /**
+ * Creates a new target mapping for an array of all local variables.
+ *
+ * @param target The compound target type.
+ * @param valueReads The stack manipulations to apply upon reading a variable array.
+ */
+ protected ForArray(TypeDescription.Generic target, List<? extends StackManipulation> valueReads) {
+ this.target = target;
+ this.valueReads = valueReads;
+ }
+
+ @Override
+ public StackManipulation resolveRead() {
+ return ArrayFactory.forType(target).withValues(valueReads);
+ }
+
+ @Override
+ public StackManipulation resolveIncrement(int value) {
+ throw new IllegalStateException("Cannot increment read-only array value");
+ }
+
+ /**
+ * A target mapping for a read-only target mapping for an array of local variables.
+ */
+ protected static class ReadOnly extends ForArray {
+
+ /**
+ * Creates a read-only target mapping for an array of all local variables.
+ *
+ * @param target The compound target type.
+ * @param valueReads The stack manipulations to apply upon reading a variable array.
+ */
+ protected ReadOnly(TypeDescription.Generic target, List<? extends StackManipulation> valueReads) {
+ super(target, valueReads);
+ }
+
+ @Override
+ public StackManipulation resolveWrite() {
+ throw new IllegalStateException("Cannot write to read-only array value");
+ }
+ }
+
+ /**
+ * A target mapping for a writable target mapping for an array of local variables.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ protected static class ReadWrite extends ForArray {
+
+ /**
+ * The stack manipulations to apply upon writing to a variable array.
+ */
+ private final List<? extends StackManipulation> valueWrites;
+
+ /**
+ * Creates a writable target mapping for an array of all local variables.
+ *
+ * @param target The compound target type.
+ * @param valueReads The stack manipulations to apply upon reading a variable array.
+ * @param valueWrites The stack manipulations to apply upon writing to a variable array.
+ */
+ protected ReadWrite(TypeDescription.Generic target, List<? extends StackManipulation> valueReads, List<? extends StackManipulation> valueWrites) {
+ super(target, valueReads);
+ this.valueWrites = valueWrites;
+ }
+
+ @Override
+ public StackManipulation resolveWrite() {
+ return ArrayAccess.of(target).forEach(valueWrites);
+ }
+ }
+ }
+
+ /**
+ * A target for an offset mapping that loads a field value.
+ */
+ @EqualsAndHashCode
+ abstract class ForField implements Target {
+
+ /**
+ * The field value to load.
+ */
+ protected final FieldDescription fieldDescription;
+
+ /**
+ * The stack manipulation to apply upon a read.
+ */
+ protected final StackManipulation readAssignment;
+
+ /**
+ * Creates a new target for a field value mapping.
+ *
+ * @param fieldDescription The field value to load.
+ * @param readAssignment The stack manipulation to apply upon a read.
+ */
+ protected ForField(FieldDescription fieldDescription, StackManipulation readAssignment) {
+ this.fieldDescription = fieldDescription;
+ this.readAssignment = readAssignment;
+ }
+
+ @Override
+ public StackManipulation resolveRead() {
+ return new StackManipulation.Compound(fieldDescription.isStatic()
+ ? StackManipulation.Trivial.INSTANCE
+ : MethodVariableAccess.loadThis(), FieldAccess.forField(fieldDescription).read(), readAssignment);
+ }
+
+ /**
+ * A read-only mapping for a field value.
+ */
+ static class ReadOnly extends ForField {
+
+ /**
+ * Creates a new read-only mapping for a field.
+ *
+ * @param fieldDescription The field value to load.
+ * @param readAssignment The stack manipulation to apply upon a read.
+ */
+ protected ReadOnly(FieldDescription fieldDescription, StackManipulation readAssignment) {
+ super(fieldDescription, readAssignment);
+ }
+
+ @Override
+ public StackManipulation resolveWrite() {
+ throw new IllegalStateException("Cannot write to read-only field value");
+ }
+
+ @Override
+ public StackManipulation resolveIncrement(int value) {
+ throw new IllegalStateException("Cannot write to read-only field value");
+ }
+ }
+
+ /**
+ * A mapping for a writable field.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ static class ReadWrite extends ForField {
+
+ /**
+ * An assignment to apply prior to a field write.
+ */
+ private final StackManipulation writeAssignment;
+
+ /**
+ * Creates a new target for a writable field.
+ *
+ * @param fieldDescription The field value to load.
+ * @param readAssignment The stack manipulation to apply upon a read.
+ * @param writeAssignment An assignment to apply prior to a field write.
+ */
+ protected ReadWrite(FieldDescription fieldDescription, StackManipulation readAssignment, StackManipulation writeAssignment) {
+ super(fieldDescription, readAssignment);
+ this.writeAssignment = writeAssignment;
+ }
+
+ @Override
+ public StackManipulation resolveWrite() {
+ StackManipulation preparation;
+ if (fieldDescription.isStatic()) {
+ preparation = StackManipulation.Trivial.INSTANCE;
+ } else {
+ preparation = new StackManipulation.Compound(
+ MethodVariableAccess.loadThis(),
+ Duplication.SINGLE.flipOver(fieldDescription.getType()),
+ Removal.SINGLE
+ );
+ }
+ return new StackManipulation.Compound(preparation, FieldAccess.forField(fieldDescription).write());
+ }
+
+ @Override
+ public StackManipulation resolveIncrement(int value) {
+ return new StackManipulation.Compound(
+ resolveRead(),
+ IntegerConstant.forValue(value),
+ Addition.INTEGER,
+ resolveWrite()
+ );
+ }
+ }
+ }
+
+ /**
+ * A target for an offset mapping that represents a read-only stack manipulation.
+ */
+ @EqualsAndHashCode
+ class ForStackManipulation implements Target {
+
+ /**
+ * The represented stack manipulation.
+ */
+ private final StackManipulation stackManipulation;
+
+ /**
+ * Creates a new target for an offset mapping for a stack manipulation.
+ *
+ * @param stackManipulation The represented stack manipulation.
+ */
+ public ForStackManipulation(StackManipulation stackManipulation) {
+ this.stackManipulation = stackManipulation;
+ }
+
+ /**
+ * Creates a target for a {@link Method} or {@link Constructor} constant.
+ *
+ * @param methodDescription The method or constructor to represent.
+ * @return A mapping for a method or constructor constant.
+ */
+ public static Target of(MethodDescription.InDefinedShape methodDescription) {
+ return new ForStackManipulation(MethodConstant.forMethod(methodDescription));
+ }
+
+ /**
+ * Creates a target for an offset mapping for a type constant.
+ *
+ * @param typeDescription The type constant to represent.
+ * @return A mapping for a type constant.
+ */
+ public static Target of(TypeDescription typeDescription) {
+ return new ForStackManipulation(ClassConstant.of(typeDescription));
+ }
+
+ /**
+ * Creates a target for an offset mapping for a constant string.
+ *
+ * @param value The constant string value to represent.
+ * @return A mapping for a constant string.
+ */
+ public static Target of(String value) {
+ return new ForStackManipulation(new TextConstant(value));
+ }
+
+ /**
+ * Creates a target for an offset mapping for a constant value.
+ *
+ * @param value The constant value to represent.
+ * @return An appropriate target for an offset mapping.
+ */
+ public static Target of(Object value) {
+ if (value instanceof Boolean) {
+ return new ForStackManipulation(IntegerConstant.forValue((Boolean) value));
+ } else if (value instanceof Byte) {
+ return new ForStackManipulation(IntegerConstant.forValue((Byte) value));
+ } else if (value instanceof Short) {
+ return new ForStackManipulation(IntegerConstant.forValue((Short) value));
+ } else if (value instanceof Character) {
+ return new ForStackManipulation(IntegerConstant.forValue((Character) value));
+ } else if (value instanceof Integer) {
+ return new ForStackManipulation(IntegerConstant.forValue((Integer) value));
+ } else if (value instanceof Long) {
+ return new ForStackManipulation(LongConstant.forValue((Long) value));
+ } else if (value instanceof Float) {
+ return new ForStackManipulation(FloatConstant.forValue((Float) value));
+ } else if (value instanceof Double) {
+ return new ForStackManipulation(DoubleConstant.forValue((Double) value));
+ } else if (value instanceof String) {
+ return new ForStackManipulation(new TextConstant((String) value));
+ } else {
+ throw new IllegalArgumentException("Not a constant value: " + value);
+ }
+ }
+
+ @Override
+ public StackManipulation resolveRead() {
+ return stackManipulation;
+ }
+
+ @Override
+ public StackManipulation resolveWrite() {
+ throw new IllegalStateException("Cannot write to constant value: " + stackManipulation);
+ }
+
+ @Override
+ public StackManipulation resolveIncrement(int value) {
+ throw new IllegalStateException("Cannot write to constant value: " + stackManipulation);
+ }
+ }
+ }
+
+ /**
+ * Represents a factory for creating a {@link OffsetMapping} for a given parameter for a given annotation.
+ *
+ * @param <T> the annotation type that triggers this factory.
+ */
+ interface Factory<T extends Annotation> {
+
+ /**
+ * Returns the annotation type of this factory.
+ *
+ * @return The factory's annotation type.
+ */
+ Class<T> getAnnotationType();
+
+ /**
+ * Creates a new offset mapping for the supplied parameter if possible.
+ *
+ * @param target The parameter description for which to resolve an offset mapping.
+ * @param annotation The annotation that triggered this factory.
+ * @param adviceType {@code true} if the binding is applied using advice method delegation.
+ * @return A resolved offset mapping or {@code null} if no mapping can be resolved for this parameter.
+ */
+ OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable<T> annotation, AdviceType adviceType);
+
+ /**
+ * Describes the type of advice being applied.
+ */
+ enum AdviceType {
+
+ /**
+ * Indicates advice where the invocation is delegated.
+ */
+ DELEGATION(true),
+
+ /**
+ * Indicates advice where the invocation's code is copied into the target method.
+ */
+ INLINING(false);
+
+ /**
+ * {@code true} if delegation is used.
+ */
+ private final boolean delegation;
+
+ /**
+ * Creates a new advice type.
+ *
+ * @param delegation {@code true} if delegation is used.
+ */
+ AdviceType(boolean delegation) {
+ this.delegation = delegation;
+ }
+
+ /**
+ * Returns {@code true} if delegation is used.
+ *
+ * @return {@code true} if delegation is used.
+ */
+ public boolean isDelegation() {
+ return delegation;
+ }
+ }
+
+ /**
+ * A simple factory that binds a constant offset mapping.
+ *
+ * @param <T>
+ */
+ @EqualsAndHashCode
+ class Simple<T extends Annotation> implements Factory<T> {
+
+ /**
+ * The annotation type being bound.
+ */
+ private final Class<T> annotationType;
+
+ /**
+ * The fixed offset mapping.
+ */
+ private final OffsetMapping offsetMapping;
+
+ /**
+ * Creates a simple factory for a simple binding for an offset mapping.
+ *
+ * @param annotationType The annotation type being bound.
+ * @param offsetMapping The fixed offset mapping.
+ */
+ public Simple(Class<T> annotationType, OffsetMapping offsetMapping) {
+ this.annotationType = annotationType;
+ this.offsetMapping = offsetMapping;
+ }
+
+ @Override
+ public Class<T> getAnnotationType() {
+ return annotationType;
+ }
+
+ @Override
+ public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable<T> annotation, AdviceType adviceType) {
+ return offsetMapping;
+ }
+ }
+
+ /**
+ * A factory for an annotation whose use is not permitted.
+ *
+ * @param <T> The annotation type this factory binds.
+ */
+ @EqualsAndHashCode
+ class Illegal<T extends Annotation> implements Factory<T> {
+
+ /**
+ * The annotation type.
+ */
+ private final Class<T> annotationType;
+
+ /**
+ * Creates a factory that does not permit the usage of the represented annotation.
+ *
+ * @param annotationType The annotation type.
+ */
+ public Illegal(Class<T> annotationType) {
+ this.annotationType = annotationType;
+ }
+
+ @Override
+ public Class<T> getAnnotationType() {
+ return annotationType;
+ }
+
+ @Override
+ public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable<T> annotation, AdviceType adviceType) {
+ throw new IllegalStateException("Usage of " + annotationType + " is not allowed on " + target);
+ }
+ }
+ }
+
+ /**
+ * An offset mapping for a given parameter of the instrumented method.
+ */
+ @EqualsAndHashCode
+ abstract class ForArgument implements OffsetMapping {
+
+ /**
+ * The type expected by the advice method.
+ */
+ protected final TypeDescription.Generic target;
+
+ /**
+ * Determines if the parameter is to be treated as read-only.
+ */
+ protected final boolean readOnly;
+
+ /**
+ * The typing to apply when assigning values.
+ */
+ private final Assigner.Typing typing;
+
+ /**
+ * Creates a new offset mapping for a parameter of the instrumented method.
+ *
+ * @param target The type expected by the advice method.
+ * @param readOnly Determines if the parameter is to be treated as read-only.
+ * @param typing The typing to apply.
+ */
+ protected ForArgument(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing) {
+ this.target = target;
+ this.readOnly = readOnly;
+ this.typing = typing;
+ }
+
+ @Override
+ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Context context) {
+ ParameterDescription parameterDescription = resolve(instrumentedMethod);
+ StackManipulation readAssignment = assigner.assign(parameterDescription.getType(), target, typing);
+ if (!readAssignment.isValid()) {
+ throw new IllegalStateException("Cannot assign " + parameterDescription + " to " + target);
+ } else if (readOnly) {
+ return new Target.ForVariable.ReadOnly(parameterDescription, readAssignment);
+ } else {
+ StackManipulation writeAssignment = assigner.assign(target, parameterDescription.getType(), typing);
+ if (!writeAssignment.isValid()) {
+ throw new IllegalStateException("Cannot assign " + parameterDescription + " to " + target);
+ }
+ return new Target.ForVariable.ReadWrite(parameterDescription, readAssignment, writeAssignment);
+ }
+ }
+
+ /**
+ * Resolves the bound parameter.
+ *
+ * @param instrumentedMethod The instrumented method.
+ * @return The bound parameter.
+ */
+ protected abstract ParameterDescription resolve(MethodDescription instrumentedMethod);
+
+ /**
+ * An offset mapping for a parameter of the instrumented method with a specific index.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ public static class Unresolved extends ForArgument {
+
+ /**
+ * The index of the parameter.
+ */
+ private final int index;
+
+ /**
+ * {@code true} if the parameter binding is optional.
+ */
+ private final boolean optional;
+
+ /**
+ * Creates a new offset binding for a parameter with a given index.
+ *
+ * @param target The target type.
+ * @param argument The annotation that triggers this binding.
+ */
+ protected Unresolved(TypeDescription.Generic target, Argument argument) {
+ this(target, argument.readOnly(), argument.typing(), argument.value(), argument.optional());
+ }
+
+ /**
+ * Creates a new offset binding for a parameter with a given index.
+ *
+ * @param parameterDescription The parameter triggering this binding.
+ */
+ protected Unresolved(ParameterDescription parameterDescription) {
+ this(parameterDescription.getType(), true, Assigner.Typing.STATIC, parameterDescription.getIndex());
+ }
+
+ /**
+ * Creates a non-optional offset binding for a parameter with a given index.
+ *
+ * @param target The type expected by the advice method.
+ * @param readOnly Determines if the parameter is to be treated as read-only.
+ * @param typing The typing to apply.
+ * @param index The index of the parameter.
+ */
+ public Unresolved(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing, int index) {
+ this(target, readOnly, typing, index, false);
+ }
+
+ /**
+ * Creates a new offset binding for a parameter with a given index.
+ *
+ * @param target The type expected by the advice method.
+ * @param readOnly Determines if the parameter is to be treated as read-only.
+ * @param typing The typing to apply.
+ * @param index The index of the parameter.
+ * @param optional {@code true} if the parameter binding is optional.
+ */
+ public Unresolved(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing, int index, boolean optional) {
+ super(target, readOnly, typing);
+ this.index = index;
+ this.optional = optional;
+ }
+
+ @Override
+ protected ParameterDescription resolve(MethodDescription instrumentedMethod) {
+ ParameterList<?> parameters = instrumentedMethod.getParameters();
+ if (parameters.size() <= index) {
+ throw new IllegalStateException(instrumentedMethod + " does not define an index " + index);
+ } else {
+ return parameters.get(index);
+ }
+ }
+
+ @Override
+ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Context context) {
+ if (optional && instrumentedMethod.getParameters().size() <= index) {
+ return readOnly
+ ? new Target.ForDefaultValue.ReadOnly(target)
+ : new Target.ForDefaultValue.ReadWrite(target);
+ }
+ return super.resolve(instrumentedType, instrumentedMethod, assigner, context);
+ }
+
+ /**
+ * A factory for a mapping of a parameter of the instrumented method.
+ */
+ protected enum Factory implements OffsetMapping.Factory<Argument> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Class<Argument> getAnnotationType() {
+ return Argument.class;
+ }
+
+ @Override
+ public OffsetMapping make(ParameterDescription.InDefinedShape target,
+ AnnotationDescription.Loadable<Argument> annotation,
+ AdviceType adviceType) {
+ if (adviceType.isDelegation() && !annotation.loadSilent().readOnly()) {
+ throw new IllegalStateException("Cannot define writable field access for " + target + " when using delegation");
+ } else {
+ return new ForArgument.Unresolved(target.getType(), annotation.loadSilent());
+ }
+ }
+ }
+ }
+
+ /**
+ * An offset mapping for a specific parameter of the instrumented method.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ public static class Resolved extends ForArgument {
+
+ /**
+ * The parameter being bound.
+ */
+ private final ParameterDescription parameterDescription;
+
+ /**
+ * Creates an offset mapping that binds a parameter of the instrumented method.
+ *
+ * @param target The type expected by the advice method.
+ * @param readOnly Determines if the parameter is to be treated as read-only.
+ * @param typing The typing to apply.
+ * @param parameterDescription The parameter being bound.
+ */
+ public Resolved(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing, ParameterDescription parameterDescription) {
+ super(target, readOnly, typing);
+ this.parameterDescription = parameterDescription;
+ }
+
+ @Override
+ protected ParameterDescription resolve(MethodDescription instrumentedMethod) {
+ if (!parameterDescription.getDeclaringMethod().equals(instrumentedMethod)) {
+ throw new IllegalStateException(parameterDescription + " is not a parameter of " + instrumentedMethod);
+ }
+ return parameterDescription;
+ }
+
+ /**
+ * A factory for a parameter argument of the instrumented method.
+ *
+ * @param <T> The type of the bound annotation.
+ */
+ @EqualsAndHashCode
+ public static class Factory<T extends Annotation> implements OffsetMapping.Factory<T> {
+
+ /**
+ * The annotation type.
+ */
+ private final Class<T> annotationType;
+
+ /**
+ * The bound parameter.
+ */
+ private final ParameterDescription parameterDescription;
+
+ /**
+ * {@code true} if the factory should create a read-only binding.
+ */
+ private final boolean readOnly;
+
+ /**
+ * The typing to use.
+ */
+ private final Assigner.Typing typing;
+
+ /**
+ * Creates a new factory for binding a parameter of the instrumented method with read-only semantics and static typing.
+ *
+ * @param annotationType The annotation type.
+ * @param parameterDescription The bound parameter.
+ */
+ public Factory(Class<T> annotationType, ParameterDescription parameterDescription) {
+ this(annotationType, parameterDescription, true, Assigner.Typing.STATIC);
+ }
+
+ /**
+ * Creates a new factory for binding a parameter of the instrumented method.
+ *
+ * @param annotationType The annotation type.
+ * @param parameterDescription The bound parameter.
+ * @param readOnly {@code true} if the factory should create a read-only binding.
+ * @param typing The typing to use.
+ */
+ public Factory(Class<T> annotationType, ParameterDescription parameterDescription, boolean readOnly, Assigner.Typing typing) {
+ this.annotationType = annotationType;
+ this.parameterDescription = parameterDescription;
+ this.readOnly = readOnly;
+ this.typing = typing;
+ }
+
+ @Override
+ public Class<T> getAnnotationType() {
+ return annotationType;
+ }
+
+ @Override
+ public OffsetMapping make(ParameterDescription.InDefinedShape target,
+ AnnotationDescription.Loadable<T> annotation,
+ AdviceType adviceType) {
+ return new Resolved(target.getType(), readOnly, typing, parameterDescription);
+ }
+ }
+ }
+ }
+
+ /**
+ * An offset mapping that provides access to the {@code this} reference of the instrumented method.
+ */
+ @EqualsAndHashCode
+ class ForThisReference implements OffsetMapping {
+
+ /**
+ * The offset of the {@code this} reference.
+ */
+ private static final int THIS_REFERENCE = 0;
+
+ /**
+ * The type that the advice method expects for the {@code this} reference.
+ */
+ private final TypeDescription.Generic target;
+
+ /**
+ * Determines if the parameter is to be treated as read-only.
+ */
+ private final boolean readOnly;
+
+ /**
+ * The typing to apply.
+ */
+ private final Assigner.Typing typing;
+
+ /**
+ * {@code true} if the parameter should be bound to {@code null} if the instrumented method is static.
+ */
+ private final boolean optional;
+
+ /**
+ * Creates a new offset mapping for a {@code this} reference.
+ *
+ * @param target The type that the advice method expects for the {@code this} reference.
+ * @param annotation The mapped annotation.
+ */
+ protected ForThisReference(TypeDescription.Generic target, This annotation) {
+ this(target, annotation.readOnly(), annotation.typing(), annotation.optional());
+ }
+
+ /**
+ * Creates a new offset mapping for a {@code this} reference.
+ *
+ * @param target The type that the advice method expects for the {@code this} reference.
+ * @param readOnly Determines if the parameter is to be treated as read-only.
+ * @param typing The typing to apply.
+ * @param optional {@code true} if the parameter should be bound to {@code null} if the instrumented method is static.
+ */
+ public ForThisReference(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing, boolean optional) {
+ this.target = target;
+ this.readOnly = readOnly;
+ this.typing = typing;
+ this.optional = optional;
+ }
+
+ @Override
+ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Context context) {
+ if (instrumentedMethod.isStatic() || !context.isInitialized()) {
+ if (optional) {
+ return readOnly
+ ? new Target.ForDefaultValue.ReadOnly(instrumentedType)
+ : new Target.ForDefaultValue.ReadWrite(instrumentedType);
+ } else {
+ throw new IllegalStateException("Cannot map this reference for static method or constructor start: " + instrumentedMethod);
+ }
+ }
+ StackManipulation readAssignment = assigner.assign(instrumentedType.asGenericType(), target, typing);
+ if (!readAssignment.isValid()) {
+ throw new IllegalStateException("Cannot assign " + instrumentedType + " to " + target);
+ } else if (readOnly) {
+ return new Target.ForVariable.ReadOnly(instrumentedType.asGenericType(), THIS_REFERENCE, readAssignment);
+ } else {
+ StackManipulation writeAssignment = assigner.assign(target, instrumentedType.asGenericType(), typing);
+ if (!writeAssignment.isValid()) {
+ throw new IllegalStateException("Cannot assign " + target + " to " + instrumentedType);
+ }
+ return new Target.ForVariable.ReadWrite(instrumentedType.asGenericType(), THIS_REFERENCE, readAssignment, writeAssignment);
+ }
+ }
+
+ /**
+ * A factory for creating a {@link ForThisReference} offset mapping.
+ */
+ protected enum Factory implements OffsetMapping.Factory<This> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Class<This> getAnnotationType() {
+ return This.class;
+ }
+
+ @Override
+ public OffsetMapping make(ParameterDescription.InDefinedShape target,
+ AnnotationDescription.Loadable<This> annotation,
+ AdviceType adviceType) {
+ if (adviceType.isDelegation() && !annotation.loadSilent().readOnly()) {
+ throw new IllegalStateException("Cannot write to this reference for " + target + " in read-only context");
+ } else {
+ return new ForThisReference(target.getType(), annotation.loadSilent());
+ }
+ }
+ }
+ }
+
+ /**
+ * An offset mapping that maps an array containing all arguments of the instrumented method.
+ */
+ @EqualsAndHashCode
+ class ForAllArguments implements OffsetMapping {
+
+ /**
+ * The component target type.
+ */
+ private final TypeDescription.Generic target;
+
+ /**
+ * {@code true} if the array is read-only.
+ */
+ private final boolean readOnly;
+
+ /**
+ * The typing to apply.
+ */
+ private final Assigner.Typing typing;
+
+ /**
+ * Creates a new offset mapping for an array containing all arguments.
+ *
+ * @param target The component target type.
+ * @param annotation The mapped annotation.
+ */
+ protected ForAllArguments(TypeDescription.Generic target, AllArguments annotation) {
+ this(target, annotation.readOnly(), annotation.typing());
+ }
+
+ /**
+ * Creates a new offset mapping for an array containing all arguments.
+ *
+ * @param target The component target type.
+ * @param readOnly {@code true} if the array is read-only.
+ * @param typing The typing to apply.
+ */
+ public ForAllArguments(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing) {
+ this.target = target;
+ this.readOnly = readOnly;
+ this.typing = typing;
+ }
+
+ @Override
+ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Context context) {
+ List<StackManipulation> valueReads = new ArrayList<StackManipulation>(instrumentedMethod.getParameters().size());
+ for (ParameterDescription parameterDescription : instrumentedMethod.getParameters()) {
+ StackManipulation readAssignment = assigner.assign(parameterDescription.getType(), target, typing);
+ if (!readAssignment.isValid()) {
+ throw new IllegalStateException("Cannot assign " + parameterDescription + " to " + target);
+ }
+ valueReads.add(new StackManipulation.Compound(MethodVariableAccess.load(parameterDescription), readAssignment));
+ }
+ if (readOnly) {
+ return new Target.ForArray.ReadOnly(target, valueReads);
+ } else {
+ List<StackManipulation> valueWrites = new ArrayList<StackManipulation>(instrumentedMethod.getParameters().size());
+ for (ParameterDescription parameterDescription : instrumentedMethod.getParameters()) {
+ StackManipulation writeAssignment = assigner.assign(target, parameterDescription.getType(), typing);
+ if (!writeAssignment.isValid()) {
+ throw new IllegalStateException("Cannot assign " + target + " to " + parameterDescription);
+ }
+ valueWrites.add(new StackManipulation.Compound(writeAssignment, MethodVariableAccess.store(parameterDescription)));
+ }
+ return new Target.ForArray.ReadWrite(target, valueReads, valueWrites);
+ }
+ }
+
+ /**
+ * A factory for an offset mapping that maps all arguments values of the instrumented method.
+ */
+ protected enum Factory implements OffsetMapping.Factory<AllArguments> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Class<AllArguments> getAnnotationType() {
+ return AllArguments.class;
+ }
+
+ @Override
+ public OffsetMapping make(ParameterDescription.InDefinedShape target,
+ AnnotationDescription.Loadable<AllArguments> annotation,
+ AdviceType adviceType) {
+ if (!target.getType().represents(Object.class) && !target.getType().isArray()) {
+ throw new IllegalStateException("Cannot use AllArguments annotation on a non-array type");
+ } else if (adviceType.isDelegation() && !annotation.loadSilent().readOnly()) {
+ throw new IllegalStateException("Cannot define writable field access for " + target);
+ } else {
+ return new ForAllArguments(target.getType().represents(Object.class)
+ ? TypeDescription.Generic.OBJECT
+ : target.getType().getComponentType(), annotation.loadSilent());
+ }
+ }
+ }
+ }
+
+ /**
+ * Maps the declaring type of the instrumented method.
+ */
+ enum ForInstrumentedType implements OffsetMapping {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Context context) {
+ return Target.ForStackManipulation.of(instrumentedType);
+ }
+ }
+
+ /**
+ * Maps a constant representing the instrumented method.
+ */
+ enum ForInstrumentedMethod implements OffsetMapping {
+
+ /**
+ * A constant that must be a {@link Method} instance.
+ */
+ METHOD {
+ @Override
+ protected boolean isRepresentable(MethodDescription instrumentedMethod) {
+ return instrumentedMethod.isMethod();
+ }
+ },
+
+ /**
+ * A constant that must be a {@link Constructor} instance.
+ */
+ CONSTRUCTOR {
+ @Override
+ protected boolean isRepresentable(MethodDescription instrumentedMethod) {
+ return instrumentedMethod.isConstructor();
+ }
+ },
+
+ /**
+ * A constant that must be a {@code java.lang.reflect.Executable} instance.
+ */
+ EXECUTABLE {
+ @Override
+ protected boolean isRepresentable(MethodDescription instrumentedMethod) {
+ return true;
+ }
+ };
+
+ @Override
+ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Context context) {
+ if (!isRepresentable(instrumentedMethod)) {
+ throw new IllegalStateException("Cannot represent " + instrumentedMethod + " as given method constant");
+ }
+ return Target.ForStackManipulation.of(instrumentedMethod.asDefined());
+ }
+
+ /**
+ * Checks if the supplied method is representable for the assigned offset mapping.
+ *
+ * @param instrumentedMethod The instrumented method to represent.
+ * @return {@code true} if this method is representable.
+ */
+ protected abstract boolean isRepresentable(MethodDescription instrumentedMethod);
+ }
+
+ /**
+ * An offset mapping for a field.
+ */
+ @EqualsAndHashCode
+ abstract class ForField implements OffsetMapping {
+
+ /**
+ * The {@link FieldValue#value()} method.
+ */
+ private static final MethodDescription.InDefinedShape VALUE;
+
+ /**
+ * The {@link FieldValue#declaringType()}} method.
+ */
+ private static final MethodDescription.InDefinedShape DECLARING_TYPE;
+
+ /**
+ * The {@link FieldValue#readOnly()}} method.
+ */
+ private static final MethodDescription.InDefinedShape READ_ONLY;
+
+ /**
+ * The {@link FieldValue#typing()}} method.
+ */
+ private static final MethodDescription.InDefinedShape TYPING;
+
+ /*
+ * Looks up all annotation properties to avoid loading of the declaring field type.
+ */
+ static {
+ MethodList<MethodDescription.InDefinedShape> methods = new TypeDescription.ForLoadedType(FieldValue.class).getDeclaredMethods();
+ VALUE = methods.filter(named("value")).getOnly();
+ DECLARING_TYPE = methods.filter(named("declaringType")).getOnly();
+ READ_ONLY = methods.filter(named("readOnly")).getOnly();
+ TYPING = methods.filter(named("typing")).getOnly();
+ }
+
+ /**
+ * The expected type that the field can be assigned to.
+ */
+ private final TypeDescription.Generic target;
+
+ /**
+ * {@code true} if this mapping is read-only.
+ */
+ private final boolean readOnly;
+
+ /**
+ * The typing to apply.
+ */
+ private final Assigner.Typing typing;
+
+ /**
+ * Creates an offset mapping for a field.
+ *
+ * @param target The target type.
+ * @param readOnly {@code true} if this mapping is read-only.
+ * @param typing The typing to apply.
+ */
+ public ForField(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing) {
+ this.target = target;
+ this.readOnly = readOnly;
+ this.typing = typing;
+ }
+
+ @Override
+ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Context context) {
+ FieldDescription fieldDescription = resolve(instrumentedType);
+ if (!fieldDescription.isStatic() && instrumentedMethod.isStatic()) {
+ throw new IllegalStateException("Cannot read non-static field " + fieldDescription + " from static method " + instrumentedMethod);
+ } else if (!context.isInitialized() && !fieldDescription.isStatic()) {
+ throw new IllegalStateException("Cannot access non-static field before calling constructor: " + instrumentedMethod);
+ }
+ StackManipulation readAssignment = assigner.assign(fieldDescription.getType(), target, typing);
+ if (!readAssignment.isValid()) {
+ throw new IllegalStateException("Cannot assign " + fieldDescription + " to " + target);
+ } else if (readOnly) {
+ return new Target.ForField.ReadOnly(fieldDescription, readAssignment);
+ } else {
+ StackManipulation writeAssignment = assigner.assign(target, fieldDescription.getType(), typing);
+ if (!writeAssignment.isValid()) {
+ throw new IllegalStateException("Cannot assign " + target + " to " + fieldDescription);
+ }
+ return new Target.ForField.ReadWrite(fieldDescription.asDefined(), readAssignment, writeAssignment);
+ }
+ }
+
+ /**
+ * Resolves the field being bound.
+ *
+ * @param instrumentedType The instrumented type.
+ * @return The field being bound.
+ */
+ protected abstract FieldDescription resolve(TypeDescription instrumentedType);
+
+ /**
+ * An offset mapping for a field that is resolved from the instrumented type by its name.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ public abstract static class Unresolved extends ForField {
+
+ /**
+ * The name of the field.
+ */
+ private final String name;
+
+ /**
+ * Creates an offset mapping for a field that is not yet resolved.
+ *
+ * @param target The target type.
+ * @param readOnly {@code true} if this mapping is read-only.
+ * @param typing The typing to apply.
+ * @param name The name of the field.
+ */
+ public Unresolved(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing, String name) {
+ super(target, readOnly, typing);
+ this.name = name;
+ }
+
+ @Override
+ protected FieldDescription resolve(TypeDescription instrumentedType) {
+ FieldLocator.Resolution resolution = fieldLocator(instrumentedType).locate(name);
+ if (!resolution.isResolved()) {
+ throw new IllegalStateException("Cannot locate field named " + name + " for " + instrumentedType);
+ } else {
+ return resolution.getField();
+ }
+ }
+
+ /**
+ * Returns a field locator for this instance.
+ *
+ * @param instrumentedType The instrumented type.
+ * @return An appropriate field locator.
+ */
+ protected abstract FieldLocator fieldLocator(TypeDescription instrumentedType);
+
+ /**
+ * An offset mapping for a field with an implicit declaring type.
+ */
+ public static class WithImplicitType extends Unresolved {
+
+ /**
+ * Creates an offset mapping for a field with an implicit declaring type.
+ *
+ * @param target The target type.
+ * @param annotation The annotation to represent.
+ */
+ protected WithImplicitType(TypeDescription.Generic target, AnnotationDescription.Loadable<FieldValue> annotation) {
+ this(target,
+ annotation.getValue(READ_ONLY).resolve(Boolean.class),
+ annotation.getValue(TYPING).loadSilent(Assigner.Typing.class.getClassLoader()).resolve(Assigner.Typing.class),
+ annotation.getValue(VALUE).resolve(String.class));
+ }
+
+ /**
+ * Creates an offset mapping for a field with an implicit declaring type.
+ *
+ * @param target The target type.
+ * @param name The name of the field.
+ * @param readOnly {@code true} if the field is read-only.
+ * @param typing The typing to apply.
+ */
+ public WithImplicitType(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing, String name) {
+ super(target, readOnly, typing, name);
+ }
+
+ @Override
+ protected FieldLocator fieldLocator(TypeDescription instrumentedType) {
+ return new FieldLocator.ForClassHierarchy(instrumentedType);
+ }
+ }
+
+ /**
+ * An offset mapping for a field with an explicit declaring type.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ public static class WithExplicitType extends Unresolved {
+
+ /**
+ * The type declaring the field.
+ */
+ private final TypeDescription declaringType;
+
+ /**
+ * Creates an offset mapping for a field with an explicit declaring type.
+ *
+ * @param target The target type.
+ * @param annotation The annotation to represent.
+ * @param declaringType The field's declaring type.
+ */
+ protected WithExplicitType(TypeDescription.Generic target,
+ AnnotationDescription.Loadable<FieldValue> annotation,
+ TypeDescription declaringType) {
+ this(target,
+ annotation.getValue(READ_ONLY).resolve(Boolean.class),
+ annotation.getValue(TYPING).loadSilent(Assigner.Typing.class.getClassLoader()).resolve(Assigner.Typing.class),
+ annotation.getValue(VALUE).resolve(String.class),
+ declaringType);
+ }
+
+ /**
+ * Creates an offset mapping for a field with an explicit declaring type.
+ *
+ * @param target The target type.
+ * @param name The name of the field.
+ * @param readOnly {@code true} if the field is read-only.
+ * @param typing The typing to apply.
+ * @param declaringType The field's declaring type.
+ */
+ public WithExplicitType(TypeDescription.Generic target,
+ boolean readOnly,
+ Assigner.Typing typing,
+ String name,
+ TypeDescription declaringType) {
+ super(target, readOnly, typing, name);
+ this.declaringType = declaringType;
+ }
+
+ @Override
+ protected FieldLocator fieldLocator(TypeDescription instrumentedType) {
+ if (!declaringType.represents(TargetType.class) && !instrumentedType.isAssignableTo(declaringType)) {
+ throw new IllegalStateException(declaringType + " is no super type of " + instrumentedType);
+ }
+ return new FieldLocator.ForExactType(TargetType.resolve(declaringType, instrumentedType));
+ }
+ }
+
+ /**
+ * A factory for a {@link Unresolved} offset mapping.
+ */
+ protected enum Factory implements OffsetMapping.Factory<FieldValue> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Class<FieldValue> getAnnotationType() {
+ return FieldValue.class;
+ }
+
+ @Override
+ public OffsetMapping make(ParameterDescription.InDefinedShape target,
+ AnnotationDescription.Loadable<FieldValue> annotation,
+ AdviceType adviceType) {
+ if (adviceType.isDelegation() && !annotation.getValue(ForField.READ_ONLY).resolve(Boolean.class)) {
+ throw new IllegalStateException("Cannot write to field for " + target + " in read-only context");
+ } else {
+ TypeDescription declaringType = annotation.getValue(DECLARING_TYPE).resolve(TypeDescription.class);
+ return declaringType.represents(void.class)
+ ? new WithImplicitType(target.getType(), annotation)
+ : new WithExplicitType(target.getType(), annotation, declaringType);
+ }
+ }
+ }
+ }
+
+ /**
+ * A binding for an offset mapping that represents a specific field.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ public static class Resolved extends ForField {
+
+ /**
+ * The accessed field.
+ */
+ private final FieldDescription fieldDescription;
+
+ /**
+ * Creates a resolved offset mapping for a field.
+ *
+ * @param target The target type.
+ * @param readOnly {@code true} if this mapping is read-only.
+ * @param typing The typing to apply.
+ * @param fieldDescription The accessed field.
+ */
+ public Resolved(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing, FieldDescription fieldDescription) {
+ super(target, readOnly, typing);
+ this.fieldDescription = fieldDescription;
+ }
+
+ @Override
+ protected FieldDescription resolve(TypeDescription instrumentedType) {
+ if (!fieldDescription.isStatic() && !fieldDescription.getDeclaringType().asErasure().isAssignableFrom(instrumentedType)) {
+ throw new IllegalStateException(fieldDescription + " is no member of " + instrumentedType);
+ } else if (!fieldDescription.isAccessibleTo(instrumentedType)) {
+ throw new IllegalStateException("Cannot access " + fieldDescription + " from " + instrumentedType);
+ }
+ return fieldDescription;
+ }
+
+ /**
+ * A factory that binds a field.
+ *
+ * @param <T> The annotation type this factory binds.
+ */
+ @EqualsAndHashCode
+ public static class Factory<T extends Annotation> implements OffsetMapping.Factory<T> {
+
+ /**
+ * The annotation type.
+ */
+ private final Class<T> annotationType;
+
+ /**
+ * The field to be bound.
+ */
+ private final FieldDescription fieldDescription;
+
+ /**
+ * {@code true} if this factory should create a read-only binding.
+ */
+ private final boolean readOnly;
+
+ /**
+ * The typing to use.
+ */
+ private final Assigner.Typing typing;
+
+ /**
+ * Creates a new factory for binding a specific field with read-only semantics and static typing.
+ *
+ * @param annotationType The annotation type.
+ * @param fieldDescription The field to bind.
+ */
+ public Factory(Class<T> annotationType, FieldDescription fieldDescription) {
+ this(annotationType, fieldDescription, true, Assigner.Typing.STATIC);
+ }
+
+ /**
+ * Creates a new factory for binding a specific field.
+ *
+ * @param annotationType The annotation type.
+ * @param fieldDescription The field to bind.
+ * @param readOnly {@code true} if this factory should create a read-only binding.
+ * @param typing The typing to use.
+ */
+ public Factory(Class<T> annotationType, FieldDescription fieldDescription, boolean readOnly, Assigner.Typing typing) {
+ this.annotationType = annotationType;
+ this.fieldDescription = fieldDescription;
+ this.readOnly = readOnly;
+ this.typing = typing;
+ }
+
+ @Override
+ public Class<T> getAnnotationType() {
+ return annotationType;
+ }
+
+ @Override
+ public OffsetMapping make(ParameterDescription.InDefinedShape target,
+ AnnotationDescription.Loadable<T> annotation,
+ AdviceType adviceType) {
+ return new Resolved(target.getType(), readOnly, typing, fieldDescription);
+ }
+ }
+ }
+ }
+
+ /**
+ * An offset mapping for the {@link Advice.Origin} annotation.
+ */
+ @EqualsAndHashCode
+ class ForOrigin implements OffsetMapping {
+
+ /**
+ * The delimiter character.
+ */
+ private static final char DELIMITER = '#';
+
+ /**
+ * The escape character.
+ */
+ private static final char ESCAPE = '\\';
+
+ /**
+ * The renderers to apply.
+ */
+ private final List<Renderer> renderers;
+
+ /**
+ * Creates a new offset mapping for an origin value.
+ *
+ * @param renderers The renderers to apply.
+ */
+ public ForOrigin(List<Renderer> renderers) {
+ this.renderers = renderers;
+ }
+
+ /**
+ * Parses a pattern of an origin annotation.
+ *
+ * @param pattern The supplied pattern.
+ * @return An appropriate offset mapping.
+ */
+ public static OffsetMapping parse(String pattern) {
+ if (pattern.equals(Origin.DEFAULT)) {
+ return new ForOrigin(Collections.<Renderer>singletonList(Renderer.ForStringRepresentation.INSTANCE));
+ } else {
+ List<Renderer> renderers = new ArrayList<Renderer>(pattern.length());
+ int from = 0;
+ for (int to = pattern.indexOf(DELIMITER); to != -1; to = pattern.indexOf(DELIMITER, from)) {
+ if (to != 0 && pattern.charAt(to - 1) == ESCAPE && (to == 1 || pattern.charAt(to - 2) != ESCAPE)) {
+ renderers.add(new Renderer.ForConstantValue(pattern.substring(from, Math.max(0, to - 1)) + DELIMITER));
+ from = to + 1;
+ continue;
+ } else if (pattern.length() == to + 1) {
+ throw new IllegalStateException("Missing sort descriptor for " + pattern + " at index " + to);
+ }
+ renderers.add(new Renderer.ForConstantValue(pattern.substring(from, to).replace("" + ESCAPE + ESCAPE, "" + ESCAPE)));
+ switch (pattern.charAt(to + 1)) {
+ case Renderer.ForMethodName.SYMBOL:
+ renderers.add(Renderer.ForMethodName.INSTANCE);
+ break;
+ case Renderer.ForTypeName.SYMBOL:
+ renderers.add(Renderer.ForTypeName.INSTANCE);
+ break;
+ case Renderer.ForDescriptor.SYMBOL:
+ renderers.add(Renderer.ForDescriptor.INSTANCE);
+ break;
+ case Renderer.ForReturnTypeName.SYMBOL:
+ renderers.add(Renderer.ForReturnTypeName.INSTANCE);
+ break;
+ case Renderer.ForJavaSignature.SYMBOL:
+ renderers.add(Renderer.ForJavaSignature.INSTANCE);
+ break;
+ default:
+ throw new IllegalStateException("Illegal sort descriptor " + pattern.charAt(to + 1) + " for " + pattern);
+ }
+ from = to + 2;
+ }
+ renderers.add(new Renderer.ForConstantValue(pattern.substring(from)));
+ return new ForOrigin(renderers);
+ }
+ }
+
+ @Override
+ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Context context) {
+ StringBuilder stringBuilder = new StringBuilder();
+ for (Renderer renderer : renderers) {
+ stringBuilder.append(renderer.apply(instrumentedType, instrumentedMethod));
+ }
+ return Target.ForStackManipulation.of(stringBuilder.toString());
+ }
+
+ /**
+ * A renderer for an origin pattern element.
+ */
+ public interface Renderer {
+
+ /**
+ * Returns a string representation for this renderer.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param instrumentedMethod The instrumented method.
+ * @return The string representation.
+ */
+ String apply(TypeDescription instrumentedType, MethodDescription instrumentedMethod);
+
+ /**
+ * A renderer for a method's internal name.
+ */
+ enum ForMethodName implements Renderer {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ /**
+ * The method name symbol.
+ */
+ public static final char SYMBOL = 'm';
+
+ @Override
+ public String apply(TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
+ return instrumentedMethod.getInternalName();
+ }
+ }
+
+ /**
+ * A renderer for a method declaring type's binary name.
+ */
+ enum ForTypeName implements Renderer {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ /**
+ * The type name symbol.
+ */
+ public static final char SYMBOL = 't';
+
+ @Override
+ public String apply(TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
+ return instrumentedType.getName();
+ }
+ }
+
+ /**
+ * A renderer for a method descriptor.
+ */
+ enum ForDescriptor implements Renderer {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ /**
+ * The descriptor symbol.
+ */
+ public static final char SYMBOL = 'd';
+
+ @Override
+ public String apply(TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
+ return instrumentedMethod.getDescriptor();
+ }
+ }
+
+ /**
+ * A renderer for a method's Java signature in binary form.
+ */
+ enum ForJavaSignature implements Renderer {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ /**
+ * The signature symbol.
+ */
+ public static final char SYMBOL = 's';
+
+ @Override
+ public String apply(TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
+ StringBuilder stringBuilder = new StringBuilder("(");
+ boolean comma = false;
+ for (TypeDescription typeDescription : instrumentedMethod.getParameters().asTypeList().asErasures()) {
+ if (comma) {
+ stringBuilder.append(',');
+ } else {
+ comma = true;
+ }
+ stringBuilder.append(typeDescription.getName());
+ }
+ return stringBuilder.append(')').toString();
+ }
+ }
+
+ /**
+ * A renderer for a method's return type in binary form.
+ */
+ enum ForReturnTypeName implements Renderer {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ /**
+ * The return type symbol.
+ */
+ public static final char SYMBOL = 'r';
+
+ @Override
+ public String apply(TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
+ return instrumentedMethod.getReturnType().asErasure().getName();
+ }
+ }
+
+ /**
+ * A renderer for a method's {@link Object#toString()} representation.
+ */
+ enum ForStringRepresentation implements Renderer {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public String apply(TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
+ return instrumentedMethod.toString();
+ }
+ }
+
+ /**
+ * A renderer for a constant value.
+ */
+ @EqualsAndHashCode
+ class ForConstantValue implements Renderer {
+
+ /**
+ * The constant value.
+ */
+ private final String value;
+
+ /**
+ * Creates a new renderer for a constant value.
+ *
+ * @param value The constant value.
+ */
+ public ForConstantValue(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public String apply(TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
+ return value;
+ }
+ }
+ }
+
+ /**
+ * A factory for a method origin.
+ */
+ protected enum Factory implements OffsetMapping.Factory<Origin> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Class<Origin> getAnnotationType() {
+ return Origin.class;
+ }
+
+ @Override
+ public OffsetMapping make(ParameterDescription.InDefinedShape target,
+ AnnotationDescription.Loadable<Origin> annotation,
+ AdviceType adviceType) {
+ if (target.getType().asErasure().represents(Class.class)) {
+ return OffsetMapping.ForInstrumentedType.INSTANCE;
+ } else if (target.getType().asErasure().represents(Method.class)) {
+ return OffsetMapping.ForInstrumentedMethod.METHOD;
+ } else if (target.getType().asErasure().represents(Constructor.class)) {
+ return OffsetMapping.ForInstrumentedMethod.CONSTRUCTOR;
+ } else if (JavaType.EXECUTABLE.getTypeStub().equals(target.getType().asErasure())) {
+ return OffsetMapping.ForInstrumentedMethod.EXECUTABLE;
+ } else if (target.getType().asErasure().isAssignableFrom(String.class)) {
+ return ForOrigin.parse(annotation.loadSilent().value());
+ } else {
+ throw new IllegalStateException("Non-supported type " + target.getType() + " for @Origin annotation");
+ }
+ }
+ }
+ }
+
+ /**
+ * An offset mapping for a parameter where assignments are fully ignored and that always return the parameter type's default value.
+ */
+ @EqualsAndHashCode
+ class ForUnusedValue implements OffsetMapping {
+
+ /**
+ * The unused type.
+ */
+ private final TypeDefinition target;
+
+ /**
+ * Creates a new offset mapping for an unused type.
+ *
+ * @param target The unused type.
+ */
+ public ForUnusedValue(TypeDefinition target) {
+ this.target = target;
+ }
+
+ @Override
+ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Context context) {
+ return new Target.ForDefaultValue.ReadWrite(target);
+ }
+
+ /**
+ * A factory for an offset mapping for an unused value.
+ */
+ protected enum Factory implements OffsetMapping.Factory<Unused> {
+
+ /**
+ * A factory for representing an unused value.
+ */
+ INSTANCE;
+
+ @Override
+ public Class<Unused> getAnnotationType() {
+ return Unused.class;
+ }
+
+ @Override
+ public OffsetMapping make(ParameterDescription.InDefinedShape target,
+ AnnotationDescription.Loadable<Unused> annotation,
+ AdviceType adviceType) {
+ return new ForUnusedValue(target.getType());
+ }
+ }
+ }
+
+ /**
+ * An offset mapping for a parameter where assignments are fully ignored and that is assigned a boxed version of the instrumented
+ * method's return valueor {@code null} if the return type is not primitive or {@code void}.
+ */
+ enum ForStubValue implements OffsetMapping, Factory<StubValue> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Context context) {
+ return new Target.ForDefaultValue.ReadOnly(instrumentedMethod.getReturnType(), assigner.assign(instrumentedMethod.getReturnType(),
+ TypeDescription.Generic.OBJECT,
+ Assigner.Typing.DYNAMIC));
+ }
+
+ @Override
+ public Class<StubValue> getAnnotationType() {
+ return StubValue.class;
+ }
+
+ @Override
+ public OffsetMapping make(ParameterDescription.InDefinedShape target,
+ AnnotationDescription.Loadable<StubValue> annotation,
+ AdviceType adviceType) {
+ if (!target.getType().represents(Object.class)) {
+ throw new IllegalStateException("Cannot use StubValue on non-Object parameter type " + target);
+ } else {
+ return this;
+ }
+ }
+ }
+
+ /**
+ * An offset mapping that provides access to the value that is returned by the enter advice.
+ */
+ @EqualsAndHashCode
+ class ForEnterValue implements OffsetMapping {
+
+ /**
+ * The represented target type.
+ */
+ private final TypeDescription.Generic target;
+
+ /**
+ * The enter type.
+ */
+ private final TypeDescription.Generic enterType;
+
+ /**
+ * {@code true} if the annotated value is read-only.
+ */
+ private final boolean readOnly;
+
+ /**
+ * The typing to apply.
+ */
+ private final Assigner.Typing typing;
+
+ /**
+ * Creates a new offset mapping for the enter type.
+ *
+ * @param target The represented target type.
+ * @param enterType The enter type.
+ * @param enter The represented annotation.
+ */
+ protected ForEnterValue(TypeDescription.Generic target, TypeDescription.Generic enterType, Enter enter) {
+ this(target, enterType, enter.readOnly(), enter.typing());
+ }
+
+ /**
+ * Creates a new offset mapping for the enter type.
+ *
+ * @param target The represented target type.
+ * @param enterType The enter type.
+ * @param readOnly {@code true} if the annotated value is read-only.
+ * @param typing The typing to apply.
+ */
+ public ForEnterValue(TypeDescription.Generic target, TypeDescription.Generic enterType, boolean readOnly, Assigner.Typing typing) {
+ this.target = target;
+ this.enterType = enterType;
+ this.readOnly = readOnly;
+ this.typing = typing;
+ }
+
+ @Override
+ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Context context) {
+ StackManipulation readAssignment = assigner.assign(enterType, target, typing);
+ if (!readAssignment.isValid()) {
+ throw new IllegalStateException("Cannot assign " + enterType + " to " + target);
+ } else if (readOnly) {
+ return new Target.ForVariable.ReadOnly(target, instrumentedMethod.getStackSize(), readAssignment);
+ } else {
+ StackManipulation writeAssignment = assigner.assign(target, enterType, typing);
+ if (!writeAssignment.isValid()) {
+ throw new IllegalStateException("Cannot assign " + target + " to " + enterType);
+ }
+ return new Target.ForVariable.ReadWrite(target, instrumentedMethod.getStackSize(), readAssignment, writeAssignment);
+ }
+ }
+
+ /**
+ * A factory for creating a {@link ForEnterValue} offset mapping.
+ */
+ @EqualsAndHashCode
+ protected static class Factory implements OffsetMapping.Factory<Enter> {
+
+ /**
+ * The supplied type of the enter method.
+ */
+ private final TypeDefinition enterType;
+
+ /**
+ * Creates a new factory for creating a {@link ForEnterValue} offset mapping.
+ *
+ * @param enterType The supplied type of the enter method.
+ */
+ protected Factory(TypeDefinition enterType) {
+ this.enterType = enterType;
+ }
+
+ @Override
+ public Class<Enter> getAnnotationType() {
+ return Enter.class;
+ }
+
+ @Override
+ public OffsetMapping make(ParameterDescription.InDefinedShape target,
+ AnnotationDescription.Loadable<Enter> annotation,
+ AdviceType adviceType) {
+ if (adviceType.isDelegation() && !annotation.loadSilent().readOnly()) {
+ throw new IllegalStateException("Cannot use writable " + target + " on read-only parameter");
+ } else {
+ return new ForEnterValue(target.getType(), enterType.asGenericType(), annotation.loadSilent());
+ }
+ }
+ }
+ }
+
+ /**
+ * An offset mapping that provides access to the value that is returned by the instrumented method.
+ */
+ @EqualsAndHashCode
+ class ForReturnValue implements OffsetMapping {
+
+ /**
+ * The type that the advice method expects for the return value.
+ */
+ private final TypeDescription.Generic target;
+
+ /**
+ * Determines if the parameter is to be treated as read-only.
+ */
+ private final boolean readOnly;
+
+ /**
+ * The typing to apply.
+ */
+ private final Assigner.Typing typing;
+
+ /**
+ * Creates a new offset mapping for a return value.
+ *
+ * @param target The type that the advice method expects for the return value.
+ * @param annotation The annotation being bound.
+ */
+ protected ForReturnValue(TypeDescription.Generic target, Return annotation) {
+ this(target, annotation.readOnly(), annotation.typing());
+ }
+
+ /**
+ * Creates a new offset mapping for a return value.
+ *
+ * @param target The type that the advice method expects for the return value.
+ * @param readOnly Determines if the parameter is to be treated as read-only.
+ * @param typing The typing to apply.
+ */
+ public ForReturnValue(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing) {
+ this.target = target;
+ this.readOnly = readOnly;
+ this.typing = typing;
+ }
+
+ @Override
+ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Context context) {
+ int offset = instrumentedMethod.getStackSize() + context.getPadding();
+ StackManipulation readAssignment = assigner.assign(instrumentedMethod.getReturnType(), target, typing);
+ if (!readAssignment.isValid()) {
+ throw new IllegalStateException("Cannot assign " + instrumentedMethod.getReturnType() + " to " + target);
+ } else if (readOnly) {
+ return instrumentedMethod.getReturnType().represents(void.class)
+ ? new Target.ForDefaultValue.ReadOnly(target)
+ : new Target.ForVariable.ReadOnly(instrumentedMethod.getReturnType(), offset, readAssignment);
+ } else {
+ StackManipulation writeAssignment = assigner.assign(target, instrumentedMethod.getReturnType(), typing);
+ if (!writeAssignment.isValid()) {
+ throw new IllegalStateException("Cannot assign " + target + " to " + instrumentedMethod.getReturnType());
+ }
+ return instrumentedMethod.getReturnType().represents(void.class)
+ ? new Target.ForDefaultValue.ReadWrite(target)
+ : new Target.ForVariable.ReadWrite(instrumentedMethod.getReturnType(), offset, readAssignment, writeAssignment);
+ }
+ }
+
+ /**
+ * A factory for creating a {@link ForReturnValue} offset mapping.
+ */
+ protected enum Factory implements OffsetMapping.Factory<Return> {
+
+ /**
+ * The singelton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Class<Return> getAnnotationType() {
+ return Return.class;
+ }
+
+ @Override
+ public OffsetMapping make(ParameterDescription.InDefinedShape target,
+ AnnotationDescription.Loadable<Return> annotation,
+ AdviceType adviceType) {
+ if (adviceType.isDelegation() && !annotation.loadSilent().readOnly()) {
+ throw new IllegalStateException("Cannot write return value for " + target + " in read-only context");
+ } else {
+ return new ForReturnValue(target.getType(), annotation.loadSilent());
+ }
+ }
+ }
+ }
+
+ /**
+ * An offset mapping for accessing a {@link Throwable} of the instrumented method.
+ */
+ @EqualsAndHashCode
+ class ForThrowable implements OffsetMapping {
+
+ /**
+ * The type of parameter that is being accessed.
+ */
+ private final TypeDescription.Generic target;
+
+ /**
+ * {@code true} if the parameter is read-only.
+ */
+ private final boolean readOnly;
+
+ /**
+ * The typing to apply.
+ */
+ private final Assigner.Typing typing;
+
+ /**
+ * Creates a new offset mapping for access of the exception that is thrown by the instrumented method..
+ *
+ * @param target The type of parameter that is being accessed.
+ * @param annotation The annotation to bind.
+ */
+ protected ForThrowable(TypeDescription.Generic target, Thrown annotation) {
+ this(target, annotation.readOnly(), annotation.typing());
+ }
+
+ /**
+ * Creates a new offset mapping for access of the exception that is thrown by the instrumented method..
+ *
+ * @param target The type of parameter that is being accessed.
+ * @param readOnly {@code true} if the parameter is read-only.
+ * @param typing The typing to apply.
+ */
+ public ForThrowable(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing) {
+ this.target = target;
+ this.readOnly = readOnly;
+ this.typing = typing;
+ }
+
+ @Override
+ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Context context) {
+ int offset = instrumentedMethod.getStackSize() + context.getPadding() + instrumentedMethod.getReturnType().getStackSize().getSize();
+ StackManipulation readAssignment = assigner.assign(TypeDescription.THROWABLE.asGenericType(), target, typing);
+ if (!readAssignment.isValid()) {
+ throw new IllegalStateException("Cannot assign Throwable to " + target);
+ } else if (readOnly) {
+ return new Target.ForVariable.ReadOnly(TypeDescription.THROWABLE, offset, readAssignment);
+ } else {
+ StackManipulation writeAssignment = assigner.assign(target, TypeDescription.THROWABLE.asGenericType(), typing);
+ if (!writeAssignment.isValid()) {
+ throw new IllegalStateException("Cannot assign " + target + " to Throwable");
+ }
+ return new Target.ForVariable.ReadWrite(TypeDescription.THROWABLE, offset, readAssignment, writeAssignment);
+ }
+ }
+
+ /**
+ * A factory for accessing an exception that was thrown by the instrumented method.
+ */
+ protected enum Factory implements OffsetMapping.Factory<Thrown> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ /**
+ * Resolves an appropriate offset mapping factory for the {@link Thrown} parameter annotation.
+ *
+ * @param adviceMethod The exit advice method, annotated with {@link OnMethodExit}.
+ * @return An appropriate offset mapping factory.
+ */
+ @SuppressWarnings("unchecked") // In absence of @SafeVarargs for Java 6
+ protected static OffsetMapping.Factory<?> of(MethodDescription.InDefinedShape adviceMethod) {
+ return adviceMethod.getDeclaredAnnotations()
+ .ofType(OnMethodExit.class)
+ .getValue(ON_THROWABLE)
+ .resolve(TypeDescription.class)
+ .represents(NoExceptionHandler.class) ? new OffsetMapping.Factory.Illegal<Thrown>(Thrown.class) : Factory.INSTANCE;
+ }
+
+ @Override
+ public Class<Thrown> getAnnotationType() {
+ return Thrown.class;
+ }
+
+ @Override
+ public OffsetMapping make(ParameterDescription.InDefinedShape target,
+ AnnotationDescription.Loadable<Thrown> annotation,
+ AdviceType adviceType) {
+ if (adviceType.isDelegation() && !annotation.loadSilent().readOnly()) {
+ throw new IllegalStateException("Cannot use writable " + target + " on read-only parameter");
+ } else {
+ return new ForThrowable(target.getType(), annotation.loadSilent());
+ }
+ }
+ }
+ }
+
+ /**
+ * An offset mapping for binding a stack manipulation.
+ */
+ @EqualsAndHashCode
+ class ForStackManipulation implements OffsetMapping {
+
+ /**
+ * The stack manipulation that loads the bound value.
+ */
+ private final StackManipulation stackManipulation;
+
+ /**
+ * The type of the loaded value.
+ */
+ private final TypeDescription.Generic typeDescription;
+
+ /**
+ * The target type of the annotated parameter.
+ */
+ private final TypeDescription.Generic targetType;
+
+ /**
+ * The typing to apply.
+ */
+ private final Assigner.Typing typing;
+
+ /**
+ * Creates an offset mapping that binds a stack manipulation.
+ *
+ * @param stackManipulation The stack manipulation that loads the bound value.
+ * @param typeDescription The type of the loaded value.
+ * @param targetType The target type of the annotated parameter.
+ * @param typing The typing to apply.
+ */
+ public ForStackManipulation(StackManipulation stackManipulation,
+ TypeDescription.Generic typeDescription,
+ TypeDescription.Generic targetType,
+ Assigner.Typing typing) {
+ this.stackManipulation = stackManipulation;
+ this.typeDescription = typeDescription;
+ this.targetType = targetType;
+ this.typing = typing;
+ }
+
+ @Override
+ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Context context) {
+ StackManipulation assigment = assigner.assign(typeDescription, targetType, typing);
+ if (!assigment.isValid()) {
+ throw new IllegalStateException("Cannot assign " + typeDescription + " to " + targetType);
+ }
+ return new Target.ForStackManipulation(new StackManipulation.Compound(stackManipulation, assigment));
+ }
+
+ /**
+ * A factory that binds a stack manipulation.
+ *
+ * @param <T> The annotation type this factory binds.
+ */
+ @EqualsAndHashCode
+ public static class Factory<T extends Annotation> implements OffsetMapping.Factory<T> {
+
+ /**
+ * The annotation type.
+ */
+ private final Class<T> annotationType;
+
+ /**
+ * The stack manipulation that loads the bound value.
+ */
+ private final StackManipulation stackManipulation;
+
+ /**
+ * The type of the loaded value.
+ */
+ private final TypeDescription.Generic typeDescription;
+
+ /**
+ * Creates a new factory for binding a type description.
+ *
+ * @param annotationType The annotation type.
+ * @param typeDescription The type to bind.
+ */
+ public Factory(Class<T> annotationType, TypeDescription typeDescription) {
+ this(annotationType, ClassConstant.of(typeDescription), TypeDescription.CLASS.asGenericType());
+ }
+
+ /**
+ * Creates a new factory for binding an enumeration.
+ *
+ * @param annotationType The annotation type.
+ * @param enumerationDescription The enumeration to bind.
+ */
+ public Factory(Class<T> annotationType, EnumerationDescription enumerationDescription) {
+ this(annotationType, FieldAccess.forEnumeration(enumerationDescription), enumerationDescription.getEnumerationType().asGenericType());
+ }
+
+ /**
+ * Creates a new factory for binding a stack manipulation.
+ *
+ * @param annotationType The annotation type.
+ * @param stackManipulation The stack manipulation that loads the bound value.
+ * @param typeDescription The type of the loaded value.
+ */
+ public Factory(Class<T> annotationType, StackManipulation stackManipulation, TypeDescription.Generic typeDescription) {
+ this.annotationType = annotationType;
+ this.stackManipulation = stackManipulation;
+ this.typeDescription = typeDescription;
+ }
+
+ /**
+ * Creates a binding for a fixed {@link String} or primitive value.
+ *
+ * @param annotationType The annotation type.
+ * @param value The primitive (wrapper) value or {@link String} value to bind.
+ * @param <S> The annotation type.
+ * @return A factory for creating an offset mapping that binds the supplied value.
+ */
+ public static <S extends Annotation> OffsetMapping.Factory<S> of(Class<S> annotationType, Object value) {
+ StackManipulation stackManipulation;
+ TypeDescription typeDescription;
+ if (value == null) {
+ return new OfDefaultValue<S>(annotationType);
+ } else if (value instanceof Boolean) {
+ stackManipulation = IntegerConstant.forValue((Boolean) value);
+ typeDescription = new TypeDescription.ForLoadedType(boolean.class);
+ } else if (value instanceof Byte) {
+ stackManipulation = IntegerConstant.forValue((Byte) value);
+ typeDescription = new TypeDescription.ForLoadedType(byte.class);
+ } else if (value instanceof Short) {
+ stackManipulation = IntegerConstant.forValue((Short) value);
+ typeDescription = new TypeDescription.ForLoadedType(short.class);
+ } else if (value instanceof Character) {
+ stackManipulation = IntegerConstant.forValue((Character) value);
+ typeDescription = new TypeDescription.ForLoadedType(char.class);
+ } else if (value instanceof Integer) {
+ stackManipulation = IntegerConstant.forValue((Integer) value);
+ typeDescription = new TypeDescription.ForLoadedType(int.class);
+ } else if (value instanceof Long) {
+ stackManipulation = LongConstant.forValue((Long) value);
+ typeDescription = new TypeDescription.ForLoadedType(long.class);
+ } else if (value instanceof Float) {
+ stackManipulation = FloatConstant.forValue((Float) value);
+ typeDescription = new TypeDescription.ForLoadedType(float.class);
+ } else if (value instanceof Double) {
+ stackManipulation = DoubleConstant.forValue((Double) value);
+ typeDescription = new TypeDescription.ForLoadedType(double.class);
+ } else if (value instanceof String) {
+ stackManipulation = new TextConstant((String) value);
+ typeDescription = TypeDescription.STRING;
+ } else {
+ throw new IllegalStateException("Not a constant value: " + value);
+ }
+ return new Factory<S>(annotationType, stackManipulation, typeDescription.asGenericType());
+ }
+
+ @Override
+ public Class<T> getAnnotationType() {
+ return annotationType;
+ }
+
+ @Override
+ public OffsetMapping make(ParameterDescription.InDefinedShape target,
+ AnnotationDescription.Loadable<T> annotation,
+ AdviceType adviceType) {
+ return new ForStackManipulation(stackManipulation, typeDescription, target.getType(), Assigner.Typing.STATIC);
+ }
+ }
+
+ /**
+ * A factory for binding the annotated parameter's default value.
+ *
+ * @param <T> The annotation type this factory binds.
+ */
+ @EqualsAndHashCode
+ public static class OfDefaultValue<T extends Annotation> implements OffsetMapping.Factory<T> {
+
+ /**
+ * The annotation type.
+ */
+ private final Class<T> annotationType;
+
+ /**
+ * Creates a factory for an offset mapping tat binds the parameter's default value.
+ *
+ * @param annotationType The annotation type.
+ */
+ public OfDefaultValue(Class<T> annotationType) {
+ this.annotationType = annotationType;
+ }
+
+ @Override
+ public Class<T> getAnnotationType() {
+ return annotationType;
+ }
+
+ @Override
+ public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable<T> annotation, AdviceType adviceType) {
+ return new ForStackManipulation(DefaultValue.of(target.getType()), target.getType(), target.getType(), Assigner.Typing.STATIC);
+ }
+ }
+
+ /**
+ * A factory for binding an annotation's property.
+ *
+ * @param <T> The annotation type this factory binds.
+ */
+ @EqualsAndHashCode
+ public static class OfAnnotationProperty<T extends Annotation> implements OffsetMapping.Factory<T> {
+
+ /**
+ * The annotation type.
+ */
+ private final Class<T> annotationType;
+
+ /**
+ * The annotation property.
+ */
+ private final MethodDescription.InDefinedShape property;
+
+ /**
+ * Creates a factory for binding an annotation property.
+ *
+ * @param annotationType The annotation type.
+ * @param property The annotation property.
+ */
+ protected OfAnnotationProperty(Class<T> annotationType, MethodDescription.InDefinedShape property) {
+ this.annotationType = annotationType;
+ this.property = property;
+ }
+
+ /**
+ * Creates a factory for an offset mapping that binds an annotation property.
+ *
+ * @param annotationType The annotion type to bind.
+ * @param property The property to bind.
+ * @param <S> The annotation type.
+ * @return A factory for binding a property of the annotation type.
+ */
+ public static <S extends Annotation> OffsetMapping.Factory<S> of(Class<S> annotationType, String property) {
+ if (!annotationType.isAnnotation()) {
+ throw new IllegalArgumentException("Not an annotation type: " + annotationType);
+ }
+ try {
+ return new OfAnnotationProperty<S>(annotationType, new MethodDescription.ForLoadedMethod(annotationType.getMethod(property)));
+ } catch (NoSuchMethodException exception) {
+ throw new IllegalArgumentException("Cannot find a property " + property + " on " + annotationType, exception);
+ }
+ }
+
+ @Override
+ public Class<T> getAnnotationType() {
+ return annotationType;
+ }
+
+ @Override
+ public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable<T> annotation, AdviceType adviceType) {
+ Object value = annotation.getValue(property).resolve();
+ OffsetMapping.Factory<T> factory;
+ if (value instanceof TypeDescription) {
+ factory = new Factory<T>(annotationType, (TypeDescription) value);
+ } else if (value instanceof EnumerationDescription) {
+ factory = new Factory<T>(annotationType, (EnumerationDescription) value);
+ } else if (value instanceof AnnotationDescription) {
+ throw new IllegalStateException("Cannot bind annotation as fixed value for " + property);
+ } else {
+ factory = Factory.of(annotationType, value);
+ }
+ return factory.make(target, annotation, adviceType);
+ }
+ }
+ }
+
+ /**
+ * An offset mapping that loads a serialized value.
+ */
+ @EqualsAndHashCode
+ class ForSerializedValue implements OffsetMapping {
+
+ /**
+ * The type of the serialized value as it is used.
+ */
+ private final TypeDescription.Generic target;
+
+ /**
+ * The class type of the serialized value.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * The stack manipulation deserializing the represented value.
+ */
+ private final StackManipulation deserialization;
+
+ /**
+ * Creates a new offset mapping for a serialized value.
+ *
+ * @param target The type of the serialized value as it is used.
+ * @param typeDescription The class type of the serialized value.
+ * @param deserialization The stack manipulation deserializing the represented value.
+ */
+ public ForSerializedValue(TypeDescription.Generic target, TypeDescription typeDescription, StackManipulation deserialization) {
+ this.target = target;
+ this.typeDescription = typeDescription;
+ this.deserialization = deserialization;
+ }
+
+ @Override
+ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Context context) {
+ StackManipulation assignment = assigner.assign(typeDescription.asGenericType(), target, Assigner.Typing.DYNAMIC);
+ if (!assignment.isValid()) {
+ throw new IllegalStateException("Cannot assign " + typeDescription + " to " + target);
+ }
+ return new Target.ForStackManipulation(new StackManipulation.Compound(deserialization, assignment));
+ }
+
+ /**
+ * A factory for loading a deserialized value.
+ *
+ * @param <T> The annotation type this factory binds.
+ */
+ @EqualsAndHashCode
+ public static class Factory<T extends Annotation> implements OffsetMapping.Factory<T> {
+
+ /**
+ * The annotation type.
+ */
+ private final Class<T> annotationType;
+
+ /**
+ * The type description as which to treat the deserialized value.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * The stack manipulation that loads the represented value.
+ */
+ private final StackManipulation deserialization;
+
+ /**
+ * Creates a factory for loading a deserialized value.
+ *
+ * @param annotationType The annotation type.
+ * @param typeDescription The type description as which to treat the deserialized value.
+ * @param deserialization The stack manipulation that loads the represented value.
+ */
+ protected Factory(Class<T> annotationType, TypeDescription typeDescription, StackManipulation deserialization) {
+ this.annotationType = annotationType;
+ this.typeDescription = typeDescription;
+ this.deserialization = deserialization;
+ }
+
+ /**
+ * Creates a factory for an offset mapping that loads the provided value.
+ *
+ * @param annotationType The annotation type to be bound.
+ * @param target The instance representing the value to be deserialized.
+ * @param targetType The target type as which to use the target value.
+ * @param <S> The annotation type the created factory binds.
+ * @return An appropriate offset mapping factory.
+ */
+ public static <S extends Annotation> OffsetMapping.Factory<S> of(Class<S> annotationType, Serializable target, Class<?> targetType) {
+ if (!targetType.isInstance(target)) {
+ throw new IllegalArgumentException(target + " is no instance of " + targetType);
+ }
+ return new Factory<S>(annotationType, new TypeDescription.ForLoadedType(targetType), SerializedConstant.of(target));
+ }
+
+ @Override
+ public Class<T> getAnnotationType() {
+ return annotationType;
+ }
+
+ @Override
+ public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable<T> annotation, AdviceType adviceType) {
+ return new ForSerializedValue(target.getType(), typeDescription, deserialization);
+ }
+ }
+ }
+ }
+
+ /**
+ * A handler for computing the instrumented method's size.
+ */
+ protected interface MethodSizeHandler {
+
+ /**
+ * Indicates that a size is not computed but handled directly by ASM.
+ */
+ int UNDEFINED_SIZE = Short.MAX_VALUE;
+
+ /**
+ * Requires a minimum length of the local variable array.
+ *
+ * @param localVariableLength The minimal required length of the local variable array.
+ */
+ void requireLocalVariableLength(int localVariableLength);
+
+ /**
+ * A method size handler for the instrumented method.
+ */
+ interface ForInstrumentedMethod extends MethodSizeHandler {
+
+ /**
+ * Binds a method size handler for the entry advice.
+ *
+ * @param adviceMethod The method representing the entry advice.
+ * @return A method size handler for the entry advice.
+ */
+ ForAdvice bindEntry(MethodDescription.InDefinedShape adviceMethod);
+
+ /**
+ * Binds the method size handler for the exit advice.
+ *
+ * @param adviceMethod The method representing the exit advice.
+ * @param skipThrowable {@code true} if the exit advice is not invoked on an exception.
+ * @return A method size handler for the exit advice.
+ */
+ ForAdvice bindExit(MethodDescription.InDefinedShape adviceMethod, boolean skipThrowable);
+
+ /**
+ * Computes a compound stack size for the advice and the translated instrumented method.
+ *
+ * @param stackSize The required stack size of the instrumented method before translation.
+ * @return The stack size required by the instrumented method and its advice methods.
+ */
+ int compoundStackSize(int stackSize);
+
+ /**
+ * Computes a compound local variable array length for the advice and the translated instrumented method.
+ *
+ * @param localVariableLength The required local variable array length of the instrumented method before translation.
+ * @return The local variable length required by the instrumented method and its advice methods.
+ */
+ int compoundLocalVariableLength(int localVariableLength);
+ }
+
+ /**
+ * A method size handler for an advice method.
+ */
+ interface ForAdvice extends MethodSizeHandler {
+
+ /**
+ * Records a minimum stack size required by the represented advice method.
+ *
+ * @param stackSize The minimum size required by the represented advice method.
+ */
+ void requireStackSize(int stackSize);
+
+ /**
+ * Records the maximum values for stack size and local variable array which are required by the advice method
+ * for its individual execution without translation.
+ *
+ * @param stackSize The minimum required stack size.
+ * @param localVariableLength The minimum required length of the local variable array.
+ */
+ void recordMaxima(int stackSize, int localVariableLength);
+
+ /**
+ * Records a minimum padding additionally to the computed stack size that is required for implementing this advice method.
+ *
+ * @param padding The minimum required padding.
+ */
+ void recordPadding(int padding);
+ }
+
+ /**
+ * A non-operational method size handler.
+ */
+ enum NoOp implements ForInstrumentedMethod, ForAdvice {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public ForAdvice bindEntry(MethodDescription.InDefinedShape adviceMethod) {
+ return this;
+ }
+
+ @Override
+ public ForAdvice bindExit(MethodDescription.InDefinedShape adviceMethod, boolean skipThrowable) {
+ return this;
+ }
+
+ @Override
+ public int compoundStackSize(int stackSize) {
+ return UNDEFINED_SIZE;
+ }
+
+ @Override
+ public int compoundLocalVariableLength(int localVariableLength) {
+ return UNDEFINED_SIZE;
+ }
+
+ @Override
+ public void requireLocalVariableLength(int localVariableLength) {
+ /* do nothing */
+ }
+
+ @Override
+ public void requireStackSize(int stackSize) {
+ /* do nothing */
+ }
+
+ @Override
+ public void recordMaxima(int stackSize, int localVariableLength) {
+ /* do nothing */
+ }
+
+ @Override
+ public void recordPadding(int padding) {
+ /* do nothing */
+ }
+ }
+
+ /**
+ * A default implementation for a method size handler.
+ */
+ class Default implements MethodSizeHandler.ForInstrumentedMethod {
+
+ /**
+ * The instrumented method.
+ */
+ private final MethodDescription instrumentedMethod;
+
+ /**
+ * The list of types that the instrumented method requires in addition to the method parameters.
+ */
+ private final TypeList requiredTypes;
+
+ /**
+ * A list of types that are yielded by the instrumented method and available to the exit advice.
+ */
+ private final TypeList yieldedTypes;
+
+ /**
+ * The maximum stack size required by a visited advice method.
+ */
+ private int stackSize;
+
+ /**
+ * The maximum length of the local variable array required by a visited advice method.
+ */
+ private int localVariableLength;
+
+ /**
+ * Creates a new default meta data handler that recomputes the space requirements of an instrumented method.
+ *
+ * @param instrumentedMethod The instrumented method.
+ * @param requiredTypes The types this meta data handler expects to be available additionally to the instrumented method's parameters.
+ * @param yieldedTypes The types that are expected to be added after the instrumented method returns.
+ */
+ protected Default(MethodDescription instrumentedMethod, TypeList requiredTypes, TypeList yieldedTypes) {
+ this.instrumentedMethod = instrumentedMethod;
+ this.requiredTypes = requiredTypes;
+ this.yieldedTypes = yieldedTypes;
+ }
+
+ /**
+ * Creates a method size handler applicable for the given instrumented method.
+ *
+ * @param instrumentedMethod The instrumented method.
+ * @param requiredTypes The list of types that the instrumented method requires in addition to the method parameters.
+ * @param yieldedTypes A list of types that are yielded by the instrumented method and available to the exit advice.
+ * @param writerFlags The flags supplied to the ASM class writer.
+ * @return An appropriate method size handler.
+ */
+ protected static MethodSizeHandler.ForInstrumentedMethod of(MethodDescription instrumentedMethod,
+ List<? extends TypeDescription> requiredTypes,
+ List<? extends TypeDescription> yieldedTypes,
+ int writerFlags) {
+ return (writerFlags & (ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES)) != 0
+ ? NoOp.INSTANCE
+ : new Default(instrumentedMethod, new TypeList.Explicit(requiredTypes), new TypeList.Explicit(yieldedTypes));
+ }
+
+ @Override
+ public MethodSizeHandler.ForAdvice bindEntry(MethodDescription.InDefinedShape adviceMethod) {
+ stackSize = Math.max(stackSize, adviceMethod.getReturnType().getStackSize().getSize());
+ return new ForAdvice(adviceMethod, new TypeList.Empty(), new TypeList.Explicit(requiredTypes));
+ }
+
+ @Override
+ public MethodSizeHandler.ForAdvice bindExit(MethodDescription.InDefinedShape adviceMethod, boolean skipThrowable) {
+ stackSize = Math.max(stackSize, adviceMethod.getReturnType().getStackSize().maximum(skipThrowable
+ ? StackSize.ZERO
+ : StackSize.SINGLE).getSize());
+ return new ForAdvice(adviceMethod, new TypeList.Explicit(CompoundList.of(requiredTypes, yieldedTypes)), new TypeList.Empty());
+ }
+
+ @Override
+ public int compoundStackSize(int stackSize) {
+ return Math.max(this.stackSize, stackSize);
+ }
+
+ @Override
+ public int compoundLocalVariableLength(int localVariableLength) {
+ return Math.max(this.localVariableLength, localVariableLength
+ + requiredTypes.getStackSize()
+ + yieldedTypes.getStackSize());
+ }
+
+ @Override
+ public void requireLocalVariableLength(int localVariableLength) {
+ this.localVariableLength = Math.max(this.localVariableLength, localVariableLength);
+ }
+
+ /**
+ * A method size handler for an advice method.
+ */
+ protected class ForAdvice implements MethodSizeHandler.ForAdvice {
+
+ /**
+ * The advice method.
+ */
+ private final MethodDescription.InDefinedShape adviceMethod;
+
+ /**
+ * A list of types required by this advice method.
+ */
+ private final TypeList requiredTypes;
+
+ /**
+ * A list of types yielded by this advice method.
+ */
+ private final TypeList yieldedTypes;
+
+ /**
+ * The padding that this advice method requires additionally to its computed size.
+ */
+ private int padding;
+
+ /**
+ * Creates a new method size handler for an advice method.
+ *
+ * @param adviceMethod The advice method.
+ * @param requiredTypes A list of types required by this advice method.
+ * @param yieldedTypes A list of types yielded by this advice method.
+ */
+ protected ForAdvice(MethodDescription.InDefinedShape adviceMethod, TypeList requiredTypes, TypeList yieldedTypes) {
+ this.adviceMethod = adviceMethod;
+ this.requiredTypes = requiredTypes;
+ this.yieldedTypes = yieldedTypes;
+ stackSize = Math.max(stackSize, adviceMethod.getReturnType().getStackSize().getSize());
+ }
+
+ @Override
+ public void requireLocalVariableLength(int localVariableLength) {
+ Default.this.requireLocalVariableLength(localVariableLength);
+ }
+
+ @Override
+ public void requireStackSize(int stackSize) {
+ Default.this.stackSize = Math.max(Default.this.stackSize, stackSize);
+ }
+
+ @Override
+ public void recordMaxima(int stackSize, int localVariableLength) {
+ Default.this.stackSize = Math.max(Default.this.stackSize, stackSize) + padding;
+ Default.this.localVariableLength = Math.max(Default.this.localVariableLength, localVariableLength
+ - adviceMethod.getStackSize()
+ + instrumentedMethod.getStackSize()
+ + requiredTypes.getStackSize()
+ + yieldedTypes.getStackSize());
+ }
+
+ @Override
+ public void recordPadding(int padding) {
+ this.padding = Math.max(this.padding, padding);
+ }
+ }
+ }
+ }
+
+ /**
+ * A handler for computing and translating stack map frames.
+ */
+ protected interface StackMapFrameHandler {
+
+ /**
+ * Translates a frame.
+ *
+ * @param methodVisitor The method visitor to write the frame to.
+ * @param type The frame's type.
+ * @param localVariableLength The local variable length.
+ * @param localVariable An array containing the types of the current local variables.
+ * @param stackSize The size of the operand stack.
+ * @param stack An array containing the types of the current operand stack.
+ */
+ void translateFrame(MethodVisitor methodVisitor, int type, int localVariableLength, Object[] localVariable, int stackSize, Object[] stack);
+
+ /**
+ * Injects a frame indicating the beginning of a return value handler for the currently handled method.
+ *
+ * @param methodVisitor The method visitor onto which to apply the stack map frame.
+ */
+ void injectReturnFrame(MethodVisitor methodVisitor);
+
+ /**
+ * Injects a frame indicating the beginning of an exception handler for the currently handled method.
+ *
+ * @param methodVisitor The method visitor onto which to apply the stack map frame.
+ */
+ void injectExceptionFrame(MethodVisitor methodVisitor);
+
+ /**
+ * Injects a frame indicating the completion of the currently handled method, i.e. all yielded types were added.
+ *
+ * @param methodVisitor The method visitor onto which to apply the stack map frame.
+ * @param secondary {@code true} if another completion frame for this method was written previously.
+ */
+ void injectCompletionFrame(MethodVisitor methodVisitor, boolean secondary);
+
+ /**
+ * A stack map frame handler for an instrumented method.
+ */
+ interface ForInstrumentedMethod extends StackMapFrameHandler {
+
+ /**
+ * Binds this meta data handler for the entry advice.
+ *
+ * @param adviceMethod The entry advice method.
+ * @return An appropriate meta data handler for the enter method.
+ */
+ ForAdvice bindEntry(MethodDescription.InDefinedShape adviceMethod);
+
+ /**
+ * Binds this meta data handler for the exit advice.
+ *
+ * @param adviceMethod The exit advice method.
+ * @return An appropriate meta data handler for the enter method.
+ */
+ ForAdvice bindExit(MethodDescription.InDefinedShape adviceMethod);
+
+ /**
+ * Returns a hint to supply to a {@link ClassReader} when parsing an advice method.
+ *
+ * @return The reader hint to supply to an ASM class reader.
+ */
+ int getReaderHint();
+ }
+
+ /**
+ * A stack map frame handler for an advice method.
+ */
+ interface ForAdvice extends StackMapFrameHandler {
+ /* marker interface */
+ }
+
+ /**
+ * A non-operational stack map frame handler.
+ */
+ enum NoOp implements ForInstrumentedMethod, ForAdvice {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public StackMapFrameHandler.ForAdvice bindEntry(MethodDescription.InDefinedShape adviceMethod) {
+ return this;
+ }
+
+ @Override
+ public StackMapFrameHandler.ForAdvice bindExit(MethodDescription.InDefinedShape adviceMethod) {
+ return this;
+ }
+
+ @Override
+ public int getReaderHint() {
+ return ClassReader.SKIP_FRAMES;
+ }
+
+ @Override
+ public void translateFrame(MethodVisitor methodVisitor,
+ int type,
+ int localVariableLength,
+ Object[] localVariable,
+ int stackSize,
+ Object[] stack) {
+ /* do nothing */
+ }
+
+
+ @Override
+ public void injectReturnFrame(MethodVisitor methodVisitor) {
+ /* do nothing */
+ }
+
+ @Override
+ public void injectExceptionFrame(MethodVisitor methodVisitor) {
+ /* do nothing */
+ }
+
+ @Override
+ public void injectCompletionFrame(MethodVisitor methodVisitor, boolean secondary) {
+ /* do nothing */
+ }
+ }
+
+ /**
+ * A default implementation of a stack map frame handler for an instrumented method.
+ */
+ class Default implements ForInstrumentedMethod {
+
+ /**
+ * An empty array indicating an empty frame.
+ */
+ private static final Object[] EMPTY = new Object[0];
+
+ /**
+ * The instrumented type.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * The instrumented method.
+ */
+ protected final MethodDescription instrumentedMethod;
+
+ /**
+ * A list of intermediate types to be considered as part of the instrumented method's steady signature.
+ */
+ protected final TypeList requiredTypes;
+
+ /**
+ * The types that are expected to be added after the instrumented method returns.
+ */
+ protected final TypeList yieldedTypes;
+
+ /**
+ * {@code true} if the meta data handler is expected to expand its frames.
+ */
+ private final boolean expandFrames;
+
+ /**
+ * The current frame's size divergence from the original local variable array.
+ */
+ private int currentFrameDivergence;
+
+ /**
+ * Creates a new default meta data handler.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param instrumentedMethod The instrumented method.
+ * @param requiredTypes A list of intermediate types to be considered as part of the instrumented method's steady signature.
+ * @param yieldedTypes The types that are expected to be added after the instrumented method returns.
+ * @param expandFrames {@code true} if the meta data handler is expected to expand its frames.
+ */
+ protected Default(TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ TypeList requiredTypes,
+ TypeList yieldedTypes,
+ boolean expandFrames) {
+ this.instrumentedType = instrumentedType;
+ this.instrumentedMethod = instrumentedMethod;
+ this.requiredTypes = requiredTypes;
+ this.yieldedTypes = yieldedTypes;
+ this.expandFrames = expandFrames;
+ }
+
+ /**
+ * Creates an appropriate stack map frame handler for an instrumented method.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param instrumentedMethod The instrumented method.
+ * @param requiredTypes A list of intermediate types to be considered as part of the instrumented method's steady signature.
+ * @param yieldedTypes The types that are expected to be added after the instrumented method returns.
+ * @param classFileVersion The instrumented type's class file version.
+ * @param writerFlags The flags supplied to the ASM writier.
+ * @param readerFlags The reader flags supplied to the ASM reader.
+ * @return An approrpiate stack map frame handler for an instrumented method.
+ */
+ protected static ForInstrumentedMethod of(TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ List<? extends TypeDescription> requiredTypes,
+ List<? extends TypeDescription> yieldedTypes,
+ ClassFileVersion classFileVersion,
+ int writerFlags,
+ int readerFlags) {
+ return (writerFlags & ClassWriter.COMPUTE_FRAMES) != 0 || classFileVersion.isLessThan(ClassFileVersion.JAVA_V6)
+ ? NoOp.INSTANCE
+ : new Default(instrumentedType, instrumentedMethod, new TypeList.Explicit(requiredTypes), new TypeList.Explicit(yieldedTypes), (readerFlags & ClassReader.EXPAND_FRAMES) != 0);
+ }
+
+ /**
+ * Translates a type into a representation of its form inside a stack map frame.
+ *
+ * @param typeDescription The type to translate.
+ * @return A stack entry representation of the supplied type.
+ */
+ protected static Object toFrame(TypeDescription typeDescription) {
+ if (typeDescription.represents(boolean.class)
+ || typeDescription.represents(byte.class)
+ || typeDescription.represents(short.class)
+ || typeDescription.represents(char.class)
+ || typeDescription.represents(int.class)) {
+ return Opcodes.INTEGER;
+ } else if (typeDescription.represents(long.class)) {
+ return Opcodes.LONG;
+ } else if (typeDescription.represents(float.class)) {
+ return Opcodes.FLOAT;
+ } else if (typeDescription.represents(double.class)) {
+ return Opcodes.DOUBLE;
+ } else {
+ return typeDescription.getInternalName();
+ }
+ }
+
+ @Override
+ public StackMapFrameHandler.ForAdvice bindEntry(MethodDescription.InDefinedShape adviceMethod) {
+ return new ForAdvice(adviceMethod, new TypeList.Empty(), requiredTypes, TranslationMode.ENTRY);
+ }
+
+ @Override
+ public StackMapFrameHandler.ForAdvice bindExit(MethodDescription.InDefinedShape adviceMethod) {
+ return new ForAdvice(adviceMethod, new TypeList.Explicit(CompoundList.of(requiredTypes, yieldedTypes)), new TypeList.Empty(), TranslationMode.EXIT);
+ }
+
+ @Override
+ public int getReaderHint() {
+ return expandFrames
+ ? ClassReader.EXPAND_FRAMES
+ : AsmVisitorWrapper.NO_FLAGS;
+ }
+
+ @Override
+ public void translateFrame(MethodVisitor methodVisitor,
+ int type,
+ int localVariableLength,
+ Object[] localVariable,
+ int stackSize,
+ Object[] stack) {
+ translateFrame(methodVisitor,
+ TranslationMode.COPY,
+ instrumentedMethod,
+ requiredTypes,
+ type,
+ localVariableLength,
+ localVariable,
+ stackSize,
+ stack);
+ }
+
+ /**
+ * Translates a frame.
+ *
+ * @param methodVisitor The method visitor to write the frame to.
+ * @param translationMode The translation mode to apply.
+ * @param methodDescription The method description for which the frame is written.
+ * @param additionalTypes The additional types to consider part of the instrumented method's parameters.
+ * @param type The frame's type.
+ * @param localVariableLength The local variable length.
+ * @param localVariable An array containing the types of the current local variables.
+ * @param stackSize The size of the operand stack.
+ * @param stack An array containing the types of the current operand stack.
+ */
+ protected void translateFrame(MethodVisitor methodVisitor,
+ TranslationMode translationMode,
+ MethodDescription methodDescription,
+ TypeList additionalTypes,
+ int type,
+ int localVariableLength,
+ Object[] localVariable,
+ int stackSize,
+ Object[] stack) {
+ switch (type) {
+ case Opcodes.F_SAME:
+ case Opcodes.F_SAME1:
+ break;
+ case Opcodes.F_APPEND:
+ currentFrameDivergence += localVariableLength;
+ break;
+ case Opcodes.F_CHOP:
+ currentFrameDivergence -= localVariableLength;
+ break;
+ case Opcodes.F_FULL:
+ case Opcodes.F_NEW:
+ if (methodDescription.getParameters().size() + (methodDescription.isStatic() ? 0 : 1) > localVariableLength) {
+ throw new IllegalStateException("Inconsistent frame length for " + methodDescription + ": " + localVariableLength);
+ }
+ int offset;
+ if (methodDescription.isStatic()) {
+ offset = 0;
+ } else {
+ if (!translationMode.isPossibleThisFrameValue(instrumentedType, instrumentedMethod, localVariable[0])) {
+ throw new IllegalStateException(methodDescription + " is inconsistent for 'this' reference: " + localVariable[0]);
+ }
+ offset = 1;
+ }
+ for (int index = 0; index < methodDescription.getParameters().size(); index++) {
+ if (!toFrame(methodDescription.getParameters().get(index).getType().asErasure()).equals(localVariable[index + offset])) {
+ throw new IllegalStateException(methodDescription + " is inconsistent at " + index + ": " + localVariable[index + offset]);
+ }
+ }
+ Object[] translated = new Object[localVariableLength
+ - methodDescription.getParameters().size()
+ - (methodDescription.isStatic() ? 0 : 1)
+ + instrumentedMethod.getParameters().size()
+ + (instrumentedMethod.isStatic() ? 0 : 1)
+ + additionalTypes.size()];
+ int index = translationMode.copy(instrumentedType, instrumentedMethod, methodDescription, localVariable, translated);
+ for (TypeDescription typeDescription : additionalTypes) {
+ translated[index++] = toFrame(typeDescription);
+ }
+ System.arraycopy(localVariable,
+ methodDescription.getParameters().size() + (methodDescription.isStatic() ? 0 : 1),
+ translated,
+ index,
+ translated.length - index);
+ localVariableLength = translated.length;
+ localVariable = translated;
+ currentFrameDivergence = translated.length - index;
+ break;
+ default:
+ throw new IllegalArgumentException("Unexpected frame type: " + type);
+ }
+ methodVisitor.visitFrame(type, localVariableLength, localVariable, stackSize, stack);
+ }
+
+ @Override
+ public void injectReturnFrame(MethodVisitor methodVisitor) {
+ if (!expandFrames && currentFrameDivergence == 0 && !instrumentedMethod.isConstructor()) {
+ if (instrumentedMethod.getReturnType().represents(void.class)) {
+ methodVisitor.visitFrame(Opcodes.F_SAME, EMPTY.length, EMPTY, EMPTY.length, EMPTY);
+ } else {
+ methodVisitor.visitFrame(Opcodes.F_SAME1, EMPTY.length, EMPTY, 1, new Object[]{toFrame(instrumentedMethod.getReturnType().asErasure())});
+ }
+ } else {
+ injectFullFrame(methodVisitor, requiredTypes, instrumentedMethod.getReturnType().represents(void.class)
+ ? Collections.<TypeDescription>emptyList()
+ : Collections.singletonList(instrumentedMethod.getReturnType().asErasure()));
+ }
+ }
+
+ @Override
+ public void injectExceptionFrame(MethodVisitor methodVisitor) {
+ if (!expandFrames && currentFrameDivergence == 0) {
+ methodVisitor.visitFrame(Opcodes.F_SAME1, EMPTY.length, EMPTY, 1, new Object[]{Type.getInternalName(Throwable.class)});
+ } else {
+ injectFullFrame(methodVisitor, requiredTypes, Collections.singletonList(TypeDescription.THROWABLE));
+ }
+ }
+
+ @Override
+ public void injectCompletionFrame(MethodVisitor methodVisitor, boolean secondary) {
+ if (!expandFrames && currentFrameDivergence == 0 && (secondary || !instrumentedMethod.isConstructor())) {
+ if (secondary) {
+ methodVisitor.visitFrame(Opcodes.F_SAME, EMPTY.length, EMPTY, EMPTY.length, EMPTY);
+ } else {
+ Object[] local = new Object[yieldedTypes.size()];
+ int index = 0;
+ for (TypeDescription typeDescription : yieldedTypes) {
+ local[index++] = toFrame(typeDescription);
+ }
+ methodVisitor.visitFrame(Opcodes.F_APPEND, local.length, local, EMPTY.length, EMPTY);
+ }
+ } else {
+ injectFullFrame(methodVisitor, CompoundList.of(requiredTypes, yieldedTypes), Collections.<TypeDescription>emptyList());
+ }
+ }
+
+ /**
+ * Injects a full stack map frame.
+ *
+ * @param methodVisitor The method visitor onto which to write the stack map frame.
+ * @param typesInArray The types that were added to the local variable array additionally to the values of the instrumented method.
+ * @param typesOnStack The types currently on the operand stack.
+ */
+ protected void injectFullFrame(MethodVisitor methodVisitor,
+ List<? extends TypeDescription> typesInArray,
+ List<? extends TypeDescription> typesOnStack) {
+ Object[] localVariable = new Object[instrumentedMethod.getParameters().size()
+ + (instrumentedMethod.isStatic() ? 0 : 1)
+ + typesInArray.size()];
+ int index = 0;
+ if (!instrumentedMethod.isStatic()) {
+ localVariable[index++] = toFrame(instrumentedType);
+ }
+ for (TypeDescription typeDescription : instrumentedMethod.getParameters().asTypeList().asErasures()) {
+ localVariable[index++] = toFrame(typeDescription);
+ }
+ for (TypeDescription typeDescription : typesInArray) {
+ localVariable[index++] = toFrame(typeDescription);
+ }
+ index = 0;
+ Object[] stackType = new Object[typesOnStack.size()];
+ for (TypeDescription typeDescription : typesOnStack) {
+ stackType[index++] = toFrame(typeDescription);
+ }
+ methodVisitor.visitFrame(expandFrames ? Opcodes.F_NEW : Opcodes.F_FULL, localVariable.length, localVariable, stackType.length, stackType);
+ currentFrameDivergence = 0;
+ }
+
+ /**
+ * A translation mode that determines how the fixed frames of the instrumented method are written.
+ */
+ protected enum TranslationMode {
+
+ /**
+ * A translation mode that simply copies the original frames which are available when translating frames of the instrumented method.
+ */
+ COPY {
+ @Override
+ protected int copy(TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ MethodDescription methodDescription,
+ Object[] localVariable,
+ Object[] translated) {
+ int length = instrumentedMethod.getParameters().size() + (instrumentedMethod.isStatic() ? 0 : 1);
+ System.arraycopy(localVariable, 0, translated, 0, length);
+ return length;
+ }
+
+ @Override
+ protected boolean isPossibleThisFrameValue(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Object frame) {
+ if (instrumentedMethod.isConstructor()) {
+ return Opcodes.UNINITIALIZED_THIS.equals(frame);
+ }
+ return toFrame(instrumentedType).equals(frame);
+ }
+ },
+
+ /**
+ * A translation mode for the entry advice that considers that the {@code this} reference might not be initialized for a constructor.
+ */
+ ENTRY {
+ @Override
+ protected int copy(TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ MethodDescription methodDescription,
+ Object[] localVariable,
+ Object[] translated) {
+ int index = 0;
+ if (!instrumentedMethod.isStatic()) {
+ translated[index++] = instrumentedMethod.isConstructor()
+ ? Opcodes.UNINITIALIZED_THIS
+ : toFrame(instrumentedType);
+ }
+ for (TypeDescription typeDescription : instrumentedMethod.getParameters().asTypeList().asErasures()) {
+ translated[index++] = toFrame(typeDescription);
+ }
+ return index;
+ }
+
+ @Override
+ protected boolean isPossibleThisFrameValue(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Object frame) {
+ return instrumentedMethod.isConstructor()
+ ? Opcodes.UNINITIALIZED_THIS.equals(frame)
+ : toFrame(instrumentedType).equals(frame);
+ }
+ },
+
+ /**
+ * A translation mode for an exit advice where the {@code this} reference is always initialized.
+ */
+ EXIT {
+ @Override
+ protected int copy(TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ MethodDescription methodDescription,
+ Object[] localVariable,
+ Object[] translated) {
+ int index = 0;
+ if (!instrumentedMethod.isStatic()) {
+ translated[index++] = toFrame(instrumentedType);
+ }
+ for (TypeDescription typeDescription : instrumentedMethod.getParameters().asTypeList().asErasures()) {
+ translated[index++] = toFrame(typeDescription);
+ }
+ return index;
+ }
+
+ @Override
+ protected boolean isPossibleThisFrameValue(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Object frame) {
+ return toFrame(instrumentedType).equals(frame);
+ }
+ };
+
+ /**
+ * Copies the fixed parameters of the instrumented method onto the operand stack.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param instrumentedMethod The instrumented method.
+ * @param methodDescription The method for which a frame is created.
+ * @param localVariable The original local variable array.
+ * @param translated The array containing the translated frames.
+ * @return The amount of frames added to the translated frame array.
+ */
+ protected abstract int copy(TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ MethodDescription methodDescription,
+ Object[] localVariable,
+ Object[] translated);
+
+ /**
+ * Checks if a variable value in a stack map frame is a legal value for describing a {@code this} reference.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param instrumentedMethod The instrumented method.
+ * @param frame The frame value representing the {@code this} reference.
+ * @return {@code true} if the value is a legal representation of the {@code this} reference.
+ */
+ protected abstract boolean isPossibleThisFrameValue(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Object frame);
+ }
+
+ /**
+ * A stack map frame handler for an advice method.
+ */
+ protected class ForAdvice implements StackMapFrameHandler.ForAdvice {
+
+ /**
+ * The method description for which frames are translated.
+ */
+ protected final MethodDescription.InDefinedShape adviceMethod;
+
+ /**
+ * A list of intermediate types to be considered as part of the instrumented method's steady signature.
+ */
+ protected final TypeList requiredTypes;
+
+ /**
+ * The types that this method yields as a result.
+ */
+ private final TypeList yieldedTypes;
+
+ /**
+ * The translation mode to apply for this advice method. Should be either {@link TranslationMode#ENTRY} or {@link TranslationMode#EXIT}.
+ */
+ protected final TranslationMode translationMode;
+
+ /**
+ * Creates a new meta data handler for an advice method.
+ *
+ * @param adviceMethod The method description for which frames are translated.
+ * @param requiredTypes A list of expected types to be considered as part of the instrumented method's steady signature.
+ * @param yieldedTypes The types that this method yields as a result.
+ * @param translationMode The translation mode to apply for this advice method. Should be
+ * either {@link TranslationMode#ENTRY} or {@link TranslationMode#EXIT}.
+ */
+ protected ForAdvice(MethodDescription.InDefinedShape adviceMethod,
+ TypeList requiredTypes,
+ TypeList yieldedTypes,
+ TranslationMode translationMode) {
+ this.adviceMethod = adviceMethod;
+ this.requiredTypes = requiredTypes;
+ this.yieldedTypes = yieldedTypes;
+ this.translationMode = translationMode;
+ }
+
+ @Override
+ public void translateFrame(MethodVisitor methodVisitor,
+ int type,
+ int localVariableLength,
+ Object[] localVariable,
+ int stackSize,
+ Object[] stack) {
+ Default.this.translateFrame(methodVisitor,
+ translationMode,
+ adviceMethod,
+ requiredTypes,
+ type,
+ localVariableLength,
+ localVariable,
+ stackSize,
+ stack);
+ }
+
+ @Override
+ public void injectReturnFrame(MethodVisitor methodVisitor) {
+ if (!expandFrames && currentFrameDivergence == 0) {
+ if (yieldedTypes.isEmpty() || adviceMethod.getReturnType().represents(void.class)) {
+ methodVisitor.visitFrame(Opcodes.F_SAME, EMPTY.length, EMPTY, EMPTY.length, EMPTY);
+ } else {
+ methodVisitor.visitFrame(Opcodes.F_SAME1, EMPTY.length, EMPTY, 1, new Object[]{toFrame(adviceMethod.getReturnType().asErasure())});
+ }
+ } else {
+ injectFullFrame(methodVisitor, requiredTypes, yieldedTypes.isEmpty() || adviceMethod.getReturnType().represents(void.class)
+ ? Collections.<TypeDescription>emptyList()
+ : Collections.singletonList(adviceMethod.getReturnType().asErasure()));
+ }
+ }
+
+ @Override
+ public void injectExceptionFrame(MethodVisitor methodVisitor) {
+ if (!expandFrames && currentFrameDivergence == 0) {
+ methodVisitor.visitFrame(Opcodes.F_SAME1, EMPTY.length, EMPTY, 1, new Object[]{Type.getInternalName(Throwable.class)});
+ } else {
+ injectFullFrame(methodVisitor, requiredTypes, Collections.singletonList(TypeDescription.THROWABLE));
+ }
+ }
+
+ @Override
+ public void injectCompletionFrame(MethodVisitor methodVisitor, boolean secondary) {
+ if ((!expandFrames && currentFrameDivergence == 0 && yieldedTypes.size() < 4)) {
+ if (secondary || yieldedTypes.isEmpty()) {
+ methodVisitor.visitFrame(Opcodes.F_SAME, EMPTY.length, EMPTY, EMPTY.length, EMPTY);
+ } else {
+ Object[] local = new Object[yieldedTypes.size()];
+ int index = 0;
+ for (TypeDescription typeDescription : yieldedTypes) {
+ local[index++] = toFrame(typeDescription);
+ }
+ methodVisitor.visitFrame(Opcodes.F_APPEND, local.length, local, EMPTY.length, EMPTY);
+ }
+ } else {
+ injectFullFrame(methodVisitor, CompoundList.of(requiredTypes, yieldedTypes), Collections.<TypeDescription>emptyList());
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * A dispatcher for implementing advice.
+ */
+ protected interface Dispatcher {
+
+ /**
+ * Indicates that a method does not represent advice and does not need to be visited.
+ */
+ MethodVisitor IGNORE_METHOD = null;
+
+ /**
+ * Expresses that an annotation should not be visited.
+ */
+ AnnotationVisitor IGNORE_ANNOTATION = null;
+
+ /**
+ * Returns {@code true} if this dispatcher is alive.
+ *
+ * @return {@code true} if this dispatcher is alive.
+ */
+ boolean isAlive();
+
+ /**
+ * A dispatcher that is not yet resolved.
+ */
+ interface Unresolved extends Dispatcher {
+
+ /**
+ * Indicates that this dispatcher requires access to the class file declaring the advice method.
+ *
+ * @return {@code true} if this dispatcher requires access to the advice method's class file.
+ */
+ boolean isBinary();
+
+ /**
+ * Resolves this dispatcher as a dispatcher for entering a method.
+ *
+ * @param userFactories A list of custom factories for binding parameters of an advice method.
+ * @param classReader A class reader to query for a class file which might be {@code null} if this dispatcher is not binary.
+ * @return This dispatcher as a dispatcher for entering a method.
+ */
+ Resolved.ForMethodEnter asMethodEnter(List<? extends OffsetMapping.Factory<?>> userFactories,
+ ClassReader classReader);
+
+ /**
+ * Resolves this dispatcher as a dispatcher for exiting a method.
+ *
+ * @param userFactories A list of custom factories for binding parameters of an advice method.
+ * @param classReader A class reader to query for a class file which might be {@code null} if this dispatcher is not binary.
+ * @param dispatcher The dispatcher for entering a method.
+ * @return This dispatcher as a dispatcher for exiting a method.
+ */
+ Resolved.ForMethodExit asMethodExitTo(List<? extends OffsetMapping.Factory<?>> userFactories,
+ ClassReader classReader,
+ Resolved.ForMethodEnter dispatcher);
+ }
+
+ /**
+ * A suppression handler for optionally suppressing exceptions.
+ */
+ interface SuppressionHandler {
+
+ /**
+ * Binds the suppression handler for instrumenting a specific method.
+ *
+ * @param exceptionHandler The stack manipulation to apply within a suppression handler.
+ * @return A bound version of the suppression handler.
+ */
+ Bound bind(StackManipulation exceptionHandler);
+
+ /**
+ * A producer for a default return value if this is applicable.
+ */
+ interface ReturnValueProducer {
+
+ /**
+ * Instructs this return value producer to assure the production of a default value for the return type of the currently handled method.
+ *
+ * @param methodVisitor The method visitor to write the default value to.
+ */
+ void onDefaultValue(MethodVisitor methodVisitor);
+ }
+
+ /**
+ * A bound version of a suppression handler that must not be reused.
+ */
+ interface Bound {
+
+ /**
+ * Invoked to prepare the suppression handler, i.e. to write an exception handler entry if appropriate.
+ *
+ * @param methodVisitor The method visitor to apply the preparation to.
+ */
+ void onPrepare(MethodVisitor methodVisitor);
+
+ /**
+ * Invoked at the start of a method.
+ *
+ * @param methodVisitor The method visitor of the instrumented method.
+ */
+ void onStart(MethodVisitor methodVisitor);
+
+ /**
+ * Invoked at the end of a method.
+ *
+ * @param methodVisitor The method visitor of the instrumented method.
+ * @param implementationContext The implementation context to use.
+ * @param methodSizeHandler The advice method's method size handler.
+ * @param stackMapFrameHandler A handler for translating and injecting stack map frames.
+ * @param returnValueProducer A producer for defining a default return value of the advised method.
+ */
+ void onEnd(MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ MethodSizeHandler.ForAdvice methodSizeHandler,
+ StackMapFrameHandler.ForAdvice stackMapFrameHandler,
+ ReturnValueProducer returnValueProducer);
+
+ /**
+ * Invoked at the end of a method. Additionally indicates that the handler block should be surrounding by a skipping instruction. This method
+ * is always followed by a stack map frame (if it is required for the class level and class writer setting).
+ *
+ * @param methodVisitor The method visitor of the instrumented method.
+ * @param implementationContext The implementation context to use.
+ * @param methodSizeHandler The advice method's method size handler.
+ * @param stackMapFrameHandler A handler for translating and injecting stack map frames.
+ * @param returnValueProducer A producer for defining a default return value of the advised method.
+ */
+ void onEndSkipped(MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ MethodSizeHandler.ForAdvice methodSizeHandler,
+ StackMapFrameHandler.ForAdvice stackMapFrameHandler,
+ ReturnValueProducer returnValueProducer);
+ }
+
+ /**
+ * A non-operational suppression handler that does not suppress any method.
+ */
+ enum NoOp implements SuppressionHandler, Bound {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Bound bind(StackManipulation exceptionHandler) {
+ return this;
+ }
+
+ @Override
+ public void onPrepare(MethodVisitor methodVisitor) {
+ /* do nothing */
+ }
+
+ @Override
+ public void onStart(MethodVisitor methodVisitor) {
+ /* do nothing */
+ }
+
+ @Override
+ public void onEnd(MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ MethodSizeHandler.ForAdvice methodSizeHandler,
+ StackMapFrameHandler.ForAdvice stackMapFrameHandler,
+ ReturnValueProducer returnValueProducer) {
+ /* do nothing */
+ }
+
+ @Override
+ public void onEndSkipped(MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ MethodSizeHandler.ForAdvice methodSizeHandler,
+ StackMapFrameHandler.ForAdvice stackMapFrameHandler,
+ ReturnValueProducer returnValueProducer) {
+ /* do nothing */
+ }
+ }
+
+ /**
+ * A suppression handler that suppresses a given throwable type.
+ */
+ @EqualsAndHashCode
+ class Suppressing implements SuppressionHandler {
+
+ /**
+ * The suppressed throwable type.
+ */
+ private final TypeDescription suppressedType;
+
+ /**
+ * Creates a new suppressing suppression handler.
+ *
+ * @param suppressedType The suppressed throwable type.
+ */
+ protected Suppressing(TypeDescription suppressedType) {
+ this.suppressedType = suppressedType;
+ }
+
+ /**
+ * Resolves an appropriate suppression handler.
+ *
+ * @param suppressedType The suppressed type or {@link NoExceptionHandler} if no type should be suppressed.
+ * @return An appropriate suppression handler.
+ */
+ protected static SuppressionHandler of(TypeDescription suppressedType) {
+ return suppressedType.represents(NoExceptionHandler.class)
+ ? NoOp.INSTANCE
+ : new Suppressing(suppressedType);
+ }
+
+ @Override
+ public SuppressionHandler.Bound bind(StackManipulation exceptionHandler) {
+ return new Bound(suppressedType, exceptionHandler);
+ }
+
+ /**
+ * An active, bound suppression handler.
+ */
+ protected static class Bound implements SuppressionHandler.Bound {
+
+ /**
+ * The suppressed throwable type.
+ */
+ private final TypeDescription suppressedType;
+
+ /**
+ * The stack manipulation to apply within a suppression handler.
+ */
+ private final StackManipulation exceptionHandler;
+
+ /**
+ * A label indicating the start of the method.
+ */
+ private final Label startOfMethod;
+
+ /**
+ * A label indicating the end of the method.
+ */
+ private final Label endOfMethod;
+
+ /**
+ * Creates a new active, bound suppression handler.
+ *
+ * @param suppressedType The suppressed throwable type.
+ * @param exceptionHandler The stack manipulation to apply within a suppression handler.
+ */
+ protected Bound(TypeDescription suppressedType, StackManipulation exceptionHandler) {
+ this.suppressedType = suppressedType;
+ this.exceptionHandler = exceptionHandler;
+ startOfMethod = new Label();
+ endOfMethod = new Label();
+ }
+
+ @Override
+ public void onPrepare(MethodVisitor methodVisitor) {
+ methodVisitor.visitTryCatchBlock(startOfMethod, endOfMethod, endOfMethod, suppressedType.getInternalName());
+ }
+
+ @Override
+ public void onStart(MethodVisitor methodVisitor) {
+ methodVisitor.visitLabel(startOfMethod);
+ }
+
+ @Override
+ public void onEnd(MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ MethodSizeHandler.ForAdvice methodSizeHandler,
+ StackMapFrameHandler.ForAdvice stackMapFrameHandler,
+ ReturnValueProducer returnValueProducer) {
+ methodVisitor.visitLabel(endOfMethod);
+ stackMapFrameHandler.injectExceptionFrame(methodVisitor);
+ methodSizeHandler.requireStackSize(1 + exceptionHandler.apply(methodVisitor, implementationContext).getMaximalSize());
+ returnValueProducer.onDefaultValue(methodVisitor);
+ }
+
+ @Override
+ public void onEndSkipped(MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ MethodSizeHandler.ForAdvice methodSizeHandler,
+ StackMapFrameHandler.ForAdvice stackMapFrameHandler,
+ ReturnValueProducer returnValueProducer) {
+ Label endOfHandler = new Label();
+ methodVisitor.visitJumpInsn(Opcodes.GOTO, endOfHandler);
+ onEnd(methodVisitor, implementationContext, methodSizeHandler, stackMapFrameHandler, returnValueProducer);
+ methodVisitor.visitLabel(endOfHandler);
+ }
+ }
+ }
+ }
+
+ /**
+ * Represents a resolved dispatcher.
+ */
+ interface Resolved extends Dispatcher {
+
+ /**
+ * Binds this dispatcher for resolution to a specific method.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param instrumentedMethod The instrumented method.
+ * @param methodVisitor The method visitor for writing the instrumented method.
+ * @param implementationContext The implementation context to use.
+ * @param assigner The assigner to use.
+ * @param methodSizeHandler A handler for computing the method size requirements.
+ * @param stackMapFrameHandler A handler for translating and injecting stack map frames.
+ * @param exceptionHandler The stack manipulation to apply within a suppression handler.
+ * @return A dispatcher that is bound to the instrumented method.
+ */
+ Bound bind(TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ Assigner assigner,
+ MethodSizeHandler.ForInstrumentedMethod methodSizeHandler,
+ StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler,
+ StackManipulation exceptionHandler);
+
+ /**
+ * Represents a resolved dispatcher for entering a method.
+ */
+ interface ForMethodEnter extends Resolved {
+
+ /**
+ * Returns the type that this dispatcher supplies as a result of its advice or a description of {@code void} if
+ * no type is supplied as a result of the enter advice.
+ *
+ * @return The type that this dispatcher supplies as a result of its advice or a description of {@code void}.
+ */
+ TypeDefinition getEnterType();
+
+ /**
+ * Returns {@code true} if the first discovered line number information should be prepended to the advice code.
+ *
+ * @return {@code true} if the first discovered line number information should be prepended to the advice code.
+ */
+ boolean isPrependLineNumber();
+
+ @Override
+ Bound.ForMethodEnter bind(TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ Assigner assigner,
+ MethodSizeHandler.ForInstrumentedMethod methodSizeHandler,
+ StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler,
+ StackManipulation exceptionHandler);
+
+ /**
+ * A skip dispatcher is responsible for skipping the instrumented method depending on the
+ * return value of the enter advice method.
+ */
+ interface SkipDispatcher {
+
+ /**
+ * Applies this skip dispatcher.
+ *
+ * @param methodVisitor The method visitor to write to.
+ * @param methodSizeHandler The method size handler of the advice method to use.
+ * @param stackMapFrameHandler The stack map frame handler of the advice method to use.
+ * @param instrumentedMethod The instrumented method.
+ * @param skipHandler The skip handler to use.
+ */
+ void apply(MethodVisitor methodVisitor,
+ MethodSizeHandler.ForAdvice methodSizeHandler,
+ StackMapFrameHandler.ForAdvice stackMapFrameHandler,
+ MethodDescription instrumentedMethod,
+ Bound.SkipHandler skipHandler);
+
+ /**
+ * A disabled skip dispatcher where the instrumented method is always executed.
+ */
+ enum Disabled implements SkipDispatcher {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public void apply(MethodVisitor methodVisitor,
+ MethodSizeHandler.ForAdvice methodSizeHandler,
+ StackMapFrameHandler.ForAdvice stackMapFrameHandler,
+ MethodDescription instrumentedMethod,
+ Bound.SkipHandler skipHandler) {
+ /* do nothing */
+ }
+ }
+
+ /**
+ * A skip dispatcher where the instrumented method is skipped for any default value of the advice method's return type.
+ * If the return type is {@code boolean}, the relationship is inversed, where the instrumented is skipped for a {@code true}
+ * return value.
+ */
+ enum ForValue implements SkipDispatcher {
+
+ /**
+ * A skip dispatcher for a {@code boolean}, {@code byte}, {@code short}, {@code char} or {@code int} value.
+ */
+ FOR_INTEGER(Opcodes.ILOAD, Opcodes.IFNE, Opcodes.IFEQ) {
+ @Override
+ protected void convertValue(MethodVisitor methodVisitor, MethodSizeHandler.ForAdvice methodSizeHandler) {
+ /* do nothing */
+ }
+ },
+
+ /**
+ * A skip dispatcher for a {@code long} value.
+ */
+ FOR_LONG(Opcodes.LLOAD, Opcodes.IFNE, Opcodes.IFEQ) {
+ @Override
+ protected void convertValue(MethodVisitor methodVisitor, MethodSizeHandler.ForAdvice methodSizeHandler) {
+ methodVisitor.visitInsn(Opcodes.L2I);
+ }
+ },
+
+ /**
+ * A skip dispatcher for a {@code float} value.
+ */
+ FOR_FLOAT(Opcodes.FLOAD, Opcodes.IFNE, Opcodes.IFEQ) {
+ @Override
+ protected void convertValue(MethodVisitor methodVisitor, MethodSizeHandler.ForAdvice methodSizeHandler) {
+ methodVisitor.visitInsn(Opcodes.FCONST_0);
+ methodVisitor.visitInsn(Opcodes.FCMPL);
+ methodSizeHandler.requireStackSize(2);
+ }
+ },
+
+ /**
+ * A skip dispatcher for a {@code double} value.
+ */
+ FOR_DOUBLE(Opcodes.DLOAD, Opcodes.IFNE, Opcodes.IFEQ) {
+ @Override
+ protected void convertValue(MethodVisitor methodVisitor, MethodSizeHandler.ForAdvice methodSizeHandler) {
+ methodVisitor.visitInsn(Opcodes.DCONST_0);
+ methodVisitor.visitInsn(Opcodes.DCMPL);
+ methodSizeHandler.requireStackSize(4);
+ }
+ },
+
+ /**
+ * A skip dispatcher for a reference value.
+ */
+ FOR_REFERENCE(Opcodes.ALOAD, Opcodes.IFNONNULL, Opcodes.IFNULL) {
+ @Override
+ protected void convertValue(MethodVisitor methodVisitor, MethodSizeHandler.ForAdvice methodSizeHandler) {
+ /* do nothing */
+ }
+ };
+
+ /**
+ * The load opcode for this skip dispatcher.
+ */
+ private final int load;
+
+ /**
+ * The jump instruction that triggers skipping upon observing a value's default value.
+ */
+ private final int defaultJump;
+
+ /**
+ * The jump instruction that triggers skipping upon observing a value's non-default value.
+ */
+ private final int nonDefaultJump;
+
+ /**
+ * Creates a new skip dispatcher.
+ *
+ * @param load The load opcode for this skip dispatcher.
+ * @param defaultJump The jump instruction that triggers skipping upon observing a value's default value.
+ * @param nonDefaultJump The jump instruction that triggers skipping upon observing a value's non-default value.
+ */
+ ForValue(int load, int defaultJump, int nonDefaultJump) {
+ this.load = load;
+ this.defaultJump = defaultJump;
+ this.nonDefaultJump = nonDefaultJump;
+ }
+
+ /**
+ * Creates an appropriate skip dispatcher.
+ *
+ * @param typeDefinition The type for which to skip a value.
+ * @param inverted {@code true} if the skip condition should be inverted to trigger upon non-default values.
+ * @return An appropriate skip dispatcher.
+ */
+ protected static SkipDispatcher of(TypeDefinition typeDefinition, boolean inverted) {
+ ForValue skipDispatcher;
+ if (typeDefinition.represents(long.class)) {
+ skipDispatcher = FOR_LONG;
+ } else if (typeDefinition.represents(float.class)) {
+ skipDispatcher = FOR_FLOAT;
+ } else if (typeDefinition.represents(double.class)) {
+ skipDispatcher = FOR_DOUBLE;
+ } else if (typeDefinition.represents(void.class)) {
+ throw new IllegalStateException("Cannot skip on default value for void return type");
+ } else if (typeDefinition.isPrimitive()) { // anyOf(byte, short, char, int)
+ skipDispatcher = FOR_INTEGER;
+ } else {
+ skipDispatcher = FOR_REFERENCE;
+ }
+ return inverted
+ ? skipDispatcher.inverted()
+ : skipDispatcher;
+ }
+
+ @Override
+ public void apply(MethodVisitor methodVisitor,
+ MethodSizeHandler.ForAdvice methodSizeHandler,
+ StackMapFrameHandler.ForAdvice stackMapFrameHandler,
+ MethodDescription instrumentedMethod,
+ Bound.SkipHandler skipHandler) {
+ doApply(methodVisitor, methodSizeHandler, stackMapFrameHandler, instrumentedMethod, skipHandler, false);
+ }
+
+ /**
+ * Applies this skip dispatcher.
+ *
+ * @param methodVisitor The method visitor to write to.
+ * @param methodSizeHandler The method size handler of the advice method to use.
+ * @param stackMapFrameHandler The stack map frame handler of the advice method to use.
+ * @param instrumentedMethod The instrumented method.
+ * @param skipHandler The skip handler to use.
+ * @param inverted {@code true} if the skip condition should be inverted.
+ */
+ protected void doApply(MethodVisitor methodVisitor,
+ MethodSizeHandler.ForAdvice methodSizeHandler,
+ StackMapFrameHandler.ForAdvice stackMapFrameHandler,
+ MethodDescription instrumentedMethod,
+ Bound.SkipHandler skipHandler,
+ boolean inverted) {
+ methodVisitor.visitVarInsn(load, instrumentedMethod.getStackSize());
+ convertValue(methodVisitor, methodSizeHandler);
+ Label noSkip = new Label();
+ methodVisitor.visitJumpInsn(inverted
+ ? nonDefaultJump
+ : defaultJump, noSkip);
+ skipHandler.apply(methodVisitor);
+ methodVisitor.visitLabel(noSkip);
+ stackMapFrameHandler.injectCompletionFrame(methodVisitor, true);
+ }
+
+ /**
+ * Converts the return value to an {@code int} value.
+ *
+ * @param methodVisitor The method visitor to use.
+ * @param methodSizeHandler The method size handler of the advice method to use.
+ */
+ protected abstract void convertValue(MethodVisitor methodVisitor, MethodSizeHandler.ForAdvice methodSizeHandler);
+
+ /**
+ * Returns an inverted version of this skip dispatcher.
+ *
+ * @return An inverted version of this skip dispatcher.
+ */
+ private SkipDispatcher inverted() {
+ return new Inverted();
+ }
+
+ /**
+ * An inverted version of a value-based skipped dispatcher that triggers upon observing a non-default value.
+ */
+ protected class Inverted implements SkipDispatcher {
+
+ @Override
+ public void apply(MethodVisitor methodVisitor,
+ MethodSizeHandler.ForAdvice methodSizeHandler,
+ StackMapFrameHandler.ForAdvice stackMapFrameHandler,
+ MethodDescription instrumentedMethod,
+ Bound.SkipHandler skipHandler) {
+ doApply(methodVisitor, methodSizeHandler, stackMapFrameHandler, instrumentedMethod, skipHandler, true);
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private SkipDispatcher getOuter() {
+ return ForValue.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return ForValue.this.hashCode();
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (other == null || other.getClass() != getClass()) return false;
+ Inverted inverted = (Inverted) other;
+ return inverted.getOuter().equals(ForValue.this);
+ }
+ }
+ }
+
+ /**
+ * A skip dispatcher that skips a value if it is of a given instance.
+ */
+ @EqualsAndHashCode
+ class ForType implements SkipDispatcher {
+
+ /**
+ * The type for which to skip instances.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new skip dispatcher for a given type.
+ *
+ * @param typeDescription The type for which to skip instances.
+ */
+ protected ForType(TypeDescription typeDescription) {
+ this.typeDescription = typeDescription;
+ }
+
+ /**
+ * Creates a skip dispatcher for an advice method.
+ *
+ * @param adviceMethod The advice method for which to resolve a skip dispatcher.
+ * @return An appropriate skip dispatcher.
+ */
+ public static SkipDispatcher of(MethodDescription adviceMethod) {
+ return of(adviceMethod.getDeclaredAnnotations()
+ .ofType(OnMethodEnter.class)
+ .getValue(SKIP_ON)
+ .resolve(TypeDescription.class), adviceMethod);
+ }
+
+ /**
+ * Creates a skip dispatcher for a given annotation type and advice method.
+ *
+ * @param typeDescription The type that was specified as an annotation value.
+ * @param adviceMethod The advice method.
+ * @return An appropriate skip dispatcher.
+ */
+ protected static SkipDispatcher of(TypeDescription typeDescription, MethodDescription adviceMethod) {
+ if (typeDescription.represents(void.class)) {
+ return Disabled.INSTANCE;
+ } else if (typeDescription.represents(OnDefaultValue.class)) {
+ return ForValue.of(adviceMethod.getReturnType(), false);
+ } else if (typeDescription.represents(OnNonDefaultValue.class)) {
+ return ForValue.of(adviceMethod.getReturnType(), true);
+ } else if (typeDescription.isPrimitive() || adviceMethod.getReturnType().isPrimitive()) {
+ throw new IllegalStateException("Cannot skip method by instance type for primitive return value on " + adviceMethod);
+ } else {
+ return new ForType(typeDescription);
+ }
+ }
+
+ @Override
+ public void apply(MethodVisitor methodVisitor,
+ MethodSizeHandler.ForAdvice methodSizeHandler,
+ StackMapFrameHandler.ForAdvice stackMapFrameHandler,
+ MethodDescription instrumentedMethod,
+ Bound.SkipHandler skipHandler) {
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, instrumentedMethod.getStackSize());
+ methodVisitor.visitTypeInsn(Opcodes.INSTANCEOF, typeDescription.getInternalName());
+ Label noSkip = new Label();
+ methodVisitor.visitJumpInsn(Opcodes.IFEQ, noSkip);
+ skipHandler.apply(methodVisitor);
+ methodVisitor.visitLabel(noSkip);
+ stackMapFrameHandler.injectCompletionFrame(methodVisitor, true);
+ }
+ }
+ }
+ }
+
+ /**
+ * Represents a resolved dispatcher for exiting a method.
+ */
+ interface ForMethodExit extends Resolved {
+
+ /**
+ * Returns the type of throwable for which this exit advice is supposed to be invoked.
+ *
+ * @return The {@link Throwable} type for which to invoke this exit advice or a description of {@link NoExceptionHandler}
+ * if this exit advice does not expect to be invoked upon any throwable.
+ */
+ TypeDescription getThrowable();
+
+ @Override
+ Bound.ForMethodExit bind(TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ Assigner assigner,
+ MethodSizeHandler.ForInstrumentedMethod methodSizeHandler,
+ StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler,
+ StackManipulation exceptionHandler);
+ }
+ }
+
+ /**
+ * A bound resolution of an advice method.
+ */
+ interface Bound {
+
+ /**
+ * Prepares the advice method's exception handlers.
+ */
+ void prepare();
+
+ /**
+ * A skip handler is responsible for writing code that skips the invocation of the original code
+ * within the instrumented method.
+ */
+ interface SkipHandler {
+
+ /**
+ * Applies this skip handler.
+ *
+ * @param methodVisitor The method visitor to write the code to.
+ */
+ void apply(MethodVisitor methodVisitor);
+ }
+
+ /**
+ * A bound dispatcher for a method enter.
+ */
+ interface ForMethodEnter extends Bound {
+
+ /**
+ * Applies this dispatcher.
+ *
+ * @param skipHandler The skip handler to use.
+ */
+ void apply(SkipHandler skipHandler);
+ }
+
+ /**
+ * A bound dispatcher for a method exit.
+ */
+ interface ForMethodExit extends Bound {
+
+ /**
+ * Applies this dispatcher.
+ */
+ void apply();
+ }
+ }
+
+ /**
+ * An implementation for inactive devise that does not write any byte code.
+ */
+ enum Inactive implements Dispatcher.Unresolved, Resolved.ForMethodEnter, Resolved.ForMethodExit, Bound.ForMethodEnter, Bound.ForMethodExit {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public boolean isAlive() {
+ return false;
+ }
+
+ @Override
+ public boolean isBinary() {
+ return false;
+ }
+
+ @Override
+ public TypeDescription getThrowable() {
+ return NoExceptionHandler.DESCRIPTION;
+ }
+
+ @Override
+ public TypeDefinition getEnterType() {
+ return TypeDescription.VOID;
+ }
+
+ @Override
+ public boolean isPrependLineNumber() {
+ return false;
+ }
+
+ @Override
+ public Resolved.ForMethodEnter asMethodEnter(List<? extends OffsetMapping.Factory<?>> userFactories,
+ ClassReader classReader) {
+ return this;
+ }
+
+ @Override
+ public Resolved.ForMethodExit asMethodExitTo(List<? extends OffsetMapping.Factory<?>> userFactories,
+ ClassReader classReader,
+ Resolved.ForMethodEnter dispatcher) {
+ return this;
+ }
+
+ @Override
+ public void prepare() {
+ /* do nothing */
+ }
+
+ @Override
+ public void apply() {
+ /* do nothing */
+ }
+
+ @Override
+ public void apply(SkipHandler skipHandler) {
+ /* do nothing */
+ }
+
+ @Override
+ public Inactive bind(TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ Assigner assigner,
+ MethodSizeHandler.ForInstrumentedMethod methodSizeHandler,
+ StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler,
+ StackManipulation exceptionHandler) {
+ return this;
+ }
+ }
+
+ /**
+ * A dispatcher for an advice method that is being inlined into the instrumented method.
+ */
+ @EqualsAndHashCode
+ class Inlining implements Unresolved {
+
+ /**
+ * The advice method.
+ */
+ protected final MethodDescription.InDefinedShape adviceMethod;
+
+ /**
+ * Creates a dispatcher for inlined advice method.
+ *
+ * @param adviceMethod The advice method.
+ */
+ protected Inlining(MethodDescription.InDefinedShape adviceMethod) {
+ this.adviceMethod = adviceMethod;
+ }
+
+ @Override
+ public boolean isAlive() {
+ return true;
+ }
+
+ @Override
+ public boolean isBinary() {
+ return true;
+ }
+
+ @Override
+ public Dispatcher.Resolved.ForMethodEnter asMethodEnter(List<? extends OffsetMapping.Factory<?>> userFactories,
+ ClassReader classReader) {
+ return new Resolved.ForMethodEnter(adviceMethod, userFactories, classReader);
+ }
+
+ @Override
+ public Dispatcher.Resolved.ForMethodExit asMethodExitTo(List<? extends OffsetMapping.Factory<?>> userFactories,
+ ClassReader classReader,
+ Dispatcher.Resolved.ForMethodEnter dispatcher) {
+ return Resolved.ForMethodExit.of(adviceMethod, userFactories, classReader, dispatcher.getEnterType());
+ }
+
+ /**
+ * A resolved version of a dispatcher.
+ */
+ protected abstract static class Resolved implements Dispatcher.Resolved {
+
+ /**
+ * Indicates a read-only mapping for an offset.
+ */
+ private static final boolean READ_ONLY = true;
+
+ /**
+ * The represented advice method.
+ */
+ protected final MethodDescription.InDefinedShape adviceMethod;
+
+ /**
+ * A class reader to query for the class file of the advice method.
+ */
+ protected final ClassReader classReader;
+
+ /**
+ * An unresolved mapping of offsets of the advice method based on the annotations discovered on each method parameter.
+ */
+ protected final Map<Integer, OffsetMapping> offsetMappings;
+
+ /**
+ * The suppression handler to use.
+ */
+ protected final SuppressionHandler suppressionHandler;
+
+ /**
+ * Creates a new resolved version of a dispatcher.
+ *
+ * @param adviceMethod The represented advice method.
+ * @param factories A list of factories to resolve for the parameters of the advice method.
+ * @param classReader A class reader to query for the class file of the advice method.
+ * @param throwableType The type to handle by a suppression handler or {@link NoExceptionHandler} to not handle any exceptions.
+ */
+ protected Resolved(MethodDescription.InDefinedShape adviceMethod,
+ List<OffsetMapping.Factory<?>> factories,
+ ClassReader classReader,
+ TypeDescription throwableType) {
+ this.adviceMethod = adviceMethod;
+ Map<TypeDescription, OffsetMapping.Factory<?>> offsetMappings = new HashMap<TypeDescription, OffsetMapping.Factory<?>>();
+ for (OffsetMapping.Factory<?> factory : factories) {
+ offsetMappings.put(new TypeDescription.ForLoadedType(factory.getAnnotationType()), factory);
+ }
+ this.offsetMappings = new HashMap<Integer, OffsetMapping>();
+ for (ParameterDescription.InDefinedShape parameterDescription : adviceMethod.getParameters()) {
+ OffsetMapping offsetMapping = null;
+ for (AnnotationDescription annotationDescription : parameterDescription.getDeclaredAnnotations()) {
+ OffsetMapping.Factory<?> factory = offsetMappings.get(annotationDescription.getAnnotationType());
+ if (factory != null) {
+ @SuppressWarnings("unchecked")
+ OffsetMapping current = factory.make(parameterDescription,
+ (AnnotationDescription.Loadable) annotationDescription.prepare(factory.getAnnotationType()),
+ OffsetMapping.Factory.AdviceType.INLINING);
+ if (offsetMapping == null) {
+ offsetMapping = current;
+ } else {
+ throw new IllegalStateException(parameterDescription + " is bound to both " + current + " and " + offsetMapping);
+ }
+ }
+ }
+ this.offsetMappings.put(parameterDescription.getOffset(), offsetMapping == null
+ ? new OffsetMapping.ForArgument.Unresolved(parameterDescription)
+ : offsetMapping);
+ }
+ this.classReader = classReader;
+ suppressionHandler = SuppressionHandler.Suppressing.of(throwableType);
+ }
+
+ @Override
+ public boolean isAlive() {
+ return true;
+ }
+
+ /**
+ * Applies a resolution for a given instrumented method.
+ *
+ * @param methodVisitor A method visitor for writing byte code to the instrumented method.
+ * @param implementationContext The implementation context to use.
+ * @param assigner The assigner to use.
+ * @param methodSizeHandler A handler for computing the method size requirements.
+ * @param stackMapFrameHandler A handler for translating and injecting stack map frames.
+ * @param instrumentedType A description of the instrumented type.
+ * @param instrumentedMethod A description of the instrumented method.
+ * @param suppressionHandler The bound suppression handler that is used for suppressing exceptions of this advice method.
+ * @return A method visitor for visiting the advice method's byte code.
+ */
+ protected abstract MethodVisitor apply(MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ Assigner assigner,
+ MethodSizeHandler.ForInstrumentedMethod methodSizeHandler,
+ StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler,
+ TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ SuppressionHandler.Bound suppressionHandler);
+
+ @Override // HE: Remove after Lombok resolves ambiguous type names correctly.
+ public boolean equals(Object object) {
+ if (this == object) return true;
+ if (object == null || getClass() != object.getClass()) return false;
+ Inlining.Resolved resolved = (Inlining.Resolved) object;
+ return adviceMethod.equals(resolved.adviceMethod)
+ && offsetMappings.equals(resolved.offsetMappings)
+ && suppressionHandler.equals(resolved.suppressionHandler);
+ }
+
+ @Override // HE: Remove after Lombok resolves ambiguous type names correctly.
+ public int hashCode() {
+ int result = adviceMethod.hashCode();
+ result = 31 * result + offsetMappings.hashCode();
+ result = 31 * result + suppressionHandler.hashCode();
+ return result;
+ }
+
+ /**
+ * A bound advice method that copies the code by first extracting the exception table and later appending the
+ * code of the method without copying any meta data.
+ */
+ protected abstract class AdviceMethodInliner extends ClassVisitor implements Bound {
+
+ /**
+ * A description of the instrumented type.
+ */
+ protected final TypeDescription instrumentedType;
+
+ /**
+ * The instrumented method.
+ */
+ protected final MethodDescription instrumentedMethod;
+
+ /**
+ * The method visitor for writing the instrumented method.
+ */
+ protected final MethodVisitor methodVisitor;
+
+ /**
+ * The implementation context to use.
+ */
+ protected final Implementation.Context implementationContext;
+
+ /**
+ * The assigner to use.
+ */
+ protected final Assigner assigner;
+
+ /**
+ * A handler for computing the method size requirements.
+ */
+ protected final MethodSizeHandler.ForInstrumentedMethod methodSizeHandler;
+
+ /**
+ * A handler for translating and injecting stack map frames.
+ */
+ protected final StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler;
+
+ /**
+ * A bound suppression handler that is used for suppressing exceptions of this advice method.
+ */
+ protected final SuppressionHandler.Bound suppressionHandler;
+
+ /**
+ * A class reader for parsing the class file containing the represented advice method.
+ */
+ protected final ClassReader classReader;
+
+ /**
+ * The labels that were found during parsing the method's exception handler in the order of their discovery.
+ */
+ protected List<Label> labels;
+
+ /**
+ * Creates a new advice method inliner.
+ *
+ * @param instrumentedType A description of the instrumented type.
+ * @param instrumentedMethod The instrumented method.
+ * @param methodVisitor The method visitor for writing the instrumented method.
+ * @param implementationContext The implementation context to use.
+ * @param assigner The assigner to use.
+ * @param methodSizeHandler A handler for computing the method size requirements.
+ * @param stackMapFrameHandler A handler for translating and injecting stack map frames.
+ * @param suppressionHandler A bound suppression handler that is used for suppressing exceptions of this advice method.
+ * @param classReader A class reader for parsing the class file containing the represented advice method.
+ */
+ protected AdviceMethodInliner(TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ Assigner assigner,
+ MethodSizeHandler.ForInstrumentedMethod methodSizeHandler,
+ StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler,
+ SuppressionHandler.Bound suppressionHandler,
+ ClassReader classReader) {
+ super(Opcodes.ASM5);
+ this.instrumentedType = instrumentedType;
+ this.instrumentedMethod = instrumentedMethod;
+ this.methodVisitor = methodVisitor;
+ this.implementationContext = implementationContext;
+ this.assigner = assigner;
+ this.methodSizeHandler = methodSizeHandler;
+ this.stackMapFrameHandler = stackMapFrameHandler;
+ this.suppressionHandler = suppressionHandler;
+ this.classReader = classReader;
+ labels = new ArrayList<Label>();
+ }
+
+ @Override
+ public void prepare() {
+ classReader.accept(new ExceptionTableExtractor(), ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
+ suppressionHandler.onPrepare(methodVisitor);
+ }
+
+ /**
+ * Inlines the advice method.
+ */
+ protected void doApply() {
+ classReader.accept(this, ClassReader.SKIP_DEBUG | stackMapFrameHandler.getReaderHint());
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int modifiers, String internalName, String descriptor, String signature, String[] exception) {
+ return adviceMethod.getInternalName().equals(internalName) && adviceMethod.getDescriptor().equals(descriptor)
+ ? new ExceptionTableSubstitutor(Inlining.Resolved.this.apply(methodVisitor,
+ implementationContext,
+ assigner,
+ methodSizeHandler,
+ stackMapFrameHandler,
+ instrumentedType,
+ instrumentedMethod,
+ suppressionHandler)) : IGNORE_METHOD;
+ }
+
+ /**
+ * A class visitor that extracts the exception tables of the advice method.
+ */
+ protected class ExceptionTableExtractor extends ClassVisitor {
+
+ /**
+ * Creates a new exception table extractor.
+ */
+ protected ExceptionTableExtractor() {
+ super(Opcodes.ASM5);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int modifiers, String internalName, String descriptor, String signature, String[] exception) {
+ return adviceMethod.getInternalName().equals(internalName) && adviceMethod.getDescriptor().equals(descriptor)
+ ? new ExceptionTableCollector(methodVisitor)
+ : IGNORE_METHOD;
+ }
+ }
+
+ /**
+ * A visitor that only writes try-catch-finally blocks to the supplied method visitor. All labels of these tables are collected
+ * for substitution when revisiting the reminder of the method.
+ */
+ protected class ExceptionTableCollector extends MethodVisitor {
+
+ /**
+ * The method visitor for which the try-catch-finally blocks should be written.
+ */
+ private final MethodVisitor methodVisitor;
+
+ /**
+ * Creates a new exception table collector.
+ *
+ * @param methodVisitor The method visitor for which the try-catch-finally blocks should be written.
+ */
+ protected ExceptionTableCollector(MethodVisitor methodVisitor) {
+ super(Opcodes.ASM5);
+ this.methodVisitor = methodVisitor;
+ }
+
+ @Override
+ public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
+ methodVisitor.visitTryCatchBlock(start, end, handler, type);
+ labels.addAll(Arrays.asList(start, end, handler));
+ }
+
+ @Override
+ public AnnotationVisitor visitTryCatchAnnotation(int typeReference, TypePath typePath, String descriptor, boolean visible) {
+ return methodVisitor.visitTryCatchAnnotation(typeReference, typePath, descriptor, visible);
+ }
+ }
+
+ /**
+ * A label substitutor allows to visit an advice method a second time after the exception handlers were already written.
+ * Doing so, this visitor substitutes all labels that were already created during the first visit to keep the mapping
+ * consistent. It is not required to resolve labels for non-code instructions as meta information is not propagated to
+ * the target method visitor for advice code.
+ */
+ protected class ExceptionTableSubstitutor extends MethodVisitor {
+
+ /**
+ * A map containing resolved substitutions.
+ */
+ private final Map<Label, Label> substitutions;
+
+ /**
+ * The current index of the visited labels that are used for try-catch-finally blocks.
+ */
+ private int index;
+
+ /**
+ * Creates a label substitor.
+ *
+ * @param methodVisitor The method visitor for which to substitute labels.
+ */
+ protected ExceptionTableSubstitutor(MethodVisitor methodVisitor) {
+ super(Opcodes.ASM5, methodVisitor);
+ substitutions = new IdentityHashMap<Label, Label>();
+ }
+
+ @Override
+ public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
+ substitutions.put(start, labels.get(index++));
+ substitutions.put(end, labels.get(index++));
+ Label actualHandler = labels.get(index++);
+ substitutions.put(handler, actualHandler);
+ ((CodeTranslationVisitor) mv).propagateHandler(actualHandler);
+ }
+
+ @Override
+ public AnnotationVisitor visitTryCatchAnnotation(int typeReference, TypePath typePath, String descriptor, boolean visible) {
+ return IGNORE_ANNOTATION;
+ }
+
+ @Override
+ public void visitLabel(Label label) {
+ super.visitLabel(resolve(label));
+ }
+
+ @Override
+ public void visitJumpInsn(int opcode, Label label) {
+ super.visitJumpInsn(opcode, resolve(label));
+ }
+
+ @Override
+ public void visitTableSwitchInsn(int minimum, int maximum, Label defaultOption, Label... label) {
+ super.visitTableSwitchInsn(minimum, maximum, defaultOption, resolve(label));
+ }
+
+ @Override
+ public void visitLookupSwitchInsn(Label defaultOption, int[] keys, Label[] label) {
+ super.visitLookupSwitchInsn(resolve(defaultOption), keys, resolve(label));
+ }
+
+ /**
+ * Resolves an array of labels.
+ *
+ * @param label The labels to resolved.
+ * @return An array containing the resolved arrays.
+ */
+ private Label[] resolve(Label[] label) {
+ Label[] resolved = new Label[label.length];
+ int index = 0;
+ for (Label aLabel : label) {
+ resolved[index++] = resolve(aLabel);
+ }
+ return resolved;
+ }
+
+ /**
+ * Resolves a single label if mapped or returns the original label.
+ *
+ * @param label The label to resolve.
+ * @return The resolved label.
+ */
+ private Label resolve(Label label) {
+ Label substitution = substitutions.get(label);
+ return substitution == null
+ ? label
+ : substitution;
+ }
+ }
+ }
+
+ /**
+ * A resolved dispatcher for implementing method enter advice.
+ */
+ protected static class ForMethodEnter extends Inlining.Resolved implements Dispatcher.Resolved.ForMethodEnter {
+
+ /**
+ * The skip dispatcher to use.
+ */
+ private final SkipDispatcher skipDispatcher;
+
+ /**
+ * {@code true} if the first discovered line number information should be prepended to the advice code.
+ */
+ private final boolean prependLineNumber;
+
+ /**
+ * Creates a new resolved dispatcher for implementing method enter advice.
+ *
+ * @param adviceMethod The represented advice method.
+ * @param userFactories A list of user-defined factories for offset mappings.
+ * @param classReader A class reader to query for the class file of the advice method.
+ */
+ @SuppressWarnings("unchecked") // In absence of @SafeVarargs for Java 6
+ protected ForMethodEnter(MethodDescription.InDefinedShape adviceMethod,
+ List<? extends OffsetMapping.Factory<?>> userFactories,
+ ClassReader classReader) {
+ super(adviceMethod,
+ CompoundList.of(Arrays.asList(OffsetMapping.ForArgument.Unresolved.Factory.INSTANCE,
+ OffsetMapping.ForAllArguments.Factory.INSTANCE,
+ OffsetMapping.ForThisReference.Factory.INSTANCE,
+ OffsetMapping.ForField.Unresolved.Factory.INSTANCE,
+ OffsetMapping.ForOrigin.Factory.INSTANCE,
+ OffsetMapping.ForUnusedValue.Factory.INSTANCE,
+ OffsetMapping.ForStubValue.INSTANCE,
+ OffsetMapping.ForThrowable.Factory.INSTANCE,
+ new OffsetMapping.Factory.Illegal<Thrown>(Thrown.class),
+ new OffsetMapping.Factory.Illegal<Enter>(Enter.class),
+ new OffsetMapping.Factory.Illegal<Return>(Return.class)), userFactories),
+ classReader,
+ adviceMethod.getDeclaredAnnotations().ofType(OnMethodEnter.class).getValue(SUPPRESS_ENTER).resolve(TypeDescription.class));
+ skipDispatcher = SkipDispatcher.ForType.of(adviceMethod);
+ prependLineNumber = adviceMethod.getDeclaredAnnotations().ofType(OnMethodEnter.class).getValue(PREPEND_LINE_NUMBER).resolve(Boolean.class);
+ }
+
+ @Override
+ public Bound.ForMethodEnter bind(TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ Assigner assigner,
+ MethodSizeHandler.ForInstrumentedMethod methodSizeHandler,
+ StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler,
+ StackManipulation exceptionHandler) {
+ return new AdviceMethodInliner(instrumentedType,
+ instrumentedMethod,
+ methodVisitor,
+ implementationContext,
+ assigner,
+ methodSizeHandler,
+ stackMapFrameHandler,
+ suppressionHandler.bind(exceptionHandler),
+ classReader,
+ skipDispatcher);
+ }
+
+ @Override
+ public TypeDefinition getEnterType() {
+ return adviceMethod.getReturnType();
+ }
+
+ @Override
+ public boolean isPrependLineNumber() {
+ return prependLineNumber;
+ }
+
+ @Override
+ protected MethodVisitor apply(MethodVisitor methodVisitor,
+ Context implementationContext,
+ Assigner assigner,
+ MethodSizeHandler.ForInstrumentedMethod methodSizeHandler,
+ StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler,
+ TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ SuppressionHandler.Bound suppressionHandler) {
+ Map<Integer, OffsetMapping.Target> offsetMappings = new HashMap<Integer, OffsetMapping.Target>();
+ for (Map.Entry<Integer, OffsetMapping> entry : this.offsetMappings.entrySet()) {
+ offsetMappings.put(entry.getKey(), entry.getValue().resolve(instrumentedType,
+ instrumentedMethod,
+ assigner,
+ OffsetMapping.Context.ForMethodEntry.of(instrumentedMethod)));
+ }
+ return new CodeTranslationVisitor.ForMethodEnter(methodVisitor,
+ implementationContext,
+ methodSizeHandler.bindEntry(adviceMethod),
+ stackMapFrameHandler.bindEntry(adviceMethod),
+ instrumentedMethod,
+ adviceMethod,
+ offsetMappings,
+ suppressionHandler);
+ }
+
+ @Override // HE: Remove after Lombok resolves ambiguous type names correctly.
+ public boolean equals(Object object) {
+ if (this == object) return true;
+ if (object == null || getClass() != object.getClass()) return false;
+ if (!super.equals(object)) return false;
+ Inlining.Resolved.ForMethodEnter that = (Inlining.Resolved.ForMethodEnter) object;
+ return prependLineNumber == that.prependLineNumber && skipDispatcher.equals(that.skipDispatcher);
+ }
+
+ @Override // HE: Remove after Lombok resolves ambiguous type names correctly.
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + skipDispatcher.hashCode();
+ result = 31 * result + (prependLineNumber ? 1 : 0);
+ return result;
+ }
+
+ /**
+ * An advice method inliner for a method enter.
+ */
+ protected class AdviceMethodInliner extends Inlining.Resolved.AdviceMethodInliner implements Bound.ForMethodEnter {
+
+ /**
+ * The skip dispatcher to use.
+ */
+ private final SkipDispatcher skipDispatcher;
+
+ /**
+ * Creates a new advice method inliner for a method enter.
+ *
+ * @param instrumentedType A description of the instrumented type.
+ * @param instrumentedMethod A description of the instrumented method.
+ * @param methodVisitor The method visitor for writing the instrumented method.
+ * @param implementationContext The implementation context to use.
+ * @param assigner The assigner to use.
+ * @param methodSizeHandler A handler for computing the method size requirements.
+ * @param stackMapFrameHandler A handler for translating and injecting stack map frames.
+ * @param suppressionHandler A bound suppression handler that is used for suppressing exceptions of this advice method.
+ * @param classReader A class reader for parsing the class file containing the represented advice method.
+ * @param skipDispatcher The skip dispatcher to use.
+ */
+ protected AdviceMethodInliner(TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ Assigner assigner,
+ MethodSizeHandler.ForInstrumentedMethod methodSizeHandler,
+ StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler,
+ SuppressionHandler.Bound suppressionHandler,
+ ClassReader classReader,
+ SkipDispatcher skipDispatcher) {
+ super(instrumentedType,
+ instrumentedMethod,
+ methodVisitor,
+ implementationContext,
+ assigner,
+ methodSizeHandler,
+ stackMapFrameHandler,
+ suppressionHandler,
+ classReader);
+ this.skipDispatcher = skipDispatcher;
+ }
+
+ @Override
+ public void apply(SkipHandler skipHandler) {
+ doApply();
+ skipDispatcher.apply(methodVisitor,
+ methodSizeHandler.bindEntry(adviceMethod),
+ stackMapFrameHandler.bindEntry(adviceMethod),
+ instrumentedMethod, skipHandler);
+ }
+ }
+ }
+
+ /**
+ * A resolved dispatcher for implementing method exit advice.
+ */
+ protected abstract static class ForMethodExit extends Inlining.Resolved implements Dispatcher.Resolved.ForMethodExit {
+
+ /**
+ * The additional stack size to consider when accessing the local variable array.
+ */
+ private final TypeDefinition enterType;
+
+ /**
+ * Creates a new resolved dispatcher for implementing method exit advice.
+ *
+ * @param adviceMethod The represented advice method.
+ * @param userFactories A list of user-defined factories for offset mappings.
+ * @param classReader The class reader for parsing the advice method's class file.
+ * @param enterType The type of the value supplied by the enter advice method or
+ * a description of {@code void} if no such value exists.
+ */
+ @SuppressWarnings("unchecked")
+ protected ForMethodExit(MethodDescription.InDefinedShape adviceMethod,
+ List<? extends OffsetMapping.Factory<?>> userFactories,
+ ClassReader classReader,
+ TypeDefinition enterType) {
+ super(adviceMethod,
+ CompoundList.of(Arrays.asList(OffsetMapping.ForArgument.Unresolved.Factory.INSTANCE,
+ OffsetMapping.ForAllArguments.Factory.INSTANCE,
+ OffsetMapping.ForThisReference.Factory.INSTANCE,
+ OffsetMapping.ForField.Unresolved.Factory.INSTANCE,
+ OffsetMapping.ForOrigin.Factory.INSTANCE,
+ OffsetMapping.ForUnusedValue.Factory.INSTANCE,
+ OffsetMapping.ForStubValue.INSTANCE,
+ new OffsetMapping.ForEnterValue.Factory(enterType),
+ OffsetMapping.ForReturnValue.Factory.INSTANCE,
+ OffsetMapping.ForThrowable.Factory.of(adviceMethod)
+ ), userFactories),
+ classReader,
+ adviceMethod.getDeclaredAnnotations().ofType(OnMethodExit.class).getValue(SUPPRESS_EXIT).resolve(TypeDescription.class));
+ this.enterType = enterType;
+ }
+
+ /**
+ * Resolves exit advice that handles exceptions depending on the specification of the exit advice.
+ *
+ * @param adviceMethod The advice method.
+ * @param userFactories A list of user-defined factories for offset mappings.
+ * @param classReader The class reader for parsing the advice method's class file.
+ * @param enterType The type of the value supplied by the enter advice method or
+ * a description of {@code void} if no such value exists.
+ * @return An appropriate exit handler.
+ */
+ protected static Resolved.ForMethodExit of(MethodDescription.InDefinedShape adviceMethod,
+ List<? extends OffsetMapping.Factory<?>> userFactories,
+ ClassReader classReader,
+ TypeDefinition enterType) {
+ TypeDescription throwable = adviceMethod.getDeclaredAnnotations()
+ .ofType(OnMethodExit.class)
+ .getValue(ON_THROWABLE).resolve(TypeDescription.class);
+ return throwable.represents(NoExceptionHandler.class)
+ ? new WithoutExceptionHandler(adviceMethod, userFactories, classReader, enterType)
+ : new WithExceptionHandler(adviceMethod, userFactories, classReader, enterType, throwable);
+ }
+
+ @Override
+ protected MethodVisitor apply(MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ Assigner assigner,
+ MethodSizeHandler.ForInstrumentedMethod methodSizeHandler,
+ StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler,
+ TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ SuppressionHandler.Bound suppressionHandler) {
+ Map<Integer, OffsetMapping.Target> offsetMappings = new HashMap<Integer, OffsetMapping.Target>();
+ for (Map.Entry<Integer, OffsetMapping> entry : this.offsetMappings.entrySet()) {
+ offsetMappings.put(entry.getKey(), entry.getValue().resolve(instrumentedType,
+ instrumentedMethod,
+ assigner,
+ OffsetMapping.Context.ForMethodExit.of(enterType)));
+ }
+ return new CodeTranslationVisitor.ForMethodExit(methodVisitor,
+ implementationContext,
+ methodSizeHandler.bindExit(adviceMethod, getThrowable().represents(NoExceptionHandler.class)),
+ stackMapFrameHandler.bindExit(adviceMethod),
+ instrumentedMethod,
+ adviceMethod,
+ offsetMappings,
+ suppressionHandler,
+ enterType.getStackSize().getSize() + getPadding().getSize());
+ }
+
+
+ @Override
+ public Bound.ForMethodExit bind(TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ Assigner assigner,
+ MethodSizeHandler.ForInstrumentedMethod methodSizeHandler,
+ StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler,
+ StackManipulation exceptionHandler) {
+ return new AdviceMethodInliner(instrumentedType,
+ instrumentedMethod,
+ methodVisitor,
+ implementationContext,
+ assigner,
+ methodSizeHandler,
+ stackMapFrameHandler,
+ suppressionHandler.bind(exceptionHandler),
+ classReader);
+ }
+
+ /**
+ * Returns the additional padding this exit advice implies.
+ *
+ * @return The additional padding this exit advice implies.
+ */
+ protected abstract StackSize getPadding();
+
+ /**
+ * An advice method inliner for a method exit.
+ */
+ protected class AdviceMethodInliner extends Inlining.Resolved.AdviceMethodInliner implements Bound.ForMethodExit {
+
+ /**
+ * Creates a new advice method inliner for a method exit.
+ *
+ * @param instrumentedType A description of the instrumented type.
+ * @param instrumentedMethod A description of the instrumented method.
+ * @param methodVisitor The method visitor for writing the instrumented method.
+ * @param implementationContext The implementation context to use.
+ * @param assigner The assigner to use.
+ * @param methodSizeHandler A handler for computing the method size requirements.
+ * @param stackMapFrameHandler A handler for translating and injecting stack map frames.
+ * @param suppressionHandler A bound suppression handler that is used for suppressing exceptions of this advice method.
+ * @param classReader A class reader for parsing the class file containing the represented advice method.
+ */
+ public AdviceMethodInliner(TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ Assigner assigner,
+ MethodSizeHandler.ForInstrumentedMethod methodSizeHandler,
+ StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler,
+ SuppressionHandler.Bound suppressionHandler,
+ ClassReader classReader) {
+ super(instrumentedType,
+ instrumentedMethod,
+ methodVisitor,
+ implementationContext,
+ assigner,
+ methodSizeHandler,
+ stackMapFrameHandler,
+ suppressionHandler,
+ classReader);
+ }
+
+ @Override
+ public void apply() {
+ doApply();
+ }
+ }
+
+ @Override // HE: Remove after Lombok resolves ambiguous type names correctly.
+ public boolean equals(Object object) {
+ if (this == object) return true;
+ if (object == null || getClass() != object.getClass()) return false;
+ if (!super.equals(object)) return false;
+ Inlining.Resolved.ForMethodExit that = (Inlining.Resolved.ForMethodExit) object;
+ return enterType.equals(that.enterType);
+ }
+
+ @Override // HE: Remove after Lombok resolves ambiguous type names correctly.
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + enterType.hashCode();
+ return result;
+ }
+
+ /**
+ * Implementation of exit advice that handles exceptions.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ protected static class WithExceptionHandler extends Inlining.Resolved.ForMethodExit {
+
+ /**
+ * The type of the handled throwable type for which this advice is invoked.
+ */
+ private final TypeDescription throwable;
+
+ /**
+ * Creates a new resolved dispatcher for implementing method exit advice that handles exceptions.
+ *
+ * @param adviceMethod The represented advice method.
+ * @param userFactories A list of user-defined factories for offset mappings.
+ * @param classReader The class reader for parsing the advice method's class file.
+ * @param enterType The type of the value supplied by the enter advice method or
+ * a description of {@code void} if no such value exists.
+ * @param throwable The type of the handled throwable type for which this advice is invoked.
+ */
+ protected WithExceptionHandler(MethodDescription.InDefinedShape adviceMethod,
+ List<? extends OffsetMapping.Factory<?>> userFactories,
+ ClassReader classReader,
+ TypeDefinition enterType,
+ TypeDescription throwable) {
+ super(adviceMethod, userFactories, classReader, enterType);
+ this.throwable = throwable;
+ }
+
+ @Override
+ protected StackSize getPadding() {
+ return throwable.getStackSize();
+ }
+
+ @Override
+ public TypeDescription getThrowable() {
+ return throwable;
+ }
+ }
+
+ /**
+ * Implementation of exit advice that ignores exceptions.
+ */
+ protected static class WithoutExceptionHandler extends Inlining.Resolved.ForMethodExit {
+
+ /**
+ * Creates a new resolved dispatcher for implementing method exit advice that does not handle exceptions.
+ *
+ * @param adviceMethod The represented advice method.
+ * @param userFactories A list of user-defined factories for offset mappings.
+ * @param classReader A class reader to query for the class file of the advice method.
+ * @param enterType The type of the value supplied by the enter advice method or
+ * a description of {@code void} if no such value exists.
+ */
+ protected WithoutExceptionHandler(MethodDescription.InDefinedShape adviceMethod,
+ List<? extends OffsetMapping.Factory<?>> userFactories,
+ ClassReader classReader,
+ TypeDefinition enterType) {
+ super(adviceMethod, userFactories, classReader, enterType);
+ }
+
+ @Override
+ protected StackSize getPadding() {
+ return StackSize.ZERO;
+ }
+
+ @Override
+ public TypeDescription getThrowable() {
+ return NoExceptionHandler.DESCRIPTION;
+ }
+ }
+ }
+ }
+
+ /**
+ * A visitor for translating an advice method's byte code for inlining into the instrumented method.
+ */
+ protected abstract static class CodeTranslationVisitor extends MethodVisitor implements SuppressionHandler.ReturnValueProducer {
+
+ /**
+ * The original method visitor to which all instructions are eventually written to.
+ */
+ protected final MethodVisitor methodVisitor;
+
+ /**
+ * The implementation context to use.
+ */
+ protected final Context implementationContext;
+
+ /**
+ * A handler for computing the method size requirements.
+ */
+ protected final MethodSizeHandler.ForAdvice methodSizeHandler;
+
+ /**
+ * A handler for translating and injecting stack map frames.
+ */
+ protected final StackMapFrameHandler.ForAdvice stackMapFrameHandler;
+
+ /**
+ * The instrumented method.
+ */
+ protected final MethodDescription instrumentedMethod;
+
+ /**
+ * The advice method.
+ */
+ protected final MethodDescription.InDefinedShape adviceMethod;
+
+ /**
+ * A mapping of offsets to resolved target offsets in the instrumented method.
+ */
+ private final Map<Integer, OffsetMapping.Target> offsetMappings;
+
+ /**
+ * A handler for optionally suppressing exceptions.
+ */
+ private final SuppressionHandler.Bound suppressionHandler;
+
+ /**
+ * A label indicating the end of the advice byte code.
+ */
+ protected final Label endOfMethod;
+
+ /**
+ * Creates a new code translation visitor.
+ *
+ * @param methodVisitor A method visitor for writing the instrumented method's byte code.
+ * @param implementationContext The implementation context to use.
+ * @param methodSizeHandler A handler for computing the method size requirements.
+ * @param stackMapFrameHandler A handler for translating and injecting stack map frames.
+ * @param instrumentedMethod The instrumented method.
+ * @param adviceMethod The advice method.
+ * @param offsetMappings A mapping of offsets to resolved target offsets in the instrumented method.
+ * @param suppressionHandler The suppression handler to use.
+ */
+ protected CodeTranslationVisitor(MethodVisitor methodVisitor,
+ Context implementationContext,
+ MethodSizeHandler.ForAdvice methodSizeHandler,
+ StackMapFrameHandler.ForAdvice stackMapFrameHandler,
+ MethodDescription instrumentedMethod,
+ MethodDescription.InDefinedShape adviceMethod,
+ Map<Integer, OffsetMapping.Target> offsetMappings,
+ SuppressionHandler.Bound suppressionHandler) {
+ super(Opcodes.ASM5, new StackAwareMethodVisitor(methodVisitor, instrumentedMethod));
+ this.methodVisitor = methodVisitor;
+ this.implementationContext = implementationContext;
+ this.methodSizeHandler = methodSizeHandler;
+ this.stackMapFrameHandler = stackMapFrameHandler;
+ this.instrumentedMethod = instrumentedMethod;
+ this.adviceMethod = adviceMethod;
+ this.offsetMappings = offsetMappings;
+ this.suppressionHandler = suppressionHandler;
+ endOfMethod = new Label();
+ }
+
+ /**
+ * Propagates a label for an exception handler that is typically suppressed by the overlaying
+ * {@link Resolved.AdviceMethodInliner.ExceptionTableSubstitutor}.
+ *
+ * @param label The label to register as a target for an exception handler.
+ */
+ protected void propagateHandler(Label label) {
+ ((StackAwareMethodVisitor) mv).register(label, Collections.singletonList(StackSize.SINGLE));
+ }
+
+ @Override
+ public void visitParameter(String name, int modifiers) {
+ /* do nothing */
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotationDefault() {
+ return IGNORE_ANNOTATION;
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+ return IGNORE_ANNOTATION;
+ }
+
+ @Override
+ public AnnotationVisitor visitTypeAnnotation(int typeReference, TypePath typePath, String descriptor, boolean visible) {
+ return IGNORE_ANNOTATION;
+ }
+
+ @Override
+ public AnnotationVisitor visitParameterAnnotation(int index, String descriptor, boolean visible) {
+ return IGNORE_ANNOTATION;
+ }
+
+ @Override
+ public void visitAttribute(Attribute attribute) {
+ /* do nothing */
+ }
+
+ @Override
+ public void visitCode() {
+ suppressionHandler.onStart(methodVisitor);
+ }
+
+ @Override
+ public void visitFrame(int type, int localVariableLength, Object[] localVariable, int stackSize, Object[] stack) {
+ stackMapFrameHandler.translateFrame(methodVisitor, type, localVariableLength, localVariable, stackSize, stack);
+ }
+
+ @Override
+ public void visitEnd() {
+ suppressionHandler.onEnd(methodVisitor, implementationContext, methodSizeHandler, stackMapFrameHandler, this);
+ methodVisitor.visitLabel(endOfMethod);
+ onMethodReturn();
+ stackMapFrameHandler.injectCompletionFrame(methodVisitor, false);
+ }
+
+ @Override
+ public void visitMaxs(int stackSize, int localVariableLength) {
+ methodSizeHandler.recordMaxima(stackSize, localVariableLength);
+ }
+
+ @Override
+ public void visitVarInsn(int opcode, int offset) {
+ OffsetMapping.Target target = offsetMappings.get(offset);
+ if (target != null) {
+ StackManipulation stackManipulation;
+ StackSize expectedGrowth;
+ switch (opcode) {
+ case Opcodes.ILOAD:
+ case Opcodes.FLOAD:
+ case Opcodes.ALOAD:
+ stackManipulation = target.resolveRead();
+ expectedGrowth = StackSize.SINGLE;
+ break;
+ case Opcodes.DLOAD:
+ case Opcodes.LLOAD:
+ stackManipulation = target.resolveRead();
+ expectedGrowth = StackSize.DOUBLE;
+ break;
+ case Opcodes.ISTORE:
+ case Opcodes.FSTORE:
+ case Opcodes.ASTORE:
+ case Opcodes.LSTORE:
+ case Opcodes.DSTORE:
+ stackManipulation = target.resolveWrite();
+ expectedGrowth = StackSize.ZERO;
+ break;
+ default:
+ throw new IllegalStateException("Unexpected opcode: " + opcode);
+ }
+ methodSizeHandler.recordPadding(stackManipulation.apply(mv, implementationContext).getMaximalSize() - expectedGrowth.getSize());
+ } else {
+ mv.visitVarInsn(opcode, adjust(offset + instrumentedMethod.getStackSize() - adviceMethod.getStackSize()));
+ }
+ }
+
+ @Override
+ public void visitIincInsn(int offset, int value) {
+ OffsetMapping.Target target = offsetMappings.get(offset);
+ if (target != null) {
+ methodSizeHandler.recordPadding(target.resolveIncrement(value).apply(mv, implementationContext).getMaximalSize());
+ } else {
+ mv.visitIincInsn(adjust(offset + instrumentedMethod.getStackSize() - adviceMethod.getStackSize()), value);
+ }
+ }
+
+ /**
+ * Adjusts the offset of a variable instruction within the advice method such that no arguments to
+ * the instrumented method are overridden.
+ *
+ * @param offset The original offset.
+ * @return The adjusted offset.
+ */
+ protected abstract int adjust(int offset);
+
+ @Override
+ public abstract void visitInsn(int opcode);
+
+ /**
+ * Invoked after returning from the advice method.
+ */
+ protected abstract void onMethodReturn();
+
+ /**
+ * A code translation visitor that retains the return value of the represented advice method.
+ */
+ protected static class ForMethodEnter extends CodeTranslationVisitor {
+
+ /**
+ * {@code true} if the method can return non-exceptionally.
+ */
+ private boolean doesReturn;
+
+ /**
+ * Creates a code translation visitor for translating exit advice.
+ *
+ * @param methodVisitor A method visitor for writing the instrumented method's byte code.
+ * @param implementationContext The implementation context to use.
+ * @param methodSizeHandler A handler for computing the method size requirements.
+ * @param stackMapFrameHandler A handler for translating and injecting stack map frames.
+ * @param instrumentedMethod The instrumented method.
+ * @param adviceMethod The advice method.
+ * @param offsetMappings A mapping of offsets to resolved target offsets in the instrumented method.
+ * @param suppressionHandler The suppression handler to use.
+ */
+ protected ForMethodEnter(MethodVisitor methodVisitor,
+ Context implementationContext,
+ MethodSizeHandler.ForAdvice methodSizeHandler,
+ StackMapFrameHandler.ForAdvice stackMapFrameHandler,
+ MethodDescription instrumentedMethod,
+ MethodDescription.InDefinedShape adviceMethod,
+ Map<Integer, OffsetMapping.Target> offsetMappings,
+ SuppressionHandler.Bound suppressionHandler) {
+ super(methodVisitor,
+ implementationContext,
+ methodSizeHandler,
+ stackMapFrameHandler,
+ instrumentedMethod,
+ adviceMethod,
+ offsetMappings,
+ suppressionHandler);
+ doesReturn = false;
+ }
+
+ @Override
+ public void visitInsn(int opcode) {
+ switch (opcode) {
+ case Opcodes.RETURN:
+ ((StackAwareMethodVisitor) mv).drainStack();
+ break;
+ case Opcodes.IRETURN:
+ methodSizeHandler.requireLocalVariableLength(((StackAwareMethodVisitor) mv).drainStack(Opcodes.ISTORE, Opcodes.ILOAD, StackSize.SINGLE));
+ break;
+ case Opcodes.ARETURN:
+ methodSizeHandler.requireLocalVariableLength(((StackAwareMethodVisitor) mv).drainStack(Opcodes.ASTORE, Opcodes.ALOAD, StackSize.SINGLE));
+ break;
+ case Opcodes.FRETURN:
+ methodSizeHandler.requireLocalVariableLength(((StackAwareMethodVisitor) mv).drainStack(Opcodes.FSTORE, Opcodes.FLOAD, StackSize.SINGLE));
+ break;
+ case Opcodes.LRETURN:
+ methodSizeHandler.requireLocalVariableLength(((StackAwareMethodVisitor) mv).drainStack(Opcodes.LSTORE, Opcodes.LLOAD, StackSize.DOUBLE));
+ break;
+ case Opcodes.DRETURN:
+ methodSizeHandler.requireLocalVariableLength(((StackAwareMethodVisitor) mv).drainStack(Opcodes.DSTORE, Opcodes.DLOAD, StackSize.DOUBLE));
+ break;
+ default:
+ mv.visitInsn(opcode);
+ return;
+ }
+ mv.visitJumpInsn(Opcodes.GOTO, endOfMethod);
+ doesReturn = true;
+ }
+
+ @Override
+ protected int adjust(int offset) {
+ return offset;
+ }
+
+ @Override
+ public void onDefaultValue(MethodVisitor methodVisitor) {
+ if (adviceMethod.getReturnType().represents(boolean.class)
+ || adviceMethod.getReturnType().represents(byte.class)
+ || adviceMethod.getReturnType().represents(short.class)
+ || adviceMethod.getReturnType().represents(char.class)
+ || adviceMethod.getReturnType().represents(int.class)) {
+ methodVisitor.visitInsn(Opcodes.ICONST_0);
+ } else if (adviceMethod.getReturnType().represents(long.class)) {
+ methodVisitor.visitInsn(Opcodes.LCONST_0);
+ } else if (adviceMethod.getReturnType().represents(float.class)) {
+ methodVisitor.visitInsn(Opcodes.FCONST_0);
+ } else if (adviceMethod.getReturnType().represents(double.class)) {
+ methodVisitor.visitInsn(Opcodes.DCONST_0);
+ } else if (!adviceMethod.getReturnType().represents(void.class)) {
+ methodVisitor.visitInsn(Opcodes.ACONST_NULL);
+ }
+ doesReturn = true;
+ }
+
+ @Override
+ protected void onMethodReturn() {
+ Type returnType = Type.getType(adviceMethod.getReturnType().asErasure().getDescriptor());
+ if (doesReturn && !returnType.equals(Type.VOID_TYPE)) {
+ stackMapFrameHandler.injectReturnFrame(methodVisitor);
+ methodVisitor.visitVarInsn(returnType.getOpcode(Opcodes.ISTORE), instrumentedMethod.getStackSize());
+ }
+ }
+ }
+
+ /**
+ * A code translation visitor that discards the return value of the represented advice method.
+ */
+ protected static class ForMethodExit extends CodeTranslationVisitor {
+
+ /**
+ * The padding after the instrumented method's arguments in the local variable array.
+ */
+ private final int padding;
+
+ /**
+ * Creates a code translation visitor for translating exit advice.
+ *
+ * @param methodVisitor A method visitor for writing the instrumented method's byte code.
+ * @param implementationContext The implementation context to use.
+ * @param methodSizeHandler A handler for computing the method size requirements.
+ * @param stackMapFrameHandler A handler for translating and injecting stack map frames.
+ * @param instrumentedMethod The instrumented method.
+ * @param adviceMethod The advice method.
+ * @param offsetMappings A mapping of offsets to resolved target offsets in the instrumented method.
+ * @param suppressionHandler The suppression handler to use.
+ * @param padding The padding after the instrumented method's arguments in the local variable array.
+ */
+ protected ForMethodExit(MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ MethodSizeHandler.ForAdvice methodSizeHandler,
+ StackMapFrameHandler.ForAdvice stackMapFrameHandler,
+ MethodDescription instrumentedMethod,
+ MethodDescription.InDefinedShape adviceMethod,
+ Map<Integer, OffsetMapping.Target> offsetMappings,
+ SuppressionHandler.Bound suppressionHandler,
+ int padding) {
+ super(methodVisitor,
+ implementationContext,
+ methodSizeHandler,
+ stackMapFrameHandler,
+ instrumentedMethod,
+ adviceMethod,
+ offsetMappings,
+ suppressionHandler);
+ this.padding = padding;
+ }
+
+ @Override
+ public void visitInsn(int opcode) {
+ switch (opcode) {
+ case Opcodes.RETURN:
+ break;
+ case Opcodes.IRETURN:
+ case Opcodes.ARETURN:
+ case Opcodes.FRETURN:
+ mv.visitInsn(Opcodes.POP);
+ break;
+ case Opcodes.LRETURN:
+ case Opcodes.DRETURN:
+ mv.visitInsn(Opcodes.POP2);
+ break;
+ default:
+ mv.visitInsn(opcode);
+ return;
+ }
+ ((StackAwareMethodVisitor) mv).drainStack();
+ mv.visitJumpInsn(Opcodes.GOTO, endOfMethod);
+ }
+
+ @Override
+ protected int adjust(int offset) {
+ return instrumentedMethod.getReturnType().getStackSize().getSize() + padding + offset;
+ }
+
+ @Override
+ public void onDefaultValue(MethodVisitor methodVisitor) {
+ /* do nothing */
+ }
+
+ @Override
+ protected void onMethodReturn() {
+ /* do nothing */
+ }
+ }
+ }
+ }
+
+ /**
+ * A dispatcher for an advice method that is being invoked from the instrumented method.
+ */
+ @EqualsAndHashCode
+ class Delegating implements Unresolved {
+
+ /**
+ * The advice method.
+ */
+ protected final MethodDescription.InDefinedShape adviceMethod;
+
+ /**
+ * Creates a new delegating advice dispatcher.
+ *
+ * @param adviceMethod The advice method.
+ */
+ protected Delegating(MethodDescription.InDefinedShape adviceMethod) {
+ this.adviceMethod = adviceMethod;
+ }
+
+ @Override
+ public boolean isAlive() {
+ return true;
+ }
+
+ @Override
+ public boolean isBinary() {
+ return false;
+ }
+
+ @Override
+ public Dispatcher.Resolved.ForMethodEnter asMethodEnter(List<? extends OffsetMapping.Factory<?>> userFactories,
+ ClassReader classReader) {
+ return new Resolved.ForMethodEnter(adviceMethod, userFactories);
+ }
+
+ @Override
+ public Dispatcher.Resolved.ForMethodExit asMethodExitTo(List<? extends OffsetMapping.Factory<?>> userFactories,
+ ClassReader classReader,
+ Dispatcher.Resolved.ForMethodEnter dispatcher) {
+ return Resolved.ForMethodExit.of(adviceMethod, userFactories, dispatcher.getEnterType());
+ }
+
+ /**
+ * A resolved version of a dispatcher.
+ *
+ * @param <T> The type of advice dispatcher that is bound.
+ */
+ protected abstract static class Resolved<T extends Bound> implements Dispatcher.Resolved {
+
+ /**
+ * The represented advice method.
+ */
+ protected final MethodDescription.InDefinedShape adviceMethod;
+
+ /**
+ * An unresolved mapping of offsets of the advice method based on the annotations discovered on each method parameter.
+ */
+ protected final List<OffsetMapping> offsetMappings;
+
+ /**
+ * The suppression handler to use.
+ */
+ protected final SuppressionHandler suppressionHandler;
+
+ /**
+ * Creates a new resolved version of a dispatcher.
+ *
+ * @param adviceMethod The represented advice method.
+ * @param factories A list of factories to resolve for the parameters of the advice method.
+ * @param throwableType The type to handle by a suppression handler or {@link NoExceptionHandler} to not handle any exceptions.
+ */
+ protected Resolved(MethodDescription.InDefinedShape adviceMethod,
+ List<? extends OffsetMapping.Factory<?>> factories,
+ TypeDescription throwableType) {
+ this.adviceMethod = adviceMethod;
+ Map<TypeDescription, OffsetMapping.Factory<?>> offsetMappings = new HashMap<TypeDescription, OffsetMapping.Factory<?>>();
+ for (OffsetMapping.Factory<?> factory : factories) {
+ offsetMappings.put(new TypeDescription.ForLoadedType(factory.getAnnotationType()), factory);
+ }
+ this.offsetMappings = new ArrayList<OffsetMapping>();
+ for (ParameterDescription.InDefinedShape parameterDescription : adviceMethod.getParameters()) {
+ OffsetMapping offsetMapping = null;
+ for (AnnotationDescription annotationDescription : parameterDescription.getDeclaredAnnotations()) {
+ OffsetMapping.Factory<?> factory = offsetMappings.get(annotationDescription.getAnnotationType());
+ if (factory != null) {
+ @SuppressWarnings("unchecked")
+ OffsetMapping current = factory.make(parameterDescription,
+ (AnnotationDescription.Loadable) annotationDescription.prepare(factory.getAnnotationType()),
+ OffsetMapping.Factory.AdviceType.DELEGATION);
+ if (offsetMapping == null) {
+ offsetMapping = current;
+ } else {
+ throw new IllegalStateException(parameterDescription + " is bound to both " + current + " and " + offsetMapping);
+ }
+ }
+ }
+ this.offsetMappings.add(offsetMapping == null
+ ? new OffsetMapping.ForArgument.Unresolved(parameterDescription)
+ : offsetMapping);
+ }
+ suppressionHandler = SuppressionHandler.Suppressing.of(throwableType);
+ }
+
+ @Override
+ public boolean isAlive() {
+ return true;
+ }
+
+ @Override
+ public T bind(TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ Assigner assigner,
+ MethodSizeHandler.ForInstrumentedMethod methodSizeHandler,
+ StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler,
+ StackManipulation exceptionHandler) {
+ if (!adviceMethod.isVisibleTo(instrumentedType)) {
+ throw new IllegalStateException(adviceMethod + " is not visible to " + instrumentedMethod.getDeclaringType());
+ }
+ return resolve(instrumentedType,
+ instrumentedMethod,
+ methodVisitor,
+ implementationContext,
+ assigner,
+ methodSizeHandler,
+ stackMapFrameHandler,
+ exceptionHandler);
+ }
+
+ /**
+ * Binds this dispatcher for resolution to a specific method.
+ *
+ * @param instrumentedType A description of the instrumented type.
+ * @param instrumentedMethod The instrumented method that is being bound.
+ * @param methodVisitor The method visitor for writing to the instrumented method.
+ * @param implementationContext The implementation context to use.
+ * @param assigner The assigner to use.
+ * @param methodSizeHandler A handler for computing the method size requirements.
+ * @param stackMapFrameHandler A handler for translating and injecting stack map frames.
+ * @param exceptionHandler The stack manipulation to apply within a suppression handler.
+ * @return An appropriate bound advice dispatcher.
+ */
+ protected abstract T resolve(TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ Assigner assigner,
+ MethodSizeHandler.ForInstrumentedMethod methodSizeHandler,
+ StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler,
+ StackManipulation exceptionHandler);
+
+ @Override // HE: Remove after Lombok resolves ambiguous type names correctly.
+ public boolean equals(Object object) {
+ if (this == object) return true;
+ if (object == null || getClass() != object.getClass()) return false;
+ Delegating.Resolved<?> resolved = (Delegating.Resolved<?>) object;
+ return adviceMethod.equals(resolved.adviceMethod)
+ && offsetMappings.equals(resolved.offsetMappings)
+ && suppressionHandler.equals(resolved.suppressionHandler);
+ }
+
+ @Override // HE: Remove after Lombok resolves ambiguous type names correctly.
+ public int hashCode() {
+ int result = adviceMethod.hashCode();
+ result = 31 * result + offsetMappings.hashCode();
+ result = 31 * result + suppressionHandler.hashCode();
+ return result;
+ }
+
+ /**
+ * A bound advice method that copies the code by first extracting the exception table and later appending the
+ * code of the method without copying any meta data.
+ */
+ protected abstract static class AdviceMethodWriter implements Bound, SuppressionHandler.ReturnValueProducer {
+
+ /**
+ * Indicates an empty local variable array which is not required for calling a method.
+ */
+ private static final int EMPTY = 0;
+
+ /**
+ * The advice method.
+ */
+ protected final MethodDescription.InDefinedShape adviceMethod;
+
+ /**
+ * The instrumented method.
+ */
+ protected final MethodDescription instrumentedMethod;
+
+ /**
+ * The offset mappings available to this advice.
+ */
+ private final List<OffsetMapping.Target> offsetMappings;
+
+ /**
+ * The method visitor for writing the instrumented method.
+ */
+ protected final MethodVisitor methodVisitor;
+
+ /**
+ * The implementation context to use.
+ */
+ protected final Implementation.Context implementationContext;
+
+ /**
+ * A handler for computing the method size requirements.
+ */
+ protected final MethodSizeHandler.ForAdvice methodSizeHandler;
+
+ /**
+ * A handler for translating and injecting stack map frmes.
+ */
+ protected final StackMapFrameHandler.ForAdvice stackMapFrameHandler;
+
+ /**
+ * A bound suppression handler that is used for suppressing exceptions of this advice method.
+ */
+ private final SuppressionHandler.Bound suppressionHandler;
+
+ /**
+ * Creates a new advice method writer.
+ *
+ * @param adviceMethod The advice method.
+ * @param instrumentedMethod The instrumented method.
+ * @param offsetMappings The offset mappings available to this advice.
+ * @param methodVisitor The method visitor for writing the instrumented method.
+ * @param implementationContext The implementation context to use.
+ * @param methodSizeHandler A handler for computing the method size requirements.
+ * @param stackMapFrameHandler A handler for translating and injecting stack map frames.
+ * @param suppressionHandler A bound suppression handler that is used for suppressing exceptions of this advice method.
+ */
+ protected AdviceMethodWriter(MethodDescription.InDefinedShape adviceMethod,
+ MethodDescription instrumentedMethod,
+ List<OffsetMapping.Target> offsetMappings,
+ MethodVisitor methodVisitor,
+ Context implementationContext,
+ MethodSizeHandler.ForAdvice methodSizeHandler,
+ StackMapFrameHandler.ForAdvice stackMapFrameHandler,
+ SuppressionHandler.Bound suppressionHandler) {
+ this.adviceMethod = adviceMethod;
+ this.instrumentedMethod = instrumentedMethod;
+ this.offsetMappings = offsetMappings;
+ this.methodVisitor = methodVisitor;
+ this.implementationContext = implementationContext;
+ this.methodSizeHandler = methodSizeHandler;
+ this.stackMapFrameHandler = stackMapFrameHandler;
+ this.suppressionHandler = suppressionHandler;
+ }
+
+ @Override
+ public void prepare() {
+ suppressionHandler.onPrepare(methodVisitor);
+ }
+
+ /**
+ * Writes the advice method invocation.
+ */
+ protected void doApply() {
+ suppressionHandler.onStart(methodVisitor);
+ int index = 0, currentStackSize = 0, maximumStackSize = 0;
+ for (OffsetMapping.Target offsetMapping : offsetMappings) {
+ currentStackSize += adviceMethod.getParameters().get(index++).getType().getStackSize().getSize();
+ maximumStackSize = Math.max(maximumStackSize, currentStackSize + offsetMapping.resolveRead()
+ .apply(methodVisitor, implementationContext)
+ .getMaximalSize());
+ }
+ methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
+ adviceMethod.getDeclaringType().getInternalName(),
+ adviceMethod.getInternalName(),
+ adviceMethod.getDescriptor(),
+ false);
+ onMethodReturn();
+ suppressionHandler.onEndSkipped(methodVisitor, implementationContext, methodSizeHandler, stackMapFrameHandler, this);
+ stackMapFrameHandler.injectCompletionFrame(methodVisitor, false);
+ methodSizeHandler.recordMaxima(Math.max(maximumStackSize, adviceMethod.getReturnType().getStackSize().getSize()), EMPTY);
+ }
+
+ /**
+ * Invoked directly after the advice method was called.
+ */
+ protected abstract void onMethodReturn();
+
+ /**
+ * An advice method writer for a method entry.
+ */
+ protected static class ForMethodEnter extends AdviceMethodWriter implements Bound.ForMethodEnter {
+
+ /**
+ * The skip dispatcher to use.
+ */
+ private final Resolved.ForMethodEnter.SkipDispatcher skipDispatcher;
+
+ /**
+ * Creates a new advice method writer.
+ *
+ * @param adviceMethod The advice method.
+ * @param instrumentedMethod The instrumented method.
+ * @param offsetMappings The offset mappings available to this advice.
+ * @param methodVisitor The method visitor for writing the instrumented method.
+ * @param implementationContext The implementation context to use.
+ * @param methodSizeHandler A handler for computing the method size requirements.
+ * @param stackMapFrameHandler A handler for translating and injecting stack map frames.
+ * @param suppressionHandler A bound suppression handler that is used for suppressing exceptions of this advice method.
+ * @param skipDispatcher The skip dispatcher to use.
+ */
+ protected ForMethodEnter(MethodDescription.InDefinedShape adviceMethod,
+ MethodDescription instrumentedMethod,
+ List<OffsetMapping.Target> offsetMappings,
+ MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ MethodSizeHandler.ForAdvice methodSizeHandler,
+ StackMapFrameHandler.ForAdvice stackMapFrameHandler,
+ SuppressionHandler.Bound suppressionHandler,
+ Resolved.ForMethodEnter.SkipDispatcher skipDispatcher) {
+ super(adviceMethod,
+ instrumentedMethod,
+ offsetMappings,
+ methodVisitor,
+ implementationContext,
+ methodSizeHandler,
+ stackMapFrameHandler,
+ suppressionHandler);
+ this.skipDispatcher = skipDispatcher;
+ }
+
+ @Override
+ protected void onMethodReturn() {
+ if (adviceMethod.getReturnType().represents(boolean.class)
+ || adviceMethod.getReturnType().represents(byte.class)
+ || adviceMethod.getReturnType().represents(short.class)
+ || adviceMethod.getReturnType().represents(char.class)
+ || adviceMethod.getReturnType().represents(int.class)) {
+ methodVisitor.visitVarInsn(Opcodes.ISTORE, instrumentedMethod.getStackSize());
+ } else if (adviceMethod.getReturnType().represents(long.class)) {
+ methodVisitor.visitVarInsn(Opcodes.LSTORE, instrumentedMethod.getStackSize());
+ } else if (adviceMethod.getReturnType().represents(float.class)) {
+ methodVisitor.visitVarInsn(Opcodes.FSTORE, instrumentedMethod.getStackSize());
+ } else if (adviceMethod.getReturnType().represents(double.class)) {
+ methodVisitor.visitVarInsn(Opcodes.DSTORE, instrumentedMethod.getStackSize());
+ } else if (!adviceMethod.getReturnType().represents(void.class)) {
+ methodVisitor.visitVarInsn(Opcodes.ASTORE, instrumentedMethod.getStackSize());
+ }
+ }
+
+ @Override
+ public void apply(SkipHandler skipHandler) {
+ doApply();
+ skipDispatcher.apply(methodVisitor, methodSizeHandler, stackMapFrameHandler, instrumentedMethod, skipHandler);
+ }
+
+ @Override
+ public void onDefaultValue(MethodVisitor methodVisitor) {
+ if (adviceMethod.getReturnType().represents(boolean.class)
+ || adviceMethod.getReturnType().represents(byte.class)
+ || adviceMethod.getReturnType().represents(short.class)
+ || adviceMethod.getReturnType().represents(char.class)
+ || adviceMethod.getReturnType().represents(int.class)) {
+ methodVisitor.visitInsn(Opcodes.ICONST_0);
+ methodVisitor.visitVarInsn(Opcodes.ISTORE, instrumentedMethod.getStackSize());
+ } else if (adviceMethod.getReturnType().represents(long.class)) {
+ methodVisitor.visitInsn(Opcodes.LCONST_0);
+ methodVisitor.visitVarInsn(Opcodes.LSTORE, instrumentedMethod.getStackSize());
+ } else if (adviceMethod.getReturnType().represents(float.class)) {
+ methodVisitor.visitInsn(Opcodes.FCONST_0);
+ methodVisitor.visitVarInsn(Opcodes.FSTORE, instrumentedMethod.getStackSize());
+ } else if (adviceMethod.getReturnType().represents(double.class)) {
+ methodVisitor.visitInsn(Opcodes.DCONST_0);
+ methodVisitor.visitVarInsn(Opcodes.DSTORE, instrumentedMethod.getStackSize());
+ } else if (!adviceMethod.getReturnType().represents(void.class)) {
+ methodVisitor.visitInsn(Opcodes.ACONST_NULL);
+ methodVisitor.visitVarInsn(Opcodes.ASTORE, instrumentedMethod.getStackSize());
+ }
+ }
+ }
+
+ /**
+ * An advice method writer for a method exit.
+ */
+ protected static class ForMethodExit extends AdviceMethodWriter implements Bound.ForMethodExit {
+
+ /**
+ * Creates a new advice method writer.
+ *
+ * @param adviceMethod The advice method.
+ * @param instrumentedMethod The instrumented method.
+ * @param offsetMappings The offset mappings available to this advice.
+ * @param methodVisitor The method visitor for writing the instrumented method.
+ * @param implementationContext The implementation context to use.
+ * @param methodSizeHandler A handler for computing the method size requirements.
+ * @param stackMapFrameHandler A handler for translating and injecting stack map frames.
+ * @param suppressionHandler A bound suppression handler that is used for suppressing exceptions of this advice method.
+ */
+ protected ForMethodExit(MethodDescription.InDefinedShape adviceMethod,
+ MethodDescription instrumentedMethod,
+ List<OffsetMapping.Target> offsetMappings,
+ MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ MethodSizeHandler.ForAdvice methodSizeHandler,
+ StackMapFrameHandler.ForAdvice stackMapFrameHandler,
+ SuppressionHandler.Bound suppressionHandler) {
+ super(adviceMethod,
+ instrumentedMethod,
+ offsetMappings,
+ methodVisitor,
+ implementationContext,
+ methodSizeHandler,
+ stackMapFrameHandler,
+ suppressionHandler);
+ }
+
+ @Override
+ public void apply() {
+ doApply();
+ }
+
+ @Override
+ protected void onMethodReturn() {
+ switch (adviceMethod.getReturnType().getStackSize()) {
+ case ZERO:
+ return;
+ case SINGLE:
+ methodVisitor.visitInsn(Opcodes.POP);
+ return;
+ case DOUBLE:
+ methodVisitor.visitInsn(Opcodes.POP2);
+ return;
+ default:
+ throw new IllegalStateException("Unexpected size: " + adviceMethod.getReturnType().getStackSize());
+ }
+ }
+
+ @Override
+ public void onDefaultValue(MethodVisitor methodVisitor) {
+ /* do nothing */
+ }
+ }
+ }
+
+ /**
+ * A resolved dispatcher for implementing method enter advice.
+ */
+ protected static class ForMethodEnter extends Delegating.Resolved<Bound.ForMethodEnter> implements Dispatcher.Resolved.ForMethodEnter {
+
+ /**
+ * The skip dispatcher to use.
+ */
+ private final SkipDispatcher skipDispatcher;
+
+ /**
+ * {@code true} if the first discovered line number information should be prepended to the advice code.
+ */
+ private final boolean prependLineNumber;
+
+ /**
+ * Creates a new resolved dispatcher for implementing method enter advice.
+ *
+ * @param adviceMethod The represented advice method.
+ * @param userFactories A list of user-defined factories for offset mappings.
+ */
+ @SuppressWarnings("unchecked") // In absence of @SafeVarargs for Java 6
+ protected ForMethodEnter(MethodDescription.InDefinedShape adviceMethod, List<? extends OffsetMapping.Factory<?>> userFactories) {
+ super(adviceMethod,
+ CompoundList.of(Arrays.asList(OffsetMapping.ForArgument.Unresolved.Factory.INSTANCE,
+ OffsetMapping.ForAllArguments.Factory.INSTANCE,
+ OffsetMapping.ForThisReference.Factory.INSTANCE,
+ OffsetMapping.ForField.Unresolved.Factory.INSTANCE,
+ OffsetMapping.ForOrigin.Factory.INSTANCE,
+ OffsetMapping.ForUnusedValue.Factory.INSTANCE,
+ OffsetMapping.ForStubValue.INSTANCE,
+ new OffsetMapping.Factory.Illegal<Thrown>(Thrown.class),
+ new OffsetMapping.Factory.Illegal<Enter>(Enter.class),
+ new OffsetMapping.Factory.Illegal<Return>(Return.class)), userFactories),
+ adviceMethod.getDeclaredAnnotations().ofType(OnMethodEnter.class).getValue(SUPPRESS_ENTER).resolve(TypeDescription.class));
+ skipDispatcher = SkipDispatcher.ForType.of(adviceMethod);
+ prependLineNumber = adviceMethod.getDeclaredAnnotations().ofType(OnMethodEnter.class).getValue(PREPEND_LINE_NUMBER).resolve(Boolean.class);
+ }
+
+ @Override
+ public TypeDefinition getEnterType() {
+ return adviceMethod.getReturnType();
+ }
+
+ @Override
+ public boolean isPrependLineNumber() {
+ return prependLineNumber;
+ }
+
+ @Override
+ protected Bound.ForMethodEnter resolve(TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ Assigner assigner,
+ MethodSizeHandler.ForInstrumentedMethod methodSizeHandler,
+ StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler,
+ StackManipulation exceptionHandler) {
+ List<OffsetMapping.Target> offsetMappings = new ArrayList<OffsetMapping.Target>(this.offsetMappings.size());
+ for (OffsetMapping offsetMapping : this.offsetMappings) {
+ offsetMappings.add(offsetMapping.resolve(instrumentedType,
+ instrumentedMethod,
+ assigner,
+ OffsetMapping.Context.ForMethodEntry.of(instrumentedMethod)));
+ }
+ return new AdviceMethodWriter.ForMethodEnter(adviceMethod,
+ instrumentedMethod,
+ offsetMappings,
+ methodVisitor,
+ implementationContext,
+ methodSizeHandler.bindEntry(adviceMethod),
+ stackMapFrameHandler.bindEntry(adviceMethod),
+ suppressionHandler.bind(exceptionHandler),
+ skipDispatcher);
+ }
+
+ @Override // HE: Remove after Lombok resolves ambiguous type names correctly.
+ public boolean equals(Object object) {
+ if (this == object) return true;
+ if (object == null || getClass() != object.getClass()) return false;
+ if (!super.equals(object)) return false;
+ Delegating.Resolved.ForMethodEnter that = (Delegating.Resolved.ForMethodEnter) object;
+ return prependLineNumber == that.prependLineNumber && skipDispatcher.equals(that.skipDispatcher);
+ }
+
+ @Override // HE: Remove after Lombok resolves ambiguous type names correctly.
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + skipDispatcher.hashCode();
+ result = 31 * result + (prependLineNumber ? 1 : 0);
+ return result;
+ }
+ }
+
+ /**
+ * A resolved dispatcher for implementing method exit advice.
+ */
+ protected abstract static class ForMethodExit extends Delegating.Resolved<Bound.ForMethodExit> implements Dispatcher.Resolved.ForMethodExit {
+
+ /**
+ * The additional stack size to consider when accessing the local variable array.
+ */
+ private final TypeDefinition enterType;
+
+ /**
+ * Creates a new resolved dispatcher for implementing method exit advice.
+ *
+ * @param adviceMethod The represented advice method.
+ * @param userFactories A list of user-defined factories for offset mappings.
+ * @param enterType The type of the value supplied by the enter advice method or
+ * a description of {@code void} if no such value exists.
+ */
+ @SuppressWarnings("unchecked")
+ protected ForMethodExit(MethodDescription.InDefinedShape adviceMethod,
+ List<? extends OffsetMapping.Factory<?>> userFactories,
+ TypeDefinition enterType) {
+ super(adviceMethod,
+ CompoundList.of(Arrays.asList(OffsetMapping.ForArgument.Unresolved.Factory.INSTANCE,
+ OffsetMapping.ForAllArguments.Factory.INSTANCE,
+ OffsetMapping.ForThisReference.Factory.INSTANCE,
+ OffsetMapping.ForField.Unresolved.Factory.INSTANCE,
+ OffsetMapping.ForOrigin.Factory.INSTANCE,
+ OffsetMapping.ForUnusedValue.Factory.INSTANCE,
+ OffsetMapping.ForStubValue.INSTANCE,
+ new OffsetMapping.ForEnterValue.Factory(enterType),
+ OffsetMapping.ForReturnValue.Factory.INSTANCE,
+ OffsetMapping.ForThrowable.Factory.of(adviceMethod)
+ ), userFactories),
+ adviceMethod.getDeclaredAnnotations().ofType(OnMethodExit.class).getValue(SUPPRESS_EXIT).resolve(TypeDescription.class));
+ this.enterType = enterType;
+ }
+
+ /**
+ * Resolves exit advice that handles exceptions depending on the specification of the exit advice.
+ *
+ * @param adviceMethod The advice method.
+ * @param userFactories A list of user-defined factories for offset mappings.
+ * @param enterType The type of the value supplied by the enter advice method or
+ * a description of {@code void} if no such value exists.
+ * @return An appropriate exit handler.
+ */
+ protected static Resolved.ForMethodExit of(MethodDescription.InDefinedShape adviceMethod,
+ List<? extends OffsetMapping.Factory<?>> userFactories,
+ TypeDefinition enterType) {
+ TypeDescription throwable = adviceMethod.getDeclaredAnnotations()
+ .ofType(OnMethodExit.class)
+ .getValue(ON_THROWABLE).resolve(TypeDescription.class);
+ return throwable.represents(NoExceptionHandler.class)
+ ? new WithoutExceptionHandler(adviceMethod, userFactories, enterType)
+ : new WithExceptionHandler(adviceMethod, userFactories, enterType, throwable);
+ }
+
+ @Override
+ protected Bound.ForMethodExit resolve(TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ Assigner assigner,
+ MethodSizeHandler.ForInstrumentedMethod methodSizeHandler,
+ StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler,
+ StackManipulation exceptionHandler) {
+ List<OffsetMapping.Target> offsetMappings = new ArrayList<OffsetMapping.Target>(this.offsetMappings.size());
+ for (OffsetMapping offsetMapping : this.offsetMappings) {
+ offsetMappings.add(offsetMapping.resolve(instrumentedType,
+ instrumentedMethod,
+ assigner,
+ OffsetMapping.Context.ForMethodExit.of(enterType)));
+ }
+ return new AdviceMethodWriter.ForMethodExit(adviceMethod,
+ instrumentedMethod,
+ offsetMappings,
+ methodVisitor,
+ implementationContext,
+ methodSizeHandler.bindExit(adviceMethod, getThrowable().represents(NoExceptionHandler.class)),
+ stackMapFrameHandler.bindExit(adviceMethod),
+ suppressionHandler.bind(exceptionHandler));
+ }
+
+ @Override // HE: Remove after Lombok resolves ambiguous type names correctly.
+ public boolean equals(Object object) {
+ if (this == object) return true;
+ if (object == null || getClass() != object.getClass()) return false;
+ if (!super.equals(object)) return false;
+ Delegating.Resolved.ForMethodExit that = (Delegating.Resolved.ForMethodExit) object;
+ return enterType.equals(that.enterType);
+ }
+
+ @Override // HE: Remove after Lombok resolves ambiguous type names correctly.
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + enterType.hashCode();
+ return result;
+ }
+
+ /**
+ * Implementation of exit advice that handles exceptions.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ protected static class WithExceptionHandler extends Delegating.Resolved.ForMethodExit {
+
+ /**
+ * The type of the handled throwable type for which this advice is invoked.
+ */
+ private final TypeDescription throwable;
+
+ /**
+ * Creates a new resolved dispatcher for implementing method exit advice that handles exceptions.
+ *
+ * @param adviceMethod The represented advice method.
+ * @param userFactories A list of user-defined factories for offset mappings.
+ * @param enterType The type of the value supplied by the enter advice method or
+ * a description of {@code void} if no such value exists.
+ * @param throwable The type of the handled throwable type for which this advice is invoked.
+ */
+ protected WithExceptionHandler(MethodDescription.InDefinedShape adviceMethod,
+ List<? extends OffsetMapping.Factory<?>> userFactories,
+ TypeDefinition enterType,
+ TypeDescription throwable) {
+ super(adviceMethod, userFactories, enterType);
+ this.throwable = throwable;
+ }
+
+ @Override
+ public TypeDescription getThrowable() {
+ return throwable;
+ }
+ }
+
+ /**
+ * Implementation of exit advice that ignores exceptions.
+ */
+ protected static class WithoutExceptionHandler extends Delegating.Resolved.ForMethodExit {
+
+ /**
+ * Creates a new resolved dispatcher for implementing method exit advice that does not handle exceptions.
+ *
+ * @param adviceMethod The represented advice method.
+ * @param userFactories A list of user-defined factories for offset mappings.
+ * @param enterType The type of the value supplied by the enter advice method or
+ * a description of {@code void} if no such value exists.
+ */
+ protected WithoutExceptionHandler(MethodDescription.InDefinedShape adviceMethod,
+ List<? extends OffsetMapping.Factory<?>> userFactories,
+ TypeDefinition enterType) {
+ super(adviceMethod, userFactories, enterType);
+ }
+
+ @Override
+ public TypeDescription getThrowable() {
+ return NoExceptionHandler.DESCRIPTION;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * A method visitor that weaves the advice methods' byte codes.
+ */
+ protected abstract static class AdviceVisitor extends ExceptionTableSensitiveMethodVisitor implements Dispatcher.Bound.SkipHandler {
+
+ /**
+ * Indicates a zero offset.
+ */
+ private static final int NO_OFFSET = 0;
+
+ /**
+ * The actual method visitor that is underlying this method visitor to which all instructions are written.
+ */
+ protected final MethodVisitor methodVisitor;
+
+ /**
+ * A description of the instrumented method.
+ */
+ protected final MethodDescription instrumentedMethod;
+
+ /**
+ * The required padding before using local variables after the instrumented method's arguments.
+ */
+ private final int padding;
+
+ /**
+ * The dispatcher to be used for method entry.
+ */
+ private final Dispatcher.Bound.ForMethodEnter methodEnter;
+
+ /**
+ * The dispatcher to be used for method exit.
+ */
+ protected final Dispatcher.Bound.ForMethodExit methodExit;
+
+ /**
+ * A handler for computing the method size requirements.
+ */
+ protected final MethodSizeHandler.ForInstrumentedMethod methodSizeHandler;
+
+ /**
+ * A handler for translating and injecting stack map frames.
+ */
+ protected final StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler;
+
+ /**
+ * Creates a new advice visitor.
+ *
+ * @param methodVisitor The actual method visitor that is underlying this method visitor to which all instructions are written.
+ * @param delegate A delegate to which all instructions of the original method are written to. Must delegate to {@code methodVisitor}.
+ * @param implementationContext The implementation context to use.
+ * @param assigner The assigner to use.
+ * @param exceptionHandler The stack manipulation to apply within a suppression handler.
+ * @param instrumentedType A description of the instrumented type.
+ * @param instrumentedMethod The instrumented method.
+ * @param methodEnter The method enter advice.
+ * @param methodExit The method exit advice.
+ * @param yieldedTypes The types that are expected to be added after the instrumented method returns.
+ * @param writerFlags The ASM writer flags that were set.
+ * @param readerFlags The ASM reader flags that were set.
+ */
+ protected AdviceVisitor(MethodVisitor methodVisitor,
+ MethodVisitor delegate,
+ Context implementationContext,
+ Assigner assigner,
+ StackManipulation exceptionHandler,
+ TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ Dispatcher.Resolved.ForMethodEnter methodEnter,
+ Dispatcher.Resolved.ForMethodExit methodExit,
+ List<? extends TypeDescription> yieldedTypes,
+ int writerFlags,
+ int readerFlags) {
+ super(Opcodes.ASM5, delegate);
+ this.methodVisitor = methodVisitor;
+ this.instrumentedMethod = instrumentedMethod;
+ padding = methodEnter.getEnterType().getStackSize().getSize();
+ List<TypeDescription> requiredTypes = methodEnter.getEnterType().represents(void.class)
+ ? Collections.<TypeDescription>emptyList()
+ : Collections.singletonList(methodEnter.getEnterType().asErasure());
+ methodSizeHandler = MethodSizeHandler.Default.of(instrumentedMethod, requiredTypes, yieldedTypes, writerFlags);
+ stackMapFrameHandler = StackMapFrameHandler.Default.of(instrumentedType,
+ instrumentedMethod,
+ requiredTypes,
+ yieldedTypes,
+ implementationContext.getClassFileVersion(),
+ writerFlags,
+ readerFlags);
+ this.methodEnter = methodEnter.bind(instrumentedType,
+ instrumentedMethod,
+ methodVisitor,
+ implementationContext,
+ assigner,
+ methodSizeHandler,
+ stackMapFrameHandler,
+ exceptionHandler);
+ this.methodExit = methodExit.bind(instrumentedType,
+ instrumentedMethod,
+ methodVisitor,
+ implementationContext,
+ assigner,
+ methodSizeHandler,
+ stackMapFrameHandler,
+ exceptionHandler);
+ }
+
+ @Override
+ protected void onAfterExceptionTable() {
+ methodEnter.prepare();
+ onUserPrepare();
+ methodExit.prepare();
+ methodEnter.apply(this);
+ onUserStart();
+ }
+
+ /**
+ * Invoked when the user method's exception handler (if any) is supposed to be prepared.
+ */
+ protected abstract void onUserPrepare();
+
+ /**
+ * Writes the advice for entering the instrumented method.
+ */
+ protected abstract void onUserStart();
+
+ @Override
+ protected void onVisitVarInsn(int opcode, int offset) {
+ mv.visitVarInsn(opcode, resolve(offset));
+ }
+
+ @Override
+ protected void onVisitIincInsn(int offset, int increment) {
+ mv.visitIincInsn(resolve(offset), increment);
+ }
+
+ /**
+ * Access the first variable after the instrumented variables and return type are stored.
+ *
+ * @param opcode The opcode for accessing the variable.
+ */
+ protected void variable(int opcode) {
+ variable(opcode, NO_OFFSET);
+ }
+
+ /**
+ * Access the first variable after the instrumented variables and return type are stored.
+ *
+ * @param opcode The opcode for accessing the variable.
+ * @param offset The additional offset of the variable.
+ */
+ protected void variable(int opcode, int offset) {
+ methodVisitor.visitVarInsn(opcode, instrumentedMethod.getStackSize() + padding + offset);
+ }
+
+ @Override
+ public void visitFrame(int type, int localVariableLength, Object[] localVariable, int stackSize, Object[] stack) {
+ stackMapFrameHandler.translateFrame(methodVisitor, type, localVariableLength, localVariable, stackSize, stack);
+ }
+
+ @Override
+ public void visitMaxs(int stackSize, int localVariableLength) {
+ onUserEnd();
+ methodVisitor.visitMaxs(methodSizeHandler.compoundStackSize(stackSize), methodSizeHandler.compoundLocalVariableLength(localVariableLength));
+ }
+
+ @Override
+ public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) {
+ mv.visitLocalVariable(name, descriptor, signature, start, end, resolve(index));
+ }
+
+ @Override
+ public AnnotationVisitor visitLocalVariableAnnotation(int typeReference,
+ TypePath typePath,
+ Label[] start,
+ Label[] end,
+ int[] index,
+ String descriptor,
+ boolean visible) {
+ return mv.visitLocalVariableAnnotation(typeReference, typePath, start, end, resolve(index), descriptor, visible);
+ }
+
+ /**
+ * Resolves the index of a local variable in the context of the instrumentation.
+ *
+ * @param index The indices to adjust.
+ * @return An array with adjusted indices.
+ */
+ private int[] resolve(int[] index) {
+ int[] resolved = new int[index.length];
+ for (int anIndex = 0; anIndex < index.length; anIndex++) {
+ resolved[anIndex] = resolve(index[anIndex]);
+ }
+ return resolved;
+ }
+
+ /**
+ * Resolves the index of a local variable in the context of the instrumentation.
+ *
+ * @param index The index to adjust.
+ * @return The adjusted index.
+ */
+ private int resolve(int index) {
+ return index < instrumentedMethod.getStackSize()
+ ? index
+ : padding + index;
+ }
+
+ /**
+ * Writes the advice for completing the instrumented method.
+ */
+ protected abstract void onUserEnd();
+
+ /**
+ * An advice visitor that does not apply exit advice.
+ */
+ protected static class WithoutExitAdvice extends AdviceVisitor {
+
+ /**
+ * Creates an advice visitor that does not apply exit advice.
+ *
+ * @param methodVisitor The method visitor for the instrumented method.
+ * @param implementationContext The implementation context to use.
+ * @param assigner The assigner to use.
+ * @param exceptionHandler The stack manipulation to apply within a suppression handler.
+ * @param instrumentedType A description of the instrumented type.
+ * @param instrumentedMethod A description of the instrumented method.
+ * @param methodEnter The dispatcher to be used for method entry.
+ * @param writerFlags The ASM writer flags that were set.
+ * @param readerFlags The ASM reader flags that were set.
+ */
+ protected WithoutExitAdvice(MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ Assigner assigner,
+ StackManipulation exceptionHandler,
+ TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ Dispatcher.Resolved.ForMethodEnter methodEnter,
+ int writerFlags,
+ int readerFlags) {
+ super(methodVisitor,
+ methodVisitor,
+ implementationContext,
+ assigner,
+ exceptionHandler,
+ instrumentedType,
+ instrumentedMethod,
+ methodEnter,
+ Dispatcher.Inactive.INSTANCE,
+ Collections.<TypeDescription>emptyList(),
+ writerFlags,
+ readerFlags);
+ }
+
+ @Override
+ protected void onUserPrepare() {
+ /* do nothing */
+ }
+
+ @Override
+ protected void onUserStart() {
+ /* do nothing */
+ }
+
+ @Override
+ protected void onUserEnd() {
+ /* do nothing */
+ }
+
+ @Override
+ public void apply(MethodVisitor methodVisitor) {
+ if (instrumentedMethod.getReturnType().represents(boolean.class)
+ || instrumentedMethod.getReturnType().represents(byte.class)
+ || instrumentedMethod.getReturnType().represents(short.class)
+ || instrumentedMethod.getReturnType().represents(char.class)
+ || instrumentedMethod.getReturnType().represents(int.class)) {
+ methodVisitor.visitInsn(Opcodes.ICONST_0);
+ methodVisitor.visitInsn(Opcodes.IRETURN);
+ } else if (instrumentedMethod.getReturnType().represents(long.class)) {
+ methodVisitor.visitInsn(Opcodes.LCONST_0);
+ methodVisitor.visitInsn(Opcodes.LRETURN);
+ } else if (instrumentedMethod.getReturnType().represents(float.class)) {
+ methodVisitor.visitInsn(Opcodes.FCONST_0);
+ methodVisitor.visitInsn(Opcodes.FRETURN);
+ } else if (instrumentedMethod.getReturnType().represents(double.class)) {
+ methodVisitor.visitInsn(Opcodes.DCONST_0);
+ methodVisitor.visitInsn(Opcodes.DRETURN);
+ } else if (instrumentedMethod.getReturnType().represents(void.class)) {
+ methodVisitor.visitInsn(Opcodes.RETURN);
+ } else {
+ methodVisitor.visitInsn(Opcodes.ACONST_NULL);
+ methodVisitor.visitInsn(Opcodes.ARETURN);
+ }
+ }
+ }
+
+ /**
+ * An advice visitor that applies exit advice.
+ */
+ protected abstract static class WithExitAdvice extends AdviceVisitor {
+
+ /**
+ * Indicates the handler for the value returned by the advice method.
+ */
+ protected final Label returnHandler;
+
+ /**
+ * {@code true} if the advice method ever returns non-exceptionally.
+ */
+ protected boolean doesReturn;
+
+ /**
+ * Creates an advice visitor that applies exit advice.
+ *
+ * @param methodVisitor The method visitor for the instrumented method.
+ * @param implementationContext The implementation context to use.
+ * @param assigner The assigner to use.
+ * @param exceptionHandler The stack manipulation to apply within a suppression handler.
+ * @param instrumentedType A description of the instrumented type.
+ * @param instrumentedMethod A description of the instrumented method.
+ * @param methodEnter The dispatcher to be used for method entry.
+ * @param methodExit The dispatcher to be used for method exit.
+ * @param yieldedTypes The types that are expected to be added after the instrumented method returns.
+ * @param writerFlags The ASM writer flags that were set.
+ * @param readerFlags The ASM reader flags that were set.
+ */
+ protected WithExitAdvice(MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ Assigner assigner,
+ StackManipulation exceptionHandler,
+ TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ Dispatcher.Resolved.ForMethodEnter methodEnter,
+ Dispatcher.Resolved.ForMethodExit methodExit,
+ List<? extends TypeDescription> yieldedTypes,
+ int writerFlags,
+ int readerFlags) {
+ super(methodVisitor,
+ new StackAwareMethodVisitor(methodVisitor, instrumentedMethod),
+ implementationContext,
+ assigner,
+ exceptionHandler,
+ instrumentedType,
+ instrumentedMethod,
+ methodEnter,
+ methodExit,
+ yieldedTypes,
+ writerFlags,
+ readerFlags);
+ returnHandler = new Label();
+ doesReturn = false;
+ }
+
+ @Override
+ public void apply(MethodVisitor methodVisitor) {
+ if (instrumentedMethod.getReturnType().represents(boolean.class)
+ || instrumentedMethod.getReturnType().represents(byte.class)
+ || instrumentedMethod.getReturnType().represents(short.class)
+ || instrumentedMethod.getReturnType().represents(char.class)
+ || instrumentedMethod.getReturnType().represents(int.class)) {
+ methodVisitor.visitInsn(Opcodes.ICONST_0);
+ } else if (instrumentedMethod.getReturnType().represents(long.class)) {
+ methodVisitor.visitInsn(Opcodes.LCONST_0);
+ } else if (instrumentedMethod.getReturnType().represents(float.class)) {
+ methodVisitor.visitInsn(Opcodes.FCONST_0);
+ } else if (instrumentedMethod.getReturnType().represents(double.class)) {
+ methodVisitor.visitInsn(Opcodes.DCONST_0);
+ } else if (!instrumentedMethod.getReturnType().represents(void.class)) {
+ methodVisitor.visitInsn(Opcodes.ACONST_NULL);
+ }
+ methodVisitor.visitJumpInsn(Opcodes.GOTO, returnHandler);
+ doesReturn = true;
+ }
+
+ @Override
+ protected void onVisitInsn(int opcode) {
+ switch (opcode) {
+ case Opcodes.RETURN:
+ ((StackAwareMethodVisitor) mv).drainStack();
+ break;
+ case Opcodes.IRETURN:
+ methodSizeHandler.requireLocalVariableLength(((StackAwareMethodVisitor) mv).drainStack(Opcodes.ISTORE, Opcodes.ILOAD, StackSize.SINGLE));
+ break;
+ case Opcodes.FRETURN:
+ methodSizeHandler.requireLocalVariableLength(((StackAwareMethodVisitor) mv).drainStack(Opcodes.FSTORE, Opcodes.FLOAD, StackSize.SINGLE));
+ break;
+ case Opcodes.DRETURN:
+ methodSizeHandler.requireLocalVariableLength(((StackAwareMethodVisitor) mv).drainStack(Opcodes.DSTORE, Opcodes.DLOAD, StackSize.DOUBLE));
+ break;
+ case Opcodes.LRETURN:
+ methodSizeHandler.requireLocalVariableLength((((StackAwareMethodVisitor) mv).drainStack(Opcodes.LSTORE, Opcodes.LLOAD, StackSize.DOUBLE)));
+ break;
+ case Opcodes.ARETURN:
+ methodSizeHandler.requireLocalVariableLength((((StackAwareMethodVisitor) mv).drainStack(Opcodes.ASTORE, Opcodes.ALOAD, StackSize.SINGLE)));
+ break;
+ default:
+ mv.visitInsn(opcode);
+ return;
+ }
+ mv.visitJumpInsn(Opcodes.GOTO, returnHandler);
+ doesReturn = true;
+ }
+
+ @Override
+ protected void onUserEnd() {
+ Type returnType = Type.getType(instrumentedMethod.getReturnType().asErasure().getDescriptor());
+ methodVisitor.visitLabel(returnHandler);
+ if (doesReturn) {
+ stackMapFrameHandler.injectReturnFrame(methodVisitor);
+ if (!returnType.equals(Type.VOID_TYPE)) {
+ variable(returnType.getOpcode(Opcodes.ISTORE));
+ }
+ }
+ onUserReturn();
+ methodExit.apply();
+ onExitAdviceReturn();
+ if (returnType.equals(Type.VOID_TYPE)) {
+ methodVisitor.visitInsn(Opcodes.RETURN);
+ } else {
+ variable(returnType.getOpcode(Opcodes.ILOAD));
+ methodVisitor.visitInsn(returnType.getOpcode(Opcodes.IRETURN));
+ }
+ }
+
+ /**
+ * Invoked after the user method has returned.
+ */
+ protected abstract void onUserReturn();
+
+ /**
+ * Invoked after the exit advice method has returned.
+ */
+ protected abstract void onExitAdviceReturn();
+
+ /**
+ * An advice visitor that does not capture exceptions.
+ */
+ protected static class WithoutExceptionHandling extends WithExitAdvice {
+
+ /**
+ * Creates a new advice visitor that does not capture exceptions.
+ *
+ * @param methodVisitor The method visitor for the instrumented method.
+ * @param implementationContext The implementation context to use.
+ * @param assigner The assigner to use.
+ * @param exceptionHandler The stack manipulation to apply within a suppression handler.
+ * @param instrumentedType A description of the instrumented type.
+ * @param instrumentedMethod A description of the instrumented method.
+ * @param methodEnter The dispatcher to be used for method entry.
+ * @param methodExit The dispatcher to be used for method exit.
+ * @param writerFlags The ASM writer flags that were set.
+ * @param readerFlags The ASM reader flags that were set.
+ */
+ protected WithoutExceptionHandling(MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ Assigner assigner,
+ StackManipulation exceptionHandler,
+ TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ Dispatcher.Resolved.ForMethodEnter methodEnter,
+ Dispatcher.Resolved.ForMethodExit methodExit,
+ int writerFlags,
+ int readerFlags) {
+ super(methodVisitor,
+ implementationContext,
+ assigner,
+ exceptionHandler,
+ instrumentedType,
+ instrumentedMethod,
+ methodEnter,
+ methodExit,
+ instrumentedMethod.getReturnType().represents(void.class)
+ ? Collections.<TypeDescription>emptyList()
+ : Collections.singletonList(instrumentedMethod.getReturnType().asErasure()),
+ writerFlags,
+ readerFlags);
+ }
+
+ @Override
+ protected void onUserPrepare() {
+ /* empty */
+ }
+
+ @Override
+ protected void onUserStart() {
+ /* empty */
+ }
+
+ @Override
+ protected void onUserReturn() {
+ if (!doesReturn || !instrumentedMethod.getReturnType().represents(void.class)) {
+ stackMapFrameHandler.injectCompletionFrame(methodVisitor, false);
+ }
+ }
+
+ @Override
+ protected void onExitAdviceReturn() {
+ /* empty */
+ }
+ }
+
+ /**
+ * An advice visitor that captures exceptions by weaving try-catch blocks around user code.
+ */
+ protected static class WithExceptionHandling extends WithExitAdvice {
+
+ /**
+ * The type of the handled throwable type for which this advice is invoked.
+ */
+ private final TypeDescription throwable;
+
+ /**
+ * Indicates the start of the user method.
+ */
+ private final Label userStart;
+
+ /**
+ * Indicates the exception handler.
+ */
+ private final Label exceptionHandler;
+
+ /**
+ * Creates a new advice visitor that captures exception by weaving try-catch blocks around user code.
+ *
+ * @param methodVisitor The method visitor for the instrumented method.
+ * @param instrumentedType A description of the instrumented type.
+ * @param implementationContext The implementation context to use.
+ * @param assigner The assigner to use.
+ * @param exceptionHandler The stack manipulation to apply within a suppression handler.
+ * @param instrumentedMethod A description of the instrumented method.
+ * @param methodEnter The dispatcher to be used for method entry.
+ * @param methodExit The dispatcher to be used for method exit.
+ * @param writerFlags The ASM writer flags that were set.
+ * @param readerFlags The ASM reader flags that were set.
+ * @param throwable The type of the handled throwable type for which this advice is invoked.
+ */
+ protected WithExceptionHandling(MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ Assigner assigner,
+ StackManipulation exceptionHandler,
+ TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ Dispatcher.Resolved.ForMethodEnter methodEnter,
+ Dispatcher.Resolved.ForMethodExit methodExit,
+ int writerFlags,
+ int readerFlags,
+ TypeDescription throwable) {
+ super(methodVisitor,
+ implementationContext,
+ assigner,
+ exceptionHandler,
+ instrumentedType,
+ instrumentedMethod,
+ methodEnter,
+ methodExit,
+ instrumentedMethod.getReturnType().represents(void.class)
+ ? Collections.singletonList(TypeDescription.THROWABLE)
+ : Arrays.asList(instrumentedMethod.getReturnType().asErasure(), TypeDescription.THROWABLE),
+ writerFlags,
+ readerFlags);
+ this.throwable = throwable;
+ userStart = new Label();
+ this.exceptionHandler = new Label();
+ }
+
+ @Override
+ protected void onUserPrepare() {
+ methodVisitor.visitTryCatchBlock(userStart, returnHandler, exceptionHandler, throwable.getInternalName());
+ }
+
+ @Override
+ protected void onUserStart() {
+ methodVisitor.visitLabel(userStart);
+ }
+
+ @Override
+ protected void onUserReturn() {
+ Label endOfHandler = new Label();
+ if (doesReturn) {
+ methodVisitor.visitInsn(Opcodes.ACONST_NULL);
+ variable(Opcodes.ASTORE, instrumentedMethod.getReturnType().getStackSize().getSize());
+ methodVisitor.visitJumpInsn(Opcodes.GOTO, endOfHandler);
+ }
+ methodVisitor.visitLabel(exceptionHandler);
+ stackMapFrameHandler.injectExceptionFrame(methodVisitor);
+ variable(Opcodes.ASTORE, instrumentedMethod.getReturnType().getStackSize().getSize());
+ storeDefaultReturn();
+ if (doesReturn) {
+ methodVisitor.visitLabel(endOfHandler);
+ }
+ stackMapFrameHandler.injectCompletionFrame(methodVisitor, false);
+ }
+
+ @Override
+ protected void onExitAdviceReturn() {
+ variable(Opcodes.ALOAD, instrumentedMethod.getReturnType().getStackSize().getSize());
+ Label endOfHandler = new Label();
+ methodVisitor.visitJumpInsn(Opcodes.IFNULL, endOfHandler);
+ variable(Opcodes.ALOAD, instrumentedMethod.getReturnType().getStackSize().getSize());
+ methodVisitor.visitInsn(Opcodes.ATHROW);
+ methodVisitor.visitLabel(endOfHandler);
+ stackMapFrameHandler.injectCompletionFrame(methodVisitor, true);
+ }
+
+ /**
+ * Stores a default return value in the designated slot of the local variable array.
+ */
+ private void storeDefaultReturn() {
+ if (instrumentedMethod.getReturnType().represents(boolean.class)
+ || instrumentedMethod.getReturnType().represents(byte.class)
+ || instrumentedMethod.getReturnType().represents(short.class)
+ || instrumentedMethod.getReturnType().represents(char.class)
+ || instrumentedMethod.getReturnType().represents(int.class)) {
+ methodVisitor.visitInsn(Opcodes.ICONST_0);
+ variable(Opcodes.ISTORE);
+ } else if (instrumentedMethod.getReturnType().represents(long.class)) {
+ methodVisitor.visitInsn(Opcodes.LCONST_0);
+ variable(Opcodes.LSTORE);
+ } else if (instrumentedMethod.getReturnType().represents(float.class)) {
+ methodVisitor.visitInsn(Opcodes.FCONST_0);
+ variable(Opcodes.FSTORE);
+ } else if (instrumentedMethod.getReturnType().represents(double.class)) {
+ methodVisitor.visitInsn(Opcodes.DCONST_0);
+ variable(Opcodes.DSTORE);
+ } else if (!instrumentedMethod.getReturnType().represents(void.class)) {
+ methodVisitor.visitInsn(Opcodes.ACONST_NULL);
+ variable(Opcodes.ASTORE);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * A byte code appender for implementing {@link Advice}.
+ */
+ @EqualsAndHashCode
+ protected static class Appender implements ByteCodeAppender {
+
+ /**
+ * The advice to implement.
+ */
+ private final Advice advice;
+
+ /**
+ * The current implementation target.
+ */
+ private final Implementation.Target implementationTarget;
+
+ /**
+ * The delegate byte code appender.
+ */
+ private final ByteCodeAppender delegate;
+
+ /**
+ * Creates a new appender for an advice component.
+ *
+ * @param advice The advice to implement.
+ * @param implementationTarget The current implementation target.
+ * @param delegate The delegate byte code appender.
+ */
+ protected Appender(Advice advice, Target implementationTarget, ByteCodeAppender delegate) {
+ this.advice = advice;
+ this.implementationTarget = implementationTarget;
+ this.delegate = delegate;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ EmulatingMethodVisitor emulatingMethodVisitor = new EmulatingMethodVisitor(methodVisitor, delegate);
+ methodVisitor = advice.doWrap(implementationTarget.getInstrumentedType(),
+ instrumentedMethod,
+ emulatingMethodVisitor,
+ implementationContext,
+ AsmVisitorWrapper.NO_FLAGS,
+ AsmVisitorWrapper.NO_FLAGS);
+ return emulatingMethodVisitor.resolve(methodVisitor, implementationContext, instrumentedMethod);
+ }
+
+ /**
+ * A method visitor that allows for the emulation of a full method visitor invocation circle without delegating initial
+ * and ending visitations to the underlying visitor.
+ */
+ protected static class EmulatingMethodVisitor extends MethodVisitor {
+
+ /**
+ * The delegate byte code appender.
+ */
+ private final ByteCodeAppender delegate;
+
+ /**
+ * The currently recorded minimal required stack size.
+ */
+ private int stackSize;
+
+ /**
+ * The currently recorded minimal required local variable array length.
+ */
+ private int localVariableLength;
+
+ /**
+ * Creates a new emulating method visitor.
+ *
+ * @param methodVisitor The underlying method visitor.
+ * @param delegate The delegate byte code appender.
+ */
+ protected EmulatingMethodVisitor(MethodVisitor methodVisitor, ByteCodeAppender delegate) {
+ super(Opcodes.ASM5, methodVisitor);
+ this.delegate = delegate;
+ }
+
+ /**
+ * Resolves this this advice emulating method visitor for its delegate.
+ *
+ * @param methodVisitor The method visitor to apply.
+ * @param implementationContext The implementation context to apply.
+ * @param instrumentedMethod The instrumented method.
+ * @return The resulting size of the implemented method.
+ */
+ protected ByteCodeAppender.Size resolve(MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ MethodDescription instrumentedMethod) {
+ methodVisitor.visitCode();
+ Size size = delegate.apply(methodVisitor, implementationContext, instrumentedMethod);
+ methodVisitor.visitMaxs(size.getOperandStackSize(), size.getLocalVariableSize());
+ methodVisitor.visitEnd();
+ return new ByteCodeAppender.Size(stackSize, localVariableLength);
+ }
+
+ @Override
+ public void visitCode() {
+ /* do nothing */
+ }
+
+ @Override
+ public void visitMaxs(int stackSize, int localVariableLength) {
+ this.stackSize = stackSize;
+ this.localVariableLength = localVariableLength;
+ }
+
+ @Override
+ public void visitEnd() {
+ /* do nothing */
+ }
+ }
+ }
+
+ /**
+ * <p>
+ * Indicates that this method should be inlined before the matched method is invoked. Any class must declare
+ * at most one method with this annotation. The annotated method must be static. When instrumenting constructors,
+ * the {@code this} values can only be accessed for writing fields but not for reading fields or invoking methods.
+ * </p>
+ * <p>
+ * The annotated method can return a value that is made accessible to another method annotated by {@link OnMethodExit}.
+ * </p>
+ *
+ * @see Advice
+ * @see Argument
+ * @see This
+ */
+ @Documented
+ @Retention(RetentionPolicy.RUNTIME)
+ @java.lang.annotation.Target(ElementType.METHOD)
+ public @interface OnMethodEnter {
+
+ /**
+ * When specifying a non-primitive type, this method's return value that is subject to an {@code instanceof} check where
+ * the instrumented method is only executed, if the returned instance is {@code not} an instance of the specified class.
+ * Alternatively, it is possible to specify either {@link OnDefaultValue} or {@link OnNonDefaultValue} where the instrumented
+ * method is only executed if the advice method returns a default or non-default value of the advice method's return type.
+ * It is illegal to specify a primitive type as an argument whereas setting the value to {@code void} indicates that the
+ * instrumented method should never be skipped.
+ *
+ * @return A value defining what return values of the advice method indicate that the instrumented method
+ * should be skipped or {@code void} if the instrumented method should never be skipped.
+ */
+ Class<?> skipOn() default void.class;
+
+ /**
+ * If set to {@code true}, the instrumented method's line number information is adjusted such that stack traces generated within
+ * this advice method appear as if they were generated within the first line of the instrumented method. If set to {@code false},
+ * no line number information is made available for such stack traces.
+ *
+ * @return {@code true} if this advice code should appear as if it was added within the first line of the instrumented method.
+ */
+ boolean prependLineNumber() default true;
+
+ /**
+ * Determines if the annotated method should be inlined into the instrumented method or invoked from it. When a method
+ * is inlined, its byte code is copied into the body of the target method. this makes it is possible to execute code
+ * with the visibility privileges of the instrumented method while loosing the privileges of the declared method methods.
+ * When a method is not inlined, it is invoked similarly to a common Java method call. Note that it is not possible to
+ * set breakpoints within a method when it is inlined as no debugging information is copied from the advice method into
+ * the instrumented method.
+ *
+ * @return {@code true} if the annotated method should be inlined into the instrumented method.
+ */
+ boolean inline() default true;
+
+ /**
+ * Indicates that this advice should suppress any {@link Throwable} type being thrown during the advice's execution. By default,
+ * any such exception is silently suppressed. Custom behavior can be configured by using {@link Advice#withExceptionHandler(StackManipulation)}.
+ *
+ * @return The type of {@link Throwable} to suppress.
+ * @see Advice#withExceptionPrinting()
+ */
+ Class<? extends Throwable> suppress() default NoExceptionHandler.class;
+ }
+
+ /**
+ * <p>
+ * Indicates that this method should be executed before exiting the instrumented method. Any class must declare
+ * at most one method with this annotation. The annotated method must be static.
+ * </p>
+ * <p>
+ * By default, the annotated method is not invoked if the instrumented method terminates exceptionally. This behavior
+ * can be changed by setting the {@link OnMethodExit#onThrowable()} property to an exception type for which this advice
+ * method should be invoked. By setting the value to {@link Throwable}, the advice method is always invoked.
+ * </p>
+ *
+ * @see Advice
+ * @see Argument
+ * @see This
+ * @see Enter
+ * @see Return
+ * @see Thrown
+ */
+ @Documented
+ @Retention(RetentionPolicy.RUNTIME)
+ @java.lang.annotation.Target(ElementType.METHOD)
+ public @interface OnMethodExit {
+
+ /**
+ * Indicates a {@link Throwable} super type for which this exit advice is invoked if it was thrown from the instrumented method.
+ * If an exception is thrown, it is available via the {@link Thrown} parameter annotation. If a method returns exceptionally,
+ * any parameter annotated with {@link Return} is assigned the parameter type's default value.
+ *
+ * @return The type of {@link Throwable} for which this exit advice handler is invoked.
+ */
+ Class<? extends Throwable> onThrowable() default NoExceptionHandler.class;
+
+ /**
+ * Determines if the annotated method should be inlined into the instrumented method or invoked from it. When a method
+ * is inlined, its byte code is copied into the body of the target method. this makes it is possible to execute code
+ * with the visibility privileges of the instrumented method while loosing the privileges of the declared method methods.
+ * When a method is not inlined, it is invoked similarly to a common Java method call. Note that it is not possible to
+ * set breakpoints within a method when it is inlined as no debugging information is copied from the advice method into
+ * the instrumented method.
+ *
+ * @return {@code true} if the annotated method should be inlined into the instrumented method.
+ */
+ boolean inline() default true;
+
+ /**
+ * Indicates that this advice should suppress any {@link Throwable} type being thrown during the advice's execution. By default,
+ * any such exception is silently suppressed. Custom behavior can be configured by using {@link Advice#withExceptionHandler(StackManipulation)}.
+ *
+ * @return The type of {@link Throwable} to suppress.
+ * @see Advice#withExceptionPrinting()
+ */
+ Class<? extends Throwable> suppress() default NoExceptionHandler.class;
+ }
+
+ /**
+ * <p>
+ * Indicates that the annotated parameter should be mapped to the {@code this} reference of the instrumented method.
+ * </p>
+ * <p>
+ * <b>Important</b>: Parameters with this option must not be used when from a constructor in combination with
+ * {@link OnMethodEnter} where the {@code this} reference is not available.
+ * </p>
+ *
+ * @see Advice
+ * @see OnMethodEnter
+ * @see OnMethodExit
+ */
+ @Documented
+ @Retention(RetentionPolicy.RUNTIME)
+ @java.lang.annotation.Target(ElementType.PARAMETER)
+ public @interface This {
+
+ /**
+ * Determines if the parameter should be assigned {@code null} if the instrumented method is static or a constructor within
+ * an entry method.
+ *
+ * @return {@code true} if the value assignment is optional.
+ */
+ boolean optional() default false;
+
+ /**
+ * Indicates if it is possible to write to this parameter. If this property is set to {@code false}, the annotated
+ * type must be equal to the type declaring the instrumented method if the typing is not also set to {@link Assigner.Typing#DYNAMIC}.
+ * If this property is set to {@code true}, the annotated parameter can be any super type of the instrumented method's declaring type.
+ *
+ * @return {@code true} if this parameter is read-only.
+ */
+ boolean readOnly() default true;
+
+ /**
+ * The typing that should be applied when assigning the {@code this} value.
+ *
+ * @return The typing to apply upon assignment.
+ */
+ Assigner.Typing typing() default Assigner.Typing.STATIC;
+ }
+
+ /**
+ * Indicates that the annotated parameter should be mapped to the parameter with index {@link Argument#value()} of
+ * the instrumented method.
+ *
+ * @see Advice
+ * @see OnMethodEnter
+ * @see OnMethodExit
+ */
+ @Documented
+ @Retention(RetentionPolicy.RUNTIME)
+ @java.lang.annotation.Target(ElementType.PARAMETER)
+ public @interface Argument {
+
+ /**
+ * Returns the index of the mapped parameter.
+ *
+ * @return The index of the mapped parameter.
+ */
+ int value();
+
+ /**
+ * Indicates if it is possible to write to this parameter. If this property is set to {@code false}, the annotated
+ * type must be equal to the parameter of the instrumented method if the typing is not also set to {@link Assigner.Typing#DYNAMIC}.
+ * If this property is set to {@code true}, the annotated parameter can be any super type of the instrumented methods parameter.
+ *
+ * @return {@code true} if this parameter is read-only.
+ */
+ boolean readOnly() default true;
+
+ /**
+ * The typing that should be applied when assigning the argument.
+ *
+ * @return The typing to apply upon assignment.
+ */
+ Assigner.Typing typing() default Assigner.Typing.STATIC;
+
+ /**
+ * Indicates if a parameter binding is optional. If a binding is optional and a parameter with the specified index does not exist,
+ * the parameter's default value is bound.
+ *
+ * @return {@code true} if the binding is optional.
+ */
+ boolean optional() default false;
+ }
+
+ /**
+ * Assigns an array containing all arguments of the instrumented method to the annotated parameter. The annotated parameter must
+ * be an array type. If the annotation indicates writability, the assigned array must have at least as many values as the
+ * instrumented method or an {@link ArrayIndexOutOfBoundsException} is thrown.
+ */
+ @Documented
+ @Retention(RetentionPolicy.RUNTIME)
+ @java.lang.annotation.Target(ElementType.PARAMETER)
+ public @interface AllArguments {
+
+ /**
+ * Indicates if it is possible to write to this parameter. If this property is set to {@code false}, the annotated
+ * type must be equal to the type declaring the instrumented method if the typing is not also set to {@link Assigner.Typing#DYNAMIC}.
+ * If this property is set to {@code true}, the annotated parameter can be any super type of the instrumented method's declaring type.
+ *
+ * @return {@code true} if this parameter is read-only.
+ */
+ boolean readOnly() default true;
+
+ /**
+ * The typing that should be applied when assigning the arguments.
+ *
+ * @return The typing to apply upon assignment.
+ */
+ Assigner.Typing typing() default Assigner.Typing.STATIC;
+ }
+
+ /**
+ * <p>
+ * Indicates that the annotated parameter should be mapped to the return value of the instrumented method. If the instrumented
+ * method terminates exceptionally, the type's default value is assigned to the parameter, i.e. {@code 0} for numeric types
+ * and {@code null} for reference types. If the return type is {@code void}, the annotated value is {@code null} if and only if
+ * {@link Return#typing()} is set to {@link Assigner.Typing#DYNAMIC}.
+ * </p>
+ * <p>
+ * <b>Note</b>: This annotation must only be used on exit advice methods.
+ * </p>
+ *
+ * @see Advice
+ * @see OnMethodExit
+ */
+ @Documented
+ @Retention(RetentionPolicy.RUNTIME)
+ @java.lang.annotation.Target(ElementType.PARAMETER)
+ public @interface Return {
+
+ /**
+ * Indicates if it is possible to write to this parameter. If this property is set to {@code false}, the annotated
+ * type must be equal to the parameter of the instrumented method if the typing is not also set to {@link Assigner.Typing#DYNAMIC}.
+ * If this property is set to {@code true}, the annotated parameter can be any super type of the instrumented methods parameter.
+ *
+ * @return {@code true} if this parameter is read-only.
+ */
+ boolean readOnly() default true;
+
+ /**
+ * Determines the typing that is applied when assigning the return value.
+ *
+ * @return The typing to apply when assigning the annotated parameter.
+ */
+ Assigner.Typing typing() default Assigner.Typing.STATIC;
+ }
+
+ /**
+ * <p>
+ * Indicates that the annotated parameter should be mapped to the return value of the instrumented method. If the instrumented method terminates
+ * regularly, {@code null} is assigned to the annotated parameter. Note that the Java runtime does not enforce checked exceptions. In order to
+ * capture any error, the parameter type should be of type {@link Throwable}.
+ * </p>
+ * <p>
+ * <b>Note</b>: This annotation must only be used on exit advice methods.
+ * </p>
+ *
+ * @see Advice
+ * @see OnMethodExit
+ */
+ @Documented
+ @Retention(RetentionPolicy.RUNTIME)
+ @java.lang.annotation.Target(ElementType.PARAMETER)
+ public @interface Thrown {
+
+ /**
+ * <p>
+ * Indicates if it is possible to write to this parameter. If this property is set to {@code false}, it is illegal to
+ * write to the annotated parameter. If this property is set to {@code true}, the annotated parameter can either be set
+ * to {@code null} to suppress an exception that was thrown by the adviced method or it can be set to any other exception
+ * that will be thrown after the advice method returned.
+ * </p>
+ * <p>
+ * If an exception is suppressed, the default value for the return type is returned from the method, i.e. {@code 0} for any
+ * numeric type and {@code null} for a reference type. The default value can be replaced via the {@link Return} annotation.
+ * </p>
+ *
+ * @return {@code true} if this parameter is read-only.
+ */
+ boolean readOnly() default true;
+
+ /**
+ * Determines the typing that is applied when assigning the captured {@link Throwable} to the annotated parameter.
+ *
+ * @return The typing to apply when assigning the annotated parameter.
+ */
+ Assigner.Typing typing() default Assigner.Typing.DYNAMIC;
+ }
+
+ /**
+ * <p>
+ * Indicates that the annotated parameter should be mapped to a field in the scope of the instrumented method.
+ * </p>
+ * <p>
+ * <b>Important</b>: Parameters with this option must not be used when from a constructor in combination with
+ * {@link OnMethodEnter} and a non-static field where the {@code this} reference is not available.
+ * </p>
+ * <p>
+ * <b>Note</b>: As the mapping is virtual, Byte Buddy might be required to reserve more space on the operand stack than the
+ * optimal value when accessing this parameter. This does not normally matter as the additional space requirement is minimal.
+ * However, if the runtime performance of class creation is secondary, one can require ASM to recompute the optimal frames by
+ * setting {@link ClassWriter#COMPUTE_MAXS}. This is however only relevant when writing to a non-static field.
+ * </p>
+ *
+ * @see Advice
+ * @see OnMethodEnter
+ * @see OnMethodExit
+ */
+ @Documented
+ @Retention(RetentionPolicy.RUNTIME)
+ @java.lang.annotation.Target(ElementType.PARAMETER)
+ public @interface FieldValue {
+
+ /**
+ * Returns the name of the field.
+ *
+ * @return The name of the field.
+ */
+ String value();
+
+ /**
+ * Returns the type that declares the field that should be mapped to the annotated parameter. If this property
+ * is set to {@code void}, the field is looked up implicitly within the instrumented class's class hierarchy.
+ * The value can also be set to {@link TargetType} in order to look up the type on the instrumented type.
+ *
+ * @return The type that declares the field, {@code void} if this type should be determined implicitly or
+ * {@link TargetType} for the instrumented type.
+ */
+ Class<?> declaringType() default void.class;
+
+ /**
+ * Indicates if it is possible to write to this parameter. If this property is set to {@code false}, the annotated
+ * type must be equal to the mapped field type if the typing is not also set to {@link Assigner.Typing#DYNAMIC}.
+ * If this property is set to {@code true}, the annotated parameter can be any super type of the field type.
+ *
+ * @return {@code true} if this parameter is read-only.
+ */
+ boolean readOnly() default true;
+
+ /**
+ * The typing that should be applied when assigning the field value.
+ *
+ * @return The typing to apply upon assignment.
+ */
+ Assigner.Typing typing() default Assigner.Typing.STATIC;
+ }
+
+ /**
+ * <p>
+ * Indicates that the annotated parameter should be mapped to a string representation of the instrumented method,
+ * a constant representing the {@link Class} declaring the adviced method or a {@link Method}, {@link Constructor}
+ * or {@code java.lang.reflect.Executable} representing this method.
+ * </p>
+ * <p>
+ * <b>Note</b>: A constant representing a {@link Method} or {@link Constructor} is not cached but is recreated for
+ * every read.
+ * </p>
+ *
+ * @see Advice
+ * @see OnMethodEnter
+ * @see OnMethodExit
+ */
+ @Documented
+ @Retention(RetentionPolicy.RUNTIME)
+ @java.lang.annotation.Target(ElementType.PARAMETER)
+ public @interface Origin {
+
+ /**
+ * Indicates that the origin string should be indicated by the {@link Object#toString()} representation of the instrumented method.
+ */
+ String DEFAULT = "";
+
+ /**
+ * Returns the pattern the annotated parameter should be assigned. By default, the {@link Origin#toString()} representation
+ * of the method is assigned. Alternatively, a pattern can be assigned where:
+ * <ul>
+ * <li>{@code #t} inserts the method's declaring type.</li>
+ * <li>{@code #m} inserts the name of the method ({@code <init>} for constructors and {@code <clinit>} for static initializers).</li>
+ * <li>{@code #d} for the method's descriptor.</li>
+ * <li>{@code #s} for the method's signature.</li>
+ * <li>{@code #r} for the method's return type.</li>
+ * </ul>
+ * Any other {@code #} character must be escaped by {@code \} which can be escaped by itself. This property is ignored if the annotated
+ * parameter is of type {@link Class}.
+ *
+ * @return The pattern the annotated parameter should be assigned.
+ */
+ String value() default DEFAULT;
+ }
+
+ /**
+ * <p>
+ * Indicates that the annotated parameter should be mapped to the value that is returned by the advice method that is annotated
+ * by {@link OnMethodEnter}.
+ * </p>
+ * <p><b>Note</b></p>: This annotation must only be used within an exit advice and is only meaningful in combination with an entry advice.
+ *
+ * @see Advice
+ * @see OnMethodExit
+ */
+ @Documented
+ @Retention(RetentionPolicy.RUNTIME)
+ @java.lang.annotation.Target(ElementType.PARAMETER)
+ public @interface Enter {
+
+ /**
+ * Indicates if it is possible to write to this parameter. If this property is set to {@code false}, the annotated
+ * type must be equal to the parameter of the instrumented method if the typing is not also set to {@link Assigner.Typing#DYNAMIC}.
+ * If this property is set to {@code true}, the annotated parameter can be any super type of the instrumented methods parameter.
+ *
+ * @return {@code true} if this parameter is read-only.
+ */
+ boolean readOnly() default true;
+
+ /**
+ * The typing that should be applied when assigning the enter value.
+ *
+ * @return The typing to apply upon assignment.
+ */
+ Assigner.Typing typing() default Assigner.Typing.STATIC;
+ }
+
+ /**
+ * Indicates that the annotated parameter should always return a default a boxed version of the instrumented methods return value
+ * (i.e. {@code 0} for numeric values, {@code false} for {@code boolean} types and {@code null} for reference types). The annotated
+ * parameter must be of type {@link Object} and cannot be assigned a value.
+ *
+ * @see Advice
+ * @see OnMethodEnter
+ * @see OnMethodExit
+ */
+ @Documented
+ @Retention(RetentionPolicy.RUNTIME)
+ @java.lang.annotation.Target(ElementType.PARAMETER)
+ public @interface StubValue {
+ /* empty */
+ }
+
+ /**
+ * Indicates that the annotated parameter should always return a default value (i.e. {@code 0} for numeric values, {@code false}
+ * for {@code boolean} types and {@code null} for reference types). Any assignments to this variable are without any effect.
+ *
+ * @see Advice
+ * @see OnMethodEnter
+ * @see OnMethodExit
+ */
+ @Documented
+ @Retention(RetentionPolicy.RUNTIME)
+ @java.lang.annotation.Target(ElementType.PARAMETER)
+ public @interface Unused {
+ /* empty */
+ }
+
+ /**
+ * A builder step for creating an {@link Advice} that uses custom mappings of annotations to constant pool values.
+ */
+ @EqualsAndHashCode
+ public static class WithCustomMapping {
+
+ /**
+ * A map containing dynamically computed constant pool values that are mapped by their triggering annotation type.
+ */
+ private final Map<Class<? extends Annotation>, OffsetMapping.Factory<?>> offsetMappings;
+
+ /**
+ * Creates a new custom mapping builder step without including any custom mappings.
+ */
+ protected WithCustomMapping() {
+ this(Collections.<Class<? extends Annotation>, OffsetMapping.Factory<?>>emptyMap());
+ }
+
+ /**
+ * Creates a new custom mapping builder step with the given custom mappings.
+ *
+ * @param offsetMappings A map containing dynamically computed constant pool values that are mapped by their triggering annotation type.
+ */
+ protected WithCustomMapping(Map<Class<? extends Annotation>, OffsetMapping.Factory<?>> offsetMappings) {
+ this.offsetMappings = offsetMappings;
+ }
+
+ /**
+ * Binds the supplied annotation to a type constant of the supplied value. Constants can be strings, method handles, method types
+ * and any primitive or the value {@code null}.
+ *
+ * @param type The type of the annotation being bound.
+ * @param value The value to bind to the annotation.
+ * @param <T> The annotation type.
+ * @return A new builder for an advice that considers the supplied annotation type during binding.
+ */
+ public <T extends Annotation> WithCustomMapping bind(Class<T> type, Object value) {
+ return bind(OffsetMapping.ForStackManipulation.Factory.of(type, value));
+ }
+
+ /**
+ * Binds the supplied annotation to the value of the supplied field. The field must be visible by the
+ * instrumented type and must be declared by a super type of the instrumented field.
+ *
+ * @param type The type of the annotation being bound.
+ * @param field The field to bind to this annotation.
+ * @param <T> The annotation type.
+ * @return A new builder for an advice that considers the supplied annotation type during binding.
+ */
+ public <T extends Annotation> WithCustomMapping bind(Class<T> type, Field field) {
+ return bind(type, new FieldDescription.ForLoadedField(field));
+ }
+
+ /**
+ * Binds the supplied annotation to the value of the supplied field. The field must be visible by the
+ * instrumented type and must be declared by a super type of the instrumented field. The binding is defined
+ * as read-only and applied static typing.
+ *
+ * @param type The type of the annotation being bound.
+ * @param fieldDescription The field to bind to this annotation.
+ * @param <T> The annotation type.
+ * @return A new builder for an advice that considers the supplied annotation type during binding.
+ */
+ public <T extends Annotation> WithCustomMapping bind(Class<T> type, FieldDescription fieldDescription) {
+ return bind(new OffsetMapping.ForField.Resolved.Factory<T>(type, fieldDescription));
+ }
+
+ /**
+ * Binds the supplied annotation to the supplied parameter's argument.
+ *
+ * @param type The type of the annotation being bound.
+ * @param method The method that defines the parameter.
+ * @param index The index of the parameter.
+ * @param <T> The annotation type.
+ * @return A new builder for an advice that considers the supplied annotation type during binding.
+ */
+ public <T extends Annotation> WithCustomMapping bind(Class<T> type, Method method, int index) {
+ if (index < 0) {
+ throw new IllegalArgumentException("A parameter cannot be negative: " + index);
+ } else if (method.getParameterTypes().length <= index) {
+ throw new IllegalArgumentException(method + " does not declare a parameter with index " + index);
+ }
+ return bind(type, new MethodDescription.ForLoadedMethod(method).getParameters().get(index));
+ }
+
+ /**
+ * Binds the supplied annotation to the supplied parameter's argument.
+ *
+ * @param type The type of the annotation being bound.
+ * @param constructor The constructor that defines the parameter.
+ * @param index The index of the parameter.
+ * @param <T> The annotation type.
+ * @return A new builder for an advice that considers the supplied annotation type during binding.
+ */
+ public <T extends Annotation> WithCustomMapping bind(Class<T> type, Constructor<?> constructor, int index) {
+ if (index < 0) {
+ throw new IllegalArgumentException("A parameter cannot be negative: " + index);
+ } else if (constructor.getParameterTypes().length <= index) {
+ throw new IllegalArgumentException(constructor + " does not declare a parameter with index " + index);
+ }
+ return bind(type, new MethodDescription.ForLoadedConstructor(constructor).getParameters().get(index));
+ }
+
+ /**
+ * Binds the supplied annotation to the supplied parameter's argument. The binding is declared read-only and
+ * applies static typing.
+ *
+ * @param type The type of the annotation being bound.
+ * @param parameterDescription The parameter for which to bind an argument.
+ * @param <T> The annotation type.
+ * @return A new builder for an advice that considers the supplied annotation type during binding.
+ */
+ public <T extends Annotation> WithCustomMapping bind(Class<T> type, ParameterDescription parameterDescription) {
+ return bind(new OffsetMapping.ForArgument.Resolved.Factory<T>(type, parameterDescription));
+ }
+
+ /**
+ * Binds the supplied annotation to the supplied type constant.
+ *
+ * @param type The type of the annotation being bound.
+ * @param value The type constant to bind.
+ * @param <T> The annotation type.
+ * @return A new builder for an advice that considers the supplied annotation type during binding.
+ */
+ public <T extends Annotation> WithCustomMapping bind(Class<T> type, Class<?> value) {
+ return bind(type, new TypeDescription.ForLoadedType(value));
+ }
+
+ /**
+ * Binds the supplied annotation to the supplied type constant.
+ *
+ * @param type The type of the annotation being bound.
+ * @param value The type constant to bind.
+ * @param <T> The annotation type.
+ * @return A new builder for an advice that considers the supplied annotation type during binding.
+ */
+ public <T extends Annotation> WithCustomMapping bind(Class<T> type, TypeDescription value) {
+ return bind(new OffsetMapping.ForStackManipulation.Factory<T>(type, value));
+ }
+
+ /**
+ * Binds the supplied annotation to the supplied enumeration constant.
+ *
+ * @param type The type of the annotation being bound.
+ * @param value The enumeration constant to bind.
+ * @param <T> The annotation type.
+ * @return A new builder for an advice that considers the supplied annotation type during binding.
+ */
+ public <T extends Annotation> WithCustomMapping bind(Class<T> type, Enum<?> value) {
+ return bind(type, new EnumerationDescription.ForLoadedEnumeration(value));
+ }
+
+ /**
+ * Binds the supplied annotation to the supplied enumeration constant.
+ *
+ * @param type The type of the annotation being bound.
+ * @param value The enumeration constant to bind.
+ * @param <T> The annotation type.
+ * @return A new builder for an advice that considers the supplied annotation type during binding.
+ */
+ public <T extends Annotation> WithCustomMapping bind(Class<T> type, EnumerationDescription value) {
+ return bind(new OffsetMapping.ForStackManipulation.Factory<T>(type, value));
+ }
+
+ /**
+ * Binds the supplied annotation to the supplied fixed value.
+ *
+ * @param type The type of the annotation being bound.
+ * @param value The value to bind to this annotation.
+ * @param <T> The annotation type.
+ * @return A new builder for an advice that considers the supplied annotation type during binding.
+ */
+ @SuppressWarnings("unchecked")
+ public <T extends Annotation> WithCustomMapping bindSerialized(Class<T> type, Serializable value) {
+ return bindSerialized(type, value, (Class<Serializable>) value.getClass());
+ }
+
+ /**
+ * Binds the supplied annotation to the supplied fixed value.
+ *
+ * @param type The type of the annotation being bound.
+ * @param value The value to bind to this annotation.
+ * @param targetType The type of {@code value} as which the instance should be treated.
+ * @param <T> The annotation type.
+ * @param <S> The type of the serialized instance.
+ * @return A new builder for an advice that considers the supplied annotation type during binding.
+ */
+ public <T extends Annotation, S extends Serializable> WithCustomMapping bindSerialized(Class<T> type, S value, Class<? super S> targetType) {
+ return bind(OffsetMapping.ForSerializedValue.Factory.of(type, value, targetType));
+ }
+
+ /**
+ * Binds the supplied annotation to the annotation's property of the specified name.
+ *
+ * @param type The type of the annotation being bound.
+ * @param property The name of the annotation property to be bound.
+ * @param <T> The annotation type.
+ * @return A new builder for an advice that considers the supplied annotation during binding.
+ */
+ public <T extends Annotation> WithCustomMapping bindProperty(Class<T> type, String property) {
+ return bind(OffsetMapping.ForStackManipulation.OfAnnotationProperty.of(type, property));
+ }
+
+ /**
+ * Binds the supplied annotation to the annotation's property of the specified name.
+ *
+ * @param type The type of the annotation being bound.
+ * @param stackManipulation The stack manipulation loading the bound value.
+ * @param targetType The type of the loaded value.
+ * @param <T> The annotation type.
+ * @return A new builder for an advice that considers the supplied annotation during binding.
+ */
+ public <T extends Annotation> WithCustomMapping bind(Class<T> type, StackManipulation stackManipulation, java.lang.reflect.Type targetType) {
+ return bind(type, stackManipulation, TypeDefinition.Sort.describe(targetType));
+ }
+
+ /**
+ * Binds the supplied annotation to the annotation's property of the specified name.
+ *
+ * @param type The type of the annotation being bound.
+ * @param stackManipulation The stack manipulation loading the bound value.
+ * @param targetType The type of the loaded value.
+ * @param <T> The annotation type.
+ * @return A new builder for an advice that considers the supplied annotation during binding.
+ */
+ public <T extends Annotation> WithCustomMapping bind(Class<T> type, StackManipulation stackManipulation, TypeDescription.Generic targetType) {
+ return bind(new OffsetMapping.ForStackManipulation.Factory<T>(type, stackManipulation, targetType));
+ }
+
+ /**
+ * Binds the supplied annotation to the annotation's property of the specified name.
+ *
+ * @param type The type of the annotation being bound.
+ * @param offsetMapping The offset mapping being bound.
+ * @param <T> The annotation type.
+ * @return A new builder for an advice that considers the supplied annotation during binding.
+ */
+ public <T extends Annotation> WithCustomMapping bind(Class<T> type, OffsetMapping offsetMapping) {
+ return bind(new OffsetMapping.Factory.Simple<T>(type, offsetMapping));
+ }
+
+ /**
+ * Binds an annotation to a dynamically computed value. Whenever the {@link Advice} component discovers the given annotation on
+ * a parameter of an advice method, the dynamic value is asked to provide a value that is then assigned to the parameter in question.
+ *
+ * @param offsetMapping The dynamic value that is computed for binding the parameter to a value.
+ * @param <T> The annotation type.
+ * @return A new builder for an advice that considers the supplied annotation type during binding.
+ */
+ public <T extends Annotation> WithCustomMapping bind(OffsetMapping.Factory<? super T> offsetMapping) {
+ Map<Class<? extends Annotation>, OffsetMapping.Factory<?>> offsetMappings = new HashMap<Class<? extends Annotation>, OffsetMapping.Factory<?>>(this.offsetMappings);
+ if (!offsetMapping.getAnnotationType().isAnnotation()) {
+ throw new IllegalArgumentException("Not an annotation type: " + offsetMapping.getAnnotationType());
+ } else if (offsetMappings.put(offsetMapping.getAnnotationType(), offsetMapping) != null) {
+ throw new IllegalArgumentException("Annotation type already mapped: " + offsetMapping.getAnnotationType());
+ }
+ return new WithCustomMapping(offsetMappings);
+ }
+
+ /**
+ * Implements advice where every matched method is advised by the given type's advisory methods. The advices binary representation is
+ * accessed by querying the class loader of the supplied class for a class file.
+ *
+ * @param advice The type declaring the advice.
+ * @return A method visitor wrapper representing the supplied advice.
+ */
+ public Advice to(Class<?> advice) {
+ return to(advice, ClassFileLocator.ForClassLoader.of(advice.getClassLoader()));
+ }
+
+ /**
+ * Implements advice where every matched method is advised by the given type's advisory methods.
+ *
+ * @param advice The type declaring the advice.
+ * @param classFileLocator The class file locator for locating the advisory class's class file.
+ * @return A method visitor wrapper representing the supplied advice.
+ */
+ public Advice to(Class<?> advice, ClassFileLocator classFileLocator) {
+ return to(new TypeDescription.ForLoadedType(advice), classFileLocator);
+ }
+
+ /**
+ * Implements advice where every matched method is advised by the given type's advisory methods.
+ *
+ * @param advice A description of the type declaring the advice.
+ * @param classFileLocator The class file locator for locating the advisory class's class file.
+ * @return A method visitor wrapper representing the supplied advice.
+ */
+ public Advice to(TypeDescription advice, ClassFileLocator classFileLocator) {
+ return Advice.to(advice, classFileLocator, new ArrayList<OffsetMapping.Factory<?>>(offsetMappings.values()));
+ }
+
+ /**
+ * Implements advice where every matched method is advised by the given type's advisory methods. The advices binary representation is
+ * accessed by querying the class loader of the supplied class for a class file.
+ *
+ * @param enterAdvice The type declaring the enter advice.
+ * @param exitAdvice The type declaring the exit advice.
+ * @return A method visitor wrapper representing the supplied advice.
+ */
+ public Advice to(Class<?> enterAdvice, Class<?> exitAdvice) {
+ ClassLoader enterLoader = enterAdvice.getClassLoader(), exitLoader = exitAdvice.getClassLoader();
+ return to(enterAdvice, exitAdvice, enterLoader == exitLoader
+ ? ClassFileLocator.ForClassLoader.of(enterLoader)
+ : new ClassFileLocator.Compound(ClassFileLocator.ForClassLoader.of(enterLoader), ClassFileLocator.ForClassLoader.of(exitLoader)));
+ }
+
+ /**
+ * Implements advice where every matched method is advised by the given type's advisory methods.
+ *
+ * @param enterAdvice The type declaring the enter advice.
+ * @param exitAdvice The type declaring the exit advice.
+ * @param classFileLocator The class file locator for locating the advisory class's class file.
+ * @return A method visitor wrapper representing the supplied advice.
+ */
+ public Advice to(Class<?> enterAdvice, Class<?> exitAdvice, ClassFileLocator classFileLocator) {
+ return to(new TypeDescription.ForLoadedType(enterAdvice), new TypeDescription.ForLoadedType(exitAdvice), classFileLocator);
+ }
+
+ /**
+ * Implements advice where every matched method is advised by the given type's advisory methods. Using this method, a non-operational
+ * class file locator is specified for the advice target. This implies that only advice targets with the <i>inline</i> target set
+ * to {@code false} are resolvable by the returned instance.
+ *
+ * @param enterAdvice The type declaring the enter advice.
+ * @param exitAdvice The type declaring the exit advice.
+ * @return A method visitor wrapper representing the supplied advice.
+ */
+ public Advice to(TypeDescription enterAdvice, TypeDescription exitAdvice) {
+ return to(enterAdvice, exitAdvice, ClassFileLocator.NoOp.INSTANCE);
+ }
+
+ /**
+ * Implements advice where every matched method is advised by the given type's advisory methods.
+ *
+ * @param enterAdvice The type declaring the enter advice.
+ * @param exitAdvice The type declaring the exit advice.
+ * @param classFileLocator The class file locator for locating the advisory class's class file.
+ * @return A method visitor wrapper representing the supplied advice.
+ */
+ public Advice to(TypeDescription enterAdvice, TypeDescription exitAdvice, ClassFileLocator classFileLocator) {
+ return Advice.to(enterAdvice, exitAdvice, classFileLocator, new ArrayList<OffsetMapping.Factory<?>>(offsetMappings.values()));
+ }
+ }
+
+ /**
+ * A marker class that indicates that an advice method does not suppress any {@link Throwable}.
+ */
+ private static class NoExceptionHandler extends Throwable {
+
+ /**
+ * A description of the {@link NoExceptionHandler} type.
+ */
+ private static final TypeDescription DESCRIPTION = new TypeDescription.ForLoadedType(NoExceptionHandler.class);
+
+ /**
+ * A private constructor as this class is not supposed to be invoked.
+ */
+ private NoExceptionHandler() {
+ throw new UnsupportedOperationException("This marker class is not supposed to be instantiated");
+ }
+ }
+
+ /**
+ * A marker type to be used as an argument for {@link OnMethodEnter#skipOn()}. If this value is set, the instrumented method
+ * is not invoked if the annotated advice method <b>returns a default value</b>. A default value is {@code false} for a
+ * {@code boolean} type, {@code 0} for a {@code byte}, {@code short}, {@code char}, {@code int}, {@code long}, {@code float}
+ * or {@code double} type and {@code null} for a reference type. It is illegal to use this value if the advice method
+ * returns {@code void}.
+ */
+ public static final class OnDefaultValue {
+
+ /**
+ * A private constructor as this class is not supposed to be invoked.
+ */
+ private OnDefaultValue() {
+ throw new UnsupportedOperationException("This marker class is not supposed to be instantiated");
+ }
+ }
+
+ /**
+ * A marker type to be used as an argument for {@link OnMethodEnter#skipOn()}. If this value is set, the instrumented method
+ * is not invoked if the annotated advice method <b>returns a non-default value</b>. A default value is {@code false} for a
+ * {@code boolean} type, {@code 0} for a {@code byte}, {@code short}, {@code char}, {@code int}, {@code long}, {@code float}
+ * or {@code double} type and {@code null} for a reference type. It is illegal to use this value if the advice method
+ * returns {@code void}.
+ */
+ public static final class OnNonDefaultValue {
+
+ /**
+ * A private constructor as this class is not supposed to be invoked.
+ */
+ private OnNonDefaultValue() {
+ throw new UnsupportedOperationException("This marker class is not supposed to be instantiated");
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/asm/AsmVisitorWrapper.java b/byte-buddy-dep/src/main/java/net/bytebuddy/asm/AsmVisitorWrapper.java
new file mode 100644
index 0000000..031eeac
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/asm/AsmVisitorWrapper.java
@@ -0,0 +1,657 @@
+package net.bytebuddy.asm;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.pool.TypePool;
+import net.bytebuddy.utility.CompoundList;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.*;
+
+/**
+ * A class visitor wrapper is used in order to register an intermediate ASM {@link org.objectweb.asm.ClassVisitor} which
+ * is applied to the main type created by a {@link net.bytebuddy.dynamic.DynamicType.Builder} but not
+ * to any {@link net.bytebuddy.implementation.auxiliary.AuxiliaryType}s, if any.
+ */
+public interface AsmVisitorWrapper {
+
+ /**
+ * Indicates that no flags should be set.
+ */
+ int NO_FLAGS = 0;
+
+ /**
+ * Defines the flags that are provided to any {@code ClassWriter} when writing a class. Typically, this gives opportunity to instruct ASM
+ * to compute stack map frames or the size of the local variables array and the operand stack. If no specific flags are required for
+ * applying this wrapper, the given value is to be returned.
+ *
+ * @param flags The currently set flags. This value should be combined (e.g. {@code flags | foo}) into the value that is returned by this wrapper.
+ * @return The flags to be provided to the ASM {@code ClassWriter}.
+ */
+ int mergeWriter(int flags);
+
+ /**
+ * Defines the flags that are provided to any {@code ClassReader} when reading a class if applicable. Typically, this gives opportunity to
+ * instruct ASM to expand or skip frames and to skip code and debug information. If no specific flags are required for applying this
+ * wrapper, the given value is to be returned.
+ *
+ * @param flags The currently set flags. This value should be combined (e.g. {@code flags | foo}) into the value that is returned by this wrapper.
+ * @return The flags to be provided to the ASM {@code ClassReader}.
+ */
+ int mergeReader(int flags);
+
+ /**
+ * Applies a {@code ClassVisitorWrapper} to the creation of a {@link net.bytebuddy.dynamic.DynamicType}.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param classVisitor A {@code ClassVisitor} to become the new primary class visitor to which the created
+ * {@link net.bytebuddy.dynamic.DynamicType} is written to.
+ * @param implementationContext The implementation context of the current instrumentation.
+ * @param typePool The type pool that was provided for the class creation.
+ * @param fields The instrumented type's fields.
+ * @param methods The instrumented type's methods non-ingored declared and virtually inherited methods.
+ * @param writerFlags The ASM {@link org.objectweb.asm.ClassWriter} flags to consider.
+ * @param readerFlags The ASM {@link org.objectweb.asm.ClassReader} flags to consider.
+ * @return A new {@code ClassVisitor} that usually delegates to the {@code ClassVisitor} delivered in the argument.
+ */
+ ClassVisitor wrap(TypeDescription instrumentedType,
+ ClassVisitor classVisitor,
+ Implementation.Context implementationContext,
+ TypePool typePool,
+ FieldList<FieldDescription.InDefinedShape> fields,
+ MethodList<?> methods,
+ int writerFlags,
+ int readerFlags);
+
+ /**
+ * A class visitor wrapper that does not apply any changes.
+ */
+ enum NoOp implements AsmVisitorWrapper {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @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 classVisitor;
+ }
+ }
+
+ /**
+ * An abstract base implementation of an ASM visitor wrapper that does not set any flags.
+ */
+ abstract class AbstractBase implements AsmVisitorWrapper {
+
+ @Override
+ public int mergeWriter(int flags) {
+ return flags;
+ }
+
+ @Override
+ public int mergeReader(int flags) {
+ return flags;
+ }
+ }
+
+ /**
+ * An ASM visitor wrapper that allows to wrap declared fields of the instrumented type with a {@link FieldVisitorWrapper}.
+ */
+ @EqualsAndHashCode(callSuper = false)
+ class ForDeclaredFields extends AbstractBase {
+
+ /**
+ * The list of entries that describe matched fields in their application order.
+ */
+ private final List<Entry> entries;
+
+ /**
+ * Creates a new visitor wrapper for declared fields.
+ */
+ public ForDeclaredFields() {
+ this(Collections.<Entry>emptyList());
+ }
+
+ /**
+ * Creates a new visitor wrapper for declared fields.
+ *
+ * @param entries The list of entries that describe matched fields in their application order.
+ */
+ protected ForDeclaredFields(List<Entry> entries) {
+ this.entries = entries;
+ }
+
+ /**
+ * Defines a new field visitor wrapper to be applied if the given field matcher is matched. Previously defined
+ * entries are applied before the given matcher is applied.
+ *
+ * @param matcher The matcher to identify fields to be wrapped.
+ * @param fieldVisitorWrapper The field visitor wrapper to be applied if the given matcher is matched.
+ * @return A new ASM visitor wrapper that applied the given field visitor wrapper if the supplied matcher is matched.
+ */
+ public ForDeclaredFields field(ElementMatcher<? super FieldDescription.InDefinedShape> matcher, FieldVisitorWrapper... fieldVisitorWrapper) {
+ return field(matcher, Arrays.asList(fieldVisitorWrapper));
+ }
+
+ /**
+ * Defines a new field visitor wrapper to be applied if the given field matcher is matched. Previously defined
+ * entries are applied before the given matcher is applied.
+ *
+ * @param matcher The matcher to identify fields to be wrapped.
+ * @param fieldVisitorWrappers The field visitor wrapper to be applied if the given matcher is matched.
+ * @return A new ASM visitor wrapper that applied the given field visitor wrapper if the supplied matcher is matched.
+ */
+ public ForDeclaredFields field(ElementMatcher<? super FieldDescription.InDefinedShape> matcher, List<? extends FieldVisitorWrapper> fieldVisitorWrappers) {
+ return new ForDeclaredFields(CompoundList.of(entries, new Entry(matcher, fieldVisitorWrappers)));
+ }
+
+ @Override
+ public ClassVisitor wrap(TypeDescription instrumentedType,
+ ClassVisitor classVisitor,
+ Implementation.Context implementationContext,
+ TypePool typePool,
+ FieldList<FieldDescription.InDefinedShape> fields,
+ MethodList<?> methods,
+ int writerFlags,
+ int readerFlags) {
+ Map<String, FieldDescription.InDefinedShape> mapped = new HashMap<String, FieldDescription.InDefinedShape>();
+ for (FieldDescription.InDefinedShape fieldDescription : fields) {
+ mapped.put(fieldDescription.getInternalName() + fieldDescription.getDescriptor(), fieldDescription);
+ }
+ return new DispatchingVisitor(classVisitor, instrumentedType, mapped);
+ }
+
+ /**
+ * A field visitor wrapper that allows for wrapping a {@link FieldVisitor} defining a declared field.
+ */
+ public interface FieldVisitorWrapper {
+
+ /**
+ * Wraps a field visitor.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param fieldDescription The field that is currently being defined.
+ * @param fieldVisitor The original field visitor that defines the given field.
+ * @return The wrapped field visitor.
+ */
+ FieldVisitor wrap(TypeDescription instrumentedType, FieldDescription.InDefinedShape fieldDescription, FieldVisitor fieldVisitor);
+ }
+
+ /**
+ * An entry describing a field visitor wrapper paired with a matcher for fields to be wrapped.
+ */
+ @EqualsAndHashCode
+ protected static class Entry implements ElementMatcher<FieldDescription.InDefinedShape>, FieldVisitorWrapper {
+
+ /**
+ * The matcher to identify fields to be wrapped.
+ */
+ private final ElementMatcher<? super FieldDescription.InDefinedShape> matcher;
+
+ /**
+ * The field visitor wrapper to be applied if the given matcher is matched.
+ */
+ private final List<? extends FieldVisitorWrapper> fieldVisitorWrappers;
+
+ /**
+ * Creates a new entry.
+ *
+ * @param matcher The matcher to identify fields to be wrapped.
+ * @param fieldVisitorWrappers The field visitor wrapper to be applied if the given matcher is matched.
+ */
+ protected Entry(ElementMatcher<? super FieldDescription.InDefinedShape> matcher, List<? extends FieldVisitorWrapper> fieldVisitorWrappers) {
+ this.matcher = matcher;
+ this.fieldVisitorWrappers = fieldVisitorWrappers;
+ }
+
+ @Override
+ public boolean matches(FieldDescription.InDefinedShape target) {
+ return target != null && matcher.matches(target);
+ }
+
+ @Override
+ public FieldVisitor wrap(TypeDescription instrumentedType, FieldDescription.InDefinedShape fieldDescription, FieldVisitor fieldVisitor) {
+ for (FieldVisitorWrapper fieldVisitorWrapper : fieldVisitorWrappers) {
+ fieldVisitor = fieldVisitorWrapper.wrap(instrumentedType, fieldDescription, fieldVisitor);
+ }
+ return fieldVisitor;
+ }
+ }
+
+ /**
+ * A class visitor that applies the outer ASM visitor for identifying declared fields.
+ */
+ protected class DispatchingVisitor extends ClassVisitor {
+
+ /**
+ * The instrumented type.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * A mapping of fields by their name and descriptor key-combination.
+ */
+ private final Map<String, FieldDescription.InDefinedShape> fields;
+
+ /**
+ * Creates a new dispatching visitor.
+ *
+ * @param classVisitor The underlying class visitor.
+ * @param instrumentedType The instrumented type.
+ * @param fields The instrumented type's declared fields.
+ */
+ protected DispatchingVisitor(ClassVisitor classVisitor, TypeDescription instrumentedType, Map<String, FieldDescription.InDefinedShape> fields) {
+ super(Opcodes.ASM5, classVisitor);
+ this.instrumentedType = instrumentedType;
+ this.fields = fields;
+ }
+
+ @Override
+ public FieldVisitor visitField(int modifiers, String internalName, String descriptor, String signature, Object defaultValue) {
+ FieldVisitor fieldVisitor = super.visitField(modifiers, internalName, descriptor, signature, defaultValue);
+ FieldDescription.InDefinedShape fieldDescription = fields.get(internalName + descriptor);
+ if (fieldVisitor != null && fieldDescription != null) {
+ for (Entry entry : entries) {
+ if (entry.matches(fieldDescription)) {
+ fieldVisitor = entry.wrap(instrumentedType, fieldDescription, fieldVisitor);
+ }
+ }
+ }
+ return fieldVisitor;
+ }
+ }
+ }
+
+ /**
+ * <p>
+ * An ASM visitor wrapper that allows to wrap <b>declared methods</b> of the instrumented type with a {@link MethodVisitorWrapper}.
+ * </p>
+ * <p>
+ * Note: Inherited methods are <b>not</b> matched by this visitor, even if they are intercepted by a normal interception.
+ * </p>
+ */
+ @EqualsAndHashCode
+ class ForDeclaredMethods implements AsmVisitorWrapper {
+
+ /**
+ * The list of entries that describe matched methods in their application order.
+ */
+ private final List<Entry> entries;
+
+ /**
+ * The writer flags to set.
+ */
+ private final int writerFlags;
+
+ /**
+ * The reader flags to set.
+ */
+ private final int readerFlags;
+
+ /**
+ * Creates a new visitor wrapper for declared methods.
+ */
+ public ForDeclaredMethods() {
+ this(Collections.<Entry>emptyList(), NO_FLAGS, NO_FLAGS);
+ }
+
+ /**
+ * Creates a new visitor wrapper for declared methods.
+ *
+ * @param entries The list of entries that describe matched methods in their application order.
+ * @param readerFlags The reader flags to set.
+ * @param writerFlags The writer flags to set.
+ */
+ protected ForDeclaredMethods(List<Entry> entries, int writerFlags, int readerFlags) {
+ this.entries = entries;
+ this.writerFlags = writerFlags;
+ this.readerFlags = readerFlags;
+ }
+
+ /**
+ * Defines a new method visitor wrapper to be applied if the given method matcher is matched. Previously defined
+ * entries are applied before the given matcher is applied.
+ *
+ * @param matcher The matcher to identify methods to be wrapped.
+ * @param methodVisitorWrapper The method visitor wrapper to be applied if the given matcher is matched.
+ * @return A new ASM visitor wrapper that applied the given method visitor wrapper if the supplied matcher is matched.
+ */
+ public ForDeclaredMethods method(ElementMatcher<? super MethodDescription> matcher, MethodVisitorWrapper... methodVisitorWrapper) {
+ return method(matcher, Arrays.asList(methodVisitorWrapper));
+ }
+
+ /**
+ * Defines a new method visitor wrapper to be applied if the given method matcher is matched. Previously defined
+ * entries are applied before the given matcher is applied.
+ *
+ * @param matcher The matcher to identify methods to be wrapped.
+ * @param methodVisitorWrappers The method visitor wrapper to be applied if the given matcher is matched.
+ * @return A new ASM visitor wrapper that applied the given method visitor wrapper if the supplied matcher is matched.
+ */
+ public ForDeclaredMethods method(ElementMatcher<? super MethodDescription> matcher, List<? extends MethodVisitorWrapper> methodVisitorWrappers) {
+ return new ForDeclaredMethods(CompoundList.of(entries, new Entry(matcher, methodVisitorWrappers)), writerFlags, readerFlags);
+ }
+
+ /**
+ * Sets flags for the {@link org.objectweb.asm.ClassWriter} this wrapper is applied to.
+ *
+ * @param flags The flags to set for the {@link org.objectweb.asm.ClassWriter}.
+ * @return A new ASM visitor wrapper that sets the supplied writer flags.
+ */
+ public ForDeclaredMethods writerFlags(int flags) {
+ return new ForDeclaredMethods(entries, writerFlags | flags, readerFlags);
+ }
+
+ /**
+ * Sets flags for the {@link org.objectweb.asm.ClassReader} this wrapper is applied to.
+ *
+ * @param flags The flags to set for the {@link org.objectweb.asm.ClassReader}.
+ * @return A new ASM visitor wrapper that sets the supplied reader flags.
+ */
+ public ForDeclaredMethods readerFlags(int flags) {
+ return new ForDeclaredMethods(entries, writerFlags, readerFlags | flags);
+ }
+
+ @Override
+ public int mergeWriter(int flags) {
+ return flags | writerFlags;
+ }
+
+ @Override
+ public int mergeReader(int flags) {
+ return flags | readerFlags;
+ }
+
+ @Override
+ public ClassVisitor wrap(TypeDescription instrumentedType,
+ ClassVisitor classVisitor,
+ Implementation.Context implementationContext,
+ TypePool typePool,
+ FieldList<FieldDescription.InDefinedShape> fields,
+ MethodList<?> methods,
+ int writerFlags,
+ int readerFlags) {
+ Map<String, MethodDescription> mapped = new HashMap<String, MethodDescription>();
+ for (MethodDescription methodDescription : CompoundList.<MethodDescription>of(methods, new MethodDescription.Latent.TypeInitializer(instrumentedType))) {
+ mapped.put(methodDescription.getInternalName() + methodDescription.getDescriptor(), methodDescription);
+ }
+ return new DispatchingVisitor(classVisitor,
+ instrumentedType,
+ implementationContext,
+ typePool,
+ mapped,
+ writerFlags,
+ readerFlags);
+ }
+
+ /**
+ * A method visitor wrapper that allows for wrapping a {@link MethodVisitor} defining a declared method.
+ */
+ public interface MethodVisitorWrapper {
+
+ /**
+ * Wraps a method visitor.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param instrumentedMethod The method that is currently being defined.
+ * @param methodVisitor The original field visitor that defines the given method.
+ * @param implementationContext The implementation context to use.
+ * @param typePool The type pool to use.
+ * @param writerFlags The ASM {@link org.objectweb.asm.ClassWriter} reader flags to consider.
+ * @param readerFlags The ASM {@link org.objectweb.asm.ClassReader} reader flags to consider.
+ * @return The wrapped method visitor.
+ */
+ MethodVisitor wrap(TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ TypePool typePool,
+ int writerFlags,
+ int readerFlags);
+ }
+
+ /**
+ * An entry describing a method visitor wrapper paired with a matcher for fields to be wrapped.
+ */
+ @EqualsAndHashCode
+ protected static class Entry implements ElementMatcher<MethodDescription>, MethodVisitorWrapper {
+
+ /**
+ * The matcher to identify methods to be wrapped.
+ */
+ private final ElementMatcher<? super MethodDescription> matcher;
+
+ /**
+ * The method visitor wrapper to be applied if the given matcher is matched.
+ */
+ private final List<? extends MethodVisitorWrapper> methodVisitorWrappers;
+
+ /**
+ * Creates a new entry.
+ *
+ * @param matcher The matcher to identify methods to be wrapped.
+ * @param methodVisitorWrappers The method visitor wrapper to be applied if the given matcher is matched.
+ */
+ protected Entry(ElementMatcher<? super MethodDescription> matcher, List<? extends MethodVisitorWrapper> methodVisitorWrappers) {
+ this.matcher = matcher;
+ this.methodVisitorWrappers = methodVisitorWrappers;
+ }
+
+ @Override
+ public boolean matches(MethodDescription target) {
+ return target != null && matcher.matches(target);
+ }
+
+ @Override
+ public MethodVisitor wrap(TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ TypePool typePool,
+ int writerFlags,
+ int readerFlags) {
+ for (MethodVisitorWrapper methodVisitorWrapper : methodVisitorWrappers) {
+ methodVisitor = methodVisitorWrapper.wrap(instrumentedType,
+ instrumentedMethod,
+ methodVisitor,
+ implementationContext,
+ typePool,
+ writerFlags,
+ readerFlags);
+ }
+ return methodVisitor;
+ }
+ }
+
+ /**
+ * A class visitor that applies the outer ASM visitor for identifying declared methods.
+ */
+ protected class DispatchingVisitor extends ClassVisitor {
+
+ /**
+ * The instrumented type.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * The implementation context to use.
+ */
+ private final Implementation.Context implementationContext;
+
+ /**
+ * The type pool to use.
+ */
+ private final TypePool typePool;
+
+ /**
+ * The ASM {@link org.objectweb.asm.ClassWriter} reader flags to consider.
+ */
+ private final int writerFlags;
+
+ /**
+ * The ASM {@link org.objectweb.asm.ClassReader} reader flags to consider.
+ */
+ private final int readerFlags;
+
+ /**
+ * A mapping of fields by their name.
+ */
+ private final Map<String, MethodDescription> methods;
+
+ /**
+ * Creates a new dispatching visitor.
+ *
+ * @param classVisitor The underlying class visitor.
+ * @param instrumentedType The instrumented type.
+ * @param implementationContext The implementation context to use.
+ * @param typePool The type pool to use.
+ * @param methods The methods that are declared by the instrumented type or virtually inherited.
+ * @param writerFlags The ASM {@link org.objectweb.asm.ClassWriter} flags to consider.
+ * @param readerFlags The ASM {@link org.objectweb.asm.ClassReader} flags to consider.
+ */
+ protected DispatchingVisitor(ClassVisitor classVisitor,
+ TypeDescription instrumentedType,
+ Implementation.Context implementationContext,
+ TypePool typePool,
+ Map<String, MethodDescription> methods,
+ int writerFlags,
+ int readerFlags) {
+ super(Opcodes.ASM5, classVisitor);
+ this.instrumentedType = instrumentedType;
+ this.implementationContext = implementationContext;
+ this.typePool = typePool;
+ this.methods = methods;
+ this.writerFlags = writerFlags;
+ this.readerFlags = readerFlags;
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int modifiers, String internalName, String descriptor, String signature, String[] exceptions) {
+ MethodVisitor methodVisitor = super.visitMethod(modifiers, internalName, descriptor, signature, exceptions);
+ MethodDescription methodDescription = methods.get(internalName + descriptor);
+ if (methodVisitor != null && methodDescription != null) {
+ for (Entry entry : entries) {
+ if (entry.matches(methodDescription)) {
+ methodVisitor = entry.wrap(instrumentedType,
+ methodDescription,
+ methodVisitor,
+ implementationContext,
+ typePool,
+ writerFlags,
+ readerFlags);
+ }
+ }
+ }
+ return methodVisitor;
+ }
+ }
+ }
+
+ /**
+ * An ordered, immutable chain of {@link AsmVisitorWrapper}s.
+ */
+ @EqualsAndHashCode
+ class Compound implements AsmVisitorWrapper {
+
+ /**
+ * The class visitor wrappers that are represented by this chain in their order. This list must not be mutated.
+ */
+ private final List<AsmVisitorWrapper> asmVisitorWrappers;
+
+ /**
+ * Creates a new immutable chain based on an existing list of {@link AsmVisitorWrapper}s
+ * where no copy of the received array is made.
+ *
+ * @param asmVisitorWrapper An array of {@link AsmVisitorWrapper}s where elements
+ * at the beginning of the list are applied first, i.e. will be at the bottom of the generated
+ * {@link org.objectweb.asm.ClassVisitor}.
+ */
+ public Compound(AsmVisitorWrapper... asmVisitorWrapper) {
+ this(Arrays.asList(asmVisitorWrapper));
+ }
+
+ /**
+ * Creates a new immutable chain based on an existing list of {@link AsmVisitorWrapper}s
+ * where no copy of the received list is made.
+ *
+ * @param asmVisitorWrappers A list of {@link AsmVisitorWrapper}s where elements
+ * at the beginning of the list are applied first, i.e. will be at the bottom of the generated
+ * {@link org.objectweb.asm.ClassVisitor}.
+ */
+ public Compound(List<? extends AsmVisitorWrapper> asmVisitorWrappers) {
+ this.asmVisitorWrappers = new ArrayList<AsmVisitorWrapper>();
+ for (AsmVisitorWrapper asmVisitorWrapper : asmVisitorWrappers) {
+ if (asmVisitorWrapper instanceof Compound) {
+ this.asmVisitorWrappers.addAll(((Compound) asmVisitorWrapper).asmVisitorWrappers);
+ } else if (!(asmVisitorWrapper instanceof NoOp)) {
+ this.asmVisitorWrappers.add(asmVisitorWrapper);
+ }
+ }
+ }
+
+ @Override
+ public int mergeWriter(int flags) {
+ for (AsmVisitorWrapper asmVisitorWrapper : asmVisitorWrappers) {
+ flags = asmVisitorWrapper.mergeWriter(flags);
+ }
+ return flags;
+ }
+
+ @Override
+ public int mergeReader(int flags) {
+ for (AsmVisitorWrapper asmVisitorWrapper : asmVisitorWrappers) {
+ flags = asmVisitorWrapper.mergeReader(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) {
+ for (AsmVisitorWrapper asmVisitorWrapper : asmVisitorWrappers) {
+ classVisitor = asmVisitorWrapper.wrap(instrumentedType,
+ classVisitor,
+ implementationContext,
+ typePool,
+ fields,
+ methods,
+ writerFlags,
+ readerFlags);
+ }
+ return classVisitor;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/asm/MemberRemoval.java b/byte-buddy-dep/src/main/java/net/bytebuddy/asm/MemberRemoval.java
new file mode 100644
index 0000000..751c37f
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/asm/MemberRemoval.java
@@ -0,0 +1,203 @@
+package net.bytebuddy.asm;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.matcher.ElementMatchers;
+import net.bytebuddy.pool.TypePool;
+import net.bytebuddy.utility.CompoundList;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
+import static net.bytebuddy.matcher.ElementMatchers.isMethod;
+
+/**
+ * <p>
+ * A visitor wrapper that removes fields or methods that match a given {@link ElementMatcher}.
+ * </p>
+ * <p>
+ * <b>Important</b>: This matcher is not capable of removing synthetic bridge methods which will be retained if they are
+ * declared by the same class. As bridge methods only invoke an overridden method, the dispatch should however not be
+ * influenced by their retention.
+ * </p>
+ * <p>
+ * <b>Important</b>: The removal of the method is not reflected in the created {@link net.bytebuddy.dynamic.DynamicType}'s
+ * type description of the instrumented type.
+ * </p>
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class MemberRemoval extends AsmVisitorWrapper.AbstractBase {
+
+ /**
+ * The matcher that decides upon field removal.
+ */
+ private final ElementMatcher.Junction<FieldDescription.InDefinedShape> fieldMatcher;
+
+ /**
+ * The matcher that decides upon method removal.
+ */
+ private final ElementMatcher.Junction<MethodDescription> methodMatcher;
+
+ /**
+ * Creates a new member removal instance that does not specify the removal of any methods.
+ */
+ public MemberRemoval() {
+ this(ElementMatchers.<FieldDescription.InDefinedShape>none(), ElementMatchers.<MethodDescription>none());
+ }
+
+ /**
+ * Creates a new member removal instance.
+ *
+ * @param fieldMatcher The matcher that decides upon field removal.
+ * @param methodMatcher The matcher that decides upon field removal.
+ */
+ protected MemberRemoval(ElementMatcher.Junction<FieldDescription.InDefinedShape> fieldMatcher,
+ ElementMatcher.Junction<MethodDescription> methodMatcher) {
+ this.fieldMatcher = fieldMatcher;
+ this.methodMatcher = methodMatcher;
+ }
+
+ /**
+ * Specifies that any field that matches the specified matcher should be removed.
+ *
+ * @param matcher The matcher that decides upon field removal.
+ * @return A new member removal instance that removes all previously specified members and any fields that match the specified matcher.
+ */
+ public MemberRemoval stripFields(ElementMatcher<? super FieldDescription.InDefinedShape> matcher) {
+ return new MemberRemoval(fieldMatcher.or(matcher), methodMatcher);
+ }
+
+ /**
+ * Specifies that any method that matches the specified matcher should be removed.
+ *
+ * @param matcher The matcher that decides upon method removal.
+ * @return A new member removal instance that removes all previously specified members and any method that matches the specified matcher.
+ */
+ public MemberRemoval stripMethods(ElementMatcher<? super MethodDescription> matcher) {
+ return stripInvokables(isMethod().and(matcher));
+ }
+
+ /**
+ * Specifies that any constructor that matches the specified matcher should be removed.
+ *
+ * @param matcher The matcher that decides upon constructor removal.
+ * @return A new member removal instance that removes all previously specified members and any constructor that matches the specified matcher.
+ */
+ public MemberRemoval stripConstructors(ElementMatcher<? super MethodDescription> matcher) {
+ return stripInvokables(isConstructor().and(matcher));
+ }
+
+ /**
+ * Specifies that any method or constructor that matches the specified matcher should be removed.
+ *
+ * @param matcher The matcher that decides upon method and constructor removal.
+ * @return A new member removal instance that removes all previously specified members and any method or constructor that matches the specified matcher.
+ */
+ public MemberRemoval stripInvokables(ElementMatcher<? super MethodDescription> matcher) {
+ return new MemberRemoval(fieldMatcher, methodMatcher.or(matcher));
+ }
+
+ @Override
+ public ClassVisitor wrap(TypeDescription instrumentedType,
+ ClassVisitor classVisitor,
+ Implementation.Context implementationContext,
+ TypePool typePool,
+ FieldList<FieldDescription.InDefinedShape> fields,
+ MethodList<?> methods,
+ int writerFlags,
+ int readerFlags) {
+ Map<String, FieldDescription.InDefinedShape> mappedFields = new HashMap<String, FieldDescription.InDefinedShape>();
+ for (FieldDescription.InDefinedShape fieldDescription : fields) {
+ mappedFields.put(fieldDescription.getInternalName() + fieldDescription.getDescriptor(), fieldDescription);
+ }
+ Map<String, MethodDescription> mappedMethods = new HashMap<String, MethodDescription>();
+ for (MethodDescription methodDescription : CompoundList.<MethodDescription>of(methods, new MethodDescription.Latent.TypeInitializer(instrumentedType))) {
+ mappedMethods.put(methodDescription.getInternalName() + methodDescription.getDescriptor(), methodDescription);
+ }
+ return new MemberRemovingClassVisitor(classVisitor, fieldMatcher, methodMatcher, mappedFields, mappedMethods);
+ }
+
+ /**
+ * A class visitor that removes members based on element matchers.
+ */
+ protected static class MemberRemovingClassVisitor extends ClassVisitor {
+
+ /**
+ * Indicates the removal of a field.
+ */
+ private static final FieldVisitor REMOVE_FIELD = null;
+
+ /**
+ * Indicates the removal of a method.
+ */
+ private static final MethodVisitor REMOVE_METHOD = null;
+
+ /**
+ * The matcher that determines field removal.
+ */
+ private final ElementMatcher.Junction<FieldDescription.InDefinedShape> fieldMatcher;
+
+ /**
+ * The matcher that determines method removal.
+ */
+ private final ElementMatcher.Junction<MethodDescription> methodMatcher;
+
+ /**
+ * A mapping of field names and descriptors to their description.
+ */
+ private final Map<String, FieldDescription.InDefinedShape> fields;
+
+ /**
+ * A mapping of method names and descriptors to their description.
+ */
+ private final Map<String, MethodDescription> methods;
+
+ /**
+ * Creates a new member removing class visitor.
+ *
+ * @param classVisitor The class visitor to delegate to.
+ * @param fieldMatcher The matcher that determines field removal.
+ * @param methodMatcher The matcher that determines method removal.
+ * @param fields A mapping of field names and descriptors to their description.
+ * @param methods A mapping of method names and descriptors to their description.
+ */
+ protected MemberRemovingClassVisitor(ClassVisitor classVisitor,
+ ElementMatcher.Junction<FieldDescription.InDefinedShape> fieldMatcher,
+ ElementMatcher.Junction<MethodDescription> methodMatcher,
+ Map<String, FieldDescription.InDefinedShape> fields,
+ Map<String, MethodDescription> methods) {
+ super(Opcodes.ASM5, classVisitor);
+ this.fieldMatcher = fieldMatcher;
+ this.methodMatcher = methodMatcher;
+ this.fields = fields;
+ this.methods = methods;
+ }
+
+ @Override
+ public FieldVisitor visitField(int modifiers, String internalName, String descriptor, String signature, Object value) {
+ FieldDescription.InDefinedShape fieldDescription = fields.get(internalName + descriptor);
+ return fieldDescription != null && fieldMatcher.matches(fieldDescription)
+ ? REMOVE_FIELD
+ : super.visitField(modifiers, internalName, descriptor, signature, value);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int modifiers, String internalName, String descriptor, String signature, String[] exception) {
+ MethodDescription methodDescription = methods.get(internalName + descriptor);
+ return methodDescription != null && methodMatcher.matches(methodDescription)
+ ? REMOVE_METHOD
+ : super.visitMethod(modifiers, internalName, descriptor, signature, exception);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/asm/MemberSubstitution.java b/byte-buddy-dep/src/main/java/net/bytebuddy/asm/MemberSubstitution.java
new file mode 100644
index 0000000..b08f86e
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/asm/MemberSubstitution.java
@@ -0,0 +1,1217 @@
+package net.bytebuddy.asm;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.ByteCodeElement;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.dynamic.scaffold.MethodGraph;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.Duplication;
+import net.bytebuddy.implementation.bytecode.Removal;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.implementation.bytecode.constant.DefaultValue;
+import net.bytebuddy.implementation.bytecode.member.FieldAccess;
+import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.pool.TypePool;
+import net.bytebuddy.utility.CompoundList;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static net.bytebuddy.matcher.ElementMatchers.*;
+
+/**
+ * <p>
+ * Substitutes field access or method invocations within a method's body.
+ * </p>
+ * <p>
+ * <b>Important</b>: This component relies on using a {@link TypePool} for locating types within method bodies. Within a redefinition
+ * or a rebasement, this type pool normally resolved correctly by Byte Buddy. When subclassing a type, the type pool must be set
+ * explicitly, using {@link net.bytebuddy.dynamic.DynamicType.Builder#make(TypePool)} or any similar method. It is however not normally
+ * necessary to use this component when subclassing a type where methods are only defined explicitly.
+ * </p>
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class MemberSubstitution implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper {
+
+ /**
+ * The method graph compiler to use.
+ */
+ private final MethodGraph.Compiler methodGraphCompiler;
+
+ /**
+ * {@code true} if the method processing should be strict where an exception is raised if a member cannot be found.
+ */
+ private final boolean strict;
+
+ /**
+ * The substitution to apply.
+ */
+ private final Substitution substitution;
+
+ /**
+ * Creates a new member substitutor.
+ *
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found.
+ * @param substitution The substitution to apply.
+ */
+ protected MemberSubstitution(MethodGraph.Compiler methodGraphCompiler, boolean strict, Substitution substitution) {
+ this.methodGraphCompiler = methodGraphCompiler;
+ this.strict = strict;
+ this.substitution = substitution;
+ }
+
+ /**
+ * Creates a member substitutor that requires the resolution of all fields and methods that are referenced within a method body. Doing so,
+ * this component raises an exception if any member cannot be resolved what makes this component unusable when facing optional types.
+ *
+ * @return A strict member substitutor.
+ */
+ public static MemberSubstitution strict() {
+ return new MemberSubstitution(MethodGraph.Compiler.DEFAULT, true, Substitution.NoOp.INSTANCE);
+ }
+
+ /**
+ * Creates a member substitutor that skips any unresolvable fields or methods that are referenced within a method body. Using a relaxed
+ * member substitutor, methods containing optional types are supported. In the process, it is however possible that misconfigurations
+ * of this component remain undiscovered.
+ *
+ * @return A relaxed member substitutor.
+ */
+ public static MemberSubstitution relaxed() {
+ return new MemberSubstitution(MethodGraph.Compiler.DEFAULT, false, Substitution.NoOp.INSTANCE);
+ }
+
+ /**
+ * Substitutes any interaction with a field or method that matches the given matcher.
+ *
+ * @param matcher The matcher to determine what access to byte code elements to substitute.
+ * @return A specification that allows to determine how to substitute any interaction with byte code elements that match the supplied matcher.
+ */
+ public WithoutSpecification element(ElementMatcher<? super ByteCodeElement> matcher) {
+ return new WithoutSpecification.ForMatchedByteCodeElement(methodGraphCompiler, strict, substitution, matcher);
+ }
+
+ /**
+ * Substitutes any field access that matches the given matcher.
+ *
+ * @param matcher The matcher to determine what fields to substitute.
+ * @return A specification that allows to determine how to substitute any field access that match the supplied matcher.
+ */
+ public WithoutSpecification.ForMatchedField field(ElementMatcher<? super FieldDescription.InDefinedShape> matcher) {
+ return new WithoutSpecification.ForMatchedField(methodGraphCompiler, strict, substitution, matcher);
+ }
+
+ /**
+ * Substitutes any method invocation that matches the given matcher.
+ *
+ * @param matcher The matcher to determine what methods to substitute.
+ * @return A specification that allows to determine how to substitute any method invocations that match the supplied matcher.
+ */
+ public WithoutSpecification.ForMatchedMethod method(ElementMatcher<? super MethodDescription> matcher) {
+ return new WithoutSpecification.ForMatchedMethod(methodGraphCompiler, strict, substitution, matcher);
+ }
+
+ /**
+ * Substitutes any constructor invocation that matches the given matcher.
+ *
+ * @param matcher The matcher to determine what constructors to substitute.
+ * @return A specification that allows to determine how to substitute any constructor invocations that match the supplied matcher.
+ */
+ public WithoutSpecification constructor(ElementMatcher<? super MethodDescription> matcher) {
+ return invokable(isConstructor().and(matcher));
+ }
+
+ /**
+ * Substitutes any method or constructor invocation that matches the given matcher.
+ *
+ * @param matcher The matcher to determine what method or constructors to substitute.
+ * @return A specification that allows to determine how to substitute any constructor invocations that match the supplied matcher.
+ */
+ public WithoutSpecification invokable(ElementMatcher<? super MethodDescription> matcher) {
+ return new WithoutSpecification.ForMatchedMethod(methodGraphCompiler, strict, substitution, matcher);
+ }
+
+ /**
+ * Specifies the use of a specific method graph compiler for the resolution of virtual methods.
+ *
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @return A new member substitution that is equal to this but uses the specified method graph compiler.
+ */
+ public MemberSubstitution with(MethodGraph.Compiler methodGraphCompiler) {
+ return new MemberSubstitution(methodGraphCompiler, strict, substitution);
+ }
+
+ /**
+ * Applies this member substitutor to any method that matches the supplied matcher.
+ *
+ * @param matcher The matcher to determine this substitutors application.
+ * @return An ASM visitor wrapper that applies all specified substitutions for any matched method.
+ */
+ public AsmVisitorWrapper.ForDeclaredMethods on(ElementMatcher<? super MethodDescription> matcher) {
+ return new AsmVisitorWrapper.ForDeclaredMethods().method(matcher, this);
+ }
+
+ @Override
+ public MethodVisitor wrap(TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ TypePool typePool,
+ int writerFlags,
+ int readerFlags) {
+ return new SubstitutingMethodVisitor(methodVisitor,
+ methodGraphCompiler,
+ strict,
+ substitution,
+ instrumentedType,
+ implementationContext,
+ typePool);
+ }
+
+ /**
+ * A member substitution that lacks a specification for how to substitute the matched members references within a method body.
+ */
+ @EqualsAndHashCode
+ public abstract static class WithoutSpecification {
+
+ /**
+ * The method graph compiler to use.
+ */
+ protected final MethodGraph.Compiler methodGraphCompiler;
+
+ /**
+ * {@code true} if the method processing should be strict where an exception is raised if a member cannot be found.
+ */
+ protected final boolean strict;
+
+ /**
+ * The substitution to apply.
+ */
+ protected final Substitution substitution;
+
+ /**
+ * Creates a new member substitution that requires a specification for how to perform a substitution.
+ *
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found.
+ * @param substitution The substitution to apply.
+ */
+ protected WithoutSpecification(MethodGraph.Compiler methodGraphCompiler,
+ boolean strict,
+ Substitution substitution) {
+ this.methodGraphCompiler = methodGraphCompiler;
+ this.strict = strict;
+ this.substitution = substitution;
+ }
+
+ /**
+ * Subs any interaction with a matched byte code element. Any value read from the element will be replaced with the stubbed
+ * value's default, i.e. {@code null} for reference types and the specific {@code 0} value for primitive types. Any writen
+ * value will simply be discarded.
+ *
+ * @return A member substitution that stubs any interaction with a matched byte code element.
+ */
+ public MemberSubstitution stub() {
+ return new MemberSubstitution(methodGraphCompiler,
+ strict,
+ new Substitution.Compound(doStub(), substitution));
+ }
+
+ /**
+ * Applies the stubbing for this instance.
+ *
+ * @return A suitable substitution.
+ */
+ protected abstract Substitution doStub();
+
+ /**
+ * <p>
+ * Replaces any interaction with a matched byte code element by an interaction with the specified field. If a field
+ * is replacing a method or constructor invocation, it is treated as if it was a field getter or setter respectively.
+ * </p>
+ * <p>
+ * A replacement can only be applied if the field is compatible to the original byte code element, i.e. consumes an
+ * instance of the declaring type if it is not {@code static} as an argument and consumes or produces an instance of
+ * the field's type.
+ * </p>
+ *
+ * @param field The field to access instead of interacting with any of the matched byte code elements.
+ * @return A member substitution that replaces any matched byte code element with an access of the specified field.
+ */
+ public MemberSubstitution replaceWith(Field field) {
+ return replaceWith(new FieldDescription.ForLoadedField(field));
+ }
+
+ /**
+ * <p>
+ * Replaces any interaction with a matched byte code element by an interaction with the specified field. If a field
+ * is replacing a method or constructor invocation, it is treated as if it was a field getter or setter respectively.
+ * </p>
+ * <p>
+ * A replacement can only be applied if the field is compatible to the original byte code element, i.e. consumes an
+ * instance of the declaring type if it is not {@code static} as an argument and consumes or produces an instance of
+ * the field's type.
+ * </p>
+ *
+ * @param fieldDescription The field to access instead of interacting with any of the matched byte code elements.
+ * @return A member substitution that replaces any matched byte code element with an access of the specified field.
+ */
+ public MemberSubstitution replaceWith(FieldDescription fieldDescription) {
+ return new MemberSubstitution(methodGraphCompiler,
+ strict,
+ new Substitution.Compound(doReplaceWith(fieldDescription), substitution));
+ }
+
+ /**
+ * Creates a substitution for replacing the byte code elements matched by this instance with an access of the specified field.
+ *
+ * @param fieldDescription The field to access.
+ * @return A suitable substitution.
+ */
+ protected abstract Substitution doReplaceWith(FieldDescription fieldDescription);
+
+ /**
+ * <p>
+ * Replaces any interaction with a matched byte code element by an invocation of the specified method. If a method
+ * is replacing a field access, it is treated as if it was replacing an invocation of the field's getter or setter respectively.
+ * </p>
+ * <p>
+ * A replacement can only be applied if the method is compatible to the original byte code element, i.e. consumes compatible
+ * arguments and returns a compatible value. If the method is not {@code static}, it is treated as if {@code this} was an implcit
+ * first argument.
+ * </p>
+ *
+ * @param method The method to invoke instead of interacting with any of the matched byte code elements.
+ * @return A member substitution that replaces any matched byte code element with an invocation of the specified method.
+ */
+ public MemberSubstitution replaceWith(Method method) {
+ return replaceWith(new MethodDescription.ForLoadedMethod(method));
+ }
+
+ /**
+ * <p>
+ * Replaces any interaction with a matched byte code element by an invocation of the specified method. If a method
+ * is replacing a field access, it is treated as if it was replacing an invocation of the field's getter or setter respectively.
+ * </p>
+ * <p>
+ * A replacement can only be applied if the method is compatible to the original byte code element, i.e. consumes compatible
+ * arguments and returns a compatible value. If the method is not {@code static}, it is treated as if {@code this} was an implcit
+ * first argument.
+ * </p>
+ * <p>
+ * <b>Important</b>: It is not allowed to specifiy a constructor or the static type initializer as a replacement.
+ * </p>
+ *
+ * @param methodDescription The method to invoke instead of interacting with any of the matched byte code elements.
+ * @return A member substitution that replaces any matched byte code element with an invocation of the specified method.
+ */
+ public MemberSubstitution replaceWith(MethodDescription methodDescription) {
+ if (!methodDescription.isMethod()) {
+ throw new IllegalArgumentException("Cannot use " + methodDescription + " as a replacement");
+ }
+ return new MemberSubstitution(methodGraphCompiler,
+ strict,
+ new Substitution.Compound(doReplaceWith(methodDescription), substitution));
+ }
+
+ /**
+ * Creates a substitution for replacing the byte code elements matched by this instance with an invocation of the specified method.
+ *
+ * @param methodDescription The method to invoke.
+ * @return A suitable substitution.
+ */
+ protected abstract Substitution doReplaceWith(MethodDescription methodDescription);
+
+ /**
+ * Describes a member substitution that requires a specification for how to replace a byte code element.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ protected static class ForMatchedByteCodeElement extends WithoutSpecification {
+
+ /**
+ * A matcher for any byte code elements that should be substituted.
+ */
+ private final ElementMatcher<? super ByteCodeElement> matcher;
+
+ /**
+ * Creates a new member substitution for a matched byte code element that requires a specification for how to perform a substitution.
+ *
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found.
+ * @param substitution The substitution to apply.
+ * @param matcher A matcher for any byte code elements that should be substituted.
+ */
+ protected ForMatchedByteCodeElement(MethodGraph.Compiler methodGraphCompiler,
+ boolean strict,
+ Substitution substitution,
+ ElementMatcher<? super ByteCodeElement> matcher) {
+ super(methodGraphCompiler, strict, substitution);
+ this.matcher = matcher;
+ }
+
+ @Override
+ protected Substitution doStub() {
+ return Substitution.ForElementMatchers.of(matcher, Substitution.Resolver.Stubbing.INSTANCE);
+ }
+
+ @Override
+ protected Substitution doReplaceWith(FieldDescription fieldDescription) {
+ return Substitution.ForElementMatchers.of(matcher, new Substitution.Resolver.FieldAccessing(fieldDescription));
+ }
+
+ @Override
+ protected Substitution doReplaceWith(MethodDescription methodDescription) {
+ return Substitution.ForElementMatchers.of(matcher, new Substitution.Resolver.MethodInvoking(methodDescription));
+ }
+ }
+
+ /**
+ * Describes a member substitution that requires a specification for how to replace a field.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ public static class ForMatchedField extends WithoutSpecification {
+
+ /**
+ * A matcher for any field that should be substituted.
+ */
+ private final ElementMatcher<? super FieldDescription.InDefinedShape> matcher;
+
+ /**
+ * {@code true} if read access to a field should be substituted.
+ */
+ private final boolean matchRead;
+
+ /**
+ * {@code true} if write access to a field should be substituted.
+ */
+ private final boolean matchWrite;
+
+ /**
+ * Creates a new member substitution for a matched field that requires a specification for how to perform a substitution.
+ *
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found.
+ * @param substitution The substitution to apply.
+ * @param matcher A matcher for any field that should be substituted.
+ */
+ protected ForMatchedField(MethodGraph.Compiler methodGraphCompiler,
+ boolean strict,
+ Substitution substitution,
+ ElementMatcher<? super FieldDescription.InDefinedShape> matcher) {
+ this(methodGraphCompiler, strict, substitution, matcher, true, true);
+ }
+
+ /**
+ * Creates a new member substitution for a matched field that requires a specification for how to perform a substitution.
+ *
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found.
+ * @param substitution The substitution to apply.
+ * @param matcher A matcher for any field that should be substituted.
+ * @param matchRead {@code true} if read access to a field should be substituted.
+ * @param matchWrite {@code true} if write access to a field should be substituted.
+ */
+ protected ForMatchedField(MethodGraph.Compiler methodGraphCompiler,
+ boolean strict,
+ Substitution substitution,
+ ElementMatcher<? super FieldDescription.InDefinedShape> matcher,
+ boolean matchRead,
+ boolean matchWrite) {
+ super(methodGraphCompiler, strict, substitution);
+ this.matcher = matcher;
+ this.matchRead = matchRead;
+ this.matchWrite = matchWrite;
+ }
+
+ /**
+ * When invoked, only read access of the previously matched field is substituted.
+ *
+ * @return This instance with the limitation that only read access to the matched field is substituted.
+ */
+ public WithoutSpecification onRead() {
+ return new ForMatchedField(methodGraphCompiler, strict, substitution, matcher, true, false);
+ }
+
+ /**
+ * When invoked, only write access of the previously matched field is substituted.
+ *
+ * @return This instance with the limitation that only write access to the matched field is substituted.
+ */
+ public WithoutSpecification onWrite() {
+ return new ForMatchedField(methodGraphCompiler, strict, substitution, matcher, false, true);
+ }
+
+ @Override
+ protected Substitution doStub() {
+ return Substitution.ForElementMatchers.ofField(matcher, matchRead, matchWrite, Substitution.Resolver.Stubbing.INSTANCE);
+ }
+
+ @Override
+ protected Substitution doReplaceWith(FieldDescription fieldDescription) {
+ return Substitution.ForElementMatchers.ofField(matcher, matchRead, matchWrite, new Substitution.Resolver.FieldAccessing(fieldDescription));
+ }
+
+ @Override
+ protected Substitution doReplaceWith(MethodDescription methodDescription) {
+ return Substitution.ForElementMatchers.ofField(matcher, matchRead, matchWrite, new Substitution.Resolver.MethodInvoking(methodDescription));
+ }
+ }
+
+ /**
+ * Describes a member substitution that requires a specification for how to replace a method or constructor.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ public static class ForMatchedMethod extends WithoutSpecification {
+
+ /**
+ * A matcher for any method or constructor that should be substituted.
+ */
+ private final ElementMatcher<? super MethodDescription> matcher;
+
+ /**
+ * {@code true} if this specification includes virtual invocations.
+ */
+ private final boolean includeVirtualCalls;
+
+ /**
+ * {@code true} if this specification includes {@code super} invocations.
+ */
+ private final boolean includeSuperCalls;
+
+ /**
+ * Creates a new member substitution for a matched method that requires a specification for how to perform a substitution.
+ *
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found.
+ * @param substitution The substitution to apply.
+ * @param matcher A matcher for any method or constructor that should be substituted.
+ */
+ protected ForMatchedMethod(MethodGraph.Compiler methodGraphCompiler,
+ boolean strict,
+ Substitution substitution,
+ ElementMatcher<? super MethodDescription> matcher) {
+ this(methodGraphCompiler, strict, substitution, matcher, true, true);
+ }
+
+ /**
+ * Creates a new member substitution for a matched method that requires a specification for how to perform a substitution.
+ *
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found.
+ * @param substitution The substitution to apply.
+ * @param matcher A matcher for any method or constructor that should be substituted.
+ * @param includeVirtualCalls {@code true} if this specification includes virtual invocations.
+ * @param includeSuperCalls {@code true} if this specification includes {@code super} invocations.
+ */
+ protected ForMatchedMethod(MethodGraph.Compiler methodGraphCompiler,
+ boolean strict,
+ Substitution substitution,
+ ElementMatcher<? super MethodDescription> matcher,
+ boolean includeVirtualCalls,
+ boolean includeSuperCalls) {
+ super(methodGraphCompiler, strict, substitution);
+ this.matcher = matcher;
+ this.includeVirtualCalls = includeVirtualCalls;
+ this.includeSuperCalls = includeSuperCalls;
+ }
+
+ /**
+ * Limits the substituted method calls to method calls that invoke a method virtually (as opposed to a {@code super} invocation).
+ *
+ * @return This specification where only virtual methods are matched if they are not invoked as a virtual call.
+ */
+ public WithoutSpecification onVirtualCall() {
+ return new ForMatchedMethod(methodGraphCompiler, strict, substitution, isVirtual().and(matcher), true, false);
+ }
+
+ /**
+ * Limits the substituted method calls to method calls that invoke a method as a {@code super} call.
+ *
+ * @return This specification where only virtual methods are matched if they are not invoked as a super call.
+ */
+ public WithoutSpecification onSuperCall() {
+ return new ForMatchedMethod(methodGraphCompiler, strict, substitution, isVirtual().and(matcher), false, true);
+ }
+
+ @Override
+ protected Substitution doStub() {
+ return Substitution.ForElementMatchers.ofMethod(matcher, includeVirtualCalls, includeSuperCalls, Substitution.Resolver.Stubbing.INSTANCE);
+ }
+
+ @Override
+ protected Substitution doReplaceWith(FieldDescription fieldDescription) {
+ return Substitution.ForElementMatchers.ofMethod(matcher,
+ includeVirtualCalls,
+ includeSuperCalls,
+ new Substitution.Resolver.FieldAccessing(fieldDescription));
+ }
+
+ @Override
+ protected Substitution doReplaceWith(MethodDescription methodDescription) {
+ return Substitution.ForElementMatchers.ofMethod(matcher,
+ includeVirtualCalls,
+ includeSuperCalls,
+ new Substitution.Resolver.MethodInvoking(methodDescription));
+ }
+ }
+ }
+
+ /**
+ * Resolves an actual substitution.
+ */
+ protected interface Substitution {
+
+ /**
+ * Resolves a field access within a method body.
+ *
+ * @param fieldDescription The field being accessed.
+ * @param writeAccess {@code true} if the access is for writing to the field, {@code false} if the field is read.
+ * @return A resolver for the supplied field access.
+ */
+ Resolver resolve(FieldDescription.InDefinedShape fieldDescription, boolean writeAccess);
+
+ /**
+ * Resolves a method invocation within a method body.
+ *
+ * @param methodDescription The method being invoked.
+ * @param invocationType The method's invocation type.
+ * @return A resolver for the supplied method invocation.
+ */
+ Resolver resolve(MethodDescription methodDescription, InvocationType invocationType);
+
+ /**
+ * A resolver supplies an implementation for a substitution.
+ */
+ interface Resolver {
+
+ /**
+ * Checks if this resolver was actually resolved, i.e. if a member should be substituted at all.
+ *
+ * @return {@code true} if a found member should be substituted.
+ */
+ boolean isResolved();
+
+ /**
+ * Applies this resolver. This is only legal for resolved resolvers.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param target The substituted byte code element.
+ * @param arguments The factual arguments to the byte code element.
+ * @param result The expected result type or {@code void} if no result is expected.
+ * @return A stack manipulation that applies the resolved byte code representing the substitution.
+ */
+ StackManipulation apply(TypeDescription instrumentedType,
+ ByteCodeElement target,
+ TypeList.Generic arguments,
+ TypeDescription.Generic result);
+
+ /**
+ * An unresolved resolver that does not apply a substitution.
+ */
+ enum Unresolved implements Resolver {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public boolean isResolved() {
+ return false;
+ }
+
+ @Override
+ public StackManipulation apply(TypeDescription instrumentedType,
+ ByteCodeElement target,
+ TypeList.Generic arguments,
+ TypeDescription.Generic result) {
+ throw new IllegalStateException("Cannot apply unresolved resolver");
+ }
+ }
+
+ /**
+ * A resolver that stubs any interaction with a byte code element.
+ */
+ enum Stubbing implements Resolver {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public boolean isResolved() {
+ return true;
+ }
+
+ @Override
+ public StackManipulation apply(TypeDescription instrumentedType,
+ ByteCodeElement target,
+ TypeList.Generic arguments,
+ TypeDescription.Generic result) {
+ List<StackManipulation> stackManipulations = new ArrayList<StackManipulation>(arguments.size());
+ for (int index = arguments.size() - 1; index >= 0; index--) {
+ stackManipulations.add(Removal.of(arguments.get(index)));
+ }
+ return new StackManipulation.Compound(CompoundList.of(stackManipulations, DefaultValue.of(result.asErasure())));
+ }
+ }
+
+ /**
+ * A resolver that replaces an interaction with a byte code element with a field access.
+ */
+ @EqualsAndHashCode
+ class FieldAccessing implements Resolver {
+
+ /**
+ * The field that is used for substitution.
+ */
+ private final FieldDescription fieldDescription;
+
+ /**
+ * Creates a resolver for a field access.
+ *
+ * @param fieldDescription The field that is used for substitution.
+ */
+ protected FieldAccessing(FieldDescription fieldDescription) {
+ this.fieldDescription = fieldDescription;
+ }
+
+ @Override
+ public boolean isResolved() {
+ return true;
+ }
+
+ @Override
+ public StackManipulation apply(TypeDescription instrumentedType,
+ ByteCodeElement target,
+ TypeList.Generic arguments,
+ TypeDescription.Generic result) {
+ if (!fieldDescription.isAccessibleTo(instrumentedType)) {
+ throw new IllegalStateException(instrumentedType + " cannot access " + fieldDescription);
+ } else if (result.represents(void.class)) {
+ if (arguments.size() != (fieldDescription.isStatic() ? 1 : 2)) {
+ throw new IllegalStateException("Cannot set " + fieldDescription + " with " + arguments);
+ } else if (!fieldDescription.isStatic() && !arguments.get(0).asErasure().isAssignableTo(fieldDescription.getDeclaringType().asErasure())) {
+ throw new IllegalStateException("Cannot set " + fieldDescription + " on " + arguments.get(0));
+ } else if (!arguments.get(fieldDescription.isStatic() ? 0 : 1).asErasure().isAssignableTo(fieldDescription.getType().asErasure())) {
+ throw new IllegalStateException("Cannot set " + fieldDescription + " to " + arguments.get(fieldDescription.isStatic() ? 0 : 1));
+ }
+ return FieldAccess.forField(fieldDescription).write();
+ } else {
+ if (arguments.size() != (fieldDescription.isStatic() ? 0 : 1)) {
+ throw new IllegalStateException("Cannot set " + fieldDescription + " with " + arguments);
+ } else if (!fieldDescription.isStatic() && !arguments.get(0).asErasure().isAssignableTo(fieldDescription.getDeclaringType().asErasure())) {
+ throw new IllegalStateException("Cannot get " + fieldDescription + " on " + arguments.get(0));
+ } else if (!fieldDescription.getType().asErasure().isAssignableTo(result.asErasure())) {
+ throw new IllegalStateException("Cannot get " + fieldDescription + " as " + result);
+ }
+ return FieldAccess.forField(fieldDescription).read();
+ }
+ }
+ }
+
+ /**
+ * A resolver that invokes a method.
+ */
+ @EqualsAndHashCode
+ class MethodInvoking implements Resolver {
+
+ /**
+ * The method that is used for substitution.
+ */
+ private final MethodDescription methodDescription;
+
+ /**
+ * Creates a resolver for a method invocation.
+ *
+ * @param methodDescription The method that is used for substitution.
+ */
+ protected MethodInvoking(MethodDescription methodDescription) {
+ this.methodDescription = methodDescription;
+ }
+
+ @Override
+ public boolean isResolved() {
+ return true;
+ }
+
+ @Override
+ public StackManipulation apply(TypeDescription instrumentedType,
+ ByteCodeElement target,
+ TypeList.Generic arguments,
+ TypeDescription.Generic result) {
+ if (!methodDescription.isAccessibleTo(instrumentedType)) {
+ throw new IllegalStateException(instrumentedType + " cannot access " + methodDescription);
+ }
+ TypeList.Generic mapped = methodDescription.isStatic()
+ ? methodDescription.getParameters().asTypeList()
+ : new TypeList.Generic.Explicit(CompoundList.of(methodDescription.getDeclaringType(), methodDescription.getParameters().asTypeList()));
+ if (!methodDescription.getReturnType().asErasure().isAssignableTo(result.asErasure())) {
+ throw new IllegalStateException("Cannot assign return value of " + methodDescription + " to " + result);
+ } else if (mapped.size() != arguments.size()) {
+ throw new IllegalStateException("Cannot invoke " + methodDescription + " on " + arguments);
+ }
+ for (int index = 0; index < mapped.size(); index++) {
+ if (!mapped.get(index).asErasure().isAssignableTo(arguments.get(index).asErasure())) {
+ throw new IllegalStateException("Cannot invoke " + methodDescription + " on " + arguments);
+ }
+ }
+ return methodDescription.isVirtual()
+ ? MethodInvocation.invoke(methodDescription).virtual(target.getDeclaringType().asErasure())
+ : MethodInvocation.invoke(methodDescription);
+ }
+ }
+ }
+
+ /**
+ * Determines a method's invocation type.
+ */
+ enum InvocationType {
+
+ /**
+ * Indicates that a method is called virtually.
+ */
+ VIRTUAL,
+
+ /**
+ * Indicates that a method is called via a super method call.
+ */
+ SUPER,
+
+ /**
+ * Indicates that an invoked method is not a virtual method.
+ */
+ OTHER;
+
+ /**
+ * Creates an invocation type.
+ *
+ * @param opcode The method call's opcode.
+ * @param methodDescription The method being invoked.
+ * @return The method's invocation type.
+ */
+ protected static InvocationType of(int opcode, MethodDescription methodDescription) {
+ switch (opcode) {
+ case Opcodes.INVOKEVIRTUAL:
+ case Opcodes.INVOKEINTERFACE:
+ return InvocationType.VIRTUAL;
+ case Opcodes.INVOKESPECIAL:
+ return methodDescription.isVirtual()
+ ? SUPER
+ : OTHER;
+ default:
+ return OTHER;
+ }
+ }
+
+ /**
+ * Determines if a method is matched by this invocation type.
+ *
+ * @param includeVirtualCalls {@code true} if virtual calls are included.
+ * @param includeSuperCalls {@code true} if super method calls are included.
+ * @return {@code true} if this instance matches the given setup.
+ */
+ protected boolean matches(boolean includeVirtualCalls, boolean includeSuperCalls) {
+ switch (this) {
+ case VIRTUAL:
+ return includeVirtualCalls;
+ case SUPER:
+ return includeSuperCalls;
+ default:
+ return true;
+ }
+ }
+ }
+
+ /**
+ * A substution that does not substitute any byte code elements.
+ */
+ enum NoOp implements Substitution {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Resolver resolve(FieldDescription.InDefinedShape fieldDescription, boolean writeAccess) {
+ return Resolver.Unresolved.INSTANCE;
+ }
+
+ @Override
+ public Resolver resolve(MethodDescription methodDescription, InvocationType invocationType) {
+ return Resolver.Unresolved.INSTANCE;
+ }
+ }
+
+ /**
+ * A substitution that uses element matchers for determining if a byte code element should be substituted.
+ */
+ @EqualsAndHashCode
+ class ForElementMatchers implements Substitution {
+
+ /**
+ * A matcher to determine field substitution.
+ */
+ private final ElementMatcher<? super FieldDescription.InDefinedShape> fieldMatcher;
+
+ /**
+ * A matcher to determine method substituion.
+ */
+ private final ElementMatcher<? super MethodDescription> methodMatcher;
+
+ /**
+ * {@code true} if field read access should be substituted.
+ */
+ private final boolean matchFieldRead;
+
+ /**
+ * {@code true} if field write access should be substituted.
+ */
+ private final boolean matchFieldWrite;
+
+ /**
+ * {@code true} if virtual method calls should be substituted.
+ */
+ private final boolean includeVirtualCalls;
+
+ /**
+ * {@code true} if super method calls should be substituted.
+ */
+ private final boolean includeSuperCalls;
+
+ /**
+ * The resolver to apply on elements to substitute.
+ */
+ private final Resolver resolver;
+
+ /**
+ * Creates a substitution for any byte code element that matches the supplied matcher.
+ *
+ * @param matcher The matcher to determine the substituted byte code elements.
+ * @param resolver The resolver to apply on elements to substitute.
+ * @return A substitution for all matched byte code elements.
+ */
+ protected static Substitution of(ElementMatcher<? super ByteCodeElement> matcher, Resolver resolver) {
+ return new ForElementMatchers(matcher, matcher, true, true, true, true, resolver);
+ }
+
+ /**
+ * Creates a substitution for any method that matches the supplied matcher.
+ *
+ * @param matcher The matcher to determine the substituted fields.
+ * @param matchFieldRead {@code true} if field read access should be substituted.
+ * @param matchFieldWrite {@code true} if field write access should be substituted.
+ * @param resolver The resolver to apply on fields to substitute.
+ * @return A substitution for all matched fields.
+ */
+ protected static Substitution ofField(ElementMatcher<? super FieldDescription.InDefinedShape> matcher,
+ boolean matchFieldRead,
+ boolean matchFieldWrite,
+ Resolver resolver) {
+ return new ForElementMatchers(matcher, none(), matchFieldRead, matchFieldWrite, false, false, resolver);
+ }
+
+ /**
+ * Creates a substitution for any method that matches the supplied matcher.
+ *
+ * @param matcher The matcher to determine the substituted fields.
+ * @param includeVirtualCalls {@code true} if virtual method calls should be substituted.
+ * @param includeSuperCalls {@code true} if super method calls should be substituted.
+ * @param resolver The resolver to apply on fields to substitute.
+ * @return A substitution for all matched fields.
+ */
+ protected static Substitution ofMethod(ElementMatcher<? super MethodDescription> matcher,
+ boolean includeVirtualCalls,
+ boolean includeSuperCalls,
+ Resolver resolver) {
+ return new ForElementMatchers(none(), matcher, false, false, includeVirtualCalls, includeSuperCalls, resolver);
+ }
+
+ /**
+ * Creates a new subsitution that applies element matchers to determine what byte code elements to substitute.
+ *
+ * @param fieldMatcher The field matcher to determine fields to substitute.
+ * @param methodMatcher The method matcher to determine methods to substitute.
+ * @param matchFieldRead {@code true} if field read access should be substituted.
+ * @param matchFieldWrite {@code true} if field write access should be substituted.
+ * @param includeVirtualCalls {@code true} if virtual method calls should be substituted.
+ * @param includeSuperCalls {@code true} if super method calls should be substituted.
+ * @param resolver The resolver to apply on elements to substitute.
+ */
+ protected ForElementMatchers(ElementMatcher<? super FieldDescription.InDefinedShape> fieldMatcher,
+ ElementMatcher<? super MethodDescription> methodMatcher,
+ boolean matchFieldRead,
+ boolean matchFieldWrite,
+ boolean includeVirtualCalls,
+ boolean includeSuperCalls,
+ Resolver resolver) {
+ this.fieldMatcher = fieldMatcher;
+ this.methodMatcher = methodMatcher;
+ this.matchFieldRead = matchFieldRead;
+ this.matchFieldWrite = matchFieldWrite;
+ this.includeVirtualCalls = includeVirtualCalls;
+ this.includeSuperCalls = includeSuperCalls;
+ this.resolver = resolver;
+ }
+
+ @Override
+ public Resolver resolve(FieldDescription.InDefinedShape fieldDescription, boolean writeAccess) {
+ return (writeAccess ? matchFieldWrite : matchFieldRead) && fieldMatcher.matches(fieldDescription)
+ ? resolver
+ : Resolver.Unresolved.INSTANCE;
+ }
+
+ @Override
+ public Resolver resolve(MethodDescription methodDescription, InvocationType invocationType) {
+ return invocationType.matches(includeVirtualCalls, includeSuperCalls) && methodMatcher.matches(methodDescription)
+ ? resolver
+ : Resolver.Unresolved.INSTANCE;
+ }
+ }
+
+ /**
+ * A compound substitution.
+ */
+ @EqualsAndHashCode
+ class Compound implements Substitution {
+
+ /**
+ * The substitutions to apply in their application order.
+ */
+ private final List<Substitution> substitutions;
+
+ /**
+ * Creates a new compound substitution.
+ *
+ * @param substitution The substitutions to apply in their application order.
+ */
+ protected Compound(Substitution... substitution) {
+ this(Arrays.asList(substitution));
+ }
+
+ /**
+ * Creates a new compound substitution.
+ *
+ * @param substitutions The substitutions to apply in their application order.
+ */
+ protected Compound(List<? extends Substitution> substitutions) {
+ this.substitutions = new ArrayList<Substitution>(substitutions.size());
+ for (Substitution substitution : substitutions) {
+ if (substitution instanceof Compound) {
+ this.substitutions.addAll(((Compound) substitution).substitutions);
+ } else if (!(substitution instanceof NoOp)) {
+ this.substitutions.add(substitution);
+ }
+ }
+ }
+
+ @Override
+ public Resolver resolve(FieldDescription.InDefinedShape fieldDescription, boolean writeAccess) {
+ for (Substitution substitution : substitutions) {
+ Resolver resolver = substitution.resolve(fieldDescription, writeAccess);
+ if (resolver.isResolved()) {
+ return resolver;
+ }
+ }
+ return Resolver.Unresolved.INSTANCE;
+ }
+
+ @Override
+ public Resolver resolve(MethodDescription methodDescription, InvocationType invocationType) {
+ for (Substitution substitution : substitutions) {
+ Resolver resolver = substitution.resolve(methodDescription, invocationType);
+ if (resolver.isResolved()) {
+ return resolver;
+ }
+ }
+ return Resolver.Unresolved.INSTANCE;
+ }
+ }
+ }
+
+ /**
+ * A method visitor that applies a substitution for matched methods.
+ */
+ protected static class SubstitutingMethodVisitor extends MethodVisitor {
+
+ /**
+ * The method graph compiler to use.
+ */
+ private final MethodGraph.Compiler methodGraphCompiler;
+
+ /**
+ * {@code true} if the method processing should be strict where an exception is raised if a member cannot be found.
+ */
+ private final boolean strict;
+
+ /**
+ * The substitution to apply.
+ */
+ private final Substitution substitution;
+
+ /**
+ * The instrumented type.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * The implementation context to use.
+ */
+ private final Implementation.Context implementationContext;
+
+ /**
+ * The type pool to use.
+ */
+ private final TypePool typePool;
+
+ /**
+ * An additional buffer for the operand stack that is required.
+ */
+ private int stackSizeBuffer;
+
+ /**
+ * Creates a new substituting method visitor.
+ *
+ * @param methodVisitor The method visitor to delegate to.
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found.
+ * @param substitution The substitution to apply.
+ * @param instrumentedType The instrumented type.
+ * @param implementationContext The implementation context to use.
+ * @param typePool The type pool to use.
+ */
+ protected SubstitutingMethodVisitor(MethodVisitor methodVisitor,
+ MethodGraph.Compiler methodGraphCompiler,
+ boolean strict,
+ Substitution substitution,
+ TypeDescription instrumentedType,
+ Implementation.Context implementationContext,
+ TypePool typePool) {
+ super(Opcodes.ASM5, methodVisitor);
+ this.methodGraphCompiler = methodGraphCompiler;
+ this.strict = strict;
+ this.substitution = substitution;
+ this.instrumentedType = instrumentedType;
+ this.implementationContext = implementationContext;
+ this.typePool = typePool;
+ stackSizeBuffer = 0;
+ }
+
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String internalName, String descriptor) {
+ TypePool.Resolution resolution = typePool.describe(owner.replace('/', '.'));
+ if (resolution.isResolved()) {
+ FieldList<FieldDescription.InDefinedShape> candidates = resolution.resolve()
+ .getDeclaredFields()
+ .filter(named(internalName).and(hasDescriptor(descriptor)));
+ if (!candidates.isEmpty()) {
+ Substitution.Resolver resolver = substitution.resolve(candidates.getOnly(), opcode == Opcodes.PUTFIELD || opcode == Opcodes.PUTSTATIC);
+ if (resolver.isResolved()) {
+ TypeList.Generic arguments;
+ TypeDescription.Generic result;
+ switch (opcode) {
+ case Opcodes.PUTFIELD:
+ arguments = new TypeList.Generic.Explicit(candidates.getOnly().getDeclaringType(), candidates.getOnly().getType());
+ result = TypeDescription.Generic.VOID;
+ break;
+ case Opcodes.PUTSTATIC:
+ arguments = new TypeList.Generic.Explicit(candidates.getOnly().getType());
+ result = TypeDescription.Generic.VOID;
+ break;
+ case Opcodes.GETFIELD:
+ arguments = new TypeList.Generic.Explicit(candidates.getOnly().getDeclaringType());
+ result = candidates.getOnly().getType();
+ break;
+ case Opcodes.GETSTATIC:
+ arguments = new TypeList.Generic.Empty();
+ result = candidates.getOnly().getType();
+ break;
+ default:
+ throw new AssertionError();
+ }
+ resolver.apply(instrumentedType, candidates.getOnly(), arguments, result).apply(mv, implementationContext);
+ return;
+ }
+ } else if (strict) {
+ throw new IllegalStateException("Could not resolve " + owner.replace('/', '.')
+ + "." + internalName + descriptor + " using " + typePool);
+ }
+ } else if (strict) {
+ throw new IllegalStateException("Could not resolve " + owner.replace('/', '.') + " using " + typePool);
+ }
+ super.visitFieldInsn(opcode, owner, internalName, descriptor);
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String internalName, String descriptor, boolean isInterface) {
+ TypePool.Resolution resolution = typePool.describe(owner.replace('/', '.'));
+ if (resolution.isResolved()) {
+ MethodList<?> candidates;
+ if (opcode == Opcodes.INVOKESPECIAL && internalName.equals(MethodDescription.CONSTRUCTOR_INTERNAL_NAME)) {
+ candidates = resolution.resolve()
+ .getDeclaredMethods()
+ .filter(isConstructor().and(hasDescriptor(descriptor)));
+ } else if (opcode == Opcodes.INVOKESTATIC || opcode == Opcodes.INVOKESPECIAL) {
+ candidates = resolution.resolve()
+ .getDeclaredMethods()
+ .filter(named(internalName).and(hasDescriptor(descriptor)));
+ } else {
+ candidates = methodGraphCompiler.compile(resolution.resolve())
+ .listNodes()
+ .asMethodList()
+ .filter(named(internalName).and(hasDescriptor(descriptor)));
+ }
+ if (!candidates.isEmpty()) {
+ Substitution.Resolver resolver = substitution.resolve(candidates.getOnly(), Substitution.InvocationType.of(opcode, candidates.getOnly()));
+ if (resolver.isResolved()) {
+ resolver.apply(instrumentedType,
+ candidates.getOnly(),
+ candidates.getOnly().isStatic() || candidates.getOnly().isConstructor()
+ ? candidates.getOnly().getParameters().asTypeList()
+ : new TypeList.Generic.Explicit(CompoundList.of(candidates.getOnly().getDeclaringType(), candidates.getOnly().getParameters().asTypeList())),
+ candidates.getOnly().isConstructor()
+ ? candidates.getOnly().getDeclaringType().asGenericType()
+ : candidates.getOnly().getReturnType()).apply(mv, implementationContext);
+ if (candidates.getOnly().isConstructor()) {
+ stackSizeBuffer = new StackManipulation.Compound(
+ Duplication.SINGLE.flipOver(TypeDescription.OBJECT),
+ Removal.SINGLE,
+ Removal.SINGLE,
+ Duplication.SINGLE.flipOver(TypeDescription.OBJECT),
+ Removal.SINGLE,
+ Removal.SINGLE
+ ).apply(mv, implementationContext).getMaximalSize() + StackSize.SINGLE.getSize();
+ }
+ return;
+ }
+ } else if (strict) {
+ throw new IllegalStateException("Could not resolve " + owner.replace('/', '.')
+ + "." + internalName + descriptor + " using " + typePool);
+ }
+ } else if (strict) {
+ throw new IllegalStateException("Could not resolve " + owner.replace('/', '.') + " using " + typePool);
+ }
+ super.visitMethodInsn(opcode, owner, internalName, descriptor, isInterface);
+ }
+
+ @Override
+ public void visitMaxs(int maxStack, int maxLocals) {
+ super.visitMaxs(maxStack + stackSizeBuffer, maxLocals);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/asm/ModifierAdjustment.java b/byte-buddy-dep/src/main/java/net/bytebuddy/asm/ModifierAdjustment.java
new file mode 100644
index 0000000..b9c993a
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/asm/ModifierAdjustment.java
@@ -0,0 +1,481 @@
+package net.bytebuddy.asm;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.modifier.ModifierContributor;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.pool.TypePool;
+import net.bytebuddy.utility.CompoundList;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.*;
+
+import static net.bytebuddy.matcher.ElementMatchers.*;
+
+/**
+ * <p>
+ * A visitor wrapper that adjusts the modifiers of the instrumented type or its members.
+ * </p>
+ * <p>
+ * <b>Important</b>: The removal of the method is not reflected in the created {@link net.bytebuddy.dynamic.DynamicType}'s
+ * type description of the instrumented type.
+ * </p>
+ *
+ * @see net.bytebuddy.dynamic.Transformer.ForField#withModifiers(ModifierContributor.ForField...)
+ * @see net.bytebuddy.dynamic.Transformer.ForMethod#withModifiers(ModifierContributor.ForMethod...)
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class ModifierAdjustment extends AsmVisitorWrapper.AbstractBase {
+
+ /**
+ * A list of adjustments to apply to the instrumented type.
+ */
+ private final List<Adjustment<TypeDescription>> typeAdjustments;
+
+ /**
+ * A list of adjustments to apply to the instrumented type's declared fields.
+ */
+ private final List<Adjustment<FieldDescription.InDefinedShape>> fieldAdjustments;
+
+ /**
+ * A list of adjustments to apply to the instrumented type's methods.
+ */
+ private final List<Adjustment<MethodDescription>> methodAdjustments;
+
+ /**
+ * Creates a new modifier adjustment that does not adjust any modifiers.
+ */
+ public ModifierAdjustment() {
+ this(Collections.<Adjustment<TypeDescription>>emptyList(),
+ Collections.<Adjustment<FieldDescription.InDefinedShape>>emptyList(),
+ Collections.<Adjustment<MethodDescription>>emptyList());
+ }
+
+ /**
+ * Creates a new modifier adjustment.
+ *
+ * @param typeAdjustments A list of adjustments to apply to the instrumented type.
+ * @param fieldAdjustments A list of adjustments to apply to the instrumented type's declared fields.
+ * @param methodAdjustments A list of adjustments to apply to the instrumented type's methods.
+ */
+ protected ModifierAdjustment(List<Adjustment<TypeDescription>> typeAdjustments,
+ List<Adjustment<FieldDescription.InDefinedShape>> fieldAdjustments,
+ List<Adjustment<MethodDescription>> methodAdjustments) {
+ this.typeAdjustments = typeAdjustments;
+ this.fieldAdjustments = fieldAdjustments;
+ this.methodAdjustments = methodAdjustments;
+ }
+
+ /**
+ * Adjusts any instrumented type's modifiers.
+ *
+ * @param modifierContributor The modifier contributors to enforce.
+ * @return A new modifier adjustment that enforces the given modifier contributors and any previous adjustments.
+ */
+ public ModifierAdjustment withTypeModifiers(ModifierContributor.ForType... modifierContributor) {
+ return withTypeModifiers(Arrays.asList(modifierContributor));
+ }
+
+ /**
+ * Adjusts any instrumented type's modifiers.
+ *
+ * @param modifierContributors The modifier contributors to enforce.
+ * @return A new modifier adjustment that enforces the given modifier contributors and any previous adjustments.
+ */
+ public ModifierAdjustment withTypeModifiers(List<? extends ModifierContributor.ForType> modifierContributors) {
+ return withTypeModifiers(any(), modifierContributors);
+ }
+
+ /**
+ * Adjusts an instrumented type's modifiers if it matches the supplied matcher.
+ *
+ * @param matcher The matcher that determines a type's eligibility.
+ * @param modifierContributor The modifier contributors to enforce.
+ * @return A new modifier adjustment that enforces the given modifier contributors and any previous adjustments.
+ */
+ public ModifierAdjustment withTypeModifiers(ElementMatcher<? super TypeDescription> matcher,
+ ModifierContributor.ForType... modifierContributor) {
+ return withTypeModifiers(matcher, Arrays.asList(modifierContributor));
+ }
+
+ /**
+ * Adjusts an instrumented type's modifiers if it matches the supplied matcher.
+ *
+ * @param matcher The matcher that determines a type's eligibility.
+ * @param modifierContributors The modifier contributors to enforce.
+ * @return A new modifier adjustment that enforces the given modifier contributors and any previous adjustments.
+ */
+ public ModifierAdjustment withTypeModifiers(ElementMatcher<? super TypeDescription> matcher,
+ List<? extends ModifierContributor.ForType> modifierContributors) {
+ return new ModifierAdjustment(CompoundList.of(typeAdjustments, new Adjustment<TypeDescription>(matcher,
+ ModifierContributor.Resolver.of(modifierContributors))), fieldAdjustments, methodAdjustments);
+ }
+
+ /**
+ * Adjusts any field's modifiers.
+ *
+ * @param modifierContributor The modifier contributors to enforce.
+ * @return A new modifier adjustment that enforces the given modifier contributors and any previous adjustments.
+ */
+ public ModifierAdjustment withFieldModifiers(ModifierContributor.ForField... modifierContributor) {
+ return withFieldModifiers(Arrays.asList(modifierContributor));
+ }
+
+ /**
+ * Adjusts any field's modifiers.
+ *
+ * @param modifierContributors The modifier contributors to enforce.
+ * @return A new modifier adjustment that enforces the given modifier contributors and any previous adjustments.
+ */
+ public ModifierAdjustment withFieldModifiers(List<? extends ModifierContributor.ForField> modifierContributors) {
+ return withFieldModifiers(any(), modifierContributors);
+ }
+
+ /**
+ * Adjusts a field's modifiers if it fulfills the supplied matcher.
+ *
+ * @param matcher The matcher that determines if a field's modifiers should be adjusted.
+ * @param modifierContributor The modifier contributors to enforce.
+ * @return A new modifier adjustment that enforces the given modifier contributors and any previous adjustments.
+ */
+ public ModifierAdjustment withFieldModifiers(ElementMatcher<? super FieldDescription.InDefinedShape> matcher,
+ ModifierContributor.ForField... modifierContributor) {
+ return withFieldModifiers(matcher, Arrays.asList(modifierContributor));
+ }
+
+ /**
+ * Adjusts a field's modifiers if it fulfills the supplied matcher.
+ *
+ * @param matcher The matcher that determines if a field's modifiers should be adjusted.
+ * @param modifierContributors The modifier contributors to enforce.
+ * @return A new modifier adjustment that enforces the given modifier contributors and any previous adjustments.
+ */
+ public ModifierAdjustment withFieldModifiers(ElementMatcher<? super FieldDescription.InDefinedShape> matcher,
+ List<? extends ModifierContributor.ForField> modifierContributors) {
+ return new ModifierAdjustment(typeAdjustments, CompoundList.of(fieldAdjustments, new Adjustment<FieldDescription.InDefinedShape>(matcher,
+ ModifierContributor.Resolver.of(modifierContributors))), methodAdjustments);
+ }
+
+ /**
+ * Adjusts any method's modifiers.
+ *
+ * @param modifierContributor The modifier contributors to enforce.
+ * @return A new modifier adjustment that enforces the given modifier contributors and any previous adjustments.
+ */
+ public ModifierAdjustment withMethodModifiers(ModifierContributor.ForMethod... modifierContributor) {
+ return withMethodModifiers(Arrays.asList(modifierContributor));
+ }
+
+ /**
+ * Adjusts any method's modifiers.
+ *
+ * @param modifierContributors The modifier contributors to enforce.
+ * @return A new modifier adjustment that enforces the given modifier contributors and any previous adjustments.
+ */
+ public ModifierAdjustment withMethodModifiers(List<? extends ModifierContributor.ForMethod> modifierContributors) {
+ return withMethodModifiers(any(), modifierContributors);
+ }
+
+ /**
+ * Adjusts a method's modifiers if it fulfills the supplied matcher.
+ *
+ * @param matcher The matcher that determines if a method's modifiers should be adjusted.
+ * @param modifierContributor The modifier contributors to enforce.
+ * @return A new modifier adjustment that enforces the given modifier contributors and any previous adjustments.
+ */
+ public ModifierAdjustment withMethodModifiers(ElementMatcher<? super MethodDescription> matcher,
+ ModifierContributor.ForMethod... modifierContributor) {
+ return withMethodModifiers(matcher, Arrays.asList(modifierContributor));
+ }
+
+ /**
+ * Adjusts a method's modifiers if it fulfills the supplied matcher.
+ *
+ * @param matcher The matcher that determines if a method's modifiers should be adjusted.
+ * @param modifierContributors The modifier contributors to enforce.
+ * @return A new modifier adjustment that enforces the given modifier contributors and any previous adjustments.
+ */
+ public ModifierAdjustment withMethodModifiers(ElementMatcher<? super MethodDescription> matcher,
+ List<? extends ModifierContributor.ForMethod> modifierContributors) {
+ return withInvokableModifiers(isMethod().and(matcher), modifierContributors);
+ }
+
+ /**
+ * Adjusts any constructor's modifiers.
+ *
+ * @param modifierContributor The modifier contributors to enforce.
+ * @return A new modifier adjustment that enforces the given modifier contributors and any previous adjustments.
+ */
+ public ModifierAdjustment withConstructorModifiers(ModifierContributor.ForMethod... modifierContributor) {
+ return withConstructorModifiers(Arrays.asList(modifierContributor));
+ }
+
+ /**
+ * Adjusts any constructor's modifiers.
+ *
+ * @param modifierContributors The modifier contributors to enforce.
+ * @return A new modifier adjustment that enforces the given modifier contributors and any previous adjustments.
+ */
+ public ModifierAdjustment withConstructorModifiers(List<? extends ModifierContributor.ForMethod> modifierContributors) {
+ return withConstructorModifiers(any(), modifierContributors);
+ }
+
+ /**
+ * Adjusts a constructor's modifiers if it fulfills the supplied matcher.
+ *
+ * @param matcher The matcher that determines if a constructor's modifiers should be adjusted.
+ * @param modifierContributor The modifier contributors to enforce.
+ * @return A new modifier adjustment that enforces the given modifier contributors and any previous adjustments.
+ */
+ public ModifierAdjustment withConstructorModifiers(ElementMatcher<? super MethodDescription> matcher,
+ ModifierContributor.ForMethod... modifierContributor) {
+ return withConstructorModifiers(matcher, Arrays.asList(modifierContributor));
+ }
+
+ /**
+ * Adjusts a constructor's modifiers if it fulfills the supplied matcher.
+ *
+ * @param matcher The matcher that determines if a constructor's modifiers should be adjusted.
+ * @param modifierContributors The modifier contributors to enforce.
+ * @return A new modifier adjustment that enforces the given modifier contributors and any previous adjustments.
+ */
+ public ModifierAdjustment withConstructorModifiers(ElementMatcher<? super MethodDescription> matcher,
+ List<? extends ModifierContributor.ForMethod> modifierContributors) {
+ return withInvokableModifiers(isConstructor().and(matcher), modifierContributors);
+ }
+
+ /**
+ * Adjusts any method's or constructor's modifiers.
+ *
+ * @param modifierContributor The modifier contributors to enforce.
+ * @return A new modifier adjustment that enforces the given modifier contributors and any previous adjustments.
+ */
+ public ModifierAdjustment withInvokableModifiers(ModifierContributor.ForMethod... modifierContributor) {
+ return withInvokableModifiers(Arrays.asList(modifierContributor));
+ }
+
+ /**
+ * Adjusts any method's or constructor's modifiers.
+ *
+ * @param modifierContributors The modifier contributors to enforce.
+ * @return A new modifier adjustment that enforces the given modifier contributors and any previous adjustments.
+ */
+ public ModifierAdjustment withInvokableModifiers(List<? extends ModifierContributor.ForMethod> modifierContributors) {
+ return withInvokableModifiers(any(), modifierContributors);
+ }
+
+ /**
+ * Adjusts a method's or constructor's modifiers if it fulfills the supplied matcher.
+ *
+ * @param matcher The matcher that determines if a method's or constructor's modifiers should be adjusted.
+ * @param modifierContributor The modifier contributors to enforce.
+ * @return A new modifier adjustment that enforces the given modifier contributors and any previous adjustments.
+ */
+ public ModifierAdjustment withInvokableModifiers(ElementMatcher<? super MethodDescription> matcher,
+ ModifierContributor.ForMethod... modifierContributor) {
+ return withInvokableModifiers(matcher, Arrays.asList(modifierContributor));
+ }
+
+ /**
+ * Adjusts a method's or constructor's modifiers if it fulfills the supplied matcher.
+ *
+ * @param matcher The matcher that determines if a method's or constructor's modifiers should be adjusted.
+ * @param modifierContributors The modifier contributors to enforce.
+ * @return A new modifier adjustment that enforces the given modifier contributors and any previous adjustments.
+ */
+ public ModifierAdjustment withInvokableModifiers(ElementMatcher<? super MethodDescription> matcher,
+ List<? extends ModifierContributor.ForMethod> modifierContributors) {
+ return new ModifierAdjustment(typeAdjustments, fieldAdjustments, CompoundList.of(methodAdjustments, new Adjustment<MethodDescription>(matcher,
+ ModifierContributor.Resolver.of(modifierContributors))));
+ }
+
+ @Override
+ public ModifierAdjustingClassVisitor wrap(TypeDescription instrumentedType,
+ ClassVisitor classVisitor,
+ Implementation.Context implementationContext,
+ TypePool typePool,
+ FieldList<FieldDescription.InDefinedShape> fields,
+ MethodList<?> methods,
+ int writerFlags,
+ int readerFlags) {
+ Map<String, FieldDescription.InDefinedShape> mappedFields = new HashMap<String, FieldDescription.InDefinedShape>();
+ for (FieldDescription.InDefinedShape fieldDescription : fields) {
+ mappedFields.put(fieldDescription.getInternalName() + fieldDescription.getDescriptor(), fieldDescription);
+ }
+ Map<String, MethodDescription> mappedMethods = new HashMap<String, MethodDescription>();
+ for (MethodDescription methodDescription : CompoundList.<MethodDescription>of(methods, new MethodDescription.Latent.TypeInitializer(instrumentedType))) {
+ mappedMethods.put(methodDescription.getInternalName() + methodDescription.getDescriptor(), methodDescription);
+ }
+ return new ModifierAdjustingClassVisitor(classVisitor,
+ typeAdjustments,
+ fieldAdjustments,
+ methodAdjustments,
+ instrumentedType,
+ mappedFields,
+ mappedMethods);
+ }
+
+ /**
+ * A description of a conditional adjustment.
+ *
+ * @param <T> The type of the adjusted element's description.
+ */
+ @EqualsAndHashCode
+ protected static class Adjustment<T> implements ElementMatcher<T> {
+
+ /**
+ * The matcher to determine an adjustment.
+ */
+ private final ElementMatcher<? super T> matcher;
+
+ /**
+ * The resolver to apply.
+ */
+ private final ModifierContributor.Resolver<?> resolver;
+
+ /**
+ * Creates a new adjustment.
+ *
+ * @param matcher The matcher to determine an adjustment.
+ * @param resolver The resolver to apply.
+ */
+ protected Adjustment(ElementMatcher<? super T> matcher, ModifierContributor.Resolver<?> resolver) {
+ this.matcher = matcher;
+ this.resolver = resolver;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ return matcher.matches(target);
+ }
+
+ /**
+ * Resolves a modifier.
+ *
+ * @param modifiers The original modifiers.
+ * @return The resolved modifiers.
+ */
+ protected int resolve(int modifiers) {
+ return resolver.resolve(modifiers);
+ }
+ }
+
+ /**
+ * A class visitor that enforces a collection of modifier adjustments.
+ */
+ protected static class ModifierAdjustingClassVisitor extends ClassVisitor {
+
+ /**
+ * A list of type modifier adjustments to apply.
+ */
+ private final List<Adjustment<TypeDescription>> typeAdjustments;
+
+ /**
+ * A list of field modifier adjustments to apply.
+ */
+ private final List<Adjustment<FieldDescription.InDefinedShape>> fieldAdjustments;
+
+ /**
+ * A list of method modifier adjustments to apply.
+ */
+ private final List<Adjustment<MethodDescription>> methodAdjustments;
+
+ /**
+ * The instrumented type.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * A mapping of field names and descriptors to their description.
+ */
+ private final Map<String, FieldDescription.InDefinedShape> fields;
+
+ /**
+ * A mapping of method names and descriptors to their description.
+ */
+ private final Map<String, MethodDescription> methods;
+
+ /**
+ * Creates a new modifier adjusting visitor.
+ *
+ * @param classVisitor The class visitor to delegate to.
+ * @param typeAdjustments A list of type modifier adjustments to apply.
+ * @param fieldAdjustments A list of field modifier adjustments to apply.
+ * @param methodAdjustments A list of method modifier adjustments to apply.
+ * @param instrumentedType The instrumented type.
+ * @param fields A mapping of field names and descriptors to their description.
+ * @param methods A mapping of method names and descriptors to their description.
+ */
+ protected ModifierAdjustingClassVisitor(ClassVisitor classVisitor,
+ List<Adjustment<TypeDescription>> typeAdjustments,
+ List<Adjustment<FieldDescription.InDefinedShape>> fieldAdjustments,
+ List<Adjustment<MethodDescription>> methodAdjustments,
+ TypeDescription instrumentedType,
+ Map<String, FieldDescription.InDefinedShape> fields,
+ Map<String, MethodDescription> methods) {
+ super(Opcodes.ASM5, classVisitor);
+ this.typeAdjustments = typeAdjustments;
+ this.fieldAdjustments = fieldAdjustments;
+ this.methodAdjustments = methodAdjustments;
+ this.instrumentedType = instrumentedType;
+ this.fields = fields;
+ this.methods = methods;
+ }
+
+ @Override
+ public void visit(int version, int modifiers, String internalName, String signature, String superClassName, String[] interfaceName) {
+ for (Adjustment<TypeDescription> adjustment : typeAdjustments) {
+ if (adjustment.matches(instrumentedType)) {
+ modifiers = adjustment.resolve(modifiers);
+ }
+ }
+ super.visit(version, modifiers, internalName, signature, superClassName, interfaceName);
+ }
+
+ @Override
+ public void visitInnerClass(String internalName, String outerName, String innerName, int modifiers) {
+ if (instrumentedType.getInternalName().equals(internalName)) {
+ for (Adjustment<TypeDescription> adjustment : typeAdjustments) {
+ if (adjustment.matches(instrumentedType)) {
+ modifiers = adjustment.resolve(modifiers);
+ }
+ }
+ }
+ super.visitInnerClass(internalName, outerName, innerName, modifiers);
+ }
+
+ @Override
+ public FieldVisitor visitField(int modifiers, String internalName, String descriptor, String signature, Object value) {
+ FieldDescription.InDefinedShape fieldDescription = fields.get(internalName + descriptor);
+ if (fieldDescription != null) {
+ for (Adjustment<FieldDescription.InDefinedShape> adjustment : fieldAdjustments) {
+ if (adjustment.matches(fieldDescription)) {
+ modifiers = adjustment.resolve(modifiers);
+ }
+ }
+ }
+ return super.visitField(modifiers, internalName, descriptor, signature, value);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int modifiers, String internalName, String descriptor, String signature, String[] exception) {
+ MethodDescription methodDescription = methods.get(internalName + descriptor);
+ if (methodDescription != null) {
+ for (Adjustment<MethodDescription> adjustment : methodAdjustments) {
+ if (adjustment.matches(methodDescription)) {
+ modifiers = adjustment.resolve(modifiers);
+ }
+ }
+ }
+ return super.visitMethod(modifiers, internalName, descriptor, signature, exception);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/asm/TypeConstantAdjustment.java b/byte-buddy-dep/src/main/java/net/bytebuddy/asm/TypeConstantAdjustment.java
new file mode 100644
index 0000000..55da8f4
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/asm/TypeConstantAdjustment.java
@@ -0,0 +1,135 @@
+package net.bytebuddy.asm;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import net.bytebuddy.ClassFileVersion;
+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 org.objectweb.asm.*;
+
+/**
+ * <p>
+ * This class visitor wrapper ensures that class files of a version previous to Java 5 do not store class entries in the generated class's constant pool.
+ * All found class instances are instead mapped as {@link String} values where the class constant is retrieved by a call to {@link Class#forName(String)}.
+ * </p>
+ * <p>
+ * <b>Warning</b>: This can lead to subtle bugs as classes that are not available yield a {@link ClassNotFoundException} instead of a
+ * {@link NoClassDefFoundError}. The former, checked exception could therefore be thrown even if the method that unsuccessfully loads a class does
+ * not declared the checked exception. Furthermore, {@link Class} constants are not cached as fields within the class as the Java compiler implemented
+ * class constants before Java 5. As a benefit for this limitation, the registered wrapper does not require any additional work by a {@link ClassWriter}
+ * or {@link ClassReader}, i.e. does not set any flags.
+ * </p>
+ */
+public enum TypeConstantAdjustment implements AsmVisitorWrapper {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @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 TypeConstantDissolvingClassVisitor(classVisitor);
+ }
+
+ /**
+ * A class visitor that checks a class file version for its support of storing class constants in the constant pool and remaps such constants
+ * on discovery if that is not the case.
+ */
+ protected static class TypeConstantDissolvingClassVisitor extends ClassVisitor {
+
+ /**
+ * {@code true} if the class file version supports class constants in a constant pool.
+ */
+ private boolean supportsTypeConstants;
+
+ /**
+ * Creates a new type constant dissolving class visitor.
+ *
+ * @param classVisitor The underlying class visitor.
+ */
+ protected TypeConstantDissolvingClassVisitor(ClassVisitor classVisitor) {
+ super(Opcodes.ASM5, classVisitor);
+ }
+
+ @Override
+ public void visit(int version, int modifiers, String name, String signature, String superClassName, String[] interfaceName) {
+ supportsTypeConstants = ClassFileVersion.ofMinorMajor(version).isAtLeast(ClassFileVersion.JAVA_V5);
+ super.visit(version, modifiers, name, signature, superClassName, interfaceName);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int modifiers, String name, String descriptor, String signature, String[] exception) {
+ MethodVisitor methodVisitor = super.visitMethod(modifiers, name, descriptor, signature, exception);
+ return supportsTypeConstants || methodVisitor == null
+ ? methodVisitor
+ : new TypeConstantDissolvingMethodVisitor(methodVisitor);
+ }
+
+ /**
+ * A method visitor that remaps class constants to invocations of {@link Class#forName(String)}.
+ */
+ protected static class TypeConstantDissolvingMethodVisitor extends MethodVisitor {
+
+ /**
+ * The internal name of the {@link Class} class.
+ */
+ private static final String JAVA_LANG_CLASS = "java/lang/Class";
+
+ /**
+ * The {@code forName} method name.
+ */
+ private static final String FOR_NAME = "forName";
+
+ /**
+ * The descriptor of the {@code forName} method.
+ */
+ private static final String DESCRIPTOR = "(Ljava/lang/String;)Ljava/lang/Class;";
+
+ /**
+ * Creates a new type constant dissolving method visitor.
+ *
+ * @param methodVisitor The underlying method visitor.
+ */
+ protected TypeConstantDissolvingMethodVisitor(MethodVisitor methodVisitor) {
+ super(Opcodes.ASM5, methodVisitor);
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "SF_SWITCH_NO_DEFAULT", justification = "Fall through to default case is intentional")
+ public void visitLdcInsn(Object constant) {
+ if (constant instanceof Type) {
+ Type type = (Type) constant;
+ switch (type.getSort()) {
+ case Type.OBJECT:
+ case Type.ARRAY:
+ super.visitLdcInsn(type.getInternalName().replace('/', '.'));
+ super.visitMethodInsn(Opcodes.INVOKESTATIC, JAVA_LANG_CLASS, FOR_NAME, DESCRIPTOR, false);
+ return;
+ }
+ }
+ super.visitLdcInsn(constant);
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/asm/package-info.java b/byte-buddy-dep/src/main/java/net/bytebuddy/asm/package-info.java
new file mode 100644
index 0000000..9c84226
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/asm/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * The ASM package contains classes that are meant for direct interaction with the ASM API.
+ */
+package net.bytebuddy.asm;
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/build/EntryPoint.java b/byte-buddy-dep/src/main/java/net/bytebuddy/build/EntryPoint.java
new file mode 100644
index 0000000..746fa4c
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/build/EntryPoint.java
@@ -0,0 +1,105 @@
+package net.bytebuddy.build;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.scaffold.inline.MethodNameTransformer;
+import net.bytebuddy.implementation.Implementation;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static net.bytebuddy.matcher.ElementMatchers.not;
+
+/**
+ * An entry point for a build tool which is responsible for the transformation's configuration.
+ */
+public interface EntryPoint {
+
+ /**
+ * Returns the Byte Buddy instance to use.
+ *
+ * @return The Byte Buddy instance to use.
+ */
+ ByteBuddy getByteBuddy();
+
+ /**
+ * Applies a transformation.
+ *
+ * @param typeDescription The type to transform.
+ * @param byteBuddy The Byte Buddy instance to use.
+ * @param classFileLocator The class file locator to use.
+ * @param methodNameTransformer The Method name transformer to use.
+ * @return A builder for the dynamic type to create.
+ */
+ DynamicType.Builder<?> transform(TypeDescription typeDescription,
+ ByteBuddy byteBuddy,
+ ClassFileLocator classFileLocator,
+ MethodNameTransformer methodNameTransformer);
+
+ /**
+ * Default implementations for an entry point.
+ */
+ @SuppressFBWarnings(value = "SE_BAD_FIELD", justification = "An enumeration does not serialize fields")
+ enum Default implements EntryPoint {
+
+ /**
+ * An entry point that rebases a type.
+ */
+ REBASE(new ByteBuddy()) {
+ @Override
+ public DynamicType.Builder<?> transform(TypeDescription typeDescription,
+ ByteBuddy byteBuddy,
+ ClassFileLocator classFileLocator,
+ MethodNameTransformer methodNameTransformer) {
+ return byteBuddy.rebase(typeDescription, classFileLocator, methodNameTransformer);
+ }
+ },
+
+ /**
+ * An entry point that redefines a type.
+ */
+ REDEFINE(new ByteBuddy()) {
+ @Override
+ public DynamicType.Builder<?> transform(TypeDescription typeDescription,
+ ByteBuddy byteBuddy,
+ ClassFileLocator classFileLocator,
+ MethodNameTransformer methodNameTransformer) {
+ return byteBuddy.redefine(typeDescription, classFileLocator);
+ }
+ },
+
+ /**
+ * An entry point that redefines a type and which does not change the dynamic type's shape, i.e. does
+ * not add any methods or considers intercepting inherited methods.
+ */
+ REDEFINE_LOCAL(new ByteBuddy().with(Implementation.Context.Disabled.Factory.INSTANCE)) {
+ @Override
+ public DynamicType.Builder<?> transform(TypeDescription typeDescription,
+ ByteBuddy byteBuddy,
+ ClassFileLocator classFileLocator,
+ MethodNameTransformer methodNameTransformer) {
+ return byteBuddy.redefine(typeDescription, classFileLocator).ignoreAlso(not(isDeclaredBy(typeDescription)));
+ }
+ };
+
+ /**
+ * The Byte Buddy instance to use.
+ */
+ private final ByteBuddy byteBuddy;
+
+ /**
+ * Creates a default entry point.
+ *
+ * @param byteBuddy The Byte Buddy instance to use.
+ */
+ Default(ByteBuddy byteBuddy) {
+ this.byteBuddy = byteBuddy;
+ }
+
+ @Override
+ public ByteBuddy getByteBuddy() {
+ return byteBuddy;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/build/Plugin.java b/byte-buddy-dep/src/main/java/net/bytebuddy/build/Plugin.java
new file mode 100644
index 0000000..0a243a8
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/build/Plugin.java
@@ -0,0 +1,22 @@
+package net.bytebuddy.build;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.matcher.ElementMatcher;
+
+/**
+ * A plugin that allows for the application of Byte Buddy transformations during a build process. This plugin's
+ * transformation is applied to any type matching this plugin's type matcher. Plugin types must be public,
+ * non-abstract and must declare a public default constructor to work.
+ */
+public interface Plugin extends ElementMatcher<TypeDescription> {
+
+ /**
+ * Applies this plugin.
+ *
+ * @param builder The builder to use as a basis for the applied transformation.
+ * @param typeDescription The type being transformed.
+ * @return The supplied builder with additional transformations registered.
+ */
+ DynamicType.Builder<?> apply(DynamicType.Builder<?> builder, TypeDescription typeDescription);
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/build/package-info.java b/byte-buddy-dep/src/main/java/net/bytebuddy/build/package-info.java
new file mode 100644
index 0000000..c5c347f
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/build/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * A package for types that allow for applying Byte Buddy transformation during a build process.
+ */
+package net.bytebuddy.build;
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/ByteCodeElement.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/ByteCodeElement.java
new file mode 100644
index 0000000..2ffbeff
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/ByteCodeElement.java
@@ -0,0 +1,182 @@
+package net.bytebuddy.description;
+
+import net.bytebuddy.description.annotation.AnnotationSource;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.matcher.FilterableList;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Implementations describe an element represented in byte code, i.e. a type, a field or a method or a constructor.
+ */
+public interface ByteCodeElement extends NamedElement.WithRuntimeName, ModifierReviewable, DeclaredByType, AnnotationSource {
+
+ /**
+ * The generic type signature of a non-generic byte code element.
+ */
+ String NON_GENERIC_SIGNATURE = null;
+
+ /**
+ * Returns the descriptor of this byte code element.
+ *
+ * @return The descriptor of this byte code element.
+ */
+ String getDescriptor();
+
+ /**
+ * Returns the generic signature of this byte code element. If this element does not reference generic types
+ * or references malformed generic types, {@code null} is returned as a signature.
+ *
+ * @return The generic signature or {@code null} if this element is not generic.
+ */
+ String getGenericSignature();
+
+ /**
+ * <p>
+ * Checks if this element is visible from a given type. Visibility is a wider criteria then accessibility which can be checked by
+ * {@link ByteCodeElement#isAccessibleTo(TypeDescription)}. Visibility allows the invocation of a method on itself or on external
+ * instances.
+ * </p>
+ * <p>
+ * <b>Note</b>: A method or field might define a signature that includes types that are not visible to a type. Such methods can be
+ * legally invoked from this type and can even be implemented as bridge methods by this type. It is however not legal to declare
+ * a method with invisible types in its signature that are not bridges what might require additional validation.
+ * </p>
+ * <p>
+ * <b>Important</b>: Virtual byte code elements, i.e. virtual methods, are only considered visible if the type they are invoked upon
+ * is visible to a given type. The visibility of such virtual members can therefore not be determined by only investigating the invoked
+ * method but requires an additional check of the target type.
+ * </p>
+ *
+ * @param typeDescription The type which is checked for its visibility of this element.
+ * @return {@code true} if this element is visible for {@code typeDescription}.
+ */
+ boolean isVisibleTo(TypeDescription typeDescription);
+
+ /**
+ * <p>
+ * Checks if this element is accessible from a given type. Accessibility is a more narrow criteria then visibility which can be
+ * checked by {@link ByteCodeElement#isVisibleTo(TypeDescription)}. Accessibility allows the invocation of a method on external
+ * instances or on itself. Methods that can be invoked from within an instance might however not be considered accessible.
+ * </p>
+ * <p>
+ * <b>Note</b>: A method or field might define a signature that includes types that are not visible to a type. Such methods can be
+ * legally invoked from this type and can even be implemented as bridge methods by this type. It is however not legal to declare
+ * a method with invisible types in its signature that are not bridges what might require additional validation.
+ * </p>
+ * <p>
+ * <b>Important</b>: Virtual byte code elements, i.e. virtual methods, are only considered visible if the type they are invoked upon
+ * is visible to a given type. The visibility of such virtual members can therefore not be determined by only investigating the invoked
+ * method but requires an additional check of the target type.
+ * </p>
+ *
+ * @param typeDescription The type which is checked for its accessibility of this element.
+ * @return {@code true} if this element is accessible for {@code typeDescription}.
+ */
+ boolean isAccessibleTo(TypeDescription typeDescription);
+
+ /**
+ * A type dependant describes an element that is an extension of a type definition, i.e. a field, method or method parameter.
+ *
+ * @param <T> The type dependant's type.
+ * @param <S> The type dependant's token type.
+ */
+ interface TypeDependant<T extends TypeDependant<?, S>, S extends ByteCodeElement.Token<S>> {
+
+ /**
+ * Returns this type dependant in its defined shape, i.e. the form it is declared in and without its type variable's resolved.
+ *
+ * @return This type dependant in its defined shape.
+ */
+ T asDefined();
+
+ /**
+ * Returns a token representative of this type dependant. All types that are matched by the supplied matcher are replaced by
+ * {@link net.bytebuddy.dynamic.TargetType} descriptions.
+ *
+ * @param matcher A matcher to identify types to be replaced by {@link net.bytebuddy.dynamic.TargetType} descriptions.
+ * @return A token representative of this type dependant.
+ */
+ S asToken(ElementMatcher<? super TypeDescription> matcher);
+ }
+
+ /**
+ * A token representing a byte code element.
+ *
+ * @param <T> The type of the implementation.
+ */
+ interface Token<T extends Token<T>> {
+
+ /**
+ * Transforms the types represented by this token by applying the given visitor to them.
+ *
+ * @param visitor The visitor to transform all types that are represented by this token.
+ * @return This token with all of its represented types transformed by the supplied visitor.
+ */
+ T accept(TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor);
+
+ /**
+ * A list of tokens.
+ *
+ * @param <S> The actual token type.
+ */
+ class TokenList<S extends Token<S>> extends FilterableList.AbstractBase<S, TokenList<S>> {
+
+ /**
+ * The tokens that this list represents.
+ */
+ private final List<? extends S> tokens;
+
+ /**
+ * Creates a list of tokens.
+ *
+ * @param token The tokens that this list represents.
+ */
+ @SuppressWarnings("unchecked")
+ public TokenList(S... token) {
+ this(Arrays.asList(token));
+ }
+
+ /**
+ * Creates a list of tokens.
+ *
+ * @param tokens The tokens that this list represents.
+ */
+ public TokenList(List<? extends S> tokens) {
+ this.tokens = tokens;
+ }
+
+ /**
+ * Transforms all tokens that are represented by this list.
+ *
+ * @param visitor The visitor to apply to all tokens.
+ * @return A list containing the transformed tokens.
+ */
+ public TokenList<S> accept(TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor) {
+ List<S> tokens = new ArrayList<S>(this.tokens.size());
+ for (S token : this.tokens) {
+ tokens.add(token.accept(visitor));
+ }
+ return new TokenList<S>(tokens);
+ }
+
+ @Override
+ protected TokenList<S> wrap(List<S> values) {
+ return new TokenList<S>(values);
+ }
+
+ @Override
+ public S get(int index) {
+ return tokens.get(index);
+ }
+
+ @Override
+ public int size() {
+ return tokens.size();
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/DeclaredByType.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/DeclaredByType.java
new file mode 100644
index 0000000..f5bf27d
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/DeclaredByType.java
@@ -0,0 +1,16 @@
+package net.bytebuddy.description;
+
+import net.bytebuddy.description.type.TypeDefinition;
+
+/**
+ * This interface represents all elements that can be declared within a type, i.e. other types and type members.
+ */
+public interface DeclaredByType {
+
+ /**
+ * Returns the declaring type of this instance.
+ *
+ * @return The declaring type or {@code null} if no such type exists.
+ */
+ TypeDefinition getDeclaringType();
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/ModifierReviewable.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/ModifierReviewable.java
new file mode 100644
index 0000000..0553437
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/ModifierReviewable.java
@@ -0,0 +1,542 @@
+package net.bytebuddy.description;
+
+import net.bytebuddy.description.modifier.*;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Implementations of this interface can be described in terms of a Java modifier.
+ */
+public interface ModifierReviewable {
+
+ /**
+ * Representation of the default modifier.
+ */
+ int EMPTY_MASK = 0;
+
+ /**
+ * Returns the modifier that is described by this object.
+ *
+ * @return The modifier that is described by this object.
+ */
+ int getModifiers();
+
+ /**
+ * Specifies if the modifier described by this object is {@code final}.
+ *
+ * @return {@code true} if the modifier described by this object is {@code final}.
+ */
+ boolean isFinal();
+
+ /**
+ * Specifies if the modifier described by this object is synthetic.
+ *
+ * @return {@code true} if the modifier described by this object is synthetic.
+ */
+ boolean isSynthetic();
+
+ /**
+ * Returns this objects synthetic state.
+ *
+ * @return This objects synthetic state.
+ */
+ SyntheticState getSyntheticState();
+
+ /**
+ * A modifier reviewable for a {@link ByteCodeElement}, i.e. a type, a field or a method.
+ */
+ interface OfByteCodeElement extends ModifierReviewable {
+
+ /**
+ * Specifies if the modifier described by this object is {@code public}.
+ *
+ * @return {@code true} if the modifier described by this object is {@code public}.
+ */
+ boolean isPublic();
+
+ /**
+ * Specifies if the modifier described by this object is {@code protected}.
+ *
+ * @return {@code true} if the modifier described by this object is {@code protected}.
+ */
+ boolean isProtected();
+
+ /**
+ * Specifies if the modifier described by this object is package private.
+ *
+ * @return {@code true} if the modifier described by this object is package private.
+ */
+ boolean isPackagePrivate();
+
+ /**
+ * Specifies if the modifier described by this object is {@code private}.
+ *
+ * @return {@code true} if the modifier described by this object is {@code private}.
+ */
+ boolean isPrivate();
+
+ /**
+ * Specifies if the modifier described by this object is {@code static}.
+ *
+ * @return {@code true} if the modifier described by this object is {@code static}.
+ */
+ boolean isStatic();
+
+ /**
+ * Specifies if the modifier described by this object represents the deprecated flag.
+ *
+ * @return {@code true} if the modifier described by this object represents the deprecated flag.
+ */
+ boolean isDeprecated();
+
+ /**
+ * Return's this byte code element's ownership.
+ *
+ * @return This byte code element's ownership.
+ */
+ Ownership getOwnership();
+
+ /**
+ * Returns this byte code element's visibility.
+ *
+ * @return This byte code element's visibility.
+ */
+ Visibility getVisibility();
+ }
+
+ /**
+ * A modifier reviewable for a byte code element that can be abstract, i.e. a {@link net.bytebuddy.description.type.TypeDescription}
+ * or a {@link net.bytebuddy.description.method.MethodDescription}.
+ */
+ interface OfAbstraction extends OfByteCodeElement {
+
+ /**
+ * Specifies if the modifier described by this object is {@code abstract}.
+ *
+ * @return {@code true} if the modifier described by this object is {@code abstract}.
+ */
+ boolean isAbstract();
+ }
+
+ /**
+ * A modifier reviewable for a byte code element that can represent an enumeration, i.e. a {@link net.bytebuddy.description.field.FieldDescription}
+ * that holds an enumeration value or a {@link net.bytebuddy.description.type.TypeDescription} that represents an enumeration.
+ */
+ interface OfEnumeration extends OfByteCodeElement {
+
+ /**
+ * Specifies if the modifier described by this object represents the enum flag.
+ *
+ * @return {@code true} if the modifier described by this object represents the enum flag.
+ */
+ boolean isEnum();
+
+ /**
+ * Returns this byte code element's enumeration state.
+ *
+ * @return This byte code element's enumeration state.
+ */
+ EnumerationState getEnumerationState();
+ }
+
+ /**
+ * A modifier reviewable for a {@link net.bytebuddy.description.type.TypeDescription}.
+ */
+ interface ForTypeDefinition extends OfAbstraction, OfEnumeration {
+
+ /**
+ * Specifies if the modifier described by this object represents the interface flag.
+ *
+ * @return {@code true} if the modifier described by this object represents the interface flag.
+ */
+ boolean isInterface();
+
+ /**
+ * Specifies if the modifier described by this object represents the annotation flag.
+ *
+ * @return {@code true} if the modifier described by this object represents the annotation flag.
+ */
+ boolean isAnnotation();
+
+ /**
+ * Returns this type's manifestation.
+ *
+ * @return This type's manifestation.
+ */
+ TypeManifestation getTypeManifestation();
+ }
+
+ /**
+ * A modifier reviewable for a {@link net.bytebuddy.description.field.FieldDescription}.
+ */
+ interface ForFieldDescription extends OfEnumeration {
+
+ /**
+ * Specifies if the modifier described by this object represents the volatile flag.
+ *
+ * @return {@code true} if the modifier described by this object represents the volatile flag.
+ */
+ boolean isVolatile();
+
+ /**
+ * Specifies if the modifier described by this object represents the transient flag.
+ *
+ * @return {@code true} if the modifier described by this object represents the transient flag.
+ */
+ boolean isTransient();
+
+ /**
+ * Returns this field's manifestation.
+ *
+ * @return This field's manifestation.
+ */
+ FieldManifestation getFieldManifestation();
+
+ /**
+ * Returns this field's persistence.
+ *
+ * @return This field's persistence.
+ */
+ FieldPersistence getFieldPersistence();
+ }
+
+ /**
+ * A modifier reviewable for a {@link net.bytebuddy.description.method.MethodDescription}.
+ */
+ interface ForMethodDescription extends OfAbstraction {
+
+ /**
+ * Specifies if the modifier described by this object is {@code synchronized}.
+ *
+ * @return {@code true} if the modifier described by this object is {@code synchronized}.
+ */
+ boolean isSynchronized();
+
+ /**
+ * Specifies if the modifier described by this object represents the var args flag.
+ *
+ * @return {@code true} if the modifier described by this object represents the var args flag.
+ */
+ boolean isVarArgs();
+
+ /**
+ * Specifies if the modifier described by this object is {@code native}.
+ *
+ * @return {@code true} if the modifier described by this object is {@code native}.
+ */
+ boolean isNative();
+
+ /**
+ * Specifies if the modifier described by this object represents the bridge flag.
+ *
+ * @return {@code true} if the modifier described by this object represents the bridge flag
+ */
+ boolean isBridge();
+
+ /**
+ * Specifies if the modifier described by this object is {@code strictfp}.
+ *
+ * @return {@code true} if the modifier described by this object is {@code strictfp}.
+ */
+ boolean isStrict();
+
+ /**
+ * Returns this method's synchronization state.
+ *
+ * @return This method's synchronization state.
+ */
+ SynchronizationState getSynchronizationState();
+
+ /**
+ * Returns this method's strictness in floating-point computation.
+ *
+ * @return This method's strictness in floating-point computation.
+ */
+ MethodStrictness getMethodStrictness();
+
+ /**
+ * Returns this method's manifestation.
+ *
+ * @return This method's manifestation.
+ */
+ MethodManifestation getMethodManifestation();
+ }
+
+ /**
+ * A modifier reviewable for a {@link net.bytebuddy.description.method.ParameterDescription}.
+ */
+ interface ForParameterDescription extends ModifierReviewable {
+
+ /**
+ * CSpecifies if the modifier described by this object is mandated.
+ *
+ * @return {@code true} if the modifier described by this object is mandated.
+ */
+ boolean isMandated();
+
+ /**
+ * Returns this parameter's manifestation.
+ *
+ * @return This parameter's manifestation.
+ */
+ ParameterManifestation getParameterManifestation();
+
+ /**
+ * Returns this parameter's provisioning state.
+ *
+ * @return This parameter's provisioning state.
+ */
+ ProvisioningState getProvisioningState();
+ }
+
+ /**
+ * An abstract base implementation of a {@link ModifierReviewable} class.
+ */
+ abstract class AbstractBase implements ForTypeDefinition, ForFieldDescription, ForMethodDescription, ForParameterDescription {
+
+ @Override
+ public boolean isAbstract() {
+ return matchesMask(Opcodes.ACC_ABSTRACT);
+ }
+
+ @Override
+ public boolean isFinal() {
+ return matchesMask(Opcodes.ACC_FINAL);
+ }
+
+ @Override
+ public boolean isStatic() {
+ return matchesMask(Opcodes.ACC_STATIC);
+ }
+
+ @Override
+ public boolean isPublic() {
+ return matchesMask(Opcodes.ACC_PUBLIC);
+ }
+
+ @Override
+ public boolean isProtected() {
+ return matchesMask(Opcodes.ACC_PROTECTED);
+ }
+
+ @Override
+ public boolean isPackagePrivate() {
+ return !isPublic() && !isProtected() && !isPrivate();
+ }
+
+ @Override
+ public boolean isPrivate() {
+ return matchesMask(Opcodes.ACC_PRIVATE);
+ }
+
+ @Override
+ public boolean isNative() {
+ return matchesMask(Opcodes.ACC_NATIVE);
+ }
+
+ @Override
+ public boolean isSynchronized() {
+ return matchesMask(Opcodes.ACC_SYNCHRONIZED);
+ }
+
+ @Override
+ public boolean isStrict() {
+ return matchesMask(Opcodes.ACC_STRICT);
+ }
+
+ @Override
+ public boolean isMandated() {
+ return matchesMask(Opcodes.ACC_MANDATED);
+ }
+
+ @Override
+ public boolean isSynthetic() {
+ return matchesMask(Opcodes.ACC_SYNTHETIC);
+ }
+
+ @Override
+ public boolean isBridge() {
+ return matchesMask(Opcodes.ACC_BRIDGE);
+ }
+
+ @Override
+ public boolean isDeprecated() {
+ return matchesMask(Opcodes.ACC_DEPRECATED);
+ }
+
+ @Override
+ public boolean isAnnotation() {
+ return matchesMask(Opcodes.ACC_ANNOTATION);
+ }
+
+ @Override
+ public boolean isEnum() {
+ return matchesMask(Opcodes.ACC_ENUM);
+ }
+
+ @Override
+ public boolean isInterface() {
+ return matchesMask(Opcodes.ACC_INTERFACE);
+ }
+
+ @Override
+ public boolean isTransient() {
+ return matchesMask(Opcodes.ACC_TRANSIENT);
+ }
+
+ @Override
+ public boolean isVolatile() {
+ return matchesMask(Opcodes.ACC_VOLATILE);
+ }
+
+ @Override
+ public boolean isVarArgs() {
+ return matchesMask(Opcodes.ACC_VARARGS);
+ }
+
+ @Override
+ public SyntheticState getSyntheticState() {
+ return isSynthetic()
+ ? SyntheticState.SYNTHETIC
+ : SyntheticState.PLAIN;
+ }
+
+ @Override
+ public Visibility getVisibility() {
+ int modifiers = getModifiers();
+ switch (modifiers & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE)) {
+ case Opcodes.ACC_PUBLIC:
+ return Visibility.PUBLIC;
+ case Opcodes.ACC_PROTECTED:
+ return Visibility.PROTECTED;
+ case EMPTY_MASK:
+ return Visibility.PACKAGE_PRIVATE;
+ case Opcodes.ACC_PRIVATE:
+ return Visibility.PRIVATE;
+ default:
+ throw new IllegalStateException("Unexpected modifiers: " + modifiers);
+ }
+ }
+
+ @Override
+ public Ownership getOwnership() {
+ return isStatic()
+ ? Ownership.STATIC
+ : Ownership.MEMBER;
+ }
+
+ @Override
+ public EnumerationState getEnumerationState() {
+ return isEnum()
+ ? EnumerationState.ENUMERATION
+ : EnumerationState.PLAIN;
+ }
+
+ @Override
+ public TypeManifestation getTypeManifestation() {
+ int modifiers = getModifiers();
+ switch (modifiers & (Opcodes.ACC_ANNOTATION | Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT | Opcodes.ACC_FINAL)) {
+ case Opcodes.ACC_FINAL:
+ return TypeManifestation.FINAL;
+ case Opcodes.ACC_ABSTRACT:
+ return TypeManifestation.ABSTRACT;
+ case Opcodes.ACC_ABSTRACT | Opcodes.ACC_INTERFACE:
+ return TypeManifestation.INTERFACE;
+ case Opcodes.ACC_ABSTRACT | Opcodes.ACC_INTERFACE | Opcodes.ACC_ANNOTATION:
+ return TypeManifestation.ANNOTATION;
+ case EMPTY_MASK:
+ return TypeManifestation.PLAIN;
+ default:
+ throw new IllegalStateException("Unexpected modifiers: " + modifiers);
+ }
+ }
+
+ @Override
+ public FieldManifestation getFieldManifestation() {
+ int modifiers = getModifiers();
+ switch (modifiers & (Opcodes.ACC_VOLATILE | Opcodes.ACC_FINAL)) {
+ case Opcodes.ACC_FINAL:
+ return FieldManifestation.FINAL;
+ case Opcodes.ACC_VOLATILE:
+ return FieldManifestation.VOLATILE;
+ case EMPTY_MASK:
+ return FieldManifestation.PLAIN;
+ default:
+ throw new IllegalStateException("Unexpected modifiers: " + modifiers);
+ }
+ }
+
+ @Override
+ public FieldPersistence getFieldPersistence() {
+ int modifiers = getModifiers();
+ switch (modifiers & Opcodes.ACC_TRANSIENT) {
+ case Opcodes.ACC_TRANSIENT:
+ return FieldPersistence.TRANSIENT;
+ case EMPTY_MASK:
+ return FieldPersistence.PLAIN;
+ default:
+ throw new IllegalStateException("Unexpected modifiers: " + modifiers);
+ }
+ }
+
+ @Override
+ public SynchronizationState getSynchronizationState() {
+ return isSynchronized()
+ ? SynchronizationState.SYNCHRONIZED
+ : SynchronizationState.PLAIN;
+ }
+
+ @Override
+ public MethodManifestation getMethodManifestation() {
+ int modifiers = getModifiers();
+ switch (modifiers & (Opcodes.ACC_NATIVE | Opcodes.ACC_ABSTRACT | Opcodes.ACC_FINAL | Opcodes.ACC_BRIDGE)) {
+ case Opcodes.ACC_NATIVE | Opcodes.ACC_FINAL:
+ return MethodManifestation.FINAL_NATIVE;
+ case Opcodes.ACC_NATIVE:
+ return MethodManifestation.NATIVE;
+ case Opcodes.ACC_FINAL:
+ return MethodManifestation.FINAL;
+ case Opcodes.ACC_BRIDGE:
+ return MethodManifestation.BRIDGE;
+ case Opcodes.ACC_BRIDGE | Opcodes.ACC_FINAL:
+ return MethodManifestation.FINAL_BRIDGE;
+ case Opcodes.ACC_ABSTRACT:
+ return MethodManifestation.ABSTRACT;
+ case EMPTY_MASK:
+ return MethodManifestation.PLAIN;
+ default:
+ throw new IllegalStateException("Unexpected modifiers: " + modifiers);
+ }
+ }
+
+ @Override
+ public MethodStrictness getMethodStrictness() {
+ return isStrict()
+ ? MethodStrictness.STRICT
+ : MethodStrictness.PLAIN;
+ }
+
+ @Override
+ public ParameterManifestation getParameterManifestation() {
+ return isFinal()
+ ? ParameterManifestation.FINAL
+ : ParameterManifestation.PLAIN;
+ }
+
+ @Override
+ public ProvisioningState getProvisioningState() {
+ return isMandated()
+ ? ProvisioningState.MANDATED
+ : ProvisioningState.PLAIN;
+ }
+
+ /**
+ * Checks if a mask is matched by this instance.
+ *
+ * @param mask The mask to check.
+ * @return {@code true} if the mask is matched.
+ */
+ private boolean matchesMask(int mask) {
+ return (getModifiers() & mask) == mask;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/NamedElement.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/NamedElement.java
new file mode 100644
index 0000000..a992c63
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/NamedElement.java
@@ -0,0 +1,71 @@
+package net.bytebuddy.description;
+
+/**
+ * Represents a Java element with a name.
+ */
+public interface NamedElement {
+
+ /**
+ * Indicates that an element is not named.
+ */
+ String NO_NAME = null;
+
+ /**
+ * Represents an element without a name in the source code.
+ */
+ String EMPTY_NAME = "";
+
+ /**
+ * Returns the name of this element as it is found in the source code. If no such name exists,
+ * an empty string is returned.
+ *
+ * @return The name of this element as given in a Java program's source code.
+ */
+ String getActualName();
+
+ /**
+ * A named element with a name that has a particular meaning to the Java runtime.
+ */
+ interface WithRuntimeName extends NamedElement {
+
+ /**
+ * Returns the internalName of this byte code element.
+ *
+ * @return The internalName of this byte code element as visible from within a running Java application.
+ */
+ String getName();
+
+ /**
+ * Returns the internal internalName of this byte code element.
+ *
+ * @return The internal internalName of this byte code element as used within the Java class file format.
+ */
+ String getInternalName();
+ }
+
+ /**
+ * Describes a named element where naming the element is optional and an alternative default name is provided if no explicit name is given.
+ */
+ interface WithOptionalName extends NamedElement {
+
+ /**
+ * Returns {@code true} if this element has an explicit name.
+ *
+ * @return {@code true} if this element has an explicit name.
+ */
+ boolean isNamed();
+ }
+
+ /**
+ * A named element with a generic type name.
+ */
+ interface WithGenericName extends WithRuntimeName {
+
+ /**
+ * Returns a generic string of this byte code element.
+ *
+ * @return A generic string of this byte code element.
+ */
+ String toGenericString();
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/TypeVariableSource.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/TypeVariableSource.java
new file mode 100644
index 0000000..fbe31ea
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/TypeVariableSource.java
@@ -0,0 +1,124 @@
+package net.bytebuddy.description;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+
+/**
+ * A type variable source represents a code element that can declare type variables.
+ */
+public interface TypeVariableSource extends ModifierReviewable.OfAbstraction {
+
+ /**
+ * Indicates that a type variable source is undefined.
+ */
+ TypeVariableSource UNDEFINED = null;
+
+ /**
+ * Returns the type variables that are declared by this element.
+ *
+ * @return The type variables that are declared by this element.
+ */
+ TypeList.Generic getTypeVariables();
+
+ /**
+ * Returns the enclosing source of type variables that are valid in the scope of this type variable source.
+ *
+ * @return The enclosing source or {@code null} if no such source exists.
+ */
+ TypeVariableSource getEnclosingSource();
+
+ /**
+ * Finds a particular variable with the given name in the closes type variable source that is visible from this instance.
+ *
+ * @param symbol The symbolic name of the type variable.
+ * @return The type variable.
+ */
+ TypeDescription.Generic findVariable(String symbol);
+
+ /**
+ * Applies a visitor on this type variable source.
+ *
+ * @param visitor The visitor to apply.
+ * @param <T> The visitor's return type.
+ * @return The visitor's return value.
+ */
+ <T> T accept(Visitor<T> visitor);
+
+ /**
+ * Checks if this type variable source has a generic declaration. This means:
+ * <ul>
+ * <li>A type declares type variables or is an inner class of a type with a generic declaration.</li>
+ * <li>A method declares at least one type variable.</li>
+ * </ul>
+ *
+ * @return {@code true} if this type code element has a generic declaration.
+ */
+ boolean isGenerified();
+
+ /**
+ * A visitor that can be applied to a type variable source.
+ *
+ * @param <T> The visitor's return type.
+ */
+ interface Visitor<T> {
+
+ /**
+ * Applies the visitor on a type.
+ *
+ * @param typeDescription The type onto which this visitor is applied.
+ * @return The visitor's return value.
+ */
+ T onType(TypeDescription typeDescription);
+
+ /**
+ * Applies the visitor on a method.
+ *
+ * @param methodDescription The method onto which this visitor is applied.
+ * @return The visitor's return value.
+ */
+ T onMethod(MethodDescription.InDefinedShape methodDescription);
+
+ /**
+ * A none-operational implementation of a type variable visitor that simply returns the visited source.
+ */
+ enum NoOp implements Visitor<TypeVariableSource> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public TypeVariableSource onType(TypeDescription typeDescription) {
+ return typeDescription;
+ }
+
+ @Override
+ public TypeVariableSource onMethod(MethodDescription.InDefinedShape methodDescription) {
+ return methodDescription;
+ }
+ }
+ }
+
+ /**
+ * An abstract base implementation of a type variable source.
+ */
+ abstract class AbstractBase extends ModifierReviewable.AbstractBase implements TypeVariableSource {
+
+ @Override
+ public TypeDescription.Generic findVariable(String symbol) {
+ TypeList.Generic typeVariables = getTypeVariables().filter(named(symbol));
+ if (typeVariables.isEmpty()) {
+ TypeVariableSource enclosingSource = getEnclosingSource();
+ return enclosingSource == null
+ ? TypeDescription.Generic.UNDEFINED
+ : enclosingSource.findVariable(symbol);
+ } else {
+ return typeVariables.getOnly();
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/annotation/AnnotationDescription.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/annotation/AnnotationDescription.java
new file mode 100644
index 0000000..ecd9e8e
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/annotation/AnnotationDescription.java
@@ -0,0 +1,1255 @@
+package net.bytebuddy.description.annotation;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.enumeration.EnumerationDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.utility.privilege.SetAccessibleAction;
+
+import java.lang.annotation.*;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.security.AccessController;
+import java.util.*;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+
+/**
+ * An annotation description describes {@link java.lang.annotation.Annotation} meta data of a class without this class
+ * being required to be loaded. All values of an annotation are therefore represented in unloaded state:
+ * <ul>
+ * <li>{@link java.lang.Class} instances are represented as {@link TypeDescription}s.</li>
+ * <li>{@link java.lang.Enum} instances are represented as
+ * {@link net.bytebuddy.description.enumeration.EnumerationDescription}s.</li>
+ * <li>{@link java.lang.annotation.Annotation}s are described as
+ * {@link AnnotationDescription}s.</li>
+ * <li>All primitive types are represented as their wrapper types.</li>
+ * </ul>
+ * An annotation can however be loaded in order to access unwrapped values. This will cause a loading of the classes
+ * of these values.
+ */
+public interface AnnotationDescription {
+
+ /**
+ * Indicates an inexistent annotation in a type-safe manner.
+ */
+ AnnotationDescription.Loadable<?> UNDEFINED = null;
+
+ /**
+ * Returns the value of this annotation.
+ *
+ * @param property The property being accessed.
+ * @return The value for the supplied property.
+ */
+ AnnotationValue<?, ?> getValue(MethodDescription.InDefinedShape property);
+
+ /**
+ * Returns a description of the annotation type of this annotation.
+ *
+ * @return A description of the annotation type of this annotation.
+ */
+ TypeDescription getAnnotationType();
+
+ /**
+ * Links this annotation description to a given annotation type such that it can be loaded. This does not cause
+ * the values of this annotation to be loaded.
+ *
+ * @param annotationType The loaded annotation type of this annotation description.
+ * @param <T> The type of the annotation.
+ * @return A loadable version of this annotation description.
+ */
+ <T extends Annotation> Loadable<T> prepare(Class<T> annotationType);
+
+ /**
+ * Returns this annotation's retention policy.
+ *
+ * @return This annotation's retention policy.
+ */
+ RetentionPolicy getRetention();
+
+ /**
+ * Returns a set of all {@link ElementType}s that can declare this annotation.
+ *
+ * @return A set of all element types that can declare this annotation.
+ */
+ Set<ElementType> getElementTypes();
+
+ /**
+ * Checks if this annotation is inherited.
+ *
+ * @return {@code true} if this annotation is inherited.
+ * @see Inherited
+ */
+ boolean isInherited();
+
+ /**
+ * Checks if this annotation is documented.
+ *
+ * @return {@code true} if this annotation is documented.
+ * @see Documented
+ */
+ boolean isDocumented();
+
+ /**
+ * An annotation description that is linked to a given loaded annotation type which allows its representation
+ * as a fully loaded instance.
+ *
+ * @param <S> The annotation type.
+ */
+ interface Loadable<S extends Annotation> extends AnnotationDescription {
+
+ /**
+ * Loads this annotation description. This causes all classes referenced by the annotation value to be loaded.
+ * Without specifying a class loader, the annotation's class loader which was used to prepare this instance
+ * is used.
+ *
+ * @return A loaded version of this annotation description.
+ * @throws java.lang.ClassNotFoundException If any linked classes of the annotation cannot be loaded.
+ */
+ S load() throws ClassNotFoundException;
+
+ /**
+ * Loads this annotation description. This causes all classes referenced by the annotation value to be loaded.
+ * Without specifying a class loader, the annotation's class loader which was used to prepare this instance
+ * is used. Any {@link java.lang.ClassNotFoundException} is wrapped in an {@link java.lang.IllegalStateException}.
+ *
+ * @return A loaded version of this annotation description.
+ */
+ S loadSilent();
+ }
+
+ /**
+ * An {@link java.lang.reflect.InvocationHandler} for implementing annotations.
+ *
+ * @param <T> The type of the handled annotation.
+ */
+ class AnnotationInvocationHandler<T extends Annotation> implements InvocationHandler {
+
+ /**
+ * The name of the {@link Object#hashCode()} method.
+ */
+ private static final String HASH_CODE = "hashCode";
+
+ /**
+ * The name of the {@link Object#equals(Object)} method.
+ */
+ private static final String EQUALS = "equals";
+
+ /**
+ * The name of the {@link Object#toString()} method.
+ */
+ private static final String TO_STRING = "toString";
+
+ /**
+ * The loaded annotation type.
+ */
+ private final Class<? extends Annotation> annotationType;
+
+ /**
+ * A sorted list of values of this annotation.
+ */
+ private final LinkedHashMap<Method, AnnotationValue.Loaded<?>> values;
+
+ /**
+ * Creates a new invocation handler.
+ *
+ * @param annotationType The loaded annotation type.
+ * @param values A sorted list of values of this annotation.
+ */
+ protected AnnotationInvocationHandler(Class<T> annotationType, LinkedHashMap<Method, AnnotationValue.Loaded<?>> values) {
+ this.annotationType = annotationType;
+ this.values = values;
+ }
+
+ /**
+ * Creates a proxy instance for the supplied annotation type and values.
+ *
+ * @param classLoader The class loader that should be used for loading the annotation's values.
+ * @param annotationType The annotation's type.
+ * @param values The values that the annotation contains.
+ * @param <S> The type of the handled annotation.
+ * @return A proxy for the annotation type and values.
+ * @throws ClassNotFoundException If the class of an instance that is contained by this annotation could not be found.
+ */
+ @SuppressWarnings("unchecked")
+ public static <S extends Annotation> S of(ClassLoader classLoader,
+ Class<S> annotationType,
+ Map<String, ? extends AnnotationValue<?, ?>> values) throws ClassNotFoundException {
+ LinkedHashMap<Method, AnnotationValue.Loaded<?>> loadedValues = new LinkedHashMap<Method, AnnotationValue.Loaded<?>>();
+ for (Method method : annotationType.getDeclaredMethods()) {
+ AnnotationValue<?, ?> annotationValue = values.get(method.getName());
+ loadedValues.put(method, (annotationValue == null
+ ? defaultValueOf(method)
+ : annotationValue).load(classLoader));
+ }
+ return (S) Proxy.newProxyInstance(classLoader, new Class<?>[]{annotationType}, new AnnotationInvocationHandler<S>(annotationType, loadedValues));
+ }
+
+ /**
+ * Creates a default value for the given method.
+ *
+ * @param method The method from which to attempt the extraction of a default value.
+ * @return A default value representation.
+ */
+ private static AnnotationValue<?, ?> defaultValueOf(Method method) {
+ Object defaultValue = method.getDefaultValue();
+ return defaultValue == null
+ ? MissingValue.of(method)
+ : AnnotationDescription.ForLoadedAnnotation.asValue(defaultValue, method.getReturnType());
+ }
+
+ /**
+ * Resolves any primitive type to its wrapper type.
+ *
+ * @param type The type to resolve.
+ * @return The resolved type.
+ */
+ private static Class<?> asWrapper(Class<?> type) {
+ if (type.isPrimitive()) {
+ if (type == boolean.class) {
+ return Boolean.class;
+ } else if (type == byte.class) {
+ return Byte.class;
+ } else if (type == short.class) {
+ return Short.class;
+ } else if (type == char.class) {
+ return Character.class;
+ } else if (type == int.class) {
+ return Integer.class;
+ } else if (type == long.class) {
+ return Long.class;
+ } else if (type == float.class) {
+ return Float.class;
+ } else if (type == double.class) {
+ return Double.class;
+ }
+ }
+ return type;
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] argument) {
+ if (method.getDeclaringClass() != annotationType) {
+ if (method.getName().equals(HASH_CODE)) {
+ return hashCodeRepresentation();
+ } else if (method.getName().equals(EQUALS) && method.getParameterTypes().length == 1) {
+ return equalsRepresentation(proxy, argument[0]);
+ } else if (method.getName().equals(TO_STRING)) {
+ return toStringRepresentation();
+ } else /* if (method.getName().equals("annotationType")) */ {
+ return annotationType;
+ }
+ }
+ Object value = values.get(method).resolve();
+ if (!asWrapper(method.getReturnType()).isAssignableFrom(value.getClass())) {
+ throw new AnnotationTypeMismatchException(method, value.getClass().toString());
+ }
+ return value;
+ }
+
+ /**
+ * Returns the string representation of the represented annotation.
+ *
+ * @return The string representation of the represented annotation.
+ */
+ protected String toStringRepresentation() {
+ StringBuilder toString = new StringBuilder();
+ toString.append('@');
+ toString.append(annotationType.getName());
+ toString.append('(');
+ boolean firstMember = true;
+ for (Map.Entry<Method, AnnotationValue.Loaded<?>> entry : values.entrySet()) {
+ if (!entry.getValue().getState().isDefined()) {
+ continue;
+ }
+ if (firstMember) {
+ firstMember = false;
+ } else {
+ toString.append(", ");
+ }
+ toString.append(entry.getKey().getName())
+ .append('=')
+ .append(entry.getValue().toString());
+ }
+ toString.append(')');
+ return toString.toString();
+ }
+
+ /**
+ * Returns the hash code of the represented annotation.
+ *
+ * @return The hash code of the represented annotation.
+ */
+ private int hashCodeRepresentation() {
+ int hashCode = 0;
+ for (Map.Entry<Method, AnnotationValue.Loaded<?>> entry : values.entrySet()) {
+ if (!entry.getValue().getState().isDefined()) {
+ continue;
+ }
+ hashCode += (127 * entry.getKey().getName().hashCode()) ^ entry.getValue().hashCode();
+ }
+ return hashCode;
+ }
+
+ /**
+ * Checks if another instance is equal to this instance.
+ *
+ * @param self The annotation proxy instance.
+ * @param other The instance to be examined for equality to the represented instance.
+ * @return {@code true} if the given instance is equal to the represented instance.
+ */
+ private boolean equalsRepresentation(Object self, Object other) {
+ if (self == other) {
+ return true;
+ } else if (!annotationType.isInstance(other)) {
+ return false;
+ } else if (Proxy.isProxyClass(other.getClass())) {
+ InvocationHandler invocationHandler = Proxy.getInvocationHandler(other);
+ if (invocationHandler instanceof AnnotationInvocationHandler) {
+ return invocationHandler.equals(this);
+ }
+ }
+ try {
+ for (Map.Entry<Method, AnnotationValue.Loaded<?>> entry : values.entrySet()) {
+ try {
+ if (!entry.getValue().represents(entry.getKey().invoke(other))) {
+ return false;
+ }
+ } catch (RuntimeException exception) {
+ return false; // Incomplete annotations are not equal to one another.
+ }
+ }
+ return true;
+ } catch (InvocationTargetException ignored) {
+ return false;
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Could not access annotation property", exception);
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (!(other instanceof AnnotationInvocationHandler)) return false;
+ AnnotationInvocationHandler that = (AnnotationInvocationHandler) other;
+ if (!annotationType.equals(that.annotationType)) {
+ return false;
+ }
+ for (Map.Entry<Method, AnnotationValue.Loaded<?>> entry : values.entrySet()) {
+ if (!entry.getValue().equals(that.values.get(entry.getKey()))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = annotationType.hashCode();
+ result = 31 * result + values.hashCode();
+ for (Map.Entry<Method, ?> entry : values.entrySet()) {
+ result = 31 * result + entry.getValue().hashCode();
+ }
+ return result;
+ }
+
+ /**
+ * Represents a missing annotation property which is not represented by a default value.
+ */
+ protected static class MissingValue extends AnnotationValue.Loaded.AbstractBase<Void> implements AnnotationValue<Void, Void> {
+
+ /**
+ * The annotation type.
+ */
+ private final Class<? extends Annotation> annotationType;
+
+ /**
+ * The name of the property without an annotation value.
+ */
+ private final String property;
+
+ /**
+ * Creates a new representation for a missing annotation property.
+ *
+ * @param annotationType The annotation type.
+ * @param property The name of the property without an annotation value.
+ */
+ protected MissingValue(Class<? extends Annotation> annotationType, String property) {
+ this.annotationType = annotationType;
+ this.property = property;
+ }
+
+ /**
+ * Creates a missing value for the supplied annotation property.
+ * @param method A method representing an annotation property.
+ * @return An annotation value for a missing property.
+ */
+ @SuppressWarnings("unchecked")
+ protected static AnnotationValue<?, ?> of(Method method) {
+ return new MissingValue((Class<? extends Annotation>) method.getDeclaringClass(), method.getName());
+ }
+
+ @Override
+ public State getState() {
+ return State.UNDEFINED;
+ }
+
+ @Override
+ public boolean represents(Object value) {
+ return false;
+ }
+
+ @Override
+ public Loaded<Void> load(ClassLoader classLoader) {
+ return this;
+ }
+
+ @Override
+ public Loaded<Void> loadSilent(ClassLoader classLoader) {
+ return this;
+ }
+
+ @Override
+ public Void resolve() {
+ throw new IncompleteAnnotationException(annotationType, property);
+ }
+
+ /* does intentionally not implement hashCode, equals and toString */
+ }
+ }
+
+ /**
+ * An adapter implementation of an annotation.
+ */
+ abstract class AbstractBase implements AnnotationDescription {
+
+ /**
+ * An array containing all element types that are a legal annotation target when such a target
+ * is not specified explicitly.
+ */
+ private static final ElementType[] DEFAULT_TARGET = new ElementType[]{ElementType.ANNOTATION_TYPE,
+ ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.METHOD,
+ ElementType.PACKAGE, ElementType.PARAMETER, ElementType.TYPE};
+
+ @Override
+ public RetentionPolicy getRetention() {
+ AnnotationDescription.Loadable<Retention> retention = getAnnotationType().getDeclaredAnnotations().ofType(Retention.class);
+ return retention == null
+ ? RetentionPolicy.CLASS
+ : retention.loadSilent().value();
+ }
+
+ @Override
+ public Set<ElementType> getElementTypes() {
+ AnnotationDescription.Loadable<Target> target = getAnnotationType().getDeclaredAnnotations().ofType(Target.class);
+ return new HashSet<ElementType>(Arrays.asList(target == null
+ ? DEFAULT_TARGET
+ : target.loadSilent().value()));
+ }
+
+ @Override
+ public boolean isInherited() {
+ return getAnnotationType().getDeclaredAnnotations().isAnnotationPresent(Inherited.class);
+ }
+
+ @Override
+ public boolean isDocumented() {
+ return getAnnotationType().getDeclaredAnnotations().isAnnotationPresent(Documented.class);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if (!(other instanceof AnnotationDescription)) {
+ return false;
+ }
+ AnnotationDescription annotationDescription = ((AnnotationDescription) other);
+ TypeDescription annotationType = getAnnotationType();
+ if (!annotationDescription.getAnnotationType().equals(annotationType)) {
+ return false;
+ }
+ for (MethodDescription.InDefinedShape methodDescription : annotationType.getDeclaredMethods()) {
+ if (!getValue(methodDescription).equals(annotationDescription.getValue(methodDescription))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int hashCode = 0;
+ for (MethodDescription.InDefinedShape methodDescription : getAnnotationType().getDeclaredMethods()) {
+ hashCode += 31 * getValue(methodDescription).hashCode();
+ }
+ return hashCode;
+ }
+
+ @Override
+ public String toString() {
+ TypeDescription annotationType = getAnnotationType();
+ StringBuilder toString = new StringBuilder().append('@').append(annotationType.getName()).append('(');
+ boolean firstMember = true;
+ for (MethodDescription.InDefinedShape methodDescription : annotationType.getDeclaredMethods()) {
+ if (firstMember) {
+ firstMember = false;
+ } else {
+ toString.append(", ");
+ }
+ toString.append(methodDescription.getName()).append('=').append(getValue(methodDescription));
+ }
+ return toString.append(')').toString();
+ }
+
+ /**
+ * An abstract implementation of a loadable annotation description.
+ *
+ * @param <S> The annotation type this instance was prepared for.
+ */
+ public abstract static class ForPrepared<S extends Annotation> extends AbstractBase implements Loadable<S> {
+
+ @Override
+ public S loadSilent() {
+ try {
+ return load();
+ } catch (ClassNotFoundException exception) {
+ throw new IllegalStateException("Could not load annotation type or referenced type", exception);
+ }
+ }
+ }
+ }
+
+ /**
+ * A description of an already loaded annotation.
+ *
+ * @param <S> The type of the annotation.
+ */
+ class ForLoadedAnnotation<S extends Annotation> extends AbstractBase.ForPrepared<S> {
+
+ /**
+ * The represented annotation value.
+ */
+ private final S annotation;
+
+ /**
+ * The annotation's loaded type which might be loaded by a different class loader than the value's
+ * annotation type but must be structually equal to it.
+ */
+ private final Class<S> annotationType;
+
+ /**
+ * Creates a new annotation description for a loaded annotation.
+ *
+ * @param annotation The annotation to represent.
+ */
+ @SuppressWarnings("unchecked")
+ protected ForLoadedAnnotation(S annotation) {
+ this(annotation, (Class<S>) annotation.annotationType());
+ }
+
+ /**
+ * Creates a new annotation description for a loaded annotation.
+ *
+ * @param annotation The annotation to represent.
+ * @param annotationType The annotation's loaded type which might be loaded by a different class loader than the value's
+ * annotation type but must be structually equal to it.
+ */
+ private ForLoadedAnnotation(S annotation, Class<S> annotationType) {
+ this.annotation = annotation;
+ this.annotationType = annotationType;
+ }
+
+ /**
+ * Creates a description of the given annotation.
+ *
+ * @param annotation The annotation to be described.
+ * @param <U> The type of the annotation.
+ * @return A description of the given annotation.
+ */
+ public static <U extends Annotation> Loadable<U> of(U annotation) {
+ return new ForLoadedAnnotation<U>(annotation);
+ }
+
+ @Override
+ public S load() throws ClassNotFoundException {
+ return annotationType == annotation.annotationType()
+ ? annotation
+ : AnnotationInvocationHandler.of(annotationType.getClassLoader(), annotationType, asValue(annotation));
+ }
+
+ /**
+ * Extracts the annotation values of an annotation into a property map.
+ *
+ * @param annotation The annotation to convert.
+ * @return A mapping of property names to their annotation value.
+ */
+ private static Map<String, AnnotationValue<?, ?>> asValue(Annotation annotation) {
+ Map<String, AnnotationValue<?, ?>> annotationValues = new HashMap<String, AnnotationValue<?, ?>>();
+ for (Method property : annotation.annotationType().getDeclaredMethods()) {
+ try {
+ annotationValues.put(property.getName(), asValue(property.invoke(annotation), property.getReturnType()));
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Cannot read " + property, exception.getCause());
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access " + property, exception);
+ }
+ }
+ return annotationValues;
+ }
+
+ /**
+ * Transforms an annotation property to an annotation value.
+ *
+ * @param type The annotation's type.
+ * @param value The annotations value.
+ * @return An annotation value representation.
+ */
+ @SuppressWarnings("unchecked")
+ public static AnnotationValue<?, ?> asValue(Object value, Class<?> type) {
+ // Because enums can implement annotation interfaces, the enum property needs to be checked first.
+ if (Enum.class.isAssignableFrom(type)) {
+ return AnnotationValue.ForEnumerationDescription.<Enum>of(new EnumerationDescription.ForLoadedEnumeration((Enum) value));
+ } else if (Enum[].class.isAssignableFrom(type)) {
+ Enum<?>[] element = (Enum<?>[]) value;
+ EnumerationDescription[] enumerationDescription = new EnumerationDescription[element.length];
+ int index = 0;
+ for (Enum<?> anElement : element) {
+ enumerationDescription[index++] = new EnumerationDescription.ForLoadedEnumeration(anElement);
+ }
+ return AnnotationValue.ForDescriptionArray.<Enum>of(new TypeDescription.ForLoadedType(type.getComponentType()), enumerationDescription);
+ } else if (Annotation.class.isAssignableFrom(type)) {
+ return AnnotationValue.ForAnnotationDescription.<Annotation>of(new TypeDescription.ForLoadedType(type), asValue((Annotation) value));
+ } else if (Annotation[].class.isAssignableFrom(type)) {
+ Annotation[] element = (Annotation[]) value;
+ AnnotationDescription[] annotationDescription = new AnnotationDescription[element.length];
+ int index = 0;
+ for (Annotation anElement : element) {
+ annotationDescription[index++] = new AnnotationDescription.Latent(new TypeDescription.ForLoadedType(type.getComponentType()), asValue(anElement));
+ }
+ return AnnotationValue.ForDescriptionArray.of(new TypeDescription.ForLoadedType(type.getComponentType()), annotationDescription);
+ } else if (Class.class.isAssignableFrom(type)) {
+ return AnnotationValue.ForTypeDescription.<Class>of(new TypeDescription.ForLoadedType((Class<?>) value));
+ } else if (Class[].class.isAssignableFrom(type)) {
+ Class<?>[] element = (Class<?>[]) value;
+ TypeDescription[] typeDescription = new TypeDescription[element.length];
+ int index = 0;
+ for (Class<?> anElement : element) {
+ typeDescription[index++] = new TypeDescription.ForLoadedType(anElement);
+ }
+ return AnnotationValue.ForDescriptionArray.of(typeDescription);
+ } else {
+ return AnnotationValue.ForConstant.of(value);
+ }
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should always be wrapped for clarity")
+ public AnnotationValue<?, ?> getValue(MethodDescription.InDefinedShape property) {
+ if (!property.getDeclaringType().represents(annotation.annotationType())) {
+ throw new IllegalArgumentException(property + " does not represent " + annotation.annotationType());
+ }
+ try {
+ boolean accessible = property.getDeclaringType().isPublic(); // method is required to be public
+ Method method = property instanceof MethodDescription.ForLoadedMethod
+ ? ((MethodDescription.ForLoadedMethod) property).getLoadedMethod()
+ : null;
+ if (method == null || method.getDeclaringClass() != annotation.annotationType() || (!accessible && !method.isAccessible())) {
+ method = annotation.annotationType().getMethod(property.getName());
+ if (!accessible) {
+ AccessController.doPrivileged(new SetAccessibleAction<Method>(method));
+ }
+ }
+ return asValue(method.invoke(annotation), method.getReturnType());
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error reading annotation property " + property, exception.getCause());
+ } catch (Exception exception) {
+ throw new IllegalStateException("Cannot access annotation property " + property, exception);
+ }
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T extends Annotation> Loadable<T> prepare(Class<T> annotationType) {
+ if (!annotation.annotationType().getName().equals(annotationType.getName())) {
+ throw new IllegalArgumentException(annotationType + " does not represent " + annotation.annotationType());
+ }
+ return annotationType == annotation.annotationType()
+ ? (Loadable<T>) this
+ : new ForLoadedAnnotation<T>((T) annotation, annotationType);
+ }
+
+ @Override
+ public TypeDescription getAnnotationType() {
+ return new TypeDescription.ForLoadedType(annotation.annotationType());
+ }
+ }
+
+ /**
+ * A latent description of an annotation value that is defined explicitly.
+ */
+ class Latent extends AbstractBase {
+
+ /**
+ * The type of the annotation.
+ */
+ private final TypeDescription annotationType;
+
+ /**
+ * The values of the annotation mapped by their property name.
+ */
+ private final Map<String, ? extends AnnotationValue<?, ?>> annotationValues;
+
+ /**
+ * Creates a new latent annotation description.
+ *
+ * @param annotationType The type of the annotation.
+ * @param annotationValues The values of the annotation mapped by their property name.
+ */
+ protected Latent(TypeDescription annotationType, Map<String, ? extends AnnotationValue<?, ?>> annotationValues) {
+ this.annotationType = annotationType;
+ this.annotationValues = annotationValues;
+ }
+
+ @Override
+ public AnnotationValue<?, ?> getValue(MethodDescription.InDefinedShape property) {
+ AnnotationValue<?, ?> value = annotationValues.get(property.getName());
+ if (value != null) {
+ return value;
+ }
+ AnnotationValue<?, ?> defaultValue = property.getDefaultValue();
+ if (defaultValue != null) {
+ return defaultValue;
+ }
+ throw new IllegalArgumentException("No value defined for: " + property);
+ }
+
+ @Override
+ public TypeDescription getAnnotationType() {
+ return annotationType;
+ }
+
+ @Override
+ public <T extends Annotation> Loadable<T> prepare(Class<T> annotationType) {
+ if (!this.annotationType.represents(annotationType)) {
+ throw new IllegalArgumentException(annotationType + " does not represent " + this.annotationType);
+ }
+ return new Loadable<T>(annotationType);
+ }
+
+ /**
+ * A loadable annotation description of a latent annotation description.
+ *
+ * @param <S> The annotation type.
+ */
+ protected class Loadable<S extends Annotation> extends AbstractBase.ForPrepared<S> {
+
+ /**
+ * The annotation type.
+ */
+ private final Class<S> annotationType;
+
+ /**
+ * Creates a loadable version of a latent annotation description.
+ *
+ * @param annotationType The annotation type.
+ */
+ protected Loadable(Class<S> annotationType) {
+ this.annotationType = annotationType;
+ }
+
+ @Override
+ public S load() throws ClassNotFoundException {
+ return AnnotationDescription.AnnotationInvocationHandler.of(annotationType.getClassLoader(), annotationType, annotationValues);
+ }
+
+ @Override
+ public AnnotationValue<?, ?> getValue(MethodDescription.InDefinedShape property) {
+ return Latent.this.getValue(property);
+ }
+
+ @Override
+ public TypeDescription getAnnotationType() {
+ return new TypeDescription.ForLoadedType(annotationType);
+ }
+
+ @Override
+ public <T extends Annotation> Loadable<T> prepare(Class<T> annotationType) {
+ return Latent.this.prepare(annotationType);
+ }
+ }
+ }
+
+ /**
+ * A builder for pragmatically creating {@link net.bytebuddy.description.annotation.AnnotationDescription}.
+ */
+ @EqualsAndHashCode
+ class Builder {
+
+ /**
+ * The annotation type.
+ */
+ private final TypeDescription annotationType;
+
+ /**
+ * A mapping of annotation properties to their annotation values.
+ */
+ private final Map<String, AnnotationValue<?, ?>> annotationValues;
+
+ /**
+ * Creates a builder for an annotation description.
+ *
+ * @param annotationType The annotation type.
+ * @param annotationValues A mapping of annotation properties to their annotation values.
+ */
+ protected Builder(TypeDescription annotationType, Map<String, AnnotationValue<?, ?>> annotationValues) {
+ this.annotationType = annotationType;
+ this.annotationValues = annotationValues;
+ }
+
+ /**
+ * Creates a builder for creating an annotation of the given type.
+ *
+ * @param annotationType The annotation type.
+ * @return A builder for creating an annotation of the given type.
+ */
+ public static Builder ofType(Class<? extends Annotation> annotationType) {
+ return ofType(new TypeDescription.ForLoadedType(annotationType));
+ }
+
+ /**
+ * Creates a builder for creating an annotation of the given type.
+ *
+ * @param annotationType A description of the annotation type.
+ * @return A builder for creating an annotation of the given type.
+ */
+ public static Builder ofType(TypeDescription annotationType) {
+ if (!annotationType.isAnnotation()) {
+ throw new IllegalArgumentException("Not an annotation type: " + annotationType);
+ }
+ return new Builder(annotationType, Collections.<String, AnnotationValue<?, ?>>emptyMap());
+ }
+
+ /**
+ * Returns a builder with the additional, given property.
+ *
+ * @param property The name of the property to define.
+ * @param value An explicit description of the annotation value.
+ * @return A builder with the additional, given property.
+ */
+ public Builder define(String property, AnnotationValue<?, ?> value) {
+ MethodList<?> methodDescriptions = annotationType.getDeclaredMethods().filter(named(property));
+ if (methodDescriptions.isEmpty()) {
+ throw new IllegalArgumentException(annotationType + " does not define a property named " + property);
+ } else if (!methodDescriptions.getOnly().getReturnType().asErasure().isAnnotationValue(value.resolve())) {
+ throw new IllegalArgumentException(value + " cannot be assigned to " + property);
+ }
+ Map<String, AnnotationValue<?, ?>> annotationValues = new HashMap<String, AnnotationValue<?, ?>>();
+ annotationValues.putAll(this.annotationValues);
+ if (annotationValues.put(methodDescriptions.getOnly().getName(), value) != null) {
+ throw new IllegalArgumentException("Property already defined: " + property);
+ }
+ return new Builder(annotationType, annotationValues);
+ }
+
+ /**
+ * Returns a builder with the additional enumeration property.
+ *
+ * @param property The name of the property to define.
+ * @param value The enumeration value to define.
+ * @return A builder with the additional enumeration property.
+ */
+ public Builder define(String property, Enum<?> value) {
+ return define(property, new EnumerationDescription.ForLoadedEnumeration(value));
+ }
+
+ /**
+ * Returns a builder with the additional enumeration property.
+ *
+ * @param property The name of the property to define.
+ * @param enumerationType The type of the enumeration.
+ * @param value The enumeration value to define.
+ * @return A builder with the additional enumeration property.
+ */
+ public Builder define(String property, TypeDescription enumerationType, String value) {
+ return define(property, new EnumerationDescription.Latent(enumerationType, value));
+ }
+
+ /**
+ * Returns a builder with the additional enumeration property.
+ *
+ * @param property The name of the property to define.
+ * @param value A description of the enumeration value to define.
+ * @return A builder with the additional enumeration property.
+ */
+ @SuppressWarnings("unchecked")
+ public Builder define(String property, EnumerationDescription value) {
+ return define(property, AnnotationValue.ForEnumerationDescription.<Enum>of(value));
+ }
+
+ /**
+ * Returns a builder with the additional annotation property.
+ *
+ * @param property The name of the property to define.
+ * @param annotation The annotation value to define.
+ * @return A builder with the additional annotation property.
+ */
+ public Builder define(String property, Annotation annotation) {
+ return define(property, new ForLoadedAnnotation<Annotation>(annotation));
+ }
+
+ /**
+ * Returns a builder with the additional annotation property.
+ *
+ * @param property The name of the property to define.
+ * @param annotationDescription A description of the annotation value to define.
+ * @return A builder with the additional annotation property.
+ */
+ public Builder define(String property, AnnotationDescription annotationDescription) {
+ return define(property, new AnnotationValue.ForAnnotationDescription<Annotation>(annotationDescription));
+ }
+
+ /**
+ * Returns a builder with the additional class property.
+ *
+ * @param property The name of the property to define.
+ * @param type The class value to define.
+ * @return A builder with the additional class property.
+ */
+ public Builder define(String property, Class<?> type) {
+ return define(property, new TypeDescription.ForLoadedType(type));
+ }
+
+ /**
+ * Returns a builder with the additional class property.
+ *
+ * @param property The name of the property to define.
+ * @param typeDescription A description of the type to define as a property value.
+ * @return A builder with the additional class property.
+ */
+ @SuppressWarnings("unchecked")
+ public Builder define(String property, TypeDescription typeDescription) {
+ return define(property, AnnotationValue.ForTypeDescription.<Class>of(typeDescription));
+ }
+
+ /**
+ * Returns a builder with the additional enumeration array property.
+ *
+ * @param property The name of the property to define.
+ * @param enumerationType The type of the enumeration, i.e. the component type of the enumeration array.
+ * @param value The enumeration values to be contained by the array.
+ * @param <T> The enumeration type.
+ * @return A builder with the additional class property.
+ */
+ @SuppressWarnings("unchecked")
+ public <T extends Enum<?>> Builder defineEnumerationArray(String property, Class<T> enumerationType, T... value) {
+ EnumerationDescription[] enumerationDescription = new EnumerationDescription[value.length];
+ int index = 0;
+ for (T aValue : value) {
+ enumerationDescription[index++] = new EnumerationDescription.ForLoadedEnumeration(aValue);
+ }
+ return defineEnumerationArray(property, new TypeDescription.ForLoadedType(enumerationType), enumerationDescription);
+ }
+
+ /**
+ * Returns a builder with the additional enumeration array property.
+ *
+ * @param property The name of the property to define.
+ * @param enumerationType The type of the enumerations, i.e. is the component type of the enumeration array.
+ * @param value The enumeration values to be contained by the array.
+ * @return A builder with the additional enumeration property.
+ */
+ public Builder defineEnumerationArray(String property, TypeDescription enumerationType, String... value) {
+ if (!enumerationType.isEnum()) {
+ throw new IllegalArgumentException("Not an enumeration type: " + enumerationType);
+ }
+ EnumerationDescription[] enumerationDescription = new EnumerationDescription[value.length];
+ for (int i = 0; i < value.length; i++) {
+ enumerationDescription[i] = new EnumerationDescription.Latent(enumerationType, value[i]);
+ }
+ return defineEnumerationArray(property, enumerationType, enumerationDescription);
+ }
+
+ /**
+ * Returns a builder with the additional enumeration array property.
+ *
+ * @param property The name of the property to define.
+ * @param enumerationType The type of the enumerations, i.e. the component type of the enumeration array.
+ * @param value Descriptions of the enumerations to be contained by the array.
+ * @return A builder with the additional enumeration property.
+ */
+ @SuppressWarnings("unchecked")
+ public Builder defineEnumerationArray(String property, TypeDescription enumerationType, EnumerationDescription... value) {
+ return define(property, AnnotationValue.ForDescriptionArray.<Enum>of(enumerationType, value));
+ }
+
+ /**
+ * Returns a builder with the additional annotation array property.
+ *
+ * @param property The name of the property to define.
+ * @param annotationType The type of the annotations, i.e. the component type of the enumeration array.
+ * @param annotation The annotation values to be contained by the array.
+ * @param <T> The annotation type.
+ * @return A builder with the additional annotation property.
+ */
+ @SuppressWarnings("unchecked")
+ public <T extends Annotation> Builder defineAnnotationArray(String property, Class<T> annotationType, T... annotation) {
+ return defineAnnotationArray(property,
+ new TypeDescription.ForLoadedType(annotationType),
+ new AnnotationList.ForLoadedAnnotations(annotation).toArray(new AnnotationDescription[annotation.length]));
+ }
+
+ /**
+ * Returns a builder with the additional annotation array property.
+ *
+ * @param property The name of the property to define.
+ * @param annotationType The type of the annotations, i.e. the component type of the enumeration array.
+ * @param annotationDescription Descriptions of the annotation values to be contained by the array.
+ * @return A builder with the additional annotation property.
+ */
+ public Builder defineAnnotationArray(String property, TypeDescription annotationType, AnnotationDescription... annotationDescription) {
+ return define(property, AnnotationValue.ForDescriptionArray.of(annotationType, annotationDescription));
+ }
+
+ /**
+ * Returns a builder with the additional type array property.
+ *
+ * @param property The name of the property to define.
+ * @param type The types that should be contained by the array.
+ * @return A builder with the additional type array property.
+ */
+ public Builder defineTypeArray(String property, Class<?>... type) {
+ return defineTypeArray(property, new TypeList.ForLoadedTypes(type).toArray(new TypeDescription[type.length]));
+ }
+
+ /**
+ * Returns a builder with the additional type array property.
+ *
+ * @param property The name of the property to define.
+ * @param typeDescription Descriptions of the types that should be contained by the array.
+ * @return A builder with the additional type array property.
+ */
+ @SuppressWarnings("unchecked")
+ public Builder defineTypeArray(String property, TypeDescription... typeDescription) {
+ return define(property, AnnotationValue.ForDescriptionArray.of(typeDescription));
+ }
+
+ /**
+ * Returns a builder with the additional {@code boolean} property.
+ *
+ * @param property The name of the property to define.
+ * @param value The {@code boolean} value to define for the property.
+ * @return A builder with the additional {@code boolean} property.
+ */
+ public Builder define(String property, boolean value) {
+ return define(property, AnnotationValue.ForConstant.of(value));
+ }
+
+ /**
+ * Returns a builder with the additional {@code byte} property.
+ *
+ * @param property The name of the property to define.
+ * @param value The {@code byte} value to define for the property.
+ * @return A builder with the additional {@code byte} property.
+ */
+ public Builder define(String property, byte value) {
+ return define(property, AnnotationValue.ForConstant.of(value));
+ }
+
+ /**
+ * Returns a builder with the additional {@code char} property.
+ *
+ * @param property The name of the property to define.
+ * @param value The {@code char} value to define for the property.
+ * @return A builder with the additional {@code char} property.
+ */
+ public Builder define(String property, char value) {
+ return define(property, AnnotationValue.ForConstant.of(value));
+ }
+
+ /**
+ * Returns a builder with the additional {@code short} property.
+ *
+ * @param property The name of the property to define.
+ * @param value The {@code short} value to define for the property.
+ * @return A builder with the additional {@code short} property.
+ */
+ public Builder define(String property, short value) {
+ return define(property, AnnotationValue.ForConstant.of(value));
+ }
+
+ /**
+ * Returns a builder with the additional {@code int} property.
+ *
+ * @param property The name of the property to define.
+ * @param value The {@code int} value to define for the property.
+ * @return A builder with the additional {@code int} property.
+ */
+ public Builder define(String property, int value) {
+ return define(property, AnnotationValue.ForConstant.of(value));
+ }
+
+ /**
+ * Returns a builder with the additional {@code long} property.
+ *
+ * @param property The name of the property to define.
+ * @param value The {@code long} value to define for the property.
+ * @return A builder with the additional {@code long} property.
+ */
+ public Builder define(String property, long value) {
+ return define(property, AnnotationValue.ForConstant.of(value));
+ }
+
+ /**
+ * Returns a builder with the additional {@code float} property.
+ *
+ * @param property The name of the property to define.
+ * @param value The {@code float} value to define for the property.
+ * @return A builder with the additional {@code float} property.
+ */
+ public Builder define(String property, float value) {
+ return define(property, AnnotationValue.ForConstant.of(value));
+ }
+
+ /**
+ * Returns a builder with the additional {@code double} property.
+ *
+ * @param property The name of the property to define.
+ * @param value The {@code double} value to define for the property.
+ * @return A builder with the additional {@code double} property.
+ */
+ public Builder define(String property, double value) {
+ return define(property, AnnotationValue.ForConstant.of(value));
+ }
+
+ /**
+ * Returns a builder with the additional {@link java.lang.String} property.
+ *
+ * @param property The name of the property to define.
+ * @param value The {@link java.lang.String} value to define for the property.
+ * @return A builder with the additional {@link java.lang.String} property.
+ */
+ public Builder define(String property, String value) {
+ return define(property, AnnotationValue.ForConstant.of(value));
+ }
+
+ /**
+ * Returns a builder with the additional {@code boolean} array property.
+ *
+ * @param property The name of the property to define.
+ * @param value The {@code boolean} values to define for the property.
+ * @return A builder with the additional {@code boolean} array property.
+ */
+ public Builder defineArray(String property, boolean... value) {
+ return define(property, AnnotationValue.ForConstant.of(value));
+ }
+
+ /**
+ * Returns a builder with the additional {@code byte} array property.
+ *
+ * @param property The name of the property to define.
+ * @param value The {@code byte} values to define for the property.
+ * @return A builder with the additional {@code byte} array property.
+ */
+ public Builder defineArray(String property, byte... value) {
+ return define(property, AnnotationValue.ForConstant.of(value));
+ }
+
+ /**
+ * Returns a builder with the additional {@code char} array property.
+ *
+ * @param property The name of the property to define.
+ * @param value The {@code char} values to define for the property.
+ * @return A builder with the additional {@code char} array property.
+ */
+ public Builder defineArray(String property, char... value) {
+ return define(property, AnnotationValue.ForConstant.of(value));
+ }
+
+ /**
+ * Returns a builder with the additional {@code short} array property.
+ *
+ * @param property The name of the property to define.
+ * @param value The {@code short} values to define for the property.
+ * @return A builder with the additional {@code short} array property.
+ */
+ public Builder defineArray(String property, short... value) {
+ return define(property, AnnotationValue.ForConstant.of(value));
+ }
+
+ /**
+ * Returns a builder with the additional {@code int} array property.
+ *
+ * @param property The name of the property to define.
+ * @param value The {@code int} values to define for the property.
+ * @return A builder with the additional {@code int} array property.
+ */
+ public Builder defineArray(String property, int... value) {
+ return define(property, AnnotationValue.ForConstant.of(value));
+ }
+
+ /**
+ * Returns a builder with the additional {@code long} array property.
+ *
+ * @param property The name of the property to define.
+ * @param value The {@code long} values to define for the property.
+ * @return A builder with the additional {@code long} array property.
+ */
+ public Builder defineArray(String property, long... value) {
+ return define(property, AnnotationValue.ForConstant.of(value));
+ }
+
+ /**
+ * Returns a builder with the additional {@code float} array property.
+ *
+ * @param property The name of the property to define.
+ * @param value The {@code float} values to define for the property.
+ * @return A builder with the additional {@code float} array property.
+ */
+ public Builder defineArray(String property, float... value) {
+ return define(property, AnnotationValue.ForConstant.of(value));
+ }
+
+ /**
+ * Returns a builder with the additional {@code double} array property.
+ *
+ * @param property The name of the property to define.
+ * @param value The {@code double} values to define for the property.
+ * @return A builder with the additional {@code double} array property.
+ */
+ public Builder defineArray(String property, double... value) {
+ return define(property, AnnotationValue.ForConstant.of(value));
+ }
+
+ /**
+ * Returns a builder with the additional {@link java.lang.String} array property.
+ *
+ * @param property The name of the property to define.
+ * @param value The {@link java.lang.String} array value to define for the property.
+ * @return A builder with the additional {@link java.lang.String} array property.
+ */
+ public Builder defineArray(String property, String... value) {
+ return define(property, AnnotationValue.ForConstant.of(value));
+ }
+
+ /**
+ * Creates an annotation description for the values that were defined for this builder.
+ *
+ * @return An appropriate annotation description.
+ */
+ public AnnotationDescription build() {
+ for (MethodDescription methodDescription : annotationType.getDeclaredMethods()) {
+ if (annotationValues.get(methodDescription.getName()) == null && methodDescription.getDefaultValue() == null) {
+ throw new IllegalStateException("No value or default value defined for " + methodDescription.getName());
+ }
+ }
+ return new Latent(annotationType, annotationValues);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/annotation/AnnotationList.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/annotation/AnnotationList.java
new file mode 100644
index 0000000..43a0690
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/annotation/AnnotationList.java
@@ -0,0 +1,321 @@
+package net.bytebuddy.description.annotation;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.matcher.FilterableList;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Defines a list of annotation instances.
+ */
+public interface AnnotationList extends FilterableList<AnnotationDescription, AnnotationList> {
+
+ /**
+ * Checks if this list contains an annotation of the given type.
+ *
+ * @param annotationType The type to find in the list.
+ * @return {@code true} if the list contains the annotation type.
+ */
+ boolean isAnnotationPresent(Class<? extends Annotation> annotationType);
+
+ /**
+ * Checks if this list contains an annotation of the given type.
+ *
+ * @param annotationType The type to find in the list.
+ * @return {@code true} if the list contains the annotation type.
+ */
+ boolean isAnnotationPresent(TypeDescription annotationType);
+
+ /**
+ * Finds the first annotation of the given type and returns it.
+ *
+ * @param annotationType The type to be found in the list.
+ * @param <T> The annotation type.
+ * @return The annotation description or {@code null} if no such annotation was found.
+ */
+ <T extends Annotation> AnnotationDescription.Loadable<T> ofType(Class<T> annotationType);
+
+ /**
+ * Finds the first annotation of the given type and returns it.
+ *
+ * @param annotationType The type to be found in the list.
+ * @return The annotation description or {@code null} if no such annotation was found.
+ */
+ AnnotationDescription ofType(TypeDescription annotationType);
+
+ /**
+ * Returns only annotations that are marked as {@link java.lang.annotation.Inherited} as long as they are not
+ * contained by the set of ignored annotation types.
+ *
+ * @param ignoredTypes A list of annotation types to be ignored from the lookup.
+ * @return A list of all inherited annotations besides of the given ignored types.
+ */
+ AnnotationList inherited(Set<? extends TypeDescription> ignoredTypes);
+
+ /**
+ * Only retains annotations with the given retention policy.
+ *
+ * @param matcher A matcher for the required retention policy.
+ * @return A of annotations only with elements
+ */
+ AnnotationList visibility(ElementMatcher<? super RetentionPolicy> matcher);
+
+ /**
+ * Returns a list of the annotation types of this list.
+ *
+ * @return A list of the annotation types of this list.
+ */
+ TypeList asTypeList();
+
+ /**
+ * An abstract base implementation of an annotation list.
+ */
+ abstract class AbstractBase extends FilterableList.AbstractBase<AnnotationDescription, AnnotationList> implements AnnotationList {
+
+ @Override
+ public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) {
+ for (AnnotationDescription annotation : this) {
+ if (annotation.getAnnotationType().represents(annotationType)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isAnnotationPresent(TypeDescription annotationType) {
+ for (AnnotationDescription annotation : this) {
+ if (annotation.getAnnotationType().equals(annotationType)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T extends Annotation> AnnotationDescription.Loadable<T> ofType(Class<T> annotationType) {
+ for (AnnotationDescription annotation : this) {
+ if (annotation.getAnnotationType().represents(annotationType)) {
+ return annotation.prepare(annotationType);
+ }
+ }
+ return (AnnotationDescription.Loadable<T>) AnnotationDescription.UNDEFINED;
+ }
+
+ @Override
+ public AnnotationDescription ofType(TypeDescription annotationType) {
+ for (AnnotationDescription annotation : this) {
+ if (annotation.getAnnotationType().equals(annotationType)) {
+ return annotation;
+ }
+ }
+ return AnnotationDescription.UNDEFINED;
+ }
+
+ @Override
+ public AnnotationList inherited(Set<? extends TypeDescription> ignoredTypes) {
+ List<AnnotationDescription> inherited = new ArrayList<AnnotationDescription>(size());
+ for (AnnotationDescription annotation : this) {
+ if (!ignoredTypes.contains(annotation.getAnnotationType()) && annotation.isInherited()) {
+ inherited.add(annotation);
+ }
+ }
+ return wrap(inherited);
+ }
+
+ @Override
+ public AnnotationList visibility(ElementMatcher<? super RetentionPolicy> matcher) {
+ List<AnnotationDescription> annotationDescriptions = new ArrayList<AnnotationDescription>(size());
+ for (AnnotationDescription annotation : this) {
+ if (matcher.matches(annotation.getRetention())) {
+ annotationDescriptions.add(annotation);
+ }
+ }
+ return wrap(annotationDescriptions);
+ }
+
+ @Override
+ public TypeList asTypeList() {
+ List<TypeDescription> annotationTypes = new ArrayList<TypeDescription>(size());
+ for (AnnotationDescription annotation : this) {
+ annotationTypes.add(annotation.getAnnotationType());
+ }
+ return new TypeList.Explicit(annotationTypes);
+ }
+
+ @Override
+ protected AnnotationList wrap(List<AnnotationDescription> values) {
+ return new Explicit(values);
+ }
+ }
+
+ /**
+ * Describes an array of loaded {@link java.lang.annotation.Annotation}s as an annotation list.
+ */
+ class ForLoadedAnnotations extends AbstractBase {
+
+ /**
+ * The represented annotations.
+ */
+ private final List<? extends Annotation> annotations;
+
+ /**
+ * Creates a new list of loaded annotations.
+ *
+ * @param annotation The represented annotations.
+ */
+ public ForLoadedAnnotations(Annotation... annotation) {
+ this(Arrays.asList(annotation));
+ }
+
+ /**
+ * Creates a new list of loaded annotations.
+ *
+ * @param annotations The represented annotations.
+ */
+ public ForLoadedAnnotations(List<? extends Annotation> annotations) {
+ this.annotations = annotations;
+ }
+
+ /**
+ * Creates a list of annotation lists representing the given loaded annotations.
+ *
+ * @param annotations The annotations to represent where each dimension is converted into a list.
+ * @return A list of annotation lists representing the given annotations.
+ */
+ public static List<AnnotationList> asList(Annotation[][] annotations) {
+ List<AnnotationList> result = new ArrayList<AnnotationList>(annotations.length);
+ for (Annotation[] annotation : annotations) {
+ result.add(new ForLoadedAnnotations(annotation));
+ }
+ return result;
+ }
+
+ @Override
+ public AnnotationDescription get(int index) {
+ return AnnotationDescription.ForLoadedAnnotation.of(annotations.get(index));
+ }
+
+ @Override
+ public int size() {
+ return annotations.size();
+ }
+ }
+
+ /**
+ * Represents a list of explicitly provided annotation descriptions.
+ */
+ class Explicit extends AbstractBase {
+
+ /**
+ * The list of represented annotation descriptions.
+ */
+ private final List<? extends AnnotationDescription> annotationDescriptions;
+
+ /**
+ * Creates a new list of annotation descriptions.
+ *
+ * @param annotationDescription The list of represented annotation descriptions.
+ */
+ public Explicit(AnnotationDescription... annotationDescription) {
+ this(Arrays.asList(annotationDescription));
+ }
+
+ /**
+ * Creates a new list of annotation descriptions.
+ *
+ * @param annotationDescriptions The list of represented annotation descriptions.
+ */
+ public Explicit(List<? extends AnnotationDescription> annotationDescriptions) {
+ this.annotationDescriptions = annotationDescriptions;
+ }
+
+ /**
+ * Creates a list of annotation lists for a given multidimensional list of annotation descriptions.
+ *
+ * @param annotations The list of annotations to represent as a list of annotation lists.
+ * @return The list of annotation lists.
+ */
+ public static List<AnnotationList> asList(List<? extends List<? extends AnnotationDescription>> annotations) {
+ List<AnnotationList> result = new ArrayList<AnnotationList>(annotations.size());
+ for (List<? extends AnnotationDescription> annotation : annotations) {
+ result.add(new Explicit(annotation));
+ }
+ return result;
+ }
+
+ @Override
+ public AnnotationDescription get(int index) {
+ return annotationDescriptions.get(index);
+ }
+
+ @Override
+ public int size() {
+ return annotationDescriptions.size();
+ }
+ }
+
+ /**
+ * Represents an empty annotation list.
+ */
+ class Empty extends FilterableList.Empty<AnnotationDescription, AnnotationList> implements AnnotationList {
+
+ /**
+ * Creates a list of empty annotation lists of the given dimension.
+ *
+ * @param length The length of the list.
+ * @return A list of empty annotation lists of the given length.
+ */
+ public static List<AnnotationList> asList(int length) {
+ List<AnnotationList> result = new ArrayList<AnnotationList>(length);
+ for (int i = 0; i < length; i++) {
+ result.add(new AnnotationList.Empty());
+ }
+ return result;
+ }
+
+ @Override
+ public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) {
+ return false;
+ }
+
+ @Override
+ public boolean isAnnotationPresent(TypeDescription annotationType) {
+ return false;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T extends Annotation> AnnotationDescription.Loadable<T> ofType(Class<T> annotationType) {
+ return (AnnotationDescription.Loadable<T>) AnnotationDescription.UNDEFINED;
+ }
+
+ @Override
+ public AnnotationDescription ofType(TypeDescription annotationType) {
+ return AnnotationDescription.UNDEFINED;
+ }
+
+ @Override
+ public AnnotationList inherited(Set<? extends TypeDescription> ignoredTypes) {
+ return this;
+ }
+
+ @Override
+ public AnnotationList visibility(ElementMatcher<? super RetentionPolicy> matcher) {
+ return this;
+ }
+
+ @Override
+ public TypeList asTypeList() {
+ return new TypeList.Empty();
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/annotation/AnnotationSource.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/annotation/AnnotationSource.java
new file mode 100644
index 0000000..3d2c238
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/annotation/AnnotationSource.java
@@ -0,0 +1,70 @@
+package net.bytebuddy.description.annotation;
+
+import lombok.EqualsAndHashCode;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Describes a declaration source for annotations.
+ */
+public interface AnnotationSource {
+
+ /**
+ * Returns a list of annotations that are declared by this instance.
+ *
+ * @return A list of declared annotations.
+ */
+ AnnotationList getDeclaredAnnotations();
+
+ /**
+ * An annotation source that does not declare any annotations.
+ */
+ enum Empty implements AnnotationSource {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return new AnnotationList.Empty();
+ }
+ }
+
+ /**
+ * An annotation source that declares a given list of annotations.
+ */
+ @EqualsAndHashCode
+ class Explicit implements AnnotationSource {
+
+ /**
+ * The represented annotations.
+ */
+ private final List<? extends AnnotationDescription> annotations;
+
+ /**
+ * Creates a new explicit annotation source.
+ *
+ * @param annotation The represented annotations.
+ */
+ public Explicit(AnnotationDescription... annotation) {
+ this(Arrays.asList(annotation));
+ }
+
+ /**
+ * Creates a new explicit annotation source.
+ *
+ * @param annotations The represented annotations.
+ */
+ public Explicit(List<? extends AnnotationDescription> annotations) {
+ this.annotations = annotations;
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return new AnnotationList.Explicit(annotations);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/annotation/AnnotationValue.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/annotation/AnnotationValue.java
new file mode 100644
index 0000000..7b751a9
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/annotation/AnnotationValue.java
@@ -0,0 +1,1928 @@
+package net.bytebuddy.description.annotation;
+
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.description.enumeration.EnumerationDescription;
+import net.bytebuddy.description.type.TypeDescription;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
+import java.util.*;
+
+/**
+ * Representation of an unloaded annotation value where all values represent either:
+ * <ul>
+ * <li>Primitive values (as their wrappers), {@link String}s or arrays of primitive types or strings.</li>
+ * <li>A {@link TypeDescription} or an array of such a descriptions.</li>
+ * <li>An {@link EnumerationDescription} or an array of such a description.</li>
+ * <li>An {@link AnnotationDescription} or an array of such a description.</li>
+ * </ul>
+ * The represented values are not necessarily resolvable, i.e. can contain non-available types, unknown enumeration
+ * constants or inconsistent annotations.
+ *
+ * @param <T> The represented value's unloaded type.
+ * @param <S> The represented value's loaded type.
+ */
+public interface AnnotationValue<T, S> {
+
+ /**
+ * An undefined annotation value.
+ */
+ AnnotationValue<?, ?> UNDEFINED = null;
+
+ /**
+ * Resolves the unloaded value of this annotation.
+ *
+ * @return The unloaded value of this annotation.
+ */
+ T resolve();
+
+ /**
+ * Resolves the unloaded value of this annotation.
+ *
+ * @param type The annotation value's unloaded type.
+ * @param <W> The annotation value's unloaded type.
+ * @return The unloaded value of this annotation.
+ */
+ <W> W resolve(Class<? extends W> type);
+
+ /**
+ * Returns the loaded value of this annotation.
+ *
+ * @param classLoader The class loader for loading this value.
+ * @return The loaded value of this annotation.
+ * @throws ClassNotFoundException If a type that represents a loaded value cannot be found.
+ */
+ Loaded<S> load(ClassLoader classLoader) throws ClassNotFoundException;
+
+ /**
+ * Returns the loaded value of this annotation without throwing a checked exception.
+ *
+ * @param classLoader The class loader for loading this value.
+ * @return The loaded value of this annotation.
+ */
+ Loaded<S> loadSilent(ClassLoader classLoader);
+
+ /**
+ * A rendering dispatcher is responsible for resolving annotation values to {@link String} representations.
+ */
+ enum RenderingDispatcher {
+
+ /**
+ * A rendering dispatcher for any VM previous to Java 9.
+ */
+ LEGACY_VM('[', ']') {
+ @Override
+ public String toSourceString(char value) {
+ return Character.toString(value);
+ }
+
+ @Override
+ public String toSourceString(long value) {
+ return Long.toString(value);
+ }
+
+ @Override
+ public String toSourceString(float value) {
+ return Float.toString(value);
+ }
+
+ @Override
+ public String toSourceString(double value) {
+ return Double.toString(value);
+ }
+
+ @Override
+ public String toSourceString(String value) {
+ return value;
+ }
+
+ @Override
+ public String toSourceString(TypeDescription value) {
+ return value.toString();
+ }
+ },
+
+ /**
+ * A rendering dispatcher for Java 9 onward.
+ */
+ JAVA_9_CAPABLE_VM('{', '}') {
+ @Override
+ public String toSourceString(char value) {
+ StringBuilder stringBuilder = new StringBuilder().append('\'');
+ if (value == '\'') {
+ stringBuilder.append("\\\'");
+ } else {
+ stringBuilder.append(value);
+ }
+ return stringBuilder.append('\'').toString();
+ }
+
+ @Override
+ public String toSourceString(long value) {
+ return Math.abs(value) <= Integer.MAX_VALUE
+ ? String.valueOf(value)
+ : value + "L";
+ }
+
+ @Override
+ public String toSourceString(float value) {
+ return Math.abs(value) <= Float.MAX_VALUE // Float.isFinite(value)
+ ? Float.toString(value) + "f"
+ : (Float.isInfinite(value) ? (value < 0.0f ? "-1.0f/0.0f" : "1.0f/0.0f") : "0.0f/0.0f");
+ }
+
+ @Override
+ public String toSourceString(double value) {
+ return Math.abs(value) <= Double.MAX_VALUE // Double.isFinite(value)
+ ? Double.toString(value)
+ : (Double.isInfinite(value) ? (value < 0.0d ? "-1.0/0.0" : "1.0/0.0") : "0.0/0.0");
+ }
+
+ @Override
+ public String toSourceString(String value) {
+ return "\"" + (value.indexOf('"') == -1
+ ? value
+ : value.replace("\"", "\\\"")) + "\"";
+ }
+
+ @Override
+ public String toSourceString(TypeDescription value) {
+ return value.getActualName() + ".class";
+ }
+ };
+
+ /**
+ * The rendering dispatcher for the current VM.
+ */
+ public static final RenderingDispatcher CURRENT = ClassFileVersion.ofThisVm(ClassFileVersion.JAVA_V6).isAtLeast(ClassFileVersion.JAVA_V9)
+ ? JAVA_9_CAPABLE_VM
+ : LEGACY_VM;
+
+ /**
+ * The opening brace of an array {@link String} representation.
+ */
+ private final char openingBrace;
+
+ /**
+ * The closing brace of an array {@link String} representation.
+ */
+ private final char closingBrace;
+
+ /**
+ * Creates a new rendering dispatcher.
+ *
+ * @param openingBrace The opening brace of an array {@link String} representation.
+ * @param closingBrace The closing brace of an array {@link String} representation.
+ */
+ RenderingDispatcher(char openingBrace, char closingBrace) {
+ this.openingBrace = openingBrace;
+ this.closingBrace = closingBrace;
+ }
+
+ /**
+ * Represents the supplied {@code boolean} value as a {@link String}.
+ *
+ * @param value The {@code boolean} value to render.
+ * @return An appropriate {@link String} representation.
+ */
+ public String toSourceString(boolean value) {
+ return Boolean.toString(value);
+ }
+
+ /**
+ * Represents the supplied {@code boolean} value as a {@link String}.
+ *
+ * @param value The {@code boolean} value to render.
+ * @return An appropriate {@link String} representation.
+ */
+ public String toSourceString(byte value) {
+ return Byte.toString(value);
+ }
+
+ /**
+ * Represents the supplied {@code short} value as a {@link String}.
+ *
+ * @param value The {@code short} value to render.
+ * @return An appropriate {@link String} representation.
+ */
+ public String toSourceString(short value) {
+ return Short.toString(value);
+ }
+
+ /**
+ * Represents the supplied {@code char} value as a {@link String}.
+ *
+ * @param value The {@code char} value to render.
+ * @return An appropriate {@link String} representation.
+ */
+ public abstract String toSourceString(char value);
+
+ /**
+ * Represents the supplied {@code int} value as a {@link String}.
+ *
+ * @param value The {@code int} value to render.
+ * @return An appropriate {@link String} representation.
+ */
+ public String toSourceString(int value) {
+ return Integer.toString(value);
+ }
+
+ /**
+ * Represents the supplied {@code long} value as a {@link String}.
+ *
+ * @param value The {@code long} value to render.
+ * @return An appropriate {@link String} representation.
+ */
+ public abstract String toSourceString(long value);
+
+ /**
+ * Represents the supplied {@code float} value as a {@link String}.
+ *
+ * @param value The {@code float} value to render.
+ * @return An appropriate {@link String} representation.
+ */
+ public abstract String toSourceString(float value);
+
+ /**
+ * Represents the supplied {@code double} value as a {@link String}.
+ *
+ * @param value The {@code double} value to render.
+ * @return An appropriate {@link String} representation.
+ */
+ public abstract String toSourceString(double value);
+
+ /**
+ * Represents the supplied {@link String} value as a {@link String}.
+ *
+ * @param value The {@link String} value to render.
+ * @return An appropriate {@link String} representation.
+ */
+ public abstract String toSourceString(String value);
+
+ /**
+ * Represents the supplied {@link TypeDescription} value as a {@link String}.
+ *
+ * @param value The {@link TypeDescription} value to render.
+ * @return An appropriate {@link String} representation.
+ */
+ public abstract String toSourceString(TypeDescription value);
+
+ /**
+ * Represents the supplied list elements as a {@link String}.
+ *
+ * @param values The elements to render where each element is represented by its {@link Object#toString()} representation.
+ * @return An appropriate {@link String} representation.
+ */
+ public String toSourceString(List<?> values) {
+ StringBuilder stringBuilder = new StringBuilder().append(openingBrace);
+ boolean first = true;
+ for (Object value : values) {
+ if (first) {
+ first = false;
+ } else {
+ stringBuilder.append(", ");
+ }
+ stringBuilder.append(value);
+ }
+ return stringBuilder.append(closingBrace).toString();
+ }
+ }
+
+ /**
+ * A loaded variant of an {@link AnnotationValue}. While
+ * implementations of this value are required to be processed successfully by a
+ * {@link java.lang.ClassLoader} they might still be unresolved. Typical errors on loading an annotation
+ * value are:
+ * <ul>
+ * <li>{@link java.lang.annotation.IncompleteAnnotationException}: An annotation does not define a value
+ * even though no default value for a property is provided.</li>
+ * <li>{@link java.lang.EnumConstantNotPresentException}: An annotation defines an unknown value for
+ * a known enumeration.</li>
+ * <li>{@link java.lang.annotation.AnnotationTypeMismatchException}: An annotation property is not
+ * of the expected type.</li>
+ * </ul>
+ * Implementations of this interface must implement methods for {@link Object#hashCode()} and
+ * {@link Object#toString()} that resemble those used for the annotation values of an actual
+ * {@link java.lang.annotation.Annotation} implementation. Also, instances must implement
+ * {@link java.lang.Object#equals(Object)} to return {@code true} for other instances of
+ * this interface that represent the same annotation value.
+ *
+ * @param <U> The represented value's type.
+ */
+ interface Loaded<U> {
+
+ /**
+ * Returns the state of the represented loaded annotation value.
+ *
+ * @return The state represented by this instance.
+ */
+ State getState();
+
+ /**
+ * Resolves the value to the actual value of an annotation. Calling this method might throw a runtime
+ * exception if this value is either not defined or not resolved.
+ *
+ * @return The actual annotation value represented by this instance.
+ */
+ U resolve();
+
+ /**
+ * Resolves the value to the actual value of an annotation. Calling this method might throw a runtime
+ * exception if this value is either not defined or not resolved.
+ *
+ * @param type The value's loaded type.
+ * @param <V> The value's loaded type.
+ * @return The actual annotation value represented by this instance.
+ */
+ <V> V resolve(Class<? extends V> type);
+
+ /**
+ * Verifies if this loaded value represents the supplied loaded value.
+ *
+ * @param value A loaded annotation value.
+ * @return {@code true} if the supplied annotation value is represented by this annotation value.
+ */
+ boolean represents(Object value);
+
+ /**
+ * Represents the state of a {@link Loaded} annotation property.
+ */
+ enum State {
+
+ /**
+ * An undefined annotation value describes an annotation property which is missing such that
+ * an {@link java.lang.annotation.IncompleteAnnotationException} would be thrown.
+ */
+ UNDEFINED,
+
+ /**
+ * An unresolved annotation value describes an annotation property which does not represent a
+ * valid value but an exceptional state.
+ */
+ UNRESOLVED,
+
+ /**
+ * A resolved annotation value describes an annotation property with an actual value.
+ */
+ RESOLVED;
+
+ /**
+ * Returns {@code true} if the related annotation value is defined, i.e. either represents
+ * an actual value or an exceptional state.
+ *
+ * @return {@code true} if the related annotation value is defined.
+ */
+ public boolean isDefined() {
+ return this != UNDEFINED;
+ }
+
+ /**
+ * Returns {@code true} if the related annotation value is resolved, i.e. represents an actual
+ * value.
+ *
+ * @return {@code true} if the related annotation value is resolved.
+ */
+ public boolean isResolved() {
+ return this == RESOLVED;
+ }
+ }
+
+ /**
+ * An abstract base implementation of a loaded annotation value.
+ *
+ * @param <W> The represented loaded type.
+ */
+ abstract class AbstractBase<W> implements Loaded<W> {
+
+ @Override
+ public <X> X resolve(Class<? extends X> type) {
+ return type.cast(resolve());
+ }
+ }
+ }
+
+ /**
+ * An abstract base implementation of an unloaded annotation value.
+ *
+ * @param <U> The represented unloaded type.
+ * @param <V> The represented loaded type.
+ */
+ abstract class AbstractBase<U, V> implements AnnotationValue<U, V> {
+
+ @Override
+ public <W> W resolve(Class<? extends W> type) {
+ return type.cast(resolve());
+ }
+
+ @Override
+ public Loaded<V> loadSilent(ClassLoader classLoader) {
+ try {
+ return load(classLoader);
+ } catch (ClassNotFoundException exception) {
+ throw new IllegalStateException("Cannot load " + this, exception);
+ }
+ }
+ }
+
+ /**
+ * Represents a primitive value, a {@link java.lang.String} or an array of the latter types.
+ *
+ * @param <U> The type where primitive values are represented by their boxed type.
+ */
+ class ForConstant<U> extends AbstractBase<U, U> {
+
+ /**
+ * The represented value.
+ */
+ private final U value;
+
+ /**
+ * The property delegate for the value's type.
+ */
+ private final PropertyDelegate propertyDelegate;
+
+ /**
+ * Creates a new constant annotation value.
+ *
+ * @param value The represented value.
+ * @param propertyDelegate The property delegate for the value's type.
+ */
+ protected ForConstant(U value, PropertyDelegate propertyDelegate) {
+ this.value = value;
+ this.propertyDelegate = propertyDelegate;
+ }
+
+ /**
+ * Creates an annotation value for a {@code boolean} value.
+ *
+ * @param value The {@code boolean} value to represent.
+ * @return An appropriate annotation value.
+ */
+ public static AnnotationValue<Boolean, Boolean> of(boolean value) {
+ return new ForConstant<Boolean>(value, PropertyDelegate.ForNonArrayType.BOOLEAN);
+ }
+
+ /**
+ * Creates an annotation value for a {@code byte} value.
+ *
+ * @param value The {@code byte} value to represent.
+ * @return An appropriate annotation value.
+ */
+ public static AnnotationValue<Byte, Byte> of(byte value) {
+ return new ForConstant<Byte>(value, PropertyDelegate.ForNonArrayType.BYTE);
+ }
+
+ /**
+ * Creates an annotation value for a {@code short} value.
+ *
+ * @param value The {@code short} value to represent.
+ * @return An appropriate annotation value.
+ */
+ public static AnnotationValue<Short, Short> of(short value) {
+ return new ForConstant<Short>(value, PropertyDelegate.ForNonArrayType.SHORT);
+ }
+
+ /**
+ * Creates an annotation value for a {@code char} value.
+ *
+ * @param value The {@code char} value to represent.
+ * @return An appropriate annotation value.
+ */
+ public static AnnotationValue<Character, Character> of(char value) {
+ return new ForConstant<Character>(value, PropertyDelegate.ForNonArrayType.CHARACTER);
+ }
+
+ /**
+ * Creates an annotation value for a {@code int} value.
+ *
+ * @param value The {@code int} value to represent.
+ * @return An appropriate annotation value.
+ */
+ public static AnnotationValue<Integer, Integer> of(int value) {
+ return new ForConstant<Integer>(value, PropertyDelegate.ForNonArrayType.INTEGER);
+ }
+
+ /**
+ * Creates an annotation value for a {@code long} value.
+ *
+ * @param value The {@code long} value to represent.
+ * @return An appropriate annotation value.
+ */
+ public static AnnotationValue<Long, Long> of(long value) {
+ return new ForConstant<Long>(value, PropertyDelegate.ForNonArrayType.LONG);
+ }
+
+ /**
+ * Creates an annotation value for a {@code float} value.
+ *
+ * @param value The {@code float} value to represent.
+ * @return An appropriate annotation value.
+ */
+ public static AnnotationValue<Float, Float> of(float value) {
+ return new ForConstant<Float>(value, PropertyDelegate.ForNonArrayType.FLOAT);
+ }
+
+ /**
+ * Creates an annotation value for a {@code double} value.
+ *
+ * @param value The {@code double} value to represent.
+ * @return An appropriate annotation value.
+ */
+ public static AnnotationValue<Double, Double> of(double value) {
+ return new ForConstant<Double>(value, PropertyDelegate.ForNonArrayType.DOUBLE);
+ }
+
+ /**
+ * Creates an annotation value for a {@link String} value.
+ *
+ * @param value The {@link String} value to represent.
+ * @return An appropriate annotation value.
+ */
+ public static AnnotationValue<String, String> of(String value) {
+ return new ForConstant<String>(value, PropertyDelegate.ForNonArrayType.STRING);
+ }
+
+ /**
+ * Creates an annotation value for a {@code boolean[]} value.
+ *
+ * @param value The {@code boolean[]} value to represent.
+ * @return An appropriate annotation value.
+ */
+ public static AnnotationValue<boolean[], boolean[]> of(boolean... value) {
+ return new ForConstant<boolean[]>(value, PropertyDelegate.ForArrayType.BOOLEAN);
+ }
+
+ /**
+ * Creates an annotation value for a {@code byte[]} value.
+ *
+ * @param value The {@code byte[]} value to represent.
+ * @return An appropriate annotation value.
+ */
+ public static AnnotationValue<byte[], byte[]> of(byte... value) {
+ return new ForConstant<byte[]>(value, PropertyDelegate.ForArrayType.BYTE);
+ }
+
+ /**
+ * Creates an annotation value for a {@code short[]} value.
+ *
+ * @param value The {@code short[]} value to represent.
+ * @return An appropriate annotation value.
+ */
+ public static AnnotationValue<short[], short[]> of(short... value) {
+ return new ForConstant<short[]>(value, PropertyDelegate.ForArrayType.SHORT);
+ }
+
+ /**
+ * Creates an annotation value for a {@code char[]} value.
+ *
+ * @param value The {@code char[]} value to represent.
+ * @return An appropriate annotation value.
+ */
+ public static AnnotationValue<char[], char[]> of(char... value) {
+ return new ForConstant<char[]>(value, PropertyDelegate.ForArrayType.CHARACTER);
+ }
+
+ /**
+ * Creates an annotation value for a {@code int[]} value.
+ *
+ * @param value The {@code int[]} value to represent.
+ * @return An appropriate annotation value.
+ */
+ public static AnnotationValue<int[], int[]> of(int... value) {
+ return new ForConstant<int[]>(value, PropertyDelegate.ForArrayType.INTEGER);
+ }
+
+ /**
+ * Creates an annotation value for a {@code long[]} value.
+ *
+ * @param value The {@code long[]} value to represent.
+ * @return An appropriate annotation value.
+ */
+ public static AnnotationValue<long[], long[]> of(long... value) {
+ return new ForConstant<long[]>(value, PropertyDelegate.ForArrayType.LONG);
+ }
+
+ /**
+ * Creates an annotation value for a {@code float[]} value.
+ *
+ * @param value The {@code float[]} value to represent.
+ * @return An appropriate annotation value.
+ */
+ public static AnnotationValue<float[], float[]> of(float... value) {
+ return new ForConstant<float[]>(value, PropertyDelegate.ForArrayType.FLOAT);
+ }
+
+ /**
+ * Creates an annotation value for a {@code double[]} value.
+ *
+ * @param value The {@code double[]} value to represent.
+ * @return An appropriate annotation value.
+ */
+ public static AnnotationValue<double[], double[]> of(double... value) {
+ return new ForConstant<double[]>(value, PropertyDelegate.ForArrayType.DOUBLE);
+ }
+
+ /**
+ * Creates an annotation value for a {@code String[]} value.
+ *
+ * @param value The {@code String[]} value to represent.
+ * @return An appropriate annotation value.
+ */
+ public static AnnotationValue<String[], String[]> of(String... value) {
+ return new ForConstant<String[]>(value, PropertyDelegate.ForArrayType.STRING);
+ }
+
+ /**
+ * Creates an annotation value for any constant value, i.e any primitive (wrapper) type,
+ * any primitive array type or any {@link String} value or array. If no constant annotation
+ * type is provided, a runtime exception is thrown.
+ *
+ * @param value The value to represent.
+ * @return An appropriate annotation value.
+ */
+ public static AnnotationValue<?, ?> of(Object value) {
+ if (value instanceof Boolean) {
+ return of(((Boolean) value).booleanValue());
+ } else if (value instanceof Byte) {
+ return of(((Byte) value).byteValue());
+ } else if (value instanceof Short) {
+ return of(((Short) value).shortValue());
+ } else if (value instanceof Character) {
+ return of(((Character) value).charValue());
+ } else if (value instanceof Integer) {
+ return of(((Integer) value).intValue());
+ } else if (value instanceof Long) {
+ return of(((Long) value).longValue());
+ } else if (value instanceof Float) {
+ return of(((Float) value).floatValue());
+ } else if (value instanceof Double) {
+ return of(((Double) value).doubleValue());
+ } else if (value instanceof String) {
+ return of((String) value);
+ } else if (value instanceof boolean[]) {
+ return of((boolean[]) value);
+ } else if (value instanceof byte[]) {
+ return of((byte[]) value);
+ } else if (value instanceof short[]) {
+ return of((short[]) value);
+ } else if (value instanceof char[]) {
+ return of((char[]) value);
+ } else if (value instanceof int[]) {
+ return of((int[]) value);
+ } else if (value instanceof long[]) {
+ return of((long[]) value);
+ } else if (value instanceof float[]) {
+ return of((float[]) value);
+ } else if (value instanceof double[]) {
+ return of((double[]) value);
+ } else if (value instanceof String[]) {
+ return of((String[]) value);
+ } else {
+ throw new IllegalArgumentException("Not a constant annotation value: " + value);
+ }
+ }
+
+ @Override
+ public U resolve() {
+ return value;
+ }
+
+ @Override
+ public AnnotationValue.Loaded<U> load(ClassLoader classLoader) {
+ return new Loaded<U>(value, propertyDelegate);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this || (other instanceof AnnotationValue<?, ?> && propertyDelegate.equals(value, ((AnnotationValue<?, ?>) other).resolve()));
+ }
+
+ @Override
+ public int hashCode() {
+ return propertyDelegate.hashCode(value);
+ }
+
+ @Override
+ public String toString() {
+ return propertyDelegate.toString(value);
+ }
+
+ /**
+ * A property delegate for a constant annotation value.
+ */
+ protected interface PropertyDelegate {
+
+ /**
+ * Copies the provided value, if it is not immutable.
+ *
+ * @param value The value to copy.
+ * @param <S> The value's type.
+ * @return A copy of the provided instance or the provided value, if it is immutable.
+ */
+ <S> S copy(S value);
+
+ /**
+ * Computes the value's hash code.
+ *
+ * @param value The value for which to compute the hash code.
+ * @return The hash code of the provided value.
+ */
+ int hashCode(Object value);
+
+ /**
+ * Determines if another value is equal to a constant annotation value.
+ *
+ * @param self The value that is represented as a constant annotation value.
+ * @param other Any other value for which to determine equality.
+ * @return {@code true} if the provided value is equal to the represented value.
+ */
+ boolean equals(Object self, Object other);
+
+ /**
+ * Renders the supplied value as a {@link String}.
+ *
+ * @param value The value to render.
+ * @return An appropriate {@link String} representation of the provided value.
+ */
+ String toString(Object value);
+
+ /**
+ * A property delegate for a non-array type.
+ */
+ enum ForNonArrayType implements PropertyDelegate {
+
+ /**
+ * A property delegate for a {@code boolean} value.
+ */
+ BOOLEAN {
+ @Override
+ public String toString(Object value) {
+ return RenderingDispatcher.CURRENT.toSourceString((Boolean) value);
+ }
+ },
+
+ /**
+ * A property delegate for a {@code byte} value.
+ */
+ BYTE {
+ @Override
+ public String toString(Object value) {
+ return RenderingDispatcher.CURRENT.toSourceString((Byte) value);
+ }
+ },
+
+ /**
+ * A property delegate for a {@code short} value.
+ */
+ SHORT {
+ @Override
+ public String toString(Object value) {
+ return RenderingDispatcher.CURRENT.toSourceString((Short) value);
+ }
+ },
+
+ /**
+ * A property delegate for a {@code char} value.
+ */
+ CHARACTER {
+ @Override
+ public String toString(Object value) {
+ return RenderingDispatcher.CURRENT.toSourceString((Character) value);
+ }
+ },
+
+ /**
+ * A property delegate for a {@code int} value.
+ */
+ INTEGER {
+ @Override
+ public String toString(Object value) {
+ return RenderingDispatcher.CURRENT.toSourceString((Integer) value);
+ }
+ },
+
+ /**
+ * A property delegate for a {@code long} value.
+ */
+ LONG {
+ @Override
+ public String toString(Object value) {
+ return RenderingDispatcher.CURRENT.toSourceString((Long) value);
+ }
+ },
+
+ /**
+ * A property delegate for a {@code float} value.
+ */
+ FLOAT {
+ @Override
+ public String toString(Object value) {
+ return RenderingDispatcher.CURRENT.toSourceString((Float) value);
+ }
+ },
+
+ /**
+ * A property delegate for a {@code double} value.
+ */
+ DOUBLE {
+ @Override
+ public String toString(Object value) {
+ return RenderingDispatcher.CURRENT.toSourceString((Double) value);
+ }
+ },
+
+ /**
+ * A property delegate for a {@link String} value.
+ */
+ STRING {
+ @Override
+ public String toString(Object value) {
+ return RenderingDispatcher.CURRENT.toSourceString((String) value);
+ }
+ };
+
+ @Override
+ public <S> S copy(S value) {
+ return value;
+ }
+
+ @Override
+ public int hashCode(Object value) {
+ return value.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object self, Object other) {
+ return self.equals(other);
+ }
+ }
+
+ /**
+ * A property delegate for an array type of a constant value.
+ */
+ enum ForArrayType implements PropertyDelegate {
+
+ /**
+ * A property delegate for a {@code boolean[]} value.
+ */
+ BOOLEAN {
+ @Override
+ protected Object doCopy(Object value) {
+ return ((boolean[]) value).clone();
+ }
+
+ @Override
+ public int hashCode(Object value) {
+ return Arrays.hashCode((boolean[]) value);
+ }
+
+ @Override
+ public boolean equals(Object self, Object other) {
+ return other instanceof boolean[] && Arrays.equals((boolean[]) self, (boolean[]) other);
+ }
+
+ @Override
+ protected String toString(Object array, int index) {
+ return ForNonArrayType.BOOLEAN.toString(Array.getBoolean(array, index));
+ }
+ },
+
+ /**
+ * A property delegate for a {@code byte[]} value.
+ */
+ BYTE {
+ @Override
+ protected Object doCopy(Object value) {
+ return ((byte[]) value).clone();
+ }
+
+ @Override
+ public int hashCode(Object value) {
+ return Arrays.hashCode((byte[]) value);
+ }
+
+ @Override
+ public boolean equals(Object self, Object other) {
+ return other instanceof byte[] && Arrays.equals((byte[]) self, (byte[]) other);
+ }
+
+ @Override
+ protected String toString(Object array, int index) {
+ return ForNonArrayType.BYTE.toString(Array.getByte(array, index));
+ }
+ },
+
+ /**
+ * A property delegate for a {@code short[]} value.
+ */
+ SHORT {
+ @Override
+ protected Object doCopy(Object value) {
+ return ((short[]) value).clone();
+ }
+
+ @Override
+ public int hashCode(Object value) {
+ return Arrays.hashCode((short[]) value);
+ }
+
+ @Override
+ public boolean equals(Object self, Object other) {
+ return other instanceof short[] && Arrays.equals((short[]) self, (short[]) other);
+ }
+
+ @Override
+ protected String toString(Object array, int index) {
+ return ForNonArrayType.SHORT.toString(Array.getShort(array, index));
+ }
+ },
+
+ /**
+ * A property delegate for a {@code char[]} value.
+ */
+ CHARACTER {
+ @Override
+ protected Object doCopy(Object value) {
+ return ((char[]) value).clone();
+ }
+
+ @Override
+ public int hashCode(Object value) {
+ return Arrays.hashCode((char[]) value);
+ }
+
+ @Override
+ public boolean equals(Object self, Object other) {
+ return other instanceof char[] && Arrays.equals((char[]) self, (char[]) other);
+ }
+
+ @Override
+ protected String toString(Object array, int index) {
+ return ForNonArrayType.CHARACTER.toString(Array.getChar(array, index));
+ }
+ },
+
+ /**
+ * A property delegate for a {@code int[]} value.
+ */
+ INTEGER {
+ @Override
+ protected Object doCopy(Object value) {
+ return ((int[]) value).clone();
+ }
+
+ @Override
+ public int hashCode(Object value) {
+ return Arrays.hashCode((int[]) value);
+ }
+
+ @Override
+ public boolean equals(Object self, Object other) {
+ return other instanceof int[] && Arrays.equals((int[]) self, (int[]) other);
+ }
+
+ @Override
+ protected String toString(Object array, int index) {
+ return ForNonArrayType.INTEGER.toString(Array.getInt(array, index));
+ }
+ },
+
+ /**
+ * A property delegate for a {@code long[]} value.
+ */
+ LONG {
+ @Override
+ protected Object doCopy(Object value) {
+ return ((long[]) value).clone();
+ }
+
+ @Override
+ public int hashCode(Object value) {
+ return Arrays.hashCode((long[]) value);
+ }
+
+ @Override
+ public boolean equals(Object self, Object other) {
+ return other instanceof long[] && Arrays.equals((long[]) self, (long[]) other);
+ }
+
+ @Override
+ protected String toString(Object array, int index) {
+ return ForNonArrayType.LONG.toString(Array.getLong(array, index));
+ }
+ },
+
+ /**
+ * A property delegate for a {@code float[]} value.
+ */
+ FLOAT {
+ @Override
+ protected Object doCopy(Object value) {
+ return ((float[]) value).clone();
+ }
+
+ @Override
+ public int hashCode(Object value) {
+ return Arrays.hashCode((float[]) value);
+ }
+
+ @Override
+ public boolean equals(Object self, Object other) {
+ return other instanceof float[] && Arrays.equals((float[]) self, (float[]) other);
+ }
+
+ @Override
+ protected String toString(Object array, int index) {
+ return ForNonArrayType.FLOAT.toString(Array.getFloat(array, index));
+ }
+ },
+
+ /**
+ * A property delegate for a {@code double[]} value.
+ */
+ DOUBLE {
+ @Override
+ protected Object doCopy(Object value) {
+ return ((double[]) value).clone();
+ }
+
+ @Override
+ public int hashCode(Object value) {
+ return Arrays.hashCode((double[]) value);
+ }
+
+ @Override
+ public boolean equals(Object self, Object other) {
+ return other instanceof double[] && Arrays.equals((double[]) self, (double[]) other);
+ }
+
+ @Override
+ protected String toString(Object array, int index) {
+ return ForNonArrayType.DOUBLE.toString(Array.getDouble(array, index));
+ }
+ },
+
+ /**
+ * A property delegate for a {@code String[]} value.
+ */
+ STRING {
+ @Override
+ protected Object doCopy(Object value) {
+ return ((String[]) value).clone();
+ }
+
+ @Override
+ public int hashCode(Object value) {
+ return Arrays.hashCode((String[]) value);
+ }
+
+ @Override
+ public boolean equals(Object self, Object other) {
+ return other instanceof String[] && Arrays.equals((String[]) self, (String[]) other);
+ }
+
+ @Override
+ protected String toString(Object array, int index) {
+ return ForNonArrayType.STRING.toString(Array.get(array, index));
+ }
+ };
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <S> S copy(S value) {
+ return (S) doCopy(value);
+ }
+
+ /**
+ * Creates a copy of the provided array.
+ *
+ * @param value The array to copy.
+ * @return A shallow copy of the provided array.
+ */
+ protected abstract Object doCopy(Object value);
+
+ @Override
+ public String toString(Object value) {
+ List<String> elements = new ArrayList<String>(Array.getLength(value));
+ for (int index = 0; index < Array.getLength(value); index++) {
+ elements.add(toString(value, index));
+ }
+ return RenderingDispatcher.CURRENT.toSourceString(elements);
+ }
+
+ /**
+ * Renders the array element at the specified index.
+ *
+ * @param array The array for which an element should be rendered.
+ * @param index The index of the array element to render.
+ * @return A {@link String} representation of the array element at the supplied index.
+ */
+ protected abstract String toString(Object array, int index);
+ }
+ }
+
+ /**
+ * Represents a trivial loaded value.
+ *
+ * @param <V> The annotation properties type.
+ */
+ protected static class Loaded<V> extends AnnotationValue.Loaded.AbstractBase<V> {
+
+ /**
+ * The represented value.
+ */
+ private final V value;
+
+ /**
+ * The property delegate for the value's type.
+ */
+ private final PropertyDelegate propertyDelegate;
+
+ /**
+ * Creates a new loaded representation of a constant value.
+ *
+ * @param value The represented value.
+ * @param propertyDelegate The property delegate for the value's type.
+ */
+ protected Loaded(V value, PropertyDelegate propertyDelegate) {
+ this.value = value;
+ this.propertyDelegate = propertyDelegate;
+ }
+
+ @Override
+ public State getState() {
+ return State.RESOLVED;
+ }
+
+ @Override
+ public V resolve() {
+ return propertyDelegate.copy(value);
+ }
+
+ @Override
+ public boolean represents(Object value) {
+ return propertyDelegate.equals(this.value, value);
+ }
+
+ @Override
+ public int hashCode() {
+ return propertyDelegate.hashCode(value);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (!(other instanceof AnnotationValue.Loaded<?>)) return false;
+ AnnotationValue.Loaded<?> loadedOther = (AnnotationValue.Loaded<?>) other;
+ return loadedOther.getState().isResolved() && propertyDelegate.equals(value, loadedOther.resolve());
+ }
+
+ @Override
+ public String toString() {
+ return propertyDelegate.toString(value);
+ }
+ }
+ }
+
+ /**
+ * A description of an {@link java.lang.annotation.Annotation} as a value of another annotation.
+ *
+ * @param <U> The type of the annotation.
+ */
+ class ForAnnotationDescription<U extends Annotation> extends AbstractBase<AnnotationDescription, U> {
+
+ /**
+ * The annotation description that this value represents.
+ */
+ private final AnnotationDescription annotationDescription;
+
+ /**
+ * Creates a new annotation value for a given annotation description.
+ *
+ * @param annotationDescription The annotation description that this value represents.
+ */
+ public ForAnnotationDescription(AnnotationDescription annotationDescription) {
+ this.annotationDescription = annotationDescription;
+ }
+
+ /**
+ * Creates an annotation value instance for describing the given annotation type and values.
+ *
+ * @param annotationType The annotation type.
+ * @param annotationValues The values of the annotation.
+ * @param <V> The type of the annotation.
+ * @return An annotation value representing the given annotation.
+ */
+ public static <V extends Annotation> AnnotationValue<AnnotationDescription, V> of(TypeDescription annotationType,
+ Map<String, ? extends AnnotationValue<?, ?>> annotationValues) {
+ return new ForAnnotationDescription<V>(new AnnotationDescription.Latent(annotationType, annotationValues));
+ }
+
+ @Override
+ public AnnotationDescription resolve() {
+ return annotationDescription;
+ }
+
+ @Override
+ public AnnotationValue.Loaded<U> load(ClassLoader classLoader) throws ClassNotFoundException {
+ @SuppressWarnings("unchecked")
+ Class<U> annotationType = (Class<U>) Class.forName(annotationDescription.getAnnotationType().getName(), false, classLoader);
+ return new Loaded<U>(annotationDescription.prepare(annotationType).load());
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return this == other || (other instanceof AnnotationValue<?, ?> && annotationDescription.equals(((AnnotationValue<?, ?>) other).resolve()));
+ }
+
+ @Override
+ public int hashCode() {
+ return annotationDescription.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return annotationDescription.toString();
+ }
+
+ /**
+ * A loaded version of the described annotation.
+ *
+ * @param <V> The annotation type.
+ */
+ public static class Loaded<V extends Annotation> extends AnnotationValue.Loaded.AbstractBase<V> {
+
+ /**
+ * The loaded version of the represented annotation.
+ */
+ private final V annotation;
+
+ /**
+ * Creates a representation of a loaded annotation.
+ *
+ * @param annotation The represented annotation.
+ */
+ public Loaded(V annotation) {
+ this.annotation = annotation;
+ }
+
+ @Override
+ public State getState() {
+ return State.RESOLVED;
+ }
+
+ @Override
+ public V resolve() {
+ return annotation;
+ }
+
+ @Override
+ public boolean represents(Object value) {
+ return annotation.equals(value);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (!(other instanceof AnnotationValue.Loaded<?>)) return false;
+ AnnotationValue.Loaded<?> loadedOther = (AnnotationValue.Loaded<?>) other;
+ return loadedOther.getState().isResolved() && annotation.equals(loadedOther.resolve());
+ }
+
+ @Override
+ public int hashCode() {
+ return annotation.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return annotation.toString();
+ }
+ }
+
+ /**
+ * <p>
+ * Represents an annotation value which was attempted to ba loaded by a type that does not represent
+ * an annotation value.
+ * </p>
+ * <p>
+ * <b>Note</b>: Neither of {@link Object#hashCode()}, {@link Object#toString()} and
+ * {@link java.lang.Object#equals(Object)} are implemented specifically what resembles the way
+ * such exceptional states are represented in the Open JDK's annotation implementations.
+ * </p>
+ */
+ public static class IncompatibleRuntimeType extends AnnotationValue.Loaded.AbstractBase<Annotation> {
+
+ /**
+ * The incompatible runtime type which is not an annotation type.
+ */
+ private final Class<?> incompatibleType;
+
+ /**
+ * Creates a new representation for an annotation with an incompatible runtime type.
+ *
+ * @param incompatibleType The incompatible runtime type which is not an annotation type.
+ */
+ public IncompatibleRuntimeType(Class<?> incompatibleType) {
+ this.incompatibleType = incompatibleType;
+ }
+
+ @Override
+ public State getState() {
+ return State.UNRESOLVED;
+ }
+
+ @Override
+ public Annotation resolve() {
+ throw new IncompatibleClassChangeError("Not an annotation type: " + incompatibleType.toString());
+ }
+
+ @Override
+ public boolean represents(Object value) {
+ return false;
+ }
+
+ /* does intentionally not implement hashCode, equals and toString */
+ }
+ }
+
+ /**
+ * A description of an {@link java.lang.Enum} as a value of an annotation.
+ *
+ * @param <U> The type of the enumeration.
+ */
+ class ForEnumerationDescription<U extends Enum<U>> extends AbstractBase<EnumerationDescription, U> {
+
+ /**
+ * The enumeration that is represented.
+ */
+ private final EnumerationDescription enumerationDescription;
+
+ /**
+ * Creates a new description of an annotation value for a given enumeration.
+ *
+ * @param enumerationDescription The enumeration that is to be represented.
+ */
+ protected ForEnumerationDescription(EnumerationDescription enumerationDescription) {
+ this.enumerationDescription = enumerationDescription;
+ }
+
+ /**
+ * Creates a new annotation value for the given enumeration description.
+ *
+ * @param value The value to represent.
+ * @param <V> The type of the represented enumeration.
+ * @return An annotation value that describes the given enumeration.
+ */
+ public static <V extends Enum<V>> AnnotationValue<EnumerationDescription, V> of(EnumerationDescription value) {
+ return new ForEnumerationDescription<V>(value);
+ }
+
+ @Override
+ public EnumerationDescription resolve() {
+ return enumerationDescription;
+ }
+
+ @Override
+ public AnnotationValue.Loaded<U> load(ClassLoader classLoader) throws ClassNotFoundException {
+ @SuppressWarnings("unchecked")
+ Class<U> enumerationType = (Class<U>) Class.forName(enumerationDescription.getEnumerationType().getName(), false, classLoader);
+ return new Loaded<U>(enumerationDescription.load(enumerationType));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return this == other || (other instanceof AnnotationValue<?, ?> && enumerationDescription.equals(((AnnotationValue<?, ?>) other).resolve()));
+ }
+
+ @Override
+ public int hashCode() {
+ return enumerationDescription.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return enumerationDescription.toString();
+ }
+
+ /**
+ * A loaded representation of an enumeration value.
+ *
+ * @param <V> The type of the represented enumeration.
+ */
+ public static class Loaded<V extends Enum<V>> extends AnnotationValue.Loaded.AbstractBase<V> {
+
+ /**
+ * The represented enumeration.
+ */
+ private final V enumeration;
+
+ /**
+ * Creates a loaded version of an enumeration description.
+ *
+ * @param enumeration The represented enumeration.
+ */
+ public Loaded(V enumeration) {
+ this.enumeration = enumeration;
+ }
+
+ @Override
+ public State getState() {
+ return State.RESOLVED;
+ }
+
+ @Override
+ public V resolve() {
+ return enumeration;
+ }
+
+ @Override
+ public boolean represents(Object value) {
+ return enumeration.equals(value);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (!(other instanceof AnnotationValue.Loaded<?>)) return false;
+ AnnotationValue.Loaded<?> loadedOther = (AnnotationValue.Loaded<?>) other;
+ return loadedOther.getState().isResolved() && enumeration.equals(loadedOther.resolve());
+ }
+
+ @Override
+ public int hashCode() {
+ return enumeration.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return enumeration.toString();
+ }
+ }
+
+ /**
+ * <p>
+ * Represents an annotation's enumeration value for a constant that does not exist for the runtime
+ * enumeration type.
+ * </p>
+ * <p>
+ * <b>Note</b>: Neither of {@link Object#hashCode()}, {@link Object#toString()} and
+ * {@link java.lang.Object#equals(Object)} are implemented specifically what resembles the way
+ * such exceptional states are represented in the Open JDK's annotation implementations.
+ * </p>
+ */
+ public static class UnknownRuntimeEnumeration extends AnnotationValue.Loaded.AbstractBase<Enum<?>> {
+
+ /**
+ * The loaded enumeration type.
+ */
+ private final Class<? extends Enum<?>> enumType;
+
+ /**
+ * The value for which no enumeration constant exists at runtime.
+ */
+ private final String value;
+
+ /**
+ * Creates a new representation for an unknown enumeration constant of an annotation.
+ *
+ * @param enumType The loaded enumeration type.
+ * @param value The value for which no enumeration constant exists at runtime.
+ */
+ public UnknownRuntimeEnumeration(Class<? extends Enum<?>> enumType, String value) {
+ this.enumType = enumType;
+ this.value = value;
+ }
+
+ @Override
+ public State getState() {
+ return State.UNRESOLVED;
+ }
+
+ @Override
+ public Enum<?> resolve() {
+ throw new EnumConstantNotPresentException(enumType, value);
+ }
+
+ @Override
+ public boolean represents(Object value) {
+ return false;
+ }
+
+ /* hashCode, equals and toString are intentionally not implemented */
+ }
+
+ /**
+ * <p>
+ * Represents an annotation's enumeration value for a runtime type that is not an enumeration type.
+ * </p>
+ * <p>
+ * <b>Note</b>: Neither of {@link Object#hashCode()}, {@link Object#toString()} and
+ * {@link java.lang.Object#equals(Object)} are implemented specifically what resembles the way
+ * such exceptional states are represented in the Open JDK's annotation implementations.
+ * </p>
+ */
+ public static class IncompatibleRuntimeType extends AnnotationValue.Loaded.AbstractBase<Enum<?>> {
+
+ /**
+ * The runtime type which is not an enumeration type.
+ */
+ private final Class<?> type;
+
+ /**
+ * Creates a new representation for an incompatible runtime type.
+ *
+ * @param type The runtime type which is not an enumeration type.
+ */
+ public IncompatibleRuntimeType(Class<?> type) {
+ this.type = type;
+ }
+
+ @Override
+ public State getState() {
+ return State.UNRESOLVED;
+ }
+
+ @Override
+ public Enum<?> resolve() {
+ throw new IncompatibleClassChangeError("Not an enumeration type: " + type.toString());
+ }
+
+ @Override
+ public boolean represents(Object value) {
+ return false;
+ }
+
+ /* hashCode, equals and toString are intentionally not implemented */
+ }
+ }
+
+ /**
+ * A description of a {@link java.lang.Class} as a value of an annotation.
+ *
+ * @param <U> The type of the {@link java.lang.Class} that is described.
+ */
+ class ForTypeDescription<U extends Class<U>> extends AbstractBase<TypeDescription, U> {
+
+ /**
+ * Indicates to a class loading process that class initializers are not required to be executed when loading a type.
+ */
+ private static final boolean NO_INITIALIZATION = false;
+
+ /**
+ * A description of the represented type.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new annotation value that represents a type.
+ *
+ * @param typeDescription The represented type.
+ */
+ protected ForTypeDescription(TypeDescription typeDescription) {
+ this.typeDescription = typeDescription;
+ }
+
+ /**
+ * Creates an annotation value for representing the given type.
+ *
+ * @param typeDescription The type to represent.
+ * @param <V> The represented type.
+ * @return An annotation value that represents the given type.
+ */
+ public static <V extends Class<V>> AnnotationValue<TypeDescription, V> of(TypeDescription typeDescription) {
+ return new ForTypeDescription<V>(typeDescription);
+ }
+
+ @Override
+ public TypeDescription resolve() {
+ return typeDescription;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public AnnotationValue.Loaded<U> load(ClassLoader classLoader) throws ClassNotFoundException {
+ return new Loaded<U>((U) Class.forName(typeDescription.getName(), NO_INITIALIZATION, classLoader));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return this == other || (other instanceof AnnotationValue<?, ?> && typeDescription.equals(((AnnotationValue<?, ?>) other).resolve()));
+ }
+
+ @Override
+ public int hashCode() {
+ return typeDescription.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return RenderingDispatcher.CURRENT.toSourceString(typeDescription);
+ }
+
+ /**
+ * A loaded annotation value for a given type.
+ *
+ * @param <U> The represented type.
+ */
+ protected static class Loaded<U extends Class<U>> extends AnnotationValue.Loaded.AbstractBase<U> {
+
+ /**
+ * The represented type.
+ */
+ private final U type;
+
+ /**
+ * Creates a new loaded annotation value for a given type.
+ *
+ * @param type The represented type.
+ */
+ public Loaded(U type) {
+ this.type = type;
+ }
+
+ @Override
+ public State getState() {
+ return State.RESOLVED;
+ }
+
+ @Override
+ public U resolve() {
+ return type;
+ }
+
+ @Override
+ public boolean represents(Object value) {
+ return type.equals(value);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (!(other instanceof AnnotationValue.Loaded<?>)) return false;
+ AnnotationValue.Loaded<?> loadedOther = (AnnotationValue.Loaded<?>) other;
+ return loadedOther.getState().isResolved() && type.equals(loadedOther.resolve());
+ }
+
+ @Override
+ public int hashCode() {
+ return type.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return RenderingDispatcher.CURRENT.toSourceString(new TypeDescription.ForLoadedType(type));
+ }
+ }
+ }
+
+ /**
+ * Describes a complex array that is the value of an annotation. Complex arrays are arrays that might trigger the loading
+ * of user-defined types, i.e. {@link java.lang.Class}, {@link java.lang.annotation.Annotation} and {@link java.lang.Enum}
+ * instances.
+ *
+ * @param <U> The component type of the annotation's value when it is not loaded.
+ * @param <V> The component type of the annotation's value when it is loaded.
+ */
+ class ForDescriptionArray<U, V> extends AbstractBase<U[], V[]> {
+
+ /**
+ * The component type for arrays containing unloaded versions of the annotation array's values.
+ */
+ private final Class<?> unloadedComponentType;
+
+ /**
+ * A description of the component type when it is loaded.
+ */
+ private final TypeDescription componentType;
+
+ /**
+ * A list of values of the array elements.
+ */
+ private final List<? extends AnnotationValue<?, ?>> values;
+
+ /**
+ * Creates a new complex array.
+ *
+ * @param unloadedComponentType The component type for arrays containing unloaded versions of the annotation array's values.
+ * @param componentType A description of the component type when it is loaded.
+ * @param values A list of values of the array elements.
+ */
+ protected ForDescriptionArray(Class<?> unloadedComponentType,
+ TypeDescription componentType,
+ List<? extends AnnotationValue<?, ?>> values) {
+ this.unloadedComponentType = unloadedComponentType;
+ this.componentType = componentType;
+ this.values = values;
+ }
+
+ /**
+ * Creates a new complex array of enumeration descriptions.
+ *
+ * @param enumerationType A description of the type of the enumeration.
+ * @param enumerationDescription An array of enumeration descriptions.
+ * @param <W> The type of the enumeration.
+ * @return A description of the array of enumeration values.
+ */
+ public static <W extends Enum<W>> AnnotationValue<EnumerationDescription[], W[]> of(TypeDescription enumerationType,
+ EnumerationDescription[] enumerationDescription) {
+ List<AnnotationValue<EnumerationDescription, W>> values = new ArrayList<AnnotationValue<EnumerationDescription, W>>(enumerationDescription.length);
+ for (EnumerationDescription value : enumerationDescription) {
+ if (!value.getEnumerationType().equals(enumerationType)) {
+ throw new IllegalArgumentException(value + " is not of " + enumerationType);
+ }
+ values.add(ForEnumerationDescription.<W>of(value));
+ }
+ return new ForDescriptionArray<EnumerationDescription, W>(EnumerationDescription.class, enumerationType, values);
+ }
+
+ /**
+ * Creates a new complex array of annotation descriptions.
+ *
+ * @param annotationType A description of the type of the annotation.
+ * @param annotationDescription An array of annotation descriptions.
+ * @param <W> The type of the annotation.
+ * @return A description of the array of enumeration values.
+ */
+ public static <W extends Annotation> AnnotationValue<AnnotationDescription[], W[]> of(TypeDescription annotationType,
+ AnnotationDescription[] annotationDescription) {
+ List<AnnotationValue<AnnotationDescription, W>> values = new ArrayList<AnnotationValue<AnnotationDescription, W>>(annotationDescription.length);
+ for (AnnotationDescription value : annotationDescription) {
+ if (!value.getAnnotationType().equals(annotationType)) {
+ throw new IllegalArgumentException(value + " is not of " + annotationType);
+ }
+ values.add(new ForAnnotationDescription<W>(value));
+ }
+ return new ForDescriptionArray<AnnotationDescription, W>(AnnotationDescription.class, annotationType, values);
+ }
+
+ /**
+ * Creates a new complex array of annotation descriptions.
+ *
+ * @param typeDescription A description of the types contained in the array.
+ * @return A description of the array of enumeration values.
+ */
+ @SuppressWarnings("unchecked")
+ public static AnnotationValue<TypeDescription[], Class<?>[]> of(TypeDescription[] typeDescription) {
+ List<AnnotationValue<TypeDescription, Class<?>>> values = new ArrayList<AnnotationValue<TypeDescription, Class<?>>>(typeDescription.length);
+ for (TypeDescription value : typeDescription) {
+ values.add((AnnotationValue) ForTypeDescription.<Class>of(value));
+ }
+ return new ForDescriptionArray<TypeDescription, Class<?>>(TypeDescription.class, TypeDescription.CLASS, values);
+ }
+
+ @Override
+ public U[] resolve() {
+ @SuppressWarnings("unchecked")
+ U[] resolved = (U[]) Array.newInstance(unloadedComponentType, values.size());
+ int index = 0;
+ for (AnnotationValue<?, ?> value : values) {
+ Array.set(resolved, index++, value.resolve());
+ }
+ return resolved;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public AnnotationValue.Loaded<V[]> load(ClassLoader classLoader) throws ClassNotFoundException {
+ List<AnnotationValue.Loaded<?>> values = new ArrayList<AnnotationValue.Loaded<?>>(this.values.size());
+ for (AnnotationValue<?, ?> value : this.values) {
+ values.add(value.load(classLoader));
+ }
+ return new Loaded<V>((Class<V>) Class.forName(componentType.getName(), false, classLoader), values);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (!(other instanceof AnnotationValue<?, ?>)) return false;
+ AnnotationValue<?, ?> loadedOther = (AnnotationValue<?, ?>) other;
+ Object otherValue = loadedOther.resolve();
+ if (!(otherValue instanceof Object[])) return false;
+ Object[] otherArrayValue = (Object[]) otherValue;
+ if (values.size() != otherArrayValue.length) return false;
+ Iterator<? extends AnnotationValue<?, ?>> iterator = values.iterator();
+ for (Object value : otherArrayValue) {
+ AnnotationValue<?, ?> self = iterator.next();
+ if (!self.resolve().equals(value)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 1;
+ for (AnnotationValue<?, ?> value : values) {
+ result = 31 * result + value.hashCode();
+ }
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return RenderingDispatcher.CURRENT.toSourceString(values);
+ }
+
+ /**
+ * Represents a loaded complex array.
+ *
+ * @param <W> The component type of the loaded array.
+ */
+ protected static class Loaded<W> extends AnnotationValue.Loaded.AbstractBase<W[]> {
+
+ /**
+ * The loaded component type of the array.
+ */
+ private final Class<W> componentType;
+
+ /**
+ * A list of loaded values that the represented array contains.
+ */
+ private final List<AnnotationValue.Loaded<?>> values;
+
+ /**
+ * Creates a new loaded value representing a complex array.
+ *
+ * @param componentType The loaded component type of the array.
+ * @param values A list of loaded values that the represented array contains.
+ */
+ protected Loaded(Class<W> componentType, List<AnnotationValue.Loaded<?>> values) {
+ this.componentType = componentType;
+ this.values = values;
+ }
+
+ @Override
+ public State getState() {
+ for (AnnotationValue.Loaded<?> value : values) {
+ if (!value.getState().isResolved()) {
+ return State.UNRESOLVED;
+ }
+ }
+ return State.RESOLVED;
+ }
+
+ @Override
+ public W[] resolve() {
+ @SuppressWarnings("unchecked")
+ W[] array = (W[]) Array.newInstance(componentType, values.size());
+ int index = 0;
+ for (AnnotationValue.Loaded<?> annotationValue : values) {
+ Array.set(array, index++, annotationValue.resolve());
+ }
+ return array;
+ }
+
+ @Override
+ public boolean represents(Object value) {
+ if (!(value instanceof Object[])) return false;
+ if (value.getClass().getComponentType() != componentType) return false;
+ Object[] array = (Object[]) value;
+ if (values.size() != array.length) return false;
+ Iterator<AnnotationValue.Loaded<?>> iterator = values.iterator();
+ for (Object aValue : array) {
+ AnnotationValue.Loaded<?> self = iterator.next();
+ if (!self.represents(aValue)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (!(other instanceof AnnotationValue.Loaded<?>)) return false;
+ AnnotationValue.Loaded<?> loadedOther = (AnnotationValue.Loaded<?>) other;
+ if (!loadedOther.getState().isResolved()) return false;
+ Object otherValue = loadedOther.resolve();
+ if (!(otherValue instanceof Object[])) return false;
+ Object[] otherArrayValue = (Object[]) otherValue;
+ if (values.size() != otherArrayValue.length) return false;
+ Iterator<AnnotationValue.Loaded<?>> iterator = values.iterator();
+ for (Object value : otherArrayValue) {
+ AnnotationValue.Loaded<?> self = iterator.next();
+ if (!self.getState().isResolved() || !self.resolve().equals(value)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 1;
+ for (AnnotationValue.Loaded<?> value : values) {
+ result = 31 * result + value.hashCode();
+ }
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return RenderingDispatcher.CURRENT.toSourceString(values);
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/annotation/package-info.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/annotation/package-info.java
new file mode 100644
index 0000000..1dc247d
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/annotation/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Contains descriptions of annotations and annotation values.
+ */
+package net.bytebuddy.description.annotation;
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/enumeration/EnumerationDescription.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/enumeration/EnumerationDescription.java
new file mode 100644
index 0000000..958fbe9
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/enumeration/EnumerationDescription.java
@@ -0,0 +1,162 @@
+package net.bytebuddy.description.enumeration;
+
+import net.bytebuddy.description.NamedElement;
+import net.bytebuddy.description.type.TypeDescription;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Describes an enumeration value. Note that the {@link java.lang.Object#toString} method always returns the
+ * value as if the method was not overridden, i.e. the name of the enumeration constant.
+ */
+public interface EnumerationDescription extends NamedElement {
+
+ /**
+ * Returns the name of this instance's enumeration value.
+ *
+ * @return The name of this enumeration constant.
+ */
+ String getValue();
+
+ /**
+ * Returns the type of this enumeration.
+ *
+ * @return The type of this enumeration.
+ */
+ TypeDescription getEnumerationType();
+
+ /**
+ * Prepares this enumeration value to be loaded.
+ *
+ * @param type A type constant representing the enumeration value.
+ * @param <T> The enumeration type.
+ * @return The loaded enumeration constant corresponding to this value.
+ */
+ <T extends Enum<T>> T load(Class<T> type);
+
+ /**
+ * An adapter implementation of an enumeration description.
+ */
+ abstract class AbstractBase implements EnumerationDescription {
+
+ @Override
+ public String getActualName() {
+ return getValue();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this || other instanceof EnumerationDescription
+ && (((EnumerationDescription) other)).getEnumerationType().equals(getEnumerationType())
+ && (((EnumerationDescription) other)).getValue().equals(getValue());
+ }
+
+ @Override
+ public int hashCode() {
+ return getValue().hashCode() + 31 * getEnumerationType().hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return getValue();
+ }
+ }
+
+ /**
+ * An enumeration description representing a loaded enumeration.
+ */
+ class ForLoadedEnumeration extends AbstractBase {
+
+ /**
+ * The loaded enumeration value.
+ */
+ private final Enum<?> value;
+
+ /**
+ * Creates a new enumeration value representation for a loaded enumeration.
+ *
+ * @param value The value to represent.
+ */
+ public ForLoadedEnumeration(Enum<?> value) {
+ this.value = value;
+ }
+
+ /**
+ * Enlists a given array of loaded enumerations as enumeration values.
+ *
+ * @param enumerations The enumerations to represent.
+ * @return A list of the given enumerations.
+ */
+ public static List<EnumerationDescription> asList(Enum<?>[] enumerations) {
+ List<EnumerationDescription> result = new ArrayList<EnumerationDescription>(enumerations.length);
+ for (Enum<?> enumeration : enumerations) {
+ result.add(new ForLoadedEnumeration(enumeration));
+ }
+ return result;
+ }
+
+ @Override
+ public String getValue() {
+ return value.name();
+ }
+
+ @Override
+ public TypeDescription getEnumerationType() {
+ return new TypeDescription.ForLoadedType(value.getDeclaringClass());
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T extends Enum<T>> T load(Class<T> type) {
+ return value.getDeclaringClass() == type
+ ? (T) value
+ : Enum.valueOf(type, value.name());
+ }
+ }
+
+ /**
+ * A latent description of an enumeration value.
+ */
+ class Latent extends AbstractBase {
+
+ /**
+ * The type of the enumeration.
+ */
+ private final TypeDescription enumerationType;
+
+ /**
+ * The value of the enumeration.
+ */
+ private final String value;
+
+ /**
+ * Creates a latent description of an enumeration value.
+ *
+ * @param enumerationType The enumeration type.
+ * @param value The value of the enumeration.
+ */
+ public Latent(TypeDescription enumerationType, String value) {
+ this.enumerationType = enumerationType;
+ this.value = value;
+ }
+
+ @Override
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public TypeDescription getEnumerationType() {
+ return enumerationType;
+ }
+
+ @Override
+ public <T extends Enum<T>> T load(Class<T> type) {
+ if (!enumerationType.represents(type)) {
+ throw new IllegalArgumentException(type + " does not represent " + enumerationType);
+ }
+ return Enum.valueOf(type, value);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/enumeration/package-info.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/enumeration/package-info.java
new file mode 100644
index 0000000..706a518
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/enumeration/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * A package that contains classes for describing enumeration values.
+ */
+package net.bytebuddy.description.enumeration;
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/field/FieldDescription.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/field/FieldDescription.java
new file mode 100644
index 0000000..bfa4cbc
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/field/FieldDescription.java
@@ -0,0 +1,585 @@
+package net.bytebuddy.description.field;
+
+import net.bytebuddy.description.ByteCodeElement;
+import net.bytebuddy.description.ModifierReviewable;
+import net.bytebuddy.description.NamedElement;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.signature.SignatureWriter;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.GenericSignatureFormatError;
+import java.lang.reflect.Modifier;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Implementations of this interface describe a Java field. Implementations of this interface must provide meaningful
+ * {@code equal(Object)} and {@code hashCode()} implementations.
+ */
+public interface FieldDescription extends ByteCodeElement,
+ ModifierReviewable.ForFieldDescription,
+ NamedElement.WithGenericName,
+ ByteCodeElement.TypeDependant<FieldDescription.InDefinedShape, FieldDescription.Token> {
+
+ /**
+ * A representative of a field's non-set default value.
+ */
+ Object NO_DEFAULT_VALUE = null;
+
+ /**
+ * Returns the type of the described field.
+ *
+ * @return The type of the described field.
+ */
+ TypeDescription.Generic getType();
+
+ /**
+ * Returns the field's actual modifiers as it is present in a class file, i.e. its modifiers including
+ * a flag if this field is deprecated.
+ *
+ * @return The field's actual modifiers.
+ */
+ int getActualModifiers();
+
+ /**
+ * Returns a signature token representing this field.
+ *
+ * @return A signature token representing this field.
+ */
+ SignatureToken asSignatureToken();
+
+ /**
+ * Represents a field description in its generic shape, i.e. in the shape it is defined by a generic or raw type.
+ */
+ interface InGenericShape extends FieldDescription {
+
+ @Override
+ TypeDescription.Generic getDeclaringType();
+ }
+
+ /**
+ * Represents a field in its defined shape, i.e. in the form it is defined by a class without its type variables being resolved.
+ */
+ interface InDefinedShape extends FieldDescription {
+
+ @Override
+ TypeDescription getDeclaringType();
+
+ /**
+ * An abstract base implementation of a field description in its defined shape.
+ */
+ abstract class AbstractBase extends FieldDescription.AbstractBase implements InDefinedShape {
+
+ @Override
+ public InDefinedShape asDefined() {
+ return this;
+ }
+ }
+ }
+
+ /**
+ * An abstract base implementation of a field description.
+ */
+ abstract class AbstractBase extends ModifierReviewable.AbstractBase implements FieldDescription {
+
+ @Override
+ public String getInternalName() {
+ return getName();
+ }
+
+ @Override
+ public String getActualName() {
+ return getName();
+ }
+
+ @Override
+ public String getDescriptor() {
+ return getType().asErasure().getDescriptor();
+ }
+
+ @Override
+ public String getGenericSignature() {
+ TypeDescription.Generic fieldType = getType();
+ try {
+ return fieldType.getSort().isNonGeneric()
+ ? NON_GENERIC_SIGNATURE
+ : fieldType.accept(new TypeDescription.Generic.Visitor.ForSignatureVisitor(new SignatureWriter())).toString();
+ } catch (GenericSignatureFormatError ignored) {
+ return NON_GENERIC_SIGNATURE;
+ }
+ }
+
+ @Override
+ public boolean isVisibleTo(TypeDescription typeDescription) {
+ return getDeclaringType().asErasure().isVisibleTo(typeDescription)
+ && (isPublic()
+ || typeDescription.equals(getDeclaringType().asErasure())
+ || (isProtected() && getDeclaringType().asErasure().isAssignableFrom(typeDescription))
+ || (!isPrivate() && typeDescription.isSamePackage(getDeclaringType().asErasure())));
+ }
+
+ @Override
+ public boolean isAccessibleTo(TypeDescription typeDescription) {
+ return isPublic()
+ || typeDescription.equals(getDeclaringType().asErasure())
+ || (!isPrivate() && typeDescription.isSamePackage(getDeclaringType().asErasure()));
+ }
+
+ @Override
+ public int getActualModifiers() {
+ return getModifiers() | (getDeclaredAnnotations().isAnnotationPresent(Deprecated.class)
+ ? Opcodes.ACC_DEPRECATED
+ : EMPTY_MASK);
+ }
+
+ @Override
+ public FieldDescription.Token asToken(ElementMatcher<? super TypeDescription> matcher) {
+ return new FieldDescription.Token(getName(),
+ getModifiers(),
+ getType().accept(new TypeDescription.Generic.Visitor.Substitutor.ForDetachment(matcher)),
+ getDeclaredAnnotations());
+ }
+
+ @Override
+ public SignatureToken asSignatureToken() {
+ return new SignatureToken(getInternalName(), getType().asErasure());
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this || other instanceof FieldDescription
+ && getName().equals(((FieldDescription) other).getName())
+ && getDeclaringType().equals(((FieldDescription) other).getDeclaringType());
+ }
+
+ @Override
+ public int hashCode() {
+ return getDeclaringType().hashCode() + 31 * getName().hashCode();
+ }
+
+ @Override
+ public String toGenericString() {
+ StringBuilder stringBuilder = new StringBuilder();
+ if (getModifiers() != EMPTY_MASK) {
+ stringBuilder.append(Modifier.toString(getModifiers())).append(" ");
+ }
+ stringBuilder.append(getType().getActualName()).append(" ");
+ stringBuilder.append(getDeclaringType().asErasure().getActualName()).append(".");
+ return stringBuilder.append(getName()).toString();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder();
+ if (getModifiers() != EMPTY_MASK) {
+ stringBuilder.append(Modifier.toString(getModifiers())).append(" ");
+ }
+ stringBuilder.append(getType().asErasure().getActualName()).append(" ");
+ stringBuilder.append(getDeclaringType().asErasure().getActualName()).append(".");
+ return stringBuilder.append(getName()).toString();
+ }
+ }
+
+ /**
+ * An implementation of a field description for a loaded field.
+ */
+ class ForLoadedField extends InDefinedShape.AbstractBase {
+
+ /**
+ * The represented loaded field.
+ */
+ private final Field field;
+
+ /**
+ * Creates an immutable field description for a loaded field.
+ *
+ * @param field The represented field.
+ */
+ public ForLoadedField(Field field) {
+ this.field = field;
+ }
+
+ @Override
+ public TypeDescription.Generic getType() {
+ return new TypeDescription.Generic.LazyProjection.ForLoadedFieldType(field);
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return new AnnotationList.ForLoadedAnnotations(field.getDeclaredAnnotations());
+ }
+
+ @Override
+ public String getName() {
+ return field.getName();
+ }
+
+ @Override
+ public TypeDescription getDeclaringType() {
+ return new TypeDescription.ForLoadedType(field.getDeclaringClass());
+ }
+
+ @Override
+ public int getModifiers() {
+ return field.getModifiers();
+ }
+
+ @Override
+ public boolean isSynthetic() {
+ return field.isSynthetic();
+ }
+ }
+
+ /**
+ * A latent field description describes a field that is not attached to a declaring
+ * {@link TypeDescription}.
+ */
+ class Latent extends InDefinedShape.AbstractBase {
+
+ /**
+ * The type for which this field is defined.
+ */
+ private final TypeDescription declaringType;
+
+ /**
+ * The name of the field.
+ */
+ private final String fieldName;
+
+ /**
+ * The field's modifiers.
+ */
+ private final int modifiers;
+
+ /**
+ * The type of the field.
+ */
+ private final TypeDescription.Generic fieldType;
+
+ /**
+ * The annotations of this field.
+ */
+ private final List<? extends AnnotationDescription> declaredAnnotations;
+
+ /**
+ * Creates a new latent field description. All provided types are attached to this instance before they are returned.
+ *
+ * @param declaringType The declaring type of the field.
+ * @param token A token representing the field's shape.
+ */
+ public Latent(TypeDescription declaringType, FieldDescription.Token token) {
+ this(declaringType,
+ token.getName(),
+ token.getModifiers(),
+ token.getType(),
+ token.getAnnotations());
+ }
+
+ /**
+ * Creates a new latent field description. All provided types are attached to this instance before they are returned.
+ *
+ * @param declaringType The declaring type of the field.
+ * @param fieldName The name of the field.
+ * @param fieldType The field's modifiers.
+ * @param modifiers The type of the field.
+ * @param declaredAnnotations The annotations of this field.
+ */
+ public Latent(TypeDescription declaringType,
+ String fieldName,
+ int modifiers,
+ TypeDescription.Generic fieldType,
+ List<? extends AnnotationDescription> declaredAnnotations) {
+ this.declaringType = declaringType;
+ this.fieldName = fieldName;
+ this.modifiers = modifiers;
+ this.fieldType = fieldType;
+ this.declaredAnnotations = declaredAnnotations;
+ }
+
+ @Override
+ public TypeDescription.Generic getType() {
+ return fieldType.accept(TypeDescription.Generic.Visitor.Substitutor.ForAttachment.of(this));
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return new AnnotationList.Explicit(declaredAnnotations);
+ }
+
+ @Override
+ public String getName() {
+ return fieldName;
+ }
+
+ @Override
+ public TypeDescription getDeclaringType() {
+ return declaringType;
+ }
+
+ @Override
+ public int getModifiers() {
+ return modifiers;
+ }
+ }
+
+ /**
+ * A field description that represents a given field but with a substituted field type.
+ */
+ class TypeSubstituting extends AbstractBase implements InGenericShape {
+
+ /**
+ * The declaring type of the field.
+ */
+ private final TypeDescription.Generic declaringType;
+
+ /**
+ * The represented field.
+ */
+ private final FieldDescription fieldDescription;
+
+ /**
+ * A visitor that is applied to the field type.
+ */
+ private final TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor;
+
+ /**
+ * Creates a field description with a substituted field type.
+ *
+ * @param declaringType The declaring type of the field.
+ * @param fieldDescription The represented field.
+ * @param visitor A visitor that is applied to the field type.
+ */
+ public TypeSubstituting(TypeDescription.Generic declaringType,
+ FieldDescription fieldDescription,
+ TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor) {
+ this.declaringType = declaringType;
+ this.fieldDescription = fieldDescription;
+ this.visitor = visitor;
+ }
+
+ @Override
+ public TypeDescription.Generic getType() {
+ return fieldDescription.getType().accept(visitor);
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return fieldDescription.getDeclaredAnnotations();
+ }
+
+ @Override
+ public TypeDescription.Generic getDeclaringType() {
+ return declaringType;
+ }
+
+ @Override
+ public int getModifiers() {
+ return fieldDescription.getModifiers();
+ }
+
+ @Override
+ public String getName() {
+ return fieldDescription.getName();
+ }
+
+ @Override
+ public InDefinedShape asDefined() {
+ return fieldDescription.asDefined();
+ }
+ }
+
+ /**
+ * A token representing a field's properties detached from a type.
+ */
+ class Token implements ByteCodeElement.Token<Token> {
+
+ /**
+ * The name of the represented field.
+ */
+ private final String name;
+
+ /**
+ * The modifiers of the represented field.
+ */
+ private final int modifiers;
+
+ /**
+ * The type of the represented field.
+ */
+ private final TypeDescription.Generic type;
+
+ /**
+ * The annotations of the represented field.
+ */
+ private final List<? extends AnnotationDescription> annotations;
+
+ /**
+ * Creates a new field token without annotations. The field type must be represented in its detached form.
+ *
+ * @param name The name of the represented field.
+ * @param modifiers The modifiers of the represented field.
+ * @param type The type of the represented field.
+ */
+ public Token(String name, int modifiers, TypeDescription.Generic type) {
+ this(name, modifiers, type, Collections.<AnnotationDescription>emptyList());
+ }
+
+ /**
+ * Creates a new field token. The field type must be represented in its detached form.
+ *
+ * @param name The name of the represented field.
+ * @param modifiers The modifiers of the represented field.
+ * @param type The type of the represented field.
+ * @param annotations The annotations of the represented field.
+ */
+ public Token(String name, int modifiers, TypeDescription.Generic type, List<? extends AnnotationDescription> annotations) {
+ this.name = name;
+ this.modifiers = modifiers;
+ this.type = type;
+ this.annotations = annotations;
+ }
+
+ /**
+ * Returns the name of the represented field.
+ *
+ * @return The name of the represented field.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the type of the represented field.
+ *
+ * @return The type of the represented field.
+ */
+ public TypeDescription.Generic getType() {
+ return type;
+ }
+
+ /**
+ * Returns the modifiers of the represented field.
+ *
+ * @return The modifiers of the represented field.
+ */
+ public int getModifiers() {
+ return modifiers;
+ }
+
+ /**
+ * Returns the annotations of the represented field.
+ *
+ * @return The annotations of the represented field.
+ */
+ public AnnotationList getAnnotations() {
+ return new AnnotationList.Explicit(annotations);
+ }
+
+ @Override
+ public Token accept(TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor) {
+ return new Token(name,
+ modifiers,
+ type.accept(visitor),
+ annotations);
+ }
+
+ /**
+ * Creates a signature token that represents the method that is represented by this token.
+ *
+ * @param declaringType The declaring type of the field that this token represents.
+ * @return A signature token representing this token.
+ */
+ public SignatureToken asSignatureToken(TypeDescription declaringType) {
+ return new SignatureToken(name, type.accept(new TypeDescription.Generic.Visitor.Reducing(declaringType)));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (other == null || getClass() != other.getClass()) return false;
+ Token token = (Token) other;
+ return modifiers == token.modifiers
+ && name.equals(token.name)
+ && type.equals(token.type)
+ && annotations.equals(token.annotations);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = name.hashCode();
+ result = 31 * result + modifiers;
+ result = 31 * result + type.hashCode();
+ result = 31 * result + annotations.hashCode();
+ return result;
+ }
+ }
+
+ /**
+ * A token that uniquely identifies a field by its name and type erasure.
+ */
+ class SignatureToken {
+
+ /**
+ * The field's name.
+ */
+ private final String name;
+
+ /**
+ * The field's raw type.
+ */
+ private final TypeDescription type;
+
+ /**
+ * Creates a new signature token.
+ *
+ * @param name The field's name.
+ * @param type The field's raw type.
+ */
+ public SignatureToken(String name, TypeDescription type) {
+ this.name = name;
+ this.type = type;
+ }
+
+ /**
+ * Returns the name of the represented field.
+ *
+ * @return The name of the represented field.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the type of the represented field.
+ *
+ * @return The type of the represented field.
+ */
+ public TypeDescription getType() {
+ return type;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (!(other instanceof SignatureToken)) return false;
+ SignatureToken that = (SignatureToken) other;
+ return name.equals(that.name) && type.equals(that.type);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = name.hashCode();
+ result = 31 * result + type.hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return type + " " + name;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/field/FieldList.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/field/FieldList.java
new file mode 100644
index 0000000..9f039b1
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/field/FieldList.java
@@ -0,0 +1,259 @@
+package net.bytebuddy.description.field;
+
+import net.bytebuddy.description.ByteCodeElement;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.matcher.FilterableList;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Implementations represent a list of field descriptions.
+ *
+ * @param <T> The type of field descriptions represented by this list.
+ */
+public interface FieldList<T extends FieldDescription> extends FilterableList<T, FieldList<T>> {
+
+ /**
+ * Transforms the list of field descriptions into a list of detached tokens. All types that are matched by the provided
+ * target type matcher are substituted by {@link net.bytebuddy.dynamic.TargetType}.
+ *
+ * @param matcher A matcher that indicates type substitution.
+ * @return The transformed token list.
+ */
+ ByteCodeElement.Token.TokenList<FieldDescription.Token> asTokenList(ElementMatcher<? super TypeDescription> matcher);
+
+ /**
+ * Returns this list of these field descriptions resolved to their defined shape.
+ *
+ * @return A list of fields in their defined shape.
+ */
+ FieldList<FieldDescription.InDefinedShape> asDefined();
+
+ /**
+ * An abstract base implementation of a {@link FieldList}.
+ *
+ * @param <S> The type of field descriptions represented by this list.
+ */
+ abstract class AbstractBase<S extends FieldDescription> extends FilterableList.AbstractBase<S, FieldList<S>> implements FieldList<S> {
+
+ @Override
+ public ByteCodeElement.Token.TokenList<FieldDescription.Token> asTokenList(ElementMatcher<? super TypeDescription> matcher) {
+ List<FieldDescription.Token> tokens = new ArrayList<FieldDescription.Token>(size());
+ for (FieldDescription fieldDescription : this) {
+ tokens.add(fieldDescription.asToken(matcher));
+ }
+ return new ByteCodeElement.Token.TokenList<FieldDescription.Token>(tokens);
+ }
+
+ @Override
+ public FieldList<FieldDescription.InDefinedShape> asDefined() {
+ List<FieldDescription.InDefinedShape> declaredForms = new ArrayList<FieldDescription.InDefinedShape>(size());
+ for (FieldDescription fieldDescription : this) {
+ declaredForms.add(fieldDescription.asDefined());
+ }
+ return new Explicit<FieldDescription.InDefinedShape>(declaredForms);
+ }
+
+ @Override
+ protected FieldList<S> wrap(List<S> values) {
+ return new Explicit<S>(values);
+ }
+ }
+
+ /**
+ * An implementation of a field list for an array of loaded fields.
+ */
+ class ForLoadedFields extends AbstractBase<FieldDescription.InDefinedShape> {
+
+ /**
+ * The loaded fields this field list represents.
+ */
+ private final List<? extends Field> fields;
+
+ /**
+ * Creates a new immutable field list that represents an array of loaded field.
+ *
+ * @param field An array of fields to be represented by this field list.
+ */
+ public ForLoadedFields(Field... field) {
+ this(Arrays.asList(field));
+ }
+
+ /**
+ * Creates a new immutable field list that represents an array of loaded field.
+ *
+ * @param fields An array of fields to be represented by this field list.
+ */
+ public ForLoadedFields(List<? extends Field> fields) {
+ this.fields = fields;
+ }
+
+ @Override
+ public FieldDescription.InDefinedShape get(int index) {
+ return new FieldDescription.ForLoadedField(fields.get(index));
+ }
+
+ @Override
+ public int size() {
+ return fields.size();
+ }
+ }
+
+ /**
+ * A wrapper implementation of a field list for a given list of field descriptions.
+ *
+ * @param <S> The type of field descriptions represented by this list.
+ */
+ class Explicit<S extends FieldDescription> extends AbstractBase<S> {
+
+ /**
+ * The list of field descriptions this list represents.
+ */
+ private final List<? extends S> fieldDescriptions;
+
+ /**
+ * Creates a new immutable wrapper field list.
+ *
+ * @param fieldDescription The list of fields to be represented by this field list.
+ */
+ @SuppressWarnings("unchecked")
+ public Explicit(S... fieldDescription) {
+ this(Arrays.asList(fieldDescription));
+ }
+
+ /**
+ * Creates a new immutable wrapper field list.
+ *
+ * @param fieldDescriptions The list of fields to be represented by this field list.
+ */
+ public Explicit(List<? extends S> fieldDescriptions) {
+ this.fieldDescriptions = fieldDescriptions;
+ }
+
+ @Override
+ public S get(int index) {
+ return fieldDescriptions.get(index);
+ }
+
+ @Override
+ public int size() {
+ return fieldDescriptions.size();
+ }
+ }
+
+ /**
+ * A list of field descriptions for a list of detached tokens. For the returned fields, each token is attached to its field representation.
+ */
+ class ForTokens extends AbstractBase<FieldDescription.InDefinedShape> {
+
+ /**
+ * The declaring type of the represented fields.
+ */
+ private final TypeDescription declaringType;
+
+ /**
+ * A list of the represented fields' tokens.
+ */
+ private final List<? extends FieldDescription.Token> tokens;
+
+ /**
+ * Creates a new field list from a list of field tokens.
+ *
+ * @param declaringType The declaring type of the represented fields.
+ * @param token A list of the represented fields' tokens.
+ */
+ public ForTokens(TypeDescription declaringType, FieldDescription.Token... token) {
+ this(declaringType, Arrays.asList(token));
+ }
+
+ /**
+ * Creates a new field list from a list of field tokens.
+ *
+ * @param declaringType The declaring type of the represented fields.
+ * @param tokens A list of the represented fields' tokens.
+ */
+ public ForTokens(TypeDescription declaringType, List<? extends FieldDescription.Token> tokens) {
+ this.declaringType = declaringType;
+ this.tokens = tokens;
+ }
+
+ @Override
+ public FieldDescription.InDefinedShape get(int index) {
+ return new FieldDescription.Latent(declaringType, tokens.get(index));
+ }
+
+ @Override
+ public int size() {
+ return tokens.size();
+ }
+ }
+
+ /**
+ * A list of field descriptions that yields {@link net.bytebuddy.description.field.FieldDescription.TypeSubstituting}.
+ */
+ class TypeSubstituting extends AbstractBase<FieldDescription.InGenericShape> {
+
+ /**
+ * The field's actual declaring type.
+ */
+ private final TypeDescription.Generic declaringType;
+
+ /**
+ * The field descriptions to be transformed.
+ */
+ private final List<? extends FieldDescription> fieldDescriptions;
+
+ /**
+ * The visitor to apply to a field description.
+ */
+ private final TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor;
+
+ /**
+ * Creates a new type substituting field list.
+ *
+ * @param declaringType The field's actual declaring type.
+ * @param fieldDescriptions The field descriptions to be transformed.
+ * @param visitor The visitor to apply to a field description.
+ */
+ public TypeSubstituting(TypeDescription.Generic declaringType,
+ List<? extends FieldDescription> fieldDescriptions,
+ TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor) {
+ this.declaringType = declaringType;
+ this.fieldDescriptions = fieldDescriptions;
+ this.visitor = visitor;
+ }
+
+ @Override
+ public FieldDescription.InGenericShape get(int index) {
+ return new FieldDescription.TypeSubstituting(declaringType, fieldDescriptions.get(index), visitor);
+ }
+
+ @Override
+ public int size() {
+ return fieldDescriptions.size();
+ }
+ }
+
+ /**
+ * An implementation of an empty field list.
+ *
+ * @param <S> The type of parameter descriptions represented by this list.
+ */
+ class Empty<S extends FieldDescription> extends FilterableList.Empty<S, FieldList<S>> implements FieldList<S> {
+
+ @Override
+ public ByteCodeElement.Token.TokenList<FieldDescription.Token> asTokenList(ElementMatcher<? super TypeDescription> matcher) {
+ return new ByteCodeElement.Token.TokenList<FieldDescription.Token>();
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public FieldList<FieldDescription.InDefinedShape> asDefined() {
+ return (FieldList<FieldDescription.InDefinedShape>) this;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/field/package-info.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/field/package-info.java
new file mode 100644
index 0000000..568caf0
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/field/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Contains descriptions of Java fields.
+ */
+package net.bytebuddy.description.field;
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/method/MethodDescription.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/method/MethodDescription.java
new file mode 100644
index 0000000..b5f45b8
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/method/MethodDescription.java
@@ -0,0 +1,1830 @@
+package net.bytebuddy.description.method;
+
+import net.bytebuddy.description.ByteCodeElement;
+import net.bytebuddy.description.ModifierReviewable;
+import net.bytebuddy.description.NamedElement;
+import net.bytebuddy.description.TypeVariableSource;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.annotation.AnnotationValue;
+import net.bytebuddy.description.enumeration.EnumerationDescription;
+import net.bytebuddy.description.modifier.ModifierContributor;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.description.type.TypeVariableToken;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.matcher.ElementMatchers;
+import net.bytebuddy.utility.JavaConstant;
+import net.bytebuddy.utility.JavaType;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.signature.SignatureWriter;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.GenericSignatureFormatError;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import static net.bytebuddy.matcher.ElementMatchers.not;
+import static net.bytebuddy.matcher.ElementMatchers.ofSort;
+
+/**
+ * Implementations of this interface describe a Java method, i.e. a method or a constructor. Implementations of this
+ * interface must provide meaningful {@code equal(Object)} and {@code hashCode()} implementations.
+ */
+public interface MethodDescription extends TypeVariableSource,
+ ModifierReviewable.ForMethodDescription,
+ NamedElement.WithGenericName,
+ ByteCodeElement,
+ ByteCodeElement.TypeDependant<MethodDescription.InDefinedShape, MethodDescription.Token> {
+
+ /**
+ * The internal name of a Java constructor.
+ */
+ String CONSTRUCTOR_INTERNAL_NAME = "<init>";
+
+ /**
+ * The internal name of a Java static initializer.
+ */
+ String TYPE_INITIALIZER_INTERNAL_NAME = "<clinit>";
+
+ /**
+ * The type initializer of any representation of a type initializer.
+ */
+ int TYPE_INITIALIZER_MODIFIER = Opcodes.ACC_STATIC;
+
+ /**
+ * Represents any undefined property of a type description that is instead represented as {@code null} in order
+ * to resemble the Java reflection API which returns {@code null} and is intuitive to many Java developers.
+ */
+ MethodDescription UNDEFINED = null;
+
+ /**
+ * Returns the return type of the described method.
+ *
+ * @return The return type of the described method.
+ */
+ TypeDescription.Generic getReturnType();
+
+ /**
+ * Returns a list of this method's parameters.
+ *
+ * @return A list of this method's parameters.
+ */
+ ParameterList<?> getParameters();
+
+ /**
+ * Returns the exception types of the described method.
+ *
+ * @return The exception types of the described method.
+ */
+ TypeList.Generic getExceptionTypes();
+
+ /**
+ * Returns this method's actual modifiers as it is present in a class file, i.e. includes a flag if this method
+ * is marked {@link Deprecated}.
+ *
+ * @return The method's actual modifiers.
+ */
+ int getActualModifiers();
+
+ /**
+ * Returns this method's actual modifiers as it is present in a class file, i.e. includes a flag if this method
+ * is marked {@link Deprecated} and adjusts the modifiers for being abstract or not.
+ *
+ * @param manifest {@code true} if the method should be treated as non-abstract.
+ * @return The method's actual modifiers.
+ */
+ int getActualModifiers(boolean manifest);
+
+ /**
+ * Returns this method's actual modifiers as it is present in a class file, i.e. includes a flag if this method
+ * is marked {@link Deprecated} and adjusts the modifiers for being abstract or not. Additionally, this method
+ * resolves a required minimal visibility.
+ *
+ * @param manifest {@code true} if the method should be treated as non-abstract.
+ * @param visibility The minimal visibility to enforce for this method.
+ * @return The method's actual modifiers.
+ */
+ int getActualModifiers(boolean manifest, Visibility visibility);
+
+ /**
+ * Checks if this method description represents a constructor.
+ *
+ * @return {@code true} if this method description represents a constructor.
+ */
+ boolean isConstructor();
+
+ /**
+ * Checks if this method description represents a method, i.e. not a constructor or a type initializer.
+ *
+ * @return {@code true} if this method description represents a Java method.
+ */
+ boolean isMethod();
+
+ /**
+ * Checks if this method is a type initializer.
+ *
+ * @return {@code true} if this method description represents a type initializer.
+ */
+ boolean isTypeInitializer();
+
+ /**
+ * Verifies if a method description represents a given loaded method.
+ *
+ * @param method The method to be checked.
+ * @return {@code true} if this method description represents the given loaded method.
+ */
+ boolean represents(Method method);
+
+ /**
+ * Verifies if a method description represents a given loaded constructor.
+ *
+ * @param constructor The constructor to be checked.
+ * @return {@code true} if this method description represents the given loaded constructor.
+ */
+ boolean represents(Constructor<?> constructor);
+
+ /**
+ * Verifies if this method describes a virtual method, i.e. a method that is inherited by a sub type of this type.
+ *
+ * @return {@code true} if this method is virtual.
+ */
+ boolean isVirtual();
+
+ /**
+ * Returns the size of the local variable array that is required for this method, i.e. the size of all parameters
+ * if they were loaded on the stack including a reference to {@code this} if this method represented a non-static
+ * method.
+ *
+ * @return The size of this method on the operand stack.
+ */
+ int getStackSize();
+
+ /**
+ * Checks if this method represents a default (defender) method.
+ *
+ * @return {@code true} if this method is a default method.
+ */
+ boolean isDefaultMethod();
+
+ /**
+ * Checks if this method can be called using the {@code INVOKESPECIAL} for a given type.
+ *
+ * @param typeDescription The type o
+ * @return {@code true} if this method can be called using the {@code INVOKESPECIAL} instruction
+ * using the given type.
+ */
+ boolean isSpecializableFor(TypeDescription typeDescription);
+
+ /**
+ * Returns the method's default annotation value or {@code null} if no default value is defined for this method.
+ *
+ * @return The method's default annotation value or {@code null} if no default value is defined for this method.
+ */
+ AnnotationValue<?, ?> getDefaultValue();
+
+ /**
+ * Returns the default value but casts it to the given type. If the type differs from the value, a
+ * {@link java.lang.ClassCastException} is thrown.
+ *
+ * @param type The type to cast the default value to.
+ * @param <T> The type to cast the default value to.
+ * @return The casted default value.
+ */
+ <T> T getDefaultValue(Class<T> type);
+
+ /**
+ * Asserts if this method is invokable on an instance of the given type, i.e. the method is an instance method or
+ * a constructor and the method is visible to the type and can be invoked on the given instance.
+ *
+ * @param typeDescription The type to check.
+ * @return {@code true} if this method is invokable on an instance of the given type.
+ */
+ boolean isInvokableOn(TypeDescription typeDescription);
+
+ /**
+ * Checks if the method is a bootstrap method.
+ *
+ * @return {@code true} if the method is a bootstrap method.
+ */
+ boolean isBootstrap();
+
+ /**
+ * Checks if the method is a bootstrap method that accepts the given arguments.
+ *
+ * @param arguments The arguments that the bootstrap method is expected to accept where primitive values
+ * are to be represented as their wrapper types, loaded types by {@link TypeDescription},
+ * method handles by {@link JavaConstant.MethodHandle} instances and
+ * method types by {@link JavaConstant.MethodType} instances.
+ * @return {@code true} if the method is a bootstrap method that accepts the given arguments.
+ */
+ boolean isBootstrap(List<?> arguments);
+
+ /**
+ * Checks if this method is capable of defining a default annotation value.
+ *
+ * @return {@code true} if it is possible to define a default annotation value for this method.
+ */
+ boolean isDefaultValue();
+
+ /**
+ * Checks if the given value can describe a default annotation value for this method.
+ *
+ * @param annotationValue The value that describes the default annotation value for this method.
+ * @return {@code true} if the given value can describe a default annotation value for this method.
+ */
+ boolean isDefaultValue(AnnotationValue<?, ?> annotationValue);
+
+ /**
+ * Returns this methods receiver type. A receiver type is undefined for {@code static} methods
+ * where {@code null} is returned. Other than a receiver type that is provided by the Java reflection
+ * API, Byte Buddy is capable of extracting annotations on type parameters of receiver types when
+ * directly accessing a class file. Therefore, a receiver type might be parameterized.
+ *
+ * @return This method's (annotated) receiver type.
+ */
+ TypeDescription.Generic getReceiverType();
+
+ /**
+ * Returns a signature token representing this method.
+ *
+ * @return A signature token representing this method.
+ */
+ SignatureToken asSignatureToken();
+
+ /**
+ * Returns a type token that represents this method's raw return and parameter types.
+ *
+ * @return A type token that represents this method's raw return and parameter types.
+ */
+ TypeToken asTypeToken();
+
+ /**
+ * Validates that the supplied type token can implement a bridge method to this method.
+ *
+ * @param typeToken A type token representing a potential bridge method to this method.
+ * @return {@code true} if the supplied type token can represent a bridge method to this method.
+ */
+ boolean isBridgeCompatible(TypeToken typeToken);
+
+ /**
+ * Represents a method description in its generic shape, i.e. in the shape it is defined by a generic or raw type.
+ */
+ interface InGenericShape extends MethodDescription {
+
+ @Override
+ TypeDescription.Generic getDeclaringType();
+
+ @Override
+ ParameterList<ParameterDescription.InGenericShape> getParameters();
+ }
+
+ /**
+ * Represents a method in its defined shape, i.e. in the form it is defined by a class without its type variables being resolved.
+ */
+ interface InDefinedShape extends MethodDescription {
+
+ @Override
+ TypeDescription getDeclaringType();
+
+ @Override
+ ParameterList<ParameterDescription.InDefinedShape> getParameters();
+
+ /**
+ * An abstract base implementation of a method description in its defined shape.
+ */
+ abstract class AbstractBase extends MethodDescription.AbstractBase implements InDefinedShape {
+
+ @Override
+ public InDefinedShape asDefined() {
+ return this;
+ }
+
+ @Override
+ public TypeDescription.Generic getReceiverType() {
+ if (isStatic()) {
+ return TypeDescription.Generic.UNDEFINED;
+ } else if (isConstructor()) {
+ TypeDescription declaringType = getDeclaringType(), enclosingDeclaringType = getDeclaringType().getEnclosingType();
+ if (enclosingDeclaringType == null) {
+ return TypeDescription.Generic.OfParameterizedType.ForGenerifiedErasure.of(declaringType);
+ } else {
+ return declaringType.isStatic()
+ ? enclosingDeclaringType.asGenericType()
+ : TypeDescription.Generic.OfParameterizedType.ForGenerifiedErasure.of(enclosingDeclaringType);
+ }
+ } else {
+ return TypeDescription.Generic.OfParameterizedType.ForGenerifiedErasure.of(getDeclaringType());
+ }
+ }
+ }
+ }
+
+ /**
+ * An abstract base implementation of a method description.
+ */
+ abstract class AbstractBase extends TypeVariableSource.AbstractBase implements MethodDescription {
+
+ /**
+ * A merger of all method modifiers that are visible in the Java source code.
+ */
+ private static final int SOURCE_MODIFIERS = Modifier.PUBLIC
+ | Modifier.PROTECTED
+ | Modifier.PRIVATE
+ | Modifier.ABSTRACT
+ | Modifier.STATIC
+ | Modifier.FINAL
+ | Modifier.SYNCHRONIZED
+ | Modifier.NATIVE;
+
+ @Override
+ public int getStackSize() {
+ return getParameters().asTypeList().getStackSize() + (isStatic() ? 0 : 1);
+ }
+
+ @Override
+ public boolean isMethod() {
+ return !isConstructor() && !isTypeInitializer();
+ }
+
+ @Override
+ public boolean isConstructor() {
+ return CONSTRUCTOR_INTERNAL_NAME.equals(getInternalName());
+ }
+
+ @Override
+ public boolean isTypeInitializer() {
+ return TYPE_INITIALIZER_INTERNAL_NAME.equals(getInternalName());
+ }
+
+ @Override
+ public boolean represents(Method method) {
+ return equals(new ForLoadedMethod(method));
+ }
+
+ @Override
+ public boolean represents(Constructor<?> constructor) {
+ return equals(new ForLoadedConstructor(constructor));
+ }
+
+ @Override
+ public String getName() {
+ return isMethod()
+ ? getInternalName()
+ : getDeclaringType().asErasure().getName();
+ }
+
+ @Override
+ public String getActualName() {
+ return isMethod()
+ ? getName()
+ : EMPTY_NAME;
+ }
+
+ @Override
+ public String getDescriptor() {
+ StringBuilder descriptor = new StringBuilder("(");
+ for (TypeDescription parameterType : getParameters().asTypeList().asErasures()) {
+ descriptor.append(parameterType.getDescriptor());
+ }
+ return descriptor.append(")").append(getReturnType().asErasure().getDescriptor()).toString();
+ }
+
+ @Override
+ public String getGenericSignature() {
+ try {
+ SignatureWriter signatureWriter = new SignatureWriter();
+ boolean generic = false;
+ for (TypeDescription.Generic typeVariable : getTypeVariables()) {
+ signatureWriter.visitFormalTypeParameter(typeVariable.getSymbol());
+ boolean classBound = true;
+ for (TypeDescription.Generic upperBound : typeVariable.getUpperBounds()) {
+ upperBound.accept(new TypeDescription.Generic.Visitor.ForSignatureVisitor(classBound
+ ? signatureWriter.visitClassBound()
+ : signatureWriter.visitInterfaceBound()));
+ classBound = false;
+ }
+ generic = true;
+ }
+ for (TypeDescription.Generic parameterType : getParameters().asTypeList()) {
+ parameterType.accept(new TypeDescription.Generic.Visitor.ForSignatureVisitor(signatureWriter.visitParameterType()));
+ generic = generic || !parameterType.getSort().isNonGeneric();
+ }
+ TypeDescription.Generic returnType = getReturnType();
+ returnType.accept(new TypeDescription.Generic.Visitor.ForSignatureVisitor(signatureWriter.visitReturnType()));
+ generic = generic || !returnType.getSort().isNonGeneric();
+ TypeList.Generic exceptionTypes = getExceptionTypes();
+ if (!exceptionTypes.filter(not(ofSort(TypeDefinition.Sort.NON_GENERIC))).isEmpty()) {
+ for (TypeDescription.Generic exceptionType : exceptionTypes) {
+ exceptionType.accept(new TypeDescription.Generic.Visitor.ForSignatureVisitor(signatureWriter.visitExceptionType()));
+ generic = generic || !exceptionType.getSort().isNonGeneric();
+ }
+ }
+ return generic
+ ? signatureWriter.toString()
+ : NON_GENERIC_SIGNATURE;
+ } catch (GenericSignatureFormatError ignored) {
+ return NON_GENERIC_SIGNATURE;
+ }
+ }
+
+ @Override
+ public int getActualModifiers() {
+ return getModifiers() | (getDeclaredAnnotations().isAnnotationPresent(Deprecated.class)
+ ? Opcodes.ACC_DEPRECATED
+ : EMPTY_MASK);
+ }
+
+ @Override
+ public int getActualModifiers(boolean manifest) {
+ return manifest
+ ? getActualModifiers() & ~(Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE)
+ : getActualModifiers() & ~Opcodes.ACC_NATIVE | Opcodes.ACC_ABSTRACT;
+ }
+
+ @Override
+ public int getActualModifiers(boolean manifest, Visibility visibility) {
+ return ModifierContributor.Resolver.of(Collections.singleton(getVisibility().expandTo(visibility))).resolve(getActualModifiers(manifest));
+ }
+
+ @Override
+ public boolean isVisibleTo(TypeDescription typeDescription) {
+ return (isVirtual() || getDeclaringType().asErasure().isVisibleTo(typeDescription))
+ && (isPublic()
+ || typeDescription.equals(getDeclaringType().asErasure())
+ || (isProtected() && getDeclaringType().asErasure().isAssignableFrom(typeDescription))
+ || (!isPrivate() && typeDescription.isSamePackage(getDeclaringType().asErasure())));
+ }
+
+ @Override
+ public boolean isAccessibleTo(TypeDescription typeDescription) {
+ return (isVirtual() || getDeclaringType().asErasure().isVisibleTo(typeDescription))
+ && (isPublic()
+ || typeDescription.equals(getDeclaringType().asErasure())
+ || (!isPrivate() && typeDescription.isSamePackage(getDeclaringType().asErasure())));
+ }
+
+ @Override
+ public boolean isVirtual() {
+ return !(isConstructor() || isPrivate() || isStatic() || isTypeInitializer());
+ }
+
+ @Override
+ public boolean isDefaultMethod() {
+ return !isAbstract() && !isBridge() && getDeclaringType().isInterface();
+ }
+
+ @Override
+ public boolean isSpecializableFor(TypeDescription targetType) {
+ if (isStatic()) { // Static private methods are never specializable, check static property first
+ return false;
+ } else if (isPrivate() || isConstructor()) {
+ return getDeclaringType().equals(targetType);
+ } else {
+ return !isAbstract() && getDeclaringType().asErasure().isAssignableFrom(targetType);
+ }
+ }
+
+ @Override
+ public <T> T getDefaultValue(Class<T> type) {
+ return type.cast(getDefaultValue());
+ }
+
+ @Override
+ public boolean isInvokableOn(TypeDescription typeDescription) {
+ return !isStatic()
+ && !isTypeInitializer()
+ && isVisibleTo(typeDescription)
+ && (isVirtual()
+ ? getDeclaringType().asErasure().isAssignableFrom(typeDescription)
+ : getDeclaringType().asErasure().equals(typeDescription));
+ }
+
+ @Override
+ public boolean isBootstrap() {
+ TypeDescription returnType = getReturnType().asErasure();
+ if ((isMethod() && (!isStatic()
+ || !(JavaType.CALL_SITE.getTypeStub().isAssignableFrom(returnType) || JavaType.CALL_SITE.getTypeStub().isAssignableTo(returnType))))
+ || (isConstructor() && !JavaType.CALL_SITE.getTypeStub().isAssignableFrom(getDeclaringType().asErasure()))) {
+ return false;
+ }
+ TypeList parameterTypes = getParameters().asTypeList().asErasures();
+ switch (parameterTypes.size()) {
+ case 0:
+ return false;
+ case 1:
+ return parameterTypes.getOnly().represents(Object[].class);
+ case 2:
+ return JavaType.METHOD_HANDLES_LOOKUP.getTypeStub().isAssignableTo(parameterTypes.get(0))
+ && parameterTypes.get(1).represents(Object[].class);
+ case 3:
+ return JavaType.METHOD_HANDLES_LOOKUP.getTypeStub().isAssignableTo(parameterTypes.get(0))
+ && (parameterTypes.get(1).represents(Object.class) || parameterTypes.get(1).represents(String.class))
+ && (parameterTypes.get(2).represents(Object[].class) || JavaType.METHOD_TYPE.getTypeStub().isAssignableTo(parameterTypes.get(2)));
+ default:
+ if (!(JavaType.METHOD_HANDLES_LOOKUP.getTypeStub().isAssignableTo(parameterTypes.get(0))
+ && (parameterTypes.get(1).represents(Object.class) || parameterTypes.get(1).represents(String.class))
+ && (JavaType.METHOD_TYPE.getTypeStub().isAssignableTo(parameterTypes.get(2))))) {
+ return false;
+ }
+ int parameterIndex = 4;
+ for (TypeDescription parameterType : parameterTypes.subList(3, parameterTypes.size())) {
+ if (!parameterType.represents(Object.class) && !parameterType.isConstantPool()) {
+ return parameterType.represents(Object[].class) && parameterIndex == parameterTypes.size();
+ }
+ parameterIndex++;
+ }
+ return true;
+ }
+ }
+
+ @Override
+ public boolean isBootstrap(List<?> arguments) {
+ if (!isBootstrap()) {
+ return false;
+ }
+ for (Object argument : arguments) {
+ Class<?> argumentType = argument.getClass();
+ if (!(argumentType == String.class
+ || argumentType == Integer.class
+ || argumentType == Long.class
+ || argumentType == Float.class
+ || argumentType == Double.class
+ || TypeDescription.class.isAssignableFrom(argumentType)
+ || JavaConstant.MethodHandle.class.isAssignableFrom(argumentType)
+ || JavaConstant.MethodType.class.isAssignableFrom(argumentType))) {
+ throw new IllegalArgumentException("Not a bootstrap argument: " + argument);
+ }
+ }
+ TypeList parameterTypes = getParameters().asTypeList().asErasures();
+ // The following assumes that the bootstrap method is a valid one, as checked above.
+ if (parameterTypes.size() < 4) {
+ return arguments.isEmpty() || parameterTypes.get(parameterTypes.size() - 1).represents(Object[].class);
+ } else {
+ int index = 4;
+ Iterator<?> argumentIterator = arguments.iterator();
+ for (TypeDescription parameterType : parameterTypes.subList(3, parameterTypes.size())) {
+ boolean finalParameterCheck = !argumentIterator.hasNext();
+ if (!finalParameterCheck) {
+ Class<?> argumentType = argumentIterator.next().getClass();
+ finalParameterCheck = !(parameterType.represents(String.class) && argumentType == String.class)
+ && !(parameterType.represents(int.class) && argumentType == Integer.class)
+ && !(parameterType.represents(long.class) && argumentType == Long.class)
+ && !(parameterType.represents(float.class) && argumentType == Float.class)
+ && !(parameterType.represents(double.class) && argumentType == Double.class)
+ && !(parameterType.represents(Class.class) && TypeDescription.class.isAssignableFrom(argumentType))
+ && !(parameterType.isAssignableFrom(JavaType.METHOD_HANDLE.getTypeStub()) && JavaConstant.MethodHandle.class.isAssignableFrom(argumentType))
+ && !(parameterType.equals(JavaType.METHOD_TYPE.getTypeStub()) && JavaConstant.MethodType.class.isAssignableFrom(argumentType));
+ }
+ if (finalParameterCheck) {
+ return index == parameterTypes.size() && parameterType.represents(Object[].class);
+ }
+ index++;
+ }
+ return true;
+ }
+ }
+
+ @Override
+ public boolean isDefaultValue() {
+ return !isConstructor()
+ && !isStatic()
+ && getReturnType().asErasure().isAnnotationReturnType()
+ && getParameters().isEmpty();
+ }
+
+ @Override
+ public boolean isDefaultValue(AnnotationValue<?, ?> annotationValue) {
+ if (!isDefaultValue()) {
+ return false;
+ }
+ TypeDescription returnType = getReturnType().asErasure();
+ Object value = annotationValue.resolve();
+ return (returnType.represents(boolean.class) && value instanceof Boolean)
+ || (returnType.represents(byte.class) && value instanceof Byte)
+ || (returnType.represents(char.class) && value instanceof Character)
+ || (returnType.represents(short.class) && value instanceof Short)
+ || (returnType.represents(int.class) && value instanceof Integer)
+ || (returnType.represents(long.class) && value instanceof Long)
+ || (returnType.represents(float.class) && value instanceof Float)
+ || (returnType.represents(double.class) && value instanceof Double)
+ || (returnType.represents(String.class) && value instanceof String)
+ || (returnType.isAssignableTo(Enum.class) && value instanceof EnumerationDescription && isEnumerationType(returnType, (EnumerationDescription) value))
+ || (returnType.isAssignableTo(Annotation.class) && value instanceof AnnotationDescription && isAnnotationType(returnType, (AnnotationDescription) value))
+ || (returnType.represents(Class.class) && value instanceof TypeDescription)
+ || (returnType.represents(boolean[].class) && value instanceof boolean[])
+ || (returnType.represents(byte[].class) && value instanceof byte[])
+ || (returnType.represents(char[].class) && value instanceof char[])
+ || (returnType.represents(short[].class) && value instanceof short[])
+ || (returnType.represents(int[].class) && value instanceof int[])
+ || (returnType.represents(long[].class) && value instanceof long[])
+ || (returnType.represents(float[].class) && value instanceof float[])
+ || (returnType.represents(double[].class) && value instanceof double[])
+ || (returnType.represents(String[].class) && value instanceof String[])
+ || (returnType.isAssignableTo(Enum[].class) && value instanceof EnumerationDescription[] && isEnumerationType(returnType.getComponentType(), (EnumerationDescription[]) value))
+ || (returnType.isAssignableTo(Annotation[].class) && value instanceof AnnotationDescription[] && isAnnotationType(returnType.getComponentType(), (AnnotationDescription[]) value))
+ || (returnType.represents(Class[].class) && value instanceof TypeDescription[]);
+ }
+
+ /**
+ * Checks if the supplied enumeration descriptions describe the correct enumeration type.
+ *
+ * @param enumerationType The enumeration type to check for.
+ * @param enumerationDescription The enumeration descriptions to check.
+ * @return {@code true} if all enumeration descriptions represent the enumeration type in question.
+ */
+ private static boolean isEnumerationType(TypeDescription enumerationType, EnumerationDescription... enumerationDescription) {
+ for (EnumerationDescription anEnumerationDescription : enumerationDescription) {
+ if (!anEnumerationDescription.getEnumerationType().equals(enumerationType)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks if the supplied enumeration descriptions describe the correct annotation type.
+ *
+ * @param annotationType The annotation type to check for.
+ * @param annotationDescription The annotation descriptions to check.
+ * @return {@code true} if all annotation descriptions represent the annotation type in question.
+ */
+ private static boolean isAnnotationType(TypeDescription annotationType, AnnotationDescription... annotationDescription) {
+ for (AnnotationDescription anAnnotationDescription : annotationDescription) {
+ if (!anAnnotationDescription.getAnnotationType().equals(annotationType)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public TypeVariableSource getEnclosingSource() {
+ return isStatic()
+ ? TypeVariableSource.UNDEFINED
+ : getDeclaringType().asErasure();
+ }
+
+ @Override
+ public <T> T accept(TypeVariableSource.Visitor<T> visitor) {
+ return visitor.onMethod(this.asDefined());
+ }
+
+ @Override
+ public boolean isGenerified() {
+ return !getTypeVariables().isEmpty();
+ }
+
+ @Override
+ public MethodDescription.Token asToken(ElementMatcher<? super TypeDescription> matcher) {
+ TypeDescription.Generic receiverType = getReceiverType();
+ return new MethodDescription.Token(getInternalName(),
+ getModifiers(),
+ getTypeVariables().asTokenList(matcher),
+ getReturnType().accept(new TypeDescription.Generic.Visitor.Substitutor.ForDetachment(matcher)),
+ getParameters().asTokenList(matcher),
+ getExceptionTypes().accept(new TypeDescription.Generic.Visitor.Substitutor.ForDetachment(matcher)),
+ getDeclaredAnnotations(),
+ getDefaultValue(),
+ receiverType == null
+ ? TypeDescription.Generic.UNDEFINED
+ : receiverType.accept(new TypeDescription.Generic.Visitor.Substitutor.ForDetachment(matcher)));
+ }
+
+ @Override
+ public SignatureToken asSignatureToken() {
+ return new SignatureToken(getInternalName(), getReturnType().asErasure(), getParameters().asTypeList().asErasures());
+ }
+
+ @Override
+ public TypeToken asTypeToken() {
+ return new TypeToken(getReturnType().asErasure(), getParameters().asTypeList().asErasures());
+ }
+
+ @Override
+ public boolean isBridgeCompatible(TypeToken typeToken) {
+ List<TypeDescription> types = getParameters().asTypeList().asErasures(), bridgeTypes = typeToken.getParameterTypes();
+ if (types.size() != bridgeTypes.size()) {
+ return false;
+ }
+ for (int index = 0; index < types.size(); index++) {
+ if (!types.get(index).equals(bridgeTypes.get(index)) && (types.get(index).isPrimitive() || bridgeTypes.get(index).isPrimitive())) {
+ return false;
+ }
+ }
+ TypeDescription returnType = getReturnType().asErasure(), bridgeReturnType = typeToken.getReturnType();
+ return returnType.equals(bridgeReturnType) || (!returnType.isPrimitive() && !bridgeReturnType.isPrimitive());
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this || (other instanceof MethodDescription
+ && getInternalName().equals(((MethodDescription) other).getInternalName())
+ && getDeclaringType().equals(((MethodDescription) other).getDeclaringType())
+ && getReturnType().asErasure().equals(((MethodDescription) other).getReturnType().asErasure())
+ && getParameters().asTypeList().asErasures().equals(((MethodDescription) other).getParameters().asTypeList().asErasures()));
+ }
+
+ @Override
+ public int hashCode() {
+ int hashCode = getDeclaringType().hashCode();
+ hashCode = 31 * hashCode + getInternalName().hashCode();
+ hashCode = 31 * hashCode + getReturnType().asErasure().hashCode();
+ return 31 * hashCode + getParameters().asTypeList().asErasures().hashCode();
+ }
+
+ @Override
+ public String toGenericString() {
+ StringBuilder stringBuilder = new StringBuilder();
+ int modifiers = getModifiers() & SOURCE_MODIFIERS;
+ if (modifiers != EMPTY_MASK) {
+ stringBuilder.append(Modifier.toString(modifiers)).append(" ");
+ }
+ if (isMethod()) {
+ stringBuilder.append(getReturnType().getActualName()).append(" ");
+ stringBuilder.append(getDeclaringType().asErasure().getActualName()).append(".");
+ }
+ stringBuilder.append(getName()).append("(");
+ boolean first = true;
+ for (TypeDescription.Generic typeDescription : getParameters().asTypeList()) {
+ if (!first) {
+ stringBuilder.append(",");
+ } else {
+ first = false;
+ }
+ stringBuilder.append(typeDescription.getActualName());
+ }
+ stringBuilder.append(")");
+ TypeList.Generic exceptionTypes = getExceptionTypes();
+ if (!exceptionTypes.isEmpty()) {
+ stringBuilder.append(" throws ");
+ first = true;
+ for (TypeDescription.Generic typeDescription : exceptionTypes) {
+ if (!first) {
+ stringBuilder.append(",");
+ } else {
+ first = false;
+ }
+ stringBuilder.append(typeDescription.getActualName());
+ }
+ }
+ return stringBuilder.toString();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder();
+ int modifiers = getModifiers() & SOURCE_MODIFIERS;
+ if (modifiers != EMPTY_MASK) {
+ stringBuilder.append(Modifier.toString(modifiers)).append(" ");
+ }
+ if (isMethod()) {
+ stringBuilder.append(getReturnType().asErasure().getActualName()).append(" ");
+ stringBuilder.append(getDeclaringType().asErasure().getActualName()).append(".");
+ }
+ stringBuilder.append(getName()).append("(");
+ boolean first = true;
+ for (TypeDescription typeDescription : getParameters().asTypeList().asErasures()) {
+ if (!first) {
+ stringBuilder.append(",");
+ } else {
+ first = false;
+ }
+ stringBuilder.append(typeDescription.getActualName());
+ }
+ stringBuilder.append(")");
+ TypeList exceptionTypes = getExceptionTypes().asErasures();
+ if (!exceptionTypes.isEmpty()) {
+ stringBuilder.append(" throws ");
+ first = true;
+ for (TypeDescription typeDescription : exceptionTypes) {
+ if (!first) {
+ stringBuilder.append(",");
+ } else {
+ first = false;
+ }
+ stringBuilder.append(typeDescription.getActualName());
+ }
+ }
+ return stringBuilder.toString();
+ }
+ }
+
+ /**
+ * An implementation of a method description for a loaded constructor.
+ */
+ class ForLoadedConstructor extends InDefinedShape.AbstractBase {
+
+ /**
+ * The loaded constructor that is represented by this instance.
+ */
+ private final Constructor<?> constructor;
+
+ /**
+ * Creates a new immutable method description for a loaded constructor.
+ *
+ * @param constructor The loaded constructor to be represented by this method description.
+ */
+ public ForLoadedConstructor(Constructor<?> constructor) {
+ this.constructor = constructor;
+ }
+
+ @Override
+ public TypeDescription getDeclaringType() {
+ return new TypeDescription.ForLoadedType(constructor.getDeclaringClass());
+ }
+
+ @Override
+ public TypeDescription.Generic getReturnType() {
+ return TypeDescription.Generic.VOID;
+ }
+
+ @Override
+ public ParameterList<ParameterDescription.InDefinedShape> getParameters() {
+ return ParameterList.ForLoadedExecutable.of(constructor);
+ }
+
+ @Override
+ public TypeList.Generic getExceptionTypes() {
+ return new TypeList.Generic.OfConstructorExceptionTypes(constructor);
+ }
+
+ @Override
+ public boolean isConstructor() {
+ return true;
+ }
+
+ @Override
+ public boolean isTypeInitializer() {
+ return false;
+ }
+
+ @Override
+ public boolean represents(Method method) {
+ return false;
+ }
+
+ @Override
+ public boolean represents(Constructor<?> constructor) {
+ return this.constructor.equals(constructor) || equals(new ForLoadedConstructor(constructor));
+ }
+
+ @Override
+ public String getName() {
+ return constructor.getName();
+ }
+
+ @Override
+ public int getModifiers() {
+ return constructor.getModifiers();
+ }
+
+ @Override
+ public boolean isSynthetic() {
+ return constructor.isSynthetic();
+ }
+
+ @Override
+ public String getInternalName() {
+ return CONSTRUCTOR_INTERNAL_NAME;
+ }
+
+ @Override
+ public String getDescriptor() {
+ return Type.getConstructorDescriptor(constructor);
+ }
+
+ @Override
+ public AnnotationValue<?, ?> getDefaultValue() {
+ return AnnotationValue.UNDEFINED;
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return new AnnotationList.ForLoadedAnnotations(constructor.getDeclaredAnnotations());
+ }
+
+ @Override
+ public TypeList.Generic getTypeVariables() {
+ return TypeList.Generic.ForLoadedTypes.OfTypeVariables.of(constructor);
+ }
+
+ @Override
+ public TypeDescription.Generic getReceiverType() {
+ TypeDescription.Generic receiverType = TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveReceiverType(constructor);
+ return receiverType == null
+ ? super.getReceiverType()
+ : receiverType;
+ }
+ }
+
+ /**
+ * An implementation of a method description for a loaded method.
+ */
+ class ForLoadedMethod extends InDefinedShape.AbstractBase {
+
+ /**
+ * The loaded method that is represented by this instance.
+ */
+ private final Method method;
+
+ /**
+ * Creates a new immutable method description for a loaded method.
+ *
+ * @param method The loaded method to be represented by this method description.
+ */
+ public ForLoadedMethod(Method method) {
+ this.method = method;
+ }
+
+ @Override
+ public TypeDescription getDeclaringType() {
+ return new TypeDescription.ForLoadedType(method.getDeclaringClass());
+ }
+
+ @Override
+ public TypeDescription.Generic getReturnType() {
+ return new TypeDescription.Generic.LazyProjection.ForLoadedReturnType(method);
+ }
+
+ @Override
+ public ParameterList<ParameterDescription.InDefinedShape> getParameters() {
+ return ParameterList.ForLoadedExecutable.of(method);
+ }
+
+ @Override
+ public TypeList.Generic getExceptionTypes() {
+ return new TypeList.Generic.OfMethodExceptionTypes(method);
+ }
+
+ @Override
+ public boolean isConstructor() {
+ return false;
+ }
+
+ @Override
+ public boolean isTypeInitializer() {
+ return false;
+ }
+
+ @Override
+ public boolean isBridge() {
+ return method.isBridge();
+ }
+
+ @Override
+ public boolean represents(Method method) {
+ return this.method.equals(method) || equals(new ForLoadedMethod(method));
+ }
+
+ @Override
+ public boolean represents(Constructor<?> constructor) {
+ return false;
+ }
+
+ @Override
+ public String getName() {
+ return method.getName();
+ }
+
+ @Override
+ public int getModifiers() {
+ return method.getModifiers();
+ }
+
+ @Override
+ public boolean isSynthetic() {
+ return method.isSynthetic();
+ }
+
+ @Override
+ public String getInternalName() {
+ return method.getName();
+ }
+
+ @Override
+ public String getDescriptor() {
+ return Type.getMethodDescriptor(method);
+ }
+
+ /**
+ * Returns the loaded method that is represented by this method description.
+ *
+ * @return The loaded method that is represented by this method description.
+ */
+ public Method getLoadedMethod() {
+ return method;
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return new AnnotationList.ForLoadedAnnotations(method.getDeclaredAnnotations());
+ }
+
+ @Override
+ public AnnotationValue<?, ?> getDefaultValue() {
+ Object value = method.getDefaultValue();
+ return value == null
+ ? AnnotationValue.UNDEFINED
+ : AnnotationDescription.ForLoadedAnnotation.asValue(value, method.getReturnType());
+ }
+
+ @Override
+ public TypeList.Generic getTypeVariables() {
+ return TypeList.Generic.ForLoadedTypes.OfTypeVariables.of(method);
+ }
+
+ @Override
+ public TypeDescription.Generic getReceiverType() {
+ TypeDescription.Generic receiverType = TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveReceiverType(method);
+ return receiverType == null
+ ? super.getReceiverType()
+ : receiverType;
+ }
+ }
+
+ /**
+ * A latent method description describes a method that is not attached to a declaring
+ * {@link TypeDescription}.
+ */
+ class Latent extends InDefinedShape.AbstractBase {
+
+ /**
+ * The type that is declaring this method.
+ */
+ private final TypeDescription declaringType;
+
+ /**
+ * The internal name of this method.
+ */
+ private final String internalName;
+
+ /**
+ * The modifiers of this method.
+ */
+ private final int modifiers;
+
+ /**
+ * A tokenized list representing the method's type variables.
+ */
+ private final List<? extends TypeVariableToken> typeVariables;
+
+ /**
+ * The return type of this method.
+ */
+ private final TypeDescription.Generic returnType;
+
+ /**
+ * The parameter tokens describing this method.
+ */
+ private final List<? extends ParameterDescription.Token> parameterTokens;
+
+ /**
+ * This method's exception types.
+ */
+ private final List<? extends TypeDescription.Generic> exceptionTypes;
+
+ /**
+ * The annotations of this method.
+ */
+ private final List<? extends AnnotationDescription> declaredAnnotations;
+
+ /**
+ * The default value of this method or {@code null} if no default annotation value is defined.
+ */
+ private final AnnotationValue<?, ?> defaultValue;
+
+ /**
+ * The receiver type of this method or {@code null} if the receiver type is defined implicitly.
+ */
+ private final TypeDescription.Generic receiverType;
+
+ /**
+ * Creates a new latent method description. All provided types are attached to this instance before they are returned.
+ *
+ * @param declaringType The declaring type of the method.
+ * @param token A token representing the method's shape.
+ */
+ public Latent(TypeDescription declaringType, MethodDescription.Token token) {
+ this(declaringType,
+ token.getName(),
+ token.getModifiers(),
+ token.getTypeVariableTokens(),
+ token.getReturnType(),
+ token.getParameterTokens(),
+ token.getExceptionTypes(),
+ token.getAnnotations(),
+ token.getDefaultValue(),
+ token.getReceiverType());
+ }
+
+ /**
+ * Creates a new latent method description. All provided types are attached to this instance before they are returned.
+ *
+ * @param declaringType The type that is declaring this method.
+ * @param internalName The internal name of this method.
+ * @param modifiers The modifiers of this method.
+ * @param typeVariables The type variables of the described method.
+ * @param returnType The return type of this method.
+ * @param parameterTokens The parameter tokens describing this method.
+ * @param exceptionTypes This method's exception types.
+ * @param declaredAnnotations The annotations of this method.
+ * @param defaultValue The default value of this method or {@code null} if no default annotation value is defined.
+ * @param receiverType The receiver type of this method or {@code null} if the receiver type is defined implicitly.
+ */
+ public Latent(TypeDescription declaringType,
+ String internalName,
+ int modifiers,
+ List<? extends TypeVariableToken> typeVariables,
+ TypeDescription.Generic returnType,
+ List<? extends ParameterDescription.Token> parameterTokens,
+ List<? extends TypeDescription.Generic> exceptionTypes,
+ List<? extends AnnotationDescription> declaredAnnotations,
+ AnnotationValue<?, ?> defaultValue,
+ TypeDescription.Generic receiverType) {
+ this.declaringType = declaringType;
+ this.internalName = internalName;
+ this.modifiers = modifiers;
+ this.typeVariables = typeVariables;
+ this.returnType = returnType;
+ this.parameterTokens = parameterTokens;
+ this.exceptionTypes = exceptionTypes;
+ this.declaredAnnotations = declaredAnnotations;
+ this.defaultValue = defaultValue;
+ this.receiverType = receiverType;
+ }
+
+ @Override
+ public TypeList.Generic getTypeVariables() {
+ return TypeList.Generic.ForDetachedTypes.attachVariables(this, typeVariables);
+ }
+
+ @Override
+ public TypeDescription.Generic getReturnType() {
+ return returnType.accept(TypeDescription.Generic.Visitor.Substitutor.ForAttachment.of(this));
+ }
+
+ @Override
+ public ParameterList<ParameterDescription.InDefinedShape> getParameters() {
+ return new ParameterList.ForTokens(this, parameterTokens);
+ }
+
+ @Override
+ public TypeList.Generic getExceptionTypes() {
+ return TypeList.Generic.ForDetachedTypes.attach(this, exceptionTypes);
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return new AnnotationList.Explicit(declaredAnnotations);
+ }
+
+ @Override
+ public String getInternalName() {
+ return internalName;
+ }
+
+ @Override
+ public TypeDescription getDeclaringType() {
+ return declaringType;
+ }
+
+ @Override
+ public int getModifiers() {
+ return modifiers;
+ }
+
+ @Override
+ public AnnotationValue<?, ?> getDefaultValue() {
+ return defaultValue;
+ }
+
+ @Override
+ public TypeDescription.Generic getReceiverType() {
+ return receiverType == null
+ ? super.getReceiverType()
+ : receiverType.accept(TypeDescription.Generic.Visitor.Substitutor.ForAttachment.of(this));
+ }
+
+ /**
+ * A method description that represents the type initializer.
+ */
+ public static class TypeInitializer extends InDefinedShape.AbstractBase {
+
+ /**
+ * The type for which the type initializer should be represented.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new method description representing the type initializer.
+ *
+ * @param typeDescription The type for which the type initializer should be represented.
+ */
+ public TypeInitializer(TypeDescription typeDescription) {
+ this.typeDescription = typeDescription;
+ }
+
+ @Override
+ public TypeDescription.Generic getReturnType() {
+ return TypeDescription.Generic.VOID;
+ }
+
+ @Override
+ public ParameterList<ParameterDescription.InDefinedShape> getParameters() {
+ return new ParameterList.Empty<ParameterDescription.InDefinedShape>();
+ }
+
+ @Override
+ public TypeList.Generic getExceptionTypes() {
+ return new TypeList.Generic.Empty();
+ }
+
+ @Override
+ public AnnotationValue<?, ?> getDefaultValue() {
+ return AnnotationValue.UNDEFINED;
+ }
+
+ @Override
+ public TypeList.Generic getTypeVariables() {
+ return new TypeList.Generic.Empty();
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return new AnnotationList.Empty();
+ }
+
+ @Override
+ public TypeDescription getDeclaringType() {
+ return typeDescription;
+ }
+
+ @Override
+ public int getModifiers() {
+ return TYPE_INITIALIZER_MODIFIER;
+ }
+
+ @Override
+ public String getInternalName() {
+ return MethodDescription.TYPE_INITIALIZER_INTERNAL_NAME;
+ }
+ }
+ }
+
+ /**
+ * A method description that represents a given method but with substituted method types.
+ */
+ class TypeSubstituting extends AbstractBase implements InGenericShape {
+
+ /**
+ * The type that declares this type-substituted method.
+ */
+ private final TypeDescription.Generic declaringType;
+
+ /**
+ * The represented method description.
+ */
+ private final MethodDescription methodDescription;
+
+ /**
+ * A visitor that is applied to the method type.
+ */
+ private final TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor;
+
+ /**
+ * Creates a method description with substituted method types.
+ *
+ * @param declaringType The type that is declaring the substituted method.
+ * @param methodDescription The represented method description.
+ * @param visitor A visitor that is applied to the method type.
+ */
+ public TypeSubstituting(TypeDescription.Generic declaringType,
+ MethodDescription methodDescription,
+ TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor) {
+ this.declaringType = declaringType;
+ this.methodDescription = methodDescription;
+ this.visitor = visitor;
+ }
+
+ @Override
+ public TypeDescription.Generic getReturnType() {
+ return methodDescription.getReturnType().accept(visitor);
+ }
+
+ @Override
+ public TypeList.Generic getTypeVariables() {
+ return methodDescription.getTypeVariables().accept(visitor).filter(ElementMatchers.ofSort(TypeDefinition.Sort.VARIABLE));
+ }
+
+ @Override
+ public ParameterList<ParameterDescription.InGenericShape> getParameters() {
+ return new ParameterList.TypeSubstituting(this, methodDescription.getParameters(), visitor);
+ }
+
+ @Override
+ public TypeList.Generic getExceptionTypes() {
+ return new TypeList.Generic.ForDetachedTypes(methodDescription.getExceptionTypes(), visitor);
+ }
+
+ @Override
+ public AnnotationValue<?, ?> getDefaultValue() {
+ return methodDescription.getDefaultValue();
+ }
+
+ @Override
+ public TypeDescription.Generic getReceiverType() {
+ TypeDescription.Generic receiverType = methodDescription.getReceiverType();
+ return receiverType == null
+ ? TypeDescription.Generic.UNDEFINED
+ : receiverType.accept(visitor);
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return methodDescription.getDeclaredAnnotations();
+ }
+
+ @Override
+ public TypeDescription.Generic getDeclaringType() {
+ return declaringType;
+ }
+
+ @Override
+ public int getModifiers() {
+ return methodDescription.getModifiers();
+ }
+
+ @Override
+ public String getInternalName() {
+ return methodDescription.getInternalName();
+ }
+
+ @Override
+ public InDefinedShape asDefined() {
+ return methodDescription.asDefined();
+ }
+ }
+
+ /**
+ * A token representing a method's properties detached from a type.
+ */
+ class Token implements ByteCodeElement.Token<Token> {
+
+ /**
+ * The internal name of the represented method.
+ */
+ private final String name;
+
+ /**
+ * The modifiers of the represented method.
+ */
+ private final int modifiers;
+
+ /**
+ * A list of tokens representing the method's type variables.
+ */
+ private final List<? extends TypeVariableToken> typeVariableTokens;
+
+ /**
+ * The return type of the represented method.
+ */
+ private final TypeDescription.Generic returnType;
+
+ /**
+ * The parameter tokens of the represented method.
+ */
+ private final List<? extends ParameterDescription.Token> parameterTokens;
+
+ /**
+ * The exception types of the represented method.
+ */
+ private final List<? extends TypeDescription.Generic> exceptionTypes;
+
+ /**
+ * The annotations of the represented method.
+ */
+ private final List<? extends AnnotationDescription> annotations;
+
+ /**
+ * The default value of the represented method or {@code null} if no such value exists.
+ */
+ private final AnnotationValue<?, ?> defaultValue;
+
+ /**
+ * The receiver type of the represented method or {@code null} if the receiver type is implicit.
+ */
+ private final TypeDescription.Generic receiverType;
+
+ /**
+ * Creates a new method token representing a constructor without any parameters, exception types, type variables or annotations.
+ * All types must be represented in an detached format.
+ *
+ * @param modifiers The constructor's modifiers.
+ */
+ public Token(int modifiers) {
+ this(MethodDescription.CONSTRUCTOR_INTERNAL_NAME, modifiers, TypeDescription.Generic.VOID);
+ }
+
+ /**
+ * Creates a new method token representing a method without any parameters, exception types, type variables or annotations.
+ * All types must be represented in an detached format.
+ *
+ * @param name The name of the method.
+ * @param modifiers The modifiers of the method.
+ * @param returnType The return type of the method.
+ */
+ public Token(String name, int modifiers, TypeDescription.Generic returnType) {
+ this(name, modifiers, returnType, Collections.<TypeDescription.Generic>emptyList());
+ }
+
+ /**
+ * Creates a new method token with simple values. All types must be represented in an detached format.
+ *
+ * @param name The internal name of the represented method.
+ * @param modifiers The modifiers of the represented method.
+ * @param returnType The return type of the represented method.
+ * @param parameterTypes The parameter types of this method.
+ */
+ public Token(String name, int modifiers, TypeDescription.Generic returnType, List<? extends TypeDescription.Generic> parameterTypes) {
+ this(name,
+ modifiers,
+ Collections.<TypeVariableToken>emptyList(),
+ returnType,
+ new ParameterDescription.Token.TypeList(parameterTypes),
+ Collections.<TypeDescription.Generic>emptyList(),
+ Collections.<AnnotationDescription>emptyList(),
+ AnnotationValue.UNDEFINED,
+ TypeDescription.Generic.UNDEFINED);
+ }
+
+ /**
+ * Creates a new token for a method description. All types must be represented in an detached format.
+ *
+ * @param name The internal name of the represented method.
+ * @param modifiers The modifiers of the represented method.
+ * @param typeVariableTokens The type variables of the the represented method.
+ * @param returnType The return type of the represented method.
+ * @param parameterTokens The parameter tokens of the represented method.
+ * @param exceptionTypes The exception types of the represented method.
+ * @param annotations The annotations of the represented method.
+ * @param defaultValue The default value of the represented method or {@code null} if no such value exists.
+ * @param receiverType The receiver type of the represented method or {@code null} if the receiver type is implicit.
+ */
+ public Token(String name,
+ int modifiers,
+ List<? extends TypeVariableToken> typeVariableTokens,
+ TypeDescription.Generic returnType,
+ List<? extends ParameterDescription.Token> parameterTokens,
+ List<? extends TypeDescription.Generic> exceptionTypes,
+ List<? extends AnnotationDescription> annotations,
+ AnnotationValue<?, ?> defaultValue,
+ TypeDescription.Generic receiverType) {
+ this.name = name;
+ this.modifiers = modifiers;
+ this.typeVariableTokens = typeVariableTokens;
+ this.returnType = returnType;
+ this.parameterTokens = parameterTokens;
+ this.exceptionTypes = exceptionTypes;
+ this.annotations = annotations;
+ this.defaultValue = defaultValue;
+ this.receiverType = receiverType;
+ }
+
+ /**
+ * Returns the internal name of the represented method.
+ *
+ * @return The internal name of the represented method.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the modifiers of the represented method.
+ *
+ * @return The modifiers of the represented method.
+ */
+ public int getModifiers() {
+ return modifiers;
+ }
+
+ /**
+ * Returns the type variables of this method token.
+ *
+ * @return A a list of tokens representing the method's type variables.
+ */
+ public TokenList<TypeVariableToken> getTypeVariableTokens() {
+ return new TokenList<TypeVariableToken>(typeVariableTokens);
+ }
+
+ /**
+ * Returns the return type of the represented method.
+ *
+ * @return The return type of the represented method.
+ */
+ public TypeDescription.Generic getReturnType() {
+ return returnType;
+ }
+
+ /**
+ * Returns the parameter tokens of the represented method.
+ *
+ * @return The parameter tokens of the represented method.
+ */
+ public TokenList<ParameterDescription.Token> getParameterTokens() {
+ return new TokenList<ParameterDescription.Token>(parameterTokens);
+ }
+
+ /**
+ * Returns the exception types of the represented method.
+ *
+ * @return The exception types of the represented method.
+ */
+ public TypeList.Generic getExceptionTypes() {
+ return new TypeList.Generic.Explicit(exceptionTypes);
+ }
+
+ /**
+ * Returns the annotations of the represented method.
+ *
+ * @return The annotations of the represented method.
+ */
+ public AnnotationList getAnnotations() {
+ return new AnnotationList.Explicit(annotations);
+ }
+
+ /**
+ * Returns the default value of the represented method.
+ *
+ * @return The default value of the represented method or {@code null} if no such value exists.
+ */
+ public AnnotationValue<?, ?> getDefaultValue() {
+ return defaultValue;
+ }
+
+ /**
+ * Returns the receiver type of this token or {@code null} if the receiver type is implicit.
+ *
+ * @return The receiver type of this token or {@code null} if the receiver type is implicit.
+ */
+ public TypeDescription.Generic getReceiverType() {
+ return receiverType;
+ }
+
+ @Override
+ public Token accept(TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor) {
+ return new Token(name,
+ modifiers,
+ getTypeVariableTokens().accept(visitor),
+ returnType.accept(visitor),
+ getParameterTokens().accept(visitor),
+ getExceptionTypes().accept(visitor),
+ annotations,
+ defaultValue,
+ receiverType == null
+ ? TypeDescription.Generic.UNDEFINED
+ : receiverType.accept(visitor));
+ }
+
+ /**
+ * Creates a signature token that represents the method that is represented by this token.
+ *
+ * @param declaringType The declaring type of the method that this token represents.
+ * @return A signature token representing this token.
+ */
+ public SignatureToken asSignatureToken(TypeDescription declaringType) {
+ TypeDescription.Generic.Visitor<TypeDescription> visitor = new TypeDescription.Generic.Visitor.Reducing(declaringType, typeVariableTokens);
+ List<TypeDescription> parameters = new ArrayList<TypeDescription>(parameterTokens.size());
+ for (ParameterDescription.Token parameter : parameterTokens) {
+ parameters.add(parameter.getType().accept(visitor));
+ }
+ return new SignatureToken(name, returnType.accept(visitor), parameters);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (other == null || getClass() != other.getClass()) return false;
+ Token token = (Token) other;
+ return modifiers == token.modifiers
+ && name.equals(token.name)
+ && typeVariableTokens.equals(token.typeVariableTokens)
+ && returnType.equals(token.returnType)
+ && parameterTokens.equals(token.parameterTokens)
+ && exceptionTypes.equals(token.exceptionTypes)
+ && annotations.equals(token.annotations)
+ && (defaultValue != null ? defaultValue.equals(token.defaultValue) : token.defaultValue == null)
+ && (receiverType != null ? receiverType.equals(token.receiverType) : token.receiverType == null);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = name.hashCode();
+ result = 31 * result + modifiers;
+ result = 31 * result + typeVariableTokens.hashCode();
+ result = 31 * result + returnType.hashCode();
+ result = 31 * result + parameterTokens.hashCode();
+ result = 31 * result + exceptionTypes.hashCode();
+ result = 31 * result + annotations.hashCode();
+ result = 31 * result + (defaultValue != null ? defaultValue.hashCode() : 0);
+ result = 31 * result + (receiverType != null ? receiverType.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "MethodDescription.Token{" +
+ "name='" + name + '\'' +
+ ", modifiers=" + modifiers +
+ ", typeVariableTokens=" + typeVariableTokens +
+ ", returnType=" + returnType +
+ ", parameterTokens=" + parameterTokens +
+ ", exceptionTypes=" + exceptionTypes +
+ ", annotations=" + annotations +
+ ", defaultValue=" + defaultValue +
+ ", receiverType=" + receiverType +
+ '}';
+ }
+ }
+
+ /**
+ * A token representing a method's name and raw return and parameter types.
+ */
+ class SignatureToken {
+
+ /**
+ * The internal name of the represented method.
+ */
+ private final String name;
+
+ /**
+ * The represented method's raw return type.
+ */
+ private final TypeDescription returnType;
+
+ /**
+ * The represented method's raw parameter types.
+ */
+ private final List<? extends TypeDescription> parameterTypes;
+
+ /**
+ * Creates a new type token.
+ *
+ * @param name The internal name of the represented method.
+ * @param returnType The represented method's raw return type.
+ * @param parameterTypes The represented method's raw parameter types.
+ */
+ public SignatureToken(String name, TypeDescription returnType, List<? extends TypeDescription> parameterTypes) {
+ this.name = name;
+ this.returnType = returnType;
+ this.parameterTypes = parameterTypes;
+ }
+
+ /**
+ * Returns the internal name of the represented method.
+ *
+ * @return The internal name of the represented method.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns this token's return type.
+ *
+ * @return This token's return type.
+ */
+ public TypeDescription getReturnType() {
+ return returnType;
+ }
+
+ /**
+ * Returns this token's parameter types.
+ *
+ * @return This token's parameter types.
+ */
+ public List<TypeDescription> getParameterTypes() {
+ return new ArrayList<TypeDescription>(parameterTypes);
+ }
+
+ /**
+ * Returns this signature token as a type token.
+ *
+ * @return This signature token as a type token.
+ */
+ public TypeToken asTypeToken() {
+ return new TypeToken(returnType, parameterTypes);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (!(other instanceof SignatureToken)) return false;
+ SignatureToken signatureToken = (SignatureToken) other;
+ return name.equals(signatureToken.name)
+ && returnType.equals(signatureToken.returnType)
+ && parameterTypes.equals(signatureToken.parameterTypes);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = name.hashCode();
+ result = 31 * result + returnType.hashCode();
+ result = 31 * result + parameterTypes.hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder().append(returnType).append(' ').append(name).append('(');
+ boolean first = true;
+ for (TypeDescription parameterType : parameterTypes) {
+ if (first) {
+ first = false;
+ } else {
+ stringBuilder.append(",");
+ }
+ stringBuilder.append(parameterType);
+ }
+ return stringBuilder.append(')').toString();
+ }
+ }
+
+ /**
+ * A token representing a method's erased return and parameter types.
+ */
+ class TypeToken {
+
+ /**
+ * The represented method's raw return type.
+ */
+ private final TypeDescription returnType;
+
+ /**
+ * The represented method's raw parameter types.
+ */
+ private final List<? extends TypeDescription> parameterTypes;
+
+ /**
+ * Creates a new type token.
+ *
+ * @param returnType The represented method's raw return type.
+ * @param parameterTypes The represented method's raw parameter types.
+ */
+ public TypeToken(TypeDescription returnType, List<? extends TypeDescription> parameterTypes) {
+ this.returnType = returnType;
+ this.parameterTypes = parameterTypes;
+ }
+
+ /**
+ * Returns this token's return type.
+ *
+ * @return This token's return type.
+ */
+ public TypeDescription getReturnType() {
+ return returnType;
+ }
+
+ /**
+ * Returns this token's parameter types.
+ *
+ * @return This token's parameter types.
+ */
+ public List<TypeDescription> getParameterTypes() {
+ return new ArrayList<TypeDescription>(parameterTypes);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (!(other instanceof TypeToken)) return false;
+ TypeToken typeToken = (TypeToken) other;
+ return returnType.equals(typeToken.returnType) && parameterTypes.equals(typeToken.parameterTypes);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = returnType.hashCode();
+ result = 31 * result + parameterTypes.hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder().append('(');
+ for (TypeDescription parameterType : parameterTypes) {
+ stringBuilder.append(parameterType.getDescriptor());
+ }
+ return stringBuilder.append(')').append(returnType.getDescriptor()).toString();
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/method/MethodList.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/method/MethodList.java
new file mode 100644
index 0000000..e15092b
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/method/MethodList.java
@@ -0,0 +1,283 @@
+package net.bytebuddy.description.method;
+
+import net.bytebuddy.description.ByteCodeElement;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.matcher.FilterableList;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Implementations represent a list of method descriptions.
+ *
+ * @param <T> The type of method descriptions represented by this list.
+ */
+public interface MethodList<T extends MethodDescription> extends FilterableList<T, MethodList<T>> {
+
+ /**
+ * Transforms the list of method descriptions into a list of detached tokens. All types that are matched by the provided
+ * target type matcher are substituted by {@link net.bytebuddy.dynamic.TargetType}.
+ *
+ * @param matcher A matcher that indicates type substitution.
+ * @return The transformed token list.
+ */
+ ByteCodeElement.Token.TokenList<MethodDescription.Token> asTokenList(ElementMatcher<? super TypeDescription> matcher);
+
+ /**
+ * Returns this list of these method descriptions resolved to their defined shape.
+ *
+ * @return A list of methods in their defined shape.
+ */
+ MethodList<MethodDescription.InDefinedShape> asDefined();
+
+ /**
+ * A base implementation of a {@link MethodList}.
+ *
+ * @param <S> The type of method descriptions represented by this list.
+ */
+ abstract class AbstractBase<S extends MethodDescription> extends FilterableList.AbstractBase<S, MethodList<S>> implements MethodList<S> {
+
+ @Override
+ protected MethodList<S> wrap(List<S> values) {
+ return new Explicit<S>(values);
+ }
+
+ @Override
+ public ByteCodeElement.Token.TokenList<MethodDescription.Token> asTokenList(ElementMatcher<? super TypeDescription> matcher) {
+ List<MethodDescription.Token> tokens = new ArrayList<MethodDescription.Token>(size());
+ for (MethodDescription methodDescription : this) {
+ tokens.add(methodDescription.asToken(matcher));
+ }
+ return new ByteCodeElement.Token.TokenList<MethodDescription.Token>(tokens);
+ }
+
+ @Override
+ public MethodList<MethodDescription.InDefinedShape> asDefined() {
+ List<MethodDescription.InDefinedShape> declaredForms = new ArrayList<MethodDescription.InDefinedShape>(size());
+ for (MethodDescription methodDescription : this) {
+ declaredForms.add(methodDescription.asDefined());
+ }
+ return new Explicit<MethodDescription.InDefinedShape>(declaredForms);
+ }
+ }
+
+ /**
+ * A method list implementation that returns all loaded byte code methods (methods and constructors) that
+ * are declared for a given type.
+ */
+ class ForLoadedMethods extends AbstractBase<MethodDescription.InDefinedShape> {
+
+ /**
+ * The loaded methods that are represented by this method list.
+ */
+ private final List<? extends Method> methods;
+
+ /**
+ * The loaded constructors that are represented by this method list.
+ */
+ private final List<? extends Constructor<?>> constructors;
+
+ /**
+ * Creates a new list for a loaded type. Method descriptions are created on demand.
+ *
+ * @param type The type to be represented by this method list.
+ */
+ public ForLoadedMethods(Class<?> type) {
+ this(type.getDeclaredConstructors(), type.getDeclaredMethods());
+ }
+
+ /**
+ * Creates a method list that represents the given constructors and methods in their given order. The
+ * constructors are assigned the indices before the methods.
+ *
+ * @param constructor The constructors to be represented by the method list.
+ * @param method The methods to be represented by the method list.
+ */
+ public ForLoadedMethods(Constructor<?>[] constructor, Method[] method) {
+ this(Arrays.asList(constructor), Arrays.asList(method));
+ }
+
+ /**
+ * Creates a method list that represents the given constructors and methods in their given order. The
+ * constructors are assigned the indices before the methods.
+ *
+ * @param constructors The constructors to be represented by the method list.
+ * @param methods The methods to be represented by the method list.
+ */
+ public ForLoadedMethods(List<? extends Constructor<?>> constructors, List<? extends Method> methods) {
+ this.constructors = constructors;
+ this.methods = methods;
+ }
+
+ @Override
+ public MethodDescription.InDefinedShape get(int index) {
+ return index < constructors.size()
+ ? new MethodDescription.ForLoadedConstructor(constructors.get(index))
+ : new MethodDescription.ForLoadedMethod(methods.get(index - constructors.size()));
+
+ }
+
+ @Override
+ public int size() {
+ return constructors.size() + methods.size();
+ }
+ }
+
+ /**
+ * A method list that is a wrapper for a given list of method descriptions.
+ *
+ * @param <S> The type of method descriptions represented by this list.
+ */
+ class Explicit<S extends MethodDescription> extends AbstractBase<S> {
+
+ /**
+ * The list of methods that is represented by this method list.
+ */
+ private final List<? extends S> methodDescriptions;
+
+ /**
+ * Creates a new wrapper for a given list of methods.
+ *
+ * @param methodDescription The underlying list of methods used for this method list.
+ */
+ @SuppressWarnings("unchecked")
+ public Explicit(S... methodDescription) {
+ this(Arrays.asList(methodDescription));
+ }
+
+ /**
+ * Creates a new wrapper for a given list of methods.
+ *
+ * @param methodDescriptions The underlying list of methods used for this method list.
+ */
+ public Explicit(List<? extends S> methodDescriptions) {
+ this.methodDescriptions = methodDescriptions;
+ }
+
+ @Override
+ public S get(int index) {
+ return methodDescriptions.get(index);
+ }
+
+ @Override
+ public int size() {
+ return methodDescriptions.size();
+ }
+ }
+
+ /**
+ * A list of method descriptions for a list of detached tokens. For the returned method, each token is attached to its method representation.
+ */
+ class ForTokens extends AbstractBase<MethodDescription.InDefinedShape> {
+
+ /**
+ * The method's declaring type.
+ */
+ private final TypeDescription declaringType;
+
+ /**
+ * The list of method tokens to represent.
+ */
+ private final List<? extends MethodDescription.Token> tokens;
+
+ /**
+ * Creates a new list of method descriptions for a list of detached tokens.
+ *
+ * @param declaringType The method's declaring type.
+ * @param token The list of method tokens to represent.
+ */
+ public ForTokens(TypeDescription declaringType, MethodDescription.Token... token) {
+ this(declaringType, Arrays.asList(token));
+ }
+
+ /**
+ * Creates a new list of method descriptions for a list of detached tokens.
+ *
+ * @param declaringType The method's declaring type.
+ * @param tokens The list of method tokens to represent.
+ */
+ public ForTokens(TypeDescription declaringType, List<? extends MethodDescription.Token> tokens) {
+ this.declaringType = declaringType;
+ this.tokens = tokens;
+ }
+
+ @Override
+ public MethodDescription.InDefinedShape get(int index) {
+ return new MethodDescription.Latent(declaringType, tokens.get(index));
+ }
+
+ @Override
+ public int size() {
+ return tokens.size();
+ }
+ }
+
+ /**
+ * A list of method descriptions that yields {@link net.bytebuddy.description.method.MethodDescription.TypeSubstituting}.
+ */
+ class TypeSubstituting extends AbstractBase<MethodDescription.InGenericShape> {
+
+ /**
+ * The methods' declaring type.
+ */
+ protected final TypeDescription.Generic declaringType;
+
+ /**
+ * The list of method descriptions to represent.
+ */
+ protected final List<? extends MethodDescription> methodDescriptions;
+
+ /**
+ * The visitor to apply to each method description before returning it.
+ */
+ protected final TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor;
+
+ /**
+ * Creates a new type substituting method list.
+ *
+ * @param declaringType The methods' declaring type.
+ * @param methodDescriptions The list of method descriptions to represent.
+ * @param visitor The visitor to apply to each method description before returning it.
+ */
+ public TypeSubstituting(TypeDescription.Generic declaringType,
+ List<? extends MethodDescription> methodDescriptions,
+ TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor) {
+ this.declaringType = declaringType;
+ this.methodDescriptions = methodDescriptions;
+ this.visitor = visitor;
+ }
+
+ @Override
+ public MethodDescription.InGenericShape get(int index) {
+ return new MethodDescription.TypeSubstituting(declaringType, methodDescriptions.get(index), visitor);
+ }
+
+ @Override
+ public int size() {
+ return methodDescriptions.size();
+ }
+ }
+
+ /**
+ * An implementation of an empty method list.
+ *
+ * @param <S> The type of parameter descriptions represented by this list.
+ */
+ class Empty<S extends MethodDescription> extends FilterableList.Empty<S, MethodList<S>> implements MethodList<S> {
+
+ @Override
+ public ByteCodeElement.Token.TokenList<MethodDescription.Token> asTokenList(ElementMatcher<? super TypeDescription> matcher) {
+ return new ByteCodeElement.Token.TokenList<MethodDescription.Token>();
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public MethodList<MethodDescription.InDefinedShape> asDefined() {
+ return (MethodList<MethodDescription.InDefinedShape>) this;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/method/ParameterDescription.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/method/ParameterDescription.java
new file mode 100644
index 0000000..a2e62ec
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/method/ParameterDescription.java
@@ -0,0 +1,1068 @@
+package net.bytebuddy.description.method;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.ByteCodeElement;
+import net.bytebuddy.description.ModifierReviewable;
+import net.bytebuddy.description.NamedElement;
+import net.bytebuddy.description.annotation.AnnotationSource;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.matcher.ElementMatcher;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.*;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.AbstractList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Description of the parameter of a Java method or constructor.
+ */
+public interface ParameterDescription extends AnnotationSource,
+ NamedElement.WithRuntimeName,
+ NamedElement.WithOptionalName,
+ ModifierReviewable.ForParameterDescription,
+ ByteCodeElement.TypeDependant<ParameterDescription.InDefinedShape, ParameterDescription.Token> {
+
+ /**
+ * The prefix for names of an unnamed parameter.
+ */
+ String NAME_PREFIX = "arg";
+
+ /**
+ * Returns the type of this parameter.
+ *
+ * @return The type of this parameter.
+ */
+ TypeDescription.Generic getType();
+
+ /**
+ * Returns the method that declares this parameter.
+ *
+ * @return The method that declares this parameter.
+ */
+ MethodDescription getDeclaringMethod();
+
+ /**
+ * Returns this parameter's index.
+ *
+ * @return The index of this parameter.
+ */
+ int getIndex();
+
+ /**
+ * Checks if this parameter has an explicit modifier. A parameter without a modifier is simply treated as
+ * if it had a modifier of zero.
+ *
+ * @return {@code true} if this parameter defines explicit modifiers.
+ */
+ boolean hasModifiers();
+
+ /**
+ * Returns the offset to the parameter value within the local method variable.
+ *
+ * @return The offset of this parameter's value.
+ */
+ int getOffset();
+
+ /**
+ * Represents a parameter description in its generic shape, i.e. in the shape it is defined by a generic or raw type.
+ */
+ interface InGenericShape extends ParameterDescription {
+
+ @Override
+ MethodDescription.InGenericShape getDeclaringMethod();
+ }
+
+ /**
+ * Represents a parameter in its defined shape, i.e. in the form it is defined by a class without its type variables being resolved.
+ */
+ interface InDefinedShape extends ParameterDescription {
+
+ @Override
+ MethodDescription.InDefinedShape getDeclaringMethod();
+
+ /**
+ * An abstract base implementation of a parameter description in its defined shape.
+ */
+ abstract class AbstractBase extends ParameterDescription.AbstractBase implements InDefinedShape {
+
+ @Override
+ public InDefinedShape asDefined() {
+ return this;
+ }
+ }
+ }
+
+ /**
+ * A base implementation of a method parameter description.
+ */
+ abstract class AbstractBase extends ModifierReviewable.AbstractBase implements ParameterDescription {
+
+ @Override
+ public String getName() {
+ return NAME_PREFIX.concat(String.valueOf(getIndex()));
+ }
+
+ @Override
+ public String getInternalName() {
+ return getName();
+ }
+
+ @Override
+ public String getActualName() {
+ return isNamed()
+ ? getName()
+ : EMPTY_NAME;
+ }
+
+ @Override
+ public int getModifiers() {
+ return EMPTY_MASK;
+ }
+
+ @Override
+ public int getOffset() {
+ TypeList parameterType = getDeclaringMethod().getParameters().asTypeList().asErasures();
+ int offset = getDeclaringMethod().isStatic()
+ ? StackSize.ZERO.getSize()
+ : StackSize.SINGLE.getSize();
+ for (int i = 0; i < getIndex(); i++) {
+ offset += parameterType.get(i).getStackSize().getSize();
+ }
+ return offset;
+ }
+
+ @Override
+ public Token asToken(ElementMatcher<? super TypeDescription> matcher) {
+ return new Token(getType().accept(new TypeDescription.Generic.Visitor.Substitutor.ForDetachment(matcher)),
+ getDeclaredAnnotations(),
+ isNamed()
+ ? getName()
+ : Token.NO_NAME,
+ hasModifiers()
+ ? (Integer) getModifiers()
+ : Token.NO_MODIFIERS);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (!(other instanceof ParameterDescription)) return false;
+ ParameterDescription parameterDescription = (ParameterDescription) other;
+ return getDeclaringMethod().equals(parameterDescription.getDeclaringMethod())
+ && getIndex() == parameterDescription.getIndex();
+ }
+
+ @Override
+ public int hashCode() {
+ return getDeclaringMethod().hashCode() ^ getIndex();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder(Modifier.toString(getModifiers()));
+ if (getModifiers() != EMPTY_MASK) {
+ stringBuilder.append(' ');
+ }
+ stringBuilder.append(isVarArgs()
+ ? getType().asErasure().getName().replaceFirst("\\[\\]$", "...")
+ : getType().asErasure().getName());
+ return stringBuilder.append(' ').append(getName()).toString();
+ }
+ }
+
+ /**
+ * Description of a loaded parameter with support for the information exposed by {@code java.lang.reflect.Parameter}.
+ *
+ * @param <T> The type of the {@code java.lang.reflect.Executable} that this list represents.
+ */
+ abstract class ForLoadedParameter<T extends AccessibleObject> extends InDefinedShape.AbstractBase {
+
+ /**
+ * A dispatcher for reading properties from {@code java.lang.reflect.Executable} instances.
+ */
+ private static final Dispatcher DISPATCHER = AccessController.doPrivileged(Dispatcher.CreationAction.INSTANCE);
+
+ /**
+ * The {@code java.lang.reflect.Executable} for which the parameter types are described.
+ */
+ protected final T executable;
+
+ /**
+ * The parameter's index.
+ */
+ protected final int index;
+
+ /**
+ * Creates a new description for a loaded parameter.
+ *
+ * @param executable The {@code java.lang.reflect.Executable} for which the parameter types are described.
+ * @param index The parameter's index.
+ */
+ protected ForLoadedParameter(T executable, int index) {
+ this.executable = executable;
+ this.index = index;
+ }
+
+ @Override
+ public String getName() {
+ return DISPATCHER.getName(executable, index);
+ }
+
+ @Override
+ public int getIndex() {
+ return index;
+ }
+
+ @Override
+ public boolean isNamed() {
+ return DISPATCHER.isNamePresent(executable, index);
+ }
+
+ @Override
+ public int getModifiers() {
+ return DISPATCHER.getModifiers(executable, index);
+ }
+
+ @Override
+ public boolean hasModifiers() {
+ // Rational: If a parameter is not named despite the information being attached,
+ // it is synthetic, i.e. it has non-default modifiers.
+ return isNamed() || getModifiers() != EMPTY_MASK;
+ }
+
+ /**
+ * A dispatcher creating parameter descriptions based on the API that is available for the current JVM.
+ */
+ protected interface Dispatcher {
+
+ /**
+ * Returns the given parameter's modifiers.
+ *
+ * @param executable The executable to introspect.
+ * @param index The parameter's index.
+ * @return The parameter's modifiers.
+ */
+ int getModifiers(AccessibleObject executable, int index);
+
+ /**
+ * Returns {@code true} if the given parameter has an explicit name.
+ *
+ * @param executable The parameter to introspect.
+ * @param index The parameter's index.
+ * @return {@code true} if the given parameter has an explicit name.
+ */
+ boolean isNamePresent(AccessibleObject executable, int index);
+
+ /**
+ * Returns the given parameter's implicit or explicit name.
+ *
+ * @param executable The parameter to introspect.
+ * @param index The parameter's index.
+ * @return The parameter's name.
+ */
+ String getName(AccessibleObject executable, int index);
+
+ /**
+ * A creation action for a dispatcher.
+ */
+ enum CreationAction implements PrivilegedAction<Dispatcher> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback")
+ public Dispatcher run() {
+ try {
+ Class<?> executableType = Class.forName("java.lang.reflect.Executable");
+ Class<?> parameterType = Class.forName("java.lang.reflect.Parameter");
+ return new Dispatcher.ForJava8CapableVm(executableType.getMethod("getParameters"),
+ parameterType.getMethod("getName"),
+ parameterType.getMethod("isNamePresent"),
+ parameterType.getMethod("getModifiers"));
+ } catch (Exception ignored) {
+ return Dispatcher.ForLegacyVm.INSTANCE;
+ }
+ }
+ }
+
+ /**
+ * A dispatcher for VMs that support the {@code java.lang.reflect.Parameter} API for Java 8+.
+ */
+ @EqualsAndHashCode
+ class ForJava8CapableVm implements Dispatcher {
+
+ /**
+ * A reference to {@code java.lang.reflect.Executable#getParameters}.
+ */
+ private final Method getParameters;
+
+ /**
+ * A reference to {@code java.lang.reflect.Parameter#getName}.
+ */
+ private final Method getName;
+
+ /**
+ * A reference to {@code java.lang.reflect.Parameter#isNamePresent}.
+ */
+ private final Method isNamePresent;
+
+ /**
+ * A reference to {@code java.lang.reflect.Parameter#getModifiers}.
+ */
+ private final Method getModifiers;
+
+ /**
+ * Creates a new dispatcher for a modern VM.
+ *
+ * @param getParameters A reference to {@code java.lang.reflect.Executable#getTypeArguments}.
+ * @param getName A reference to {@code java.lang.reflect.Parameter#getName}.
+ * @param isNamePresent A reference to {@code java.lang.reflect.Parameter#isNamePresent}.
+ * @param getModifiers A reference to {@code java.lang.reflect.Parameter#getModifiers}.
+ */
+ protected ForJava8CapableVm(Method getParameters, Method getName, Method isNamePresent, Method getModifiers) {
+ this.getParameters = getParameters;
+ this.getName = getName;
+ this.isNamePresent = isNamePresent;
+ this.getModifiers = getModifiers;
+ }
+
+ @Override
+ public int getModifiers(AccessibleObject executable, int index) {
+ try {
+ return (Integer) getModifiers.invoke(getParameter(executable, index));
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.reflect.Parameter#getModifiers", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.reflect.Parameter#getModifiers", exception.getCause());
+ }
+ }
+
+ @Override
+ public boolean isNamePresent(AccessibleObject executable, int index) {
+ try {
+ return (Boolean) isNamePresent.invoke(getParameter(executable, index));
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.reflect.Parameter#isNamePresent", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.reflect.Parameter#isNamePresent", exception.getCause());
+ }
+ }
+
+ @Override
+ public String getName(AccessibleObject executable, int index) {
+ try {
+ return (String) getName.invoke(getParameter(executable, index));
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.reflect.Parameter#getName", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.reflect.Parameter#getName", exception.getCause());
+ }
+ }
+
+ /**
+ * Returns the {@code java.lang.reflect.Parameter} of an executable at a given index.
+ *
+ * @param executable The executable for which a parameter should be read.
+ * @param index The index of the parameter.
+ * @return The parameter for the given index.
+ */
+ private Object getParameter(AccessibleObject executable, int index) {
+ try {
+ return Array.get(getParameters.invoke(executable), index);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.reflect.Executable#getParameters", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.reflect.Executable#getParameters", exception.getCause());
+ }
+ }
+ }
+
+ /**
+ * A dispatcher for a legacy VM that does not know the {@code java.lang.reflect.Parameter} type that only throws
+ * exceptions on any property extraction.
+ */
+ enum ForLegacyVm implements Dispatcher {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public int getModifiers(AccessibleObject executable, int index) {
+ throw new IllegalStateException("Cannot dispatch method for java.lang.reflect.Parameter");
+ }
+
+ @Override
+ public boolean isNamePresent(AccessibleObject executable, int index) {
+ throw new IllegalStateException("Cannot dispatch method for java.lang.reflect.Parameter");
+ }
+
+ @Override
+ public String getName(AccessibleObject executable, int index) {
+ throw new IllegalStateException("Cannot dispatch method for java.lang.reflect.Parameter");
+ }
+ }
+ }
+
+ /**
+ * A description of a loaded {@link Method} parameter for a modern VM.
+ */
+ protected static class OfMethod extends ForLoadedParameter<Method> {
+
+ /**
+ * Creates a new description for a loaded method.
+ *
+ * @param method The method for which a parameter is represented.
+ * @param index The index of the parameter.
+ */
+ protected OfMethod(Method method, int index) {
+ super(method, index);
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST", justification = "The implicit field type casting is not understood by Findbugs")
+ public MethodDescription.InDefinedShape getDeclaringMethod() {
+ return new MethodDescription.ForLoadedMethod(executable);
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST", justification = "The implicit field type casting is not understood by Findbugs")
+ public TypeDescription.Generic getType() {
+ return new TypeDescription.Generic.LazyProjection.OfMethodParameter(executable, index, executable.getParameterTypes());
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST", justification = "The implicit field type casting is not understood by Findbugs")
+ public AnnotationList getDeclaredAnnotations() {
+ return new AnnotationList.ForLoadedAnnotations(executable.getParameterAnnotations()[index]);
+ }
+ }
+
+ /**
+ * A description of a loaded {@link Constructor} parameter for a modern VM.
+ */
+ protected static class OfConstructor extends ForLoadedParameter<Constructor<?>> {
+
+ /**
+ * Creates a new description for a loaded constructor.
+ *
+ * @param constructor The constructor for which a parameter is represented.
+ * @param index The index of the parameter.
+ */
+ protected OfConstructor(Constructor<?> constructor, int index) {
+ super(constructor, index);
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST", justification = "The implicit field type casting is not understood by Findbugs")
+ public MethodDescription.InDefinedShape getDeclaringMethod() {
+ return new MethodDescription.ForLoadedConstructor(executable);
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST", justification = "The implicit field type casting is not understood by Findbugs")
+ public TypeDescription.Generic getType() {
+ return new TypeDescription.Generic.LazyProjection.OfConstructorParameter(executable, index, executable.getParameterTypes());
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST", justification = "The implicit field type casting is not understood by Findbugs")
+ public AnnotationList getDeclaredAnnotations() {
+ return new AnnotationList.ForLoadedAnnotations(executable.getParameterAnnotations()[index]);
+ }
+ }
+
+ /**
+ * Description of a loaded method's parameter on a virtual machine where {@code java.lang.reflect.Parameter}
+ * is not available.
+ */
+ protected static class OfLegacyVmMethod extends InDefinedShape.AbstractBase {
+
+ /**
+ * The method that declares this parameter.
+ */
+ private final Method method;
+
+ /**
+ * The index of this parameter.
+ */
+ private final int index;
+
+ /**
+ * The type erasures of the represented method.
+ */
+ private final Class<?>[] parameterType;
+
+ /**
+ * The annotations of the represented method's parameters.
+ */
+ private final Annotation[][] parameterAnnotation;
+
+ /**
+ * Creates a legacy representation of a method's parameter.
+ *
+ * @param method The method that declares this parameter.
+ * @param index The index of this parameter.
+ * @param parameterType The type erasures of the represented method.
+ * @param parameterAnnotation The annotations of the represented method's parameters.
+ */
+ protected OfLegacyVmMethod(Method method, int index, Class<?>[] parameterType, Annotation[][] parameterAnnotation) {
+ this.method = method;
+ this.index = index;
+ this.parameterType = parameterType;
+ this.parameterAnnotation = parameterAnnotation;
+ }
+
+ @Override
+ public TypeDescription.Generic getType() {
+ return new TypeDescription.Generic.LazyProjection.OfMethodParameter(method, index, parameterType);
+ }
+
+ @Override
+ public MethodDescription.InDefinedShape getDeclaringMethod() {
+ return new MethodDescription.ForLoadedMethod(method);
+ }
+
+ @Override
+ public int getIndex() {
+ return index;
+ }
+
+ @Override
+ public boolean isNamed() {
+ return false;
+ }
+
+ @Override
+ public boolean hasModifiers() {
+ return false;
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return new AnnotationList.ForLoadedAnnotations(parameterAnnotation[index]);
+ }
+ }
+
+ /**
+ * Description of a loaded constructor's parameter on a virtual machine where {@code java.lang.reflect.Parameter}
+ * is not available.
+ */
+ protected static class OfLegacyVmConstructor extends InDefinedShape.AbstractBase {
+
+ /**
+ * The method that declares this parameter.
+ */
+ private final Constructor<?> constructor;
+
+ /**
+ * The index of this parameter.
+ */
+ private final int index;
+
+ /**
+ * The type erasures of the represented method.
+ */
+ private final Class<?>[] parameterType;
+
+ /**
+ * The annotations of this parameter.
+ */
+ private final Annotation[][] parameterAnnotation;
+
+ /**
+ * Creates a legacy representation of a method's parameter.
+ *
+ * @param constructor The constructor that declares this parameter.
+ * @param index The index of this parameter.
+ * @param parameterType The type erasures of the represented method.
+ * @param parameterAnnotation An array of all parameter annotations of the represented method.
+ */
+ protected OfLegacyVmConstructor(Constructor<?> constructor, int index, Class<?>[] parameterType, Annotation[][] parameterAnnotation) {
+ this.constructor = constructor;
+ this.index = index;
+ this.parameterType = parameterType;
+ this.parameterAnnotation = parameterAnnotation;
+ }
+
+ @Override
+ public TypeDescription.Generic getType() {
+ return new TypeDescription.Generic.LazyProjection.OfConstructorParameter(constructor, index, parameterType);
+ }
+
+ @Override
+ public MethodDescription.InDefinedShape getDeclaringMethod() {
+ return new MethodDescription.ForLoadedConstructor(constructor);
+ }
+
+ @Override
+ public int getIndex() {
+ return index;
+ }
+
+ @Override
+ public boolean isNamed() {
+ return false;
+ }
+
+ @Override
+ public boolean hasModifiers() {
+ return false;
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return new AnnotationList.ForLoadedAnnotations(parameterAnnotation[index]);
+ }
+ }
+ }
+
+ /**
+ * A latent description of a parameter that is not attached to a method or constructor.
+ */
+ class Latent extends InDefinedShape.AbstractBase {
+
+ /**
+ * The method that is declaring the parameter.
+ */
+ private final MethodDescription.InDefinedShape declaringMethod;
+
+ /**
+ * The type of the parameter.
+ */
+ private final TypeDescription.Generic parameterType;
+
+ /**
+ * The annotations of the parameter.
+ */
+ private final List<? extends AnnotationDescription> declaredAnnotations;
+
+ /**
+ * The name of the parameter or {@code null} if no name is explicitly defined.
+ */
+ private final String name;
+
+ /**
+ * The modifiers of the parameter or {@code null} if no modifiers are explicitly defined.
+ */
+ private final Integer modifiers;
+
+ /**
+ * The index of the parameter.
+ */
+ private final int index;
+
+ /**
+ * The parameter's offset in the local method variables array.
+ */
+ private final int offset;
+
+ /**
+ * Creates a latent parameter description. All provided types are attached to this instance before they are returned.
+ *
+ * @param declaringMethod The method that is declaring the parameter.
+ * @param token The token describing the shape of the parameter.
+ * @param index The index of the parameter.
+ * @param offset The parameter's offset in the local method variables array.
+ */
+ public Latent(MethodDescription.InDefinedShape declaringMethod, Token token, int index, int offset) {
+ this(declaringMethod,
+ token.getType(),
+ token.getAnnotations(),
+ token.getName(),
+ token.getModifiers(),
+ index,
+ offset);
+ }
+
+ /**
+ * Creates a new latent parameter descriptions for a parameter without explicit meta data or annotations.
+ *
+ * @param declaringMethod The method declaring this parameter.
+ * @param parameterType The type of the parameter.
+ * @param index The index of the parameter.
+ * @param offset The offset of the parameter.
+ */
+ public Latent(MethodDescription.InDefinedShape declaringMethod,
+ TypeDescription.Generic parameterType,
+ int index,
+ int offset) {
+ this(declaringMethod,
+ parameterType,
+ Collections.<AnnotationDescription>emptyList(),
+ Token.NO_NAME,
+ Token.NO_MODIFIERS,
+ index,
+ offset);
+ }
+
+ /**
+ * Creates a latent parameter description. All provided types are attached to this instance before they are returned.
+ *
+ * @param declaringMethod The method that is declaring the parameter.
+ * @param parameterType The parameter's type.
+ * @param declaredAnnotations The annotations of the parameter.
+ * @param name The name of the parameter or {@code null} if no name is explicitly defined.
+ * @param modifiers The modifiers of the parameter or {@code null} if no modifiers are explicitly defined.
+ * @param index The index of the parameter.
+ * @param offset The parameter's offset in the local method variables array.
+ */
+ public Latent(MethodDescription.InDefinedShape declaringMethod,
+ TypeDescription.Generic parameterType,
+ List<? extends AnnotationDescription> declaredAnnotations,
+ String name,
+ Integer modifiers,
+ int index,
+ int offset) {
+ this.declaringMethod = declaringMethod;
+ this.parameterType = parameterType;
+ this.declaredAnnotations = declaredAnnotations;
+ this.name = name;
+ this.modifiers = modifiers;
+ this.index = index;
+ this.offset = offset;
+ }
+
+ @Override
+ public TypeDescription.Generic getType() {
+ return parameterType.accept(TypeDescription.Generic.Visitor.Substitutor.ForAttachment.of(this));
+ }
+
+ @Override
+ public MethodDescription.InDefinedShape getDeclaringMethod() {
+ return declaringMethod;
+ }
+
+ @Override
+ public int getIndex() {
+ return index;
+ }
+
+ @Override
+ public int getOffset() {
+ return offset;
+ }
+
+ @Override
+ public boolean isNamed() {
+ return name != null;
+ }
+
+ @Override
+ public boolean hasModifiers() {
+ return modifiers != null;
+ }
+
+ @Override
+ public String getName() {
+ return isNamed()
+ ? name
+ : super.getName();
+ }
+
+ @Override
+ public int getModifiers() {
+ return hasModifiers()
+ ? modifiers
+ : super.getModifiers();
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return new AnnotationList.Explicit(declaredAnnotations);
+ }
+ }
+
+ /**
+ * <p>
+ * A parameter description that represents a given parameter but with a substituted parameter type.
+ * </p>
+ * <p>
+ * <b>Note</b>: The supplied visitor must assure to not substitute
+ * </p>
+ */
+ class TypeSubstituting extends AbstractBase implements InGenericShape {
+
+ /**
+ * The method that declares this type-substituted parameter.
+ */
+ private final MethodDescription.InGenericShape declaringMethod;
+
+ /**
+ * The represented parameter.
+ */
+ private final ParameterDescription parameterDescription;
+
+ /**
+ * A visitor that is applied to the parameter type.
+ */
+ private final TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor;
+
+ /**
+ * Creates a new type substituting parameter.
+ *
+ * @param declaringMethod The method that declares this type-substituted parameter.
+ * @param parameterDescription The represented parameter.
+ * @param visitor A visitor that is applied to the parameter type.
+ */
+ public TypeSubstituting(MethodDescription.InGenericShape declaringMethod,
+ ParameterDescription parameterDescription,
+ TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor) {
+ this.declaringMethod = declaringMethod;
+ this.parameterDescription = parameterDescription;
+ this.visitor = visitor;
+ }
+
+ @Override
+ public TypeDescription.Generic getType() {
+ return parameterDescription.getType().accept(visitor);
+ }
+
+ @Override
+ public MethodDescription.InGenericShape getDeclaringMethod() {
+ return declaringMethod;
+ }
+
+ @Override
+ public int getIndex() {
+ return parameterDescription.getIndex();
+ }
+
+ @Override
+ public boolean isNamed() {
+ return parameterDescription.isNamed();
+ }
+
+ @Override
+ public boolean hasModifiers() {
+ return parameterDescription.hasModifiers();
+ }
+
+ @Override
+ public int getOffset() {
+ return parameterDescription.getOffset();
+ }
+
+ @Override
+ public String getName() {
+ return parameterDescription.getName();
+ }
+
+ @Override
+ public int getModifiers() {
+ return parameterDescription.getModifiers();
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return parameterDescription.getDeclaredAnnotations();
+ }
+
+ @Override
+ public InDefinedShape asDefined() {
+ return parameterDescription.asDefined();
+ }
+ }
+
+ /**
+ * A token representing a parameter's properties detached from a type.
+ */
+ class Token implements ByteCodeElement.Token<Token> {
+
+ /**
+ * Indicator for a method parameter without an explicit name.
+ */
+ public static final String NO_NAME = null;
+
+ /**
+ * Indicator for a method parameter without explicit modifiers.
+ */
+ public static final Integer NO_MODIFIERS = null;
+
+ /**
+ * The type of the represented parameter.
+ */
+ private final TypeDescription.Generic type;
+
+ /**
+ * A list of parameter annotations.
+ */
+ private final List<? extends AnnotationDescription> annotations;
+
+ /**
+ * The name of the parameter or {@code null} if no explicit name is defined.
+ */
+ private final String name;
+
+ /**
+ * The modifiers of the parameter or {@code null} if no explicit modifiers is defined.
+ */
+ private final Integer modifiers;
+
+ /**
+ * Creates a new parameter token without an explicit name, an explicit modifier or annotations.
+ * The parameter type must be represented in its detached format.
+ *
+ * @param type The type of the represented parameter.
+ */
+ public Token(TypeDescription.Generic type) {
+ this(type, Collections.<AnnotationDescription>emptyList());
+ }
+
+ /**
+ * Creates a new parameter token without an explicit name or an explicit modifier. The parameter type must be represented in its detached format.
+ *
+ * @param type The type of the represented parameter.
+ * @param annotations The annotations of the parameter.
+ */
+ public Token(TypeDescription.Generic type, List<? extends AnnotationDescription> annotations) {
+ this(type, annotations, NO_NAME, NO_MODIFIERS);
+ }
+
+ /**
+ * Creates a parameter token without annotations. The parameter type must be represented in its detached format.
+ *
+ * @param type The type of the represented parameter.
+ * @param name The name of the parameter or {@code null} if no explicit name is defined.
+ * @param modifiers The modifiers of the parameter or {@code null} if no explicit modifiers is defined.
+ */
+ public Token(TypeDescription.Generic type, String name, Integer modifiers) {
+ this(type, Collections.<AnnotationDescription>emptyList(), name, modifiers);
+ }
+
+ /**
+ * Creates a new parameter token. The parameter type must be represented in its detached format.
+ *
+ * @param type The type of the represented parameter.
+ * @param annotations The annotations of the parameter.
+ * @param name The name of the parameter or {@code null} if no explicit name is defined.
+ * @param modifiers The modifiers of the parameter or {@code null} if no explicit modifiers is defined.
+ */
+ public Token(TypeDescription.Generic type,
+ List<? extends AnnotationDescription> annotations,
+ String name,
+ Integer modifiers) {
+ this.type = type;
+ this.annotations = annotations;
+ this.name = name;
+ this.modifiers = modifiers;
+ }
+
+ /**
+ * Returns the type of the represented method parameter.
+ *
+ * @return The type of the represented method parameter.
+ */
+ public TypeDescription.Generic getType() {
+ return type;
+ }
+
+ /**
+ * Returns the annotations of the represented method parameter.
+ *
+ * @return The annotations of the represented method parameter.
+ */
+ public AnnotationList getAnnotations() {
+ return new AnnotationList.Explicit(annotations);
+ }
+
+ /**
+ * Returns the name of the represented method parameter.
+ *
+ * @return The name of the parameter or {@code null} if no explicit name is defined.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the modifiers of the represented method parameter.
+ *
+ * @return The modifiers of the parameter or {@code null} if no explicit modifiers is defined.
+ */
+ public Integer getModifiers() {
+ return modifiers;
+ }
+
+ @Override
+ public Token accept(TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor) {
+ return new Token(type.accept(visitor),
+ annotations,
+ name,
+ modifiers);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (!(other instanceof Token)) return false;
+ Token token = (Token) other;
+ return type.equals(token.type)
+ && annotations.equals(token.annotations)
+ && (name != null ? name.equals(token.name) : token.name == null)
+ && (modifiers != null ? modifiers.equals(token.modifiers) : token.modifiers == null);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = type.hashCode();
+ result = 31 * result + annotations.hashCode();
+ result = 31 * result + (name != null ? name.hashCode() : 0);
+ result = 31 * result + (modifiers != null ? modifiers.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "ParameterDescription.Token{" +
+ "type=" + type +
+ ", annotations=" + annotations +
+ ", name='" + name + '\'' +
+ ", modifiers=" + modifiers +
+ '}';
+ }
+
+ /**
+ * A list of types represented as a list of parameter tokens.
+ */
+ public static class TypeList extends AbstractList<Token> {
+
+ /**
+ * The list of types to represent as parameter tokens.
+ */
+ private final List<? extends TypeDefinition> typeDescriptions;
+
+ /**
+ * Creates a new list of types that represent parameters.
+ *
+ * @param typeDescriptions The types to represent.
+ */
+ public TypeList(List<? extends TypeDefinition> typeDescriptions) {
+ this.typeDescriptions = typeDescriptions;
+ }
+
+ @Override
+ public Token get(int index) {
+ return new Token(typeDescriptions.get(index).asGenericType());
+ }
+
+ @Override
+ public int size() {
+ return typeDescriptions.size();
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/method/ParameterList.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/method/ParameterList.java
new file mode 100644
index 0000000..361df18
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/method/ParameterList.java
@@ -0,0 +1,614 @@
+package net.bytebuddy.description.method;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.ByteCodeElement;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.matcher.FilterableList;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Represents a list of parameters of a method or a constructor.
+ *
+ * @param <T> The type of parameter descriptions represented by this list.
+ */
+public interface ParameterList<T extends ParameterDescription> extends FilterableList<T, ParameterList<T>> {
+
+ /**
+ * Transforms this list of parameters into a list of the types of the represented parameters.
+ *
+ * @return A list of types representing the parameters of this list.
+ */
+ TypeList.Generic asTypeList();
+
+ /**
+ * Transforms the list of parameter descriptions into a list of detached tokens. All types that are matched by the provided
+ * target type matcher are substituted by {@link net.bytebuddy.dynamic.TargetType}.
+ *
+ * @param matcher A matcher that indicates type substitution.
+ * @return The transformed token list.
+ */
+ ByteCodeElement.Token.TokenList<ParameterDescription.Token> asTokenList(ElementMatcher<? super TypeDescription> matcher);
+
+ /**
+ * Returns this list of these parameter descriptions resolved to their defined shape.
+ *
+ * @return A list of parameters in their defined shape.
+ */
+ ParameterList<ParameterDescription.InDefinedShape> asDefined();
+
+ /**
+ * Checks if all parameters in this list define both an explicit name and an explicit modifier.
+ *
+ * @return {@code true} if all parameters in this list define both an explicit name and an explicit modifier.
+ */
+ boolean hasExplicitMetaData();
+
+ /**
+ * An base implementation for a {@link ParameterList}.
+ *
+ * @param <S> The type of parameter descriptions represented by this list.
+ */
+ abstract class AbstractBase<S extends ParameterDescription> extends FilterableList.AbstractBase<S, ParameterList<S>> implements ParameterList<S> {
+
+ @Override
+ public boolean hasExplicitMetaData() {
+ for (ParameterDescription parameterDescription : this) {
+ if (!parameterDescription.isNamed() || !parameterDescription.hasModifiers()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public ByteCodeElement.Token.TokenList<ParameterDescription.Token> asTokenList(ElementMatcher<? super TypeDescription> matcher) {
+ List<ParameterDescription.Token> tokens = new ArrayList<ParameterDescription.Token>(size());
+ for (ParameterDescription parameterDescription : this) {
+ tokens.add(parameterDescription.asToken(matcher));
+ }
+ return new ByteCodeElement.Token.TokenList<ParameterDescription.Token>(tokens);
+ }
+
+ @Override
+ public TypeList.Generic asTypeList() {
+ List<TypeDescription.Generic> types = new ArrayList<TypeDescription.Generic>(size());
+ for (ParameterDescription parameterDescription : this) {
+ types.add(parameterDescription.getType());
+ }
+ return new TypeList.Generic.Explicit(types);
+ }
+
+ @Override
+ public ParameterList<ParameterDescription.InDefinedShape> asDefined() {
+ List<ParameterDescription.InDefinedShape> declaredForms = new ArrayList<ParameterDescription.InDefinedShape>(size());
+ for (ParameterDescription parameterDescription : this) {
+ declaredForms.add(parameterDescription.asDefined());
+ }
+ return new Explicit<ParameterDescription.InDefinedShape>(declaredForms);
+ }
+
+ @Override
+ protected ParameterList<S> wrap(List<S> values) {
+ return new Explicit<S>(values);
+ }
+ }
+
+ /**
+ * Represents a list of parameters for an executable, i.e. a {@link java.lang.reflect.Method} or {@link java.lang.reflect.Constructor}.
+ *
+ * @param <T> The type of the {@code java.lang.reflect.Executable} that this list represents.
+ */
+ abstract class ForLoadedExecutable<T> extends AbstractBase<ParameterDescription.InDefinedShape> {
+
+ /**
+ * The dispatcher used creating parameter list instances and for accessing {@code java.lang.reflect.Executable} instances.
+ */
+ private static final Dispatcher DISPATCHER = AccessController.doPrivileged(Dispatcher.CreationAction.INSTANCE);
+
+ /**
+ * The executable for which a parameter list is represented.
+ */
+ protected final T executable;
+
+ /**
+ * Creates a new description for a loaded executable.
+ *
+ * @param executable The executable for which a parameter list is represented.
+ */
+ protected ForLoadedExecutable(T executable) {
+ this.executable = executable;
+ }
+
+ /**
+ * Creates a new list that describes the parameters of the given {@link Constructor}.
+ *
+ * @param constructor The constructor for which the parameters should be described.
+ * @return A list describing the constructor's parameters.
+ */
+ public static ParameterList<ParameterDescription.InDefinedShape> of(Constructor<?> constructor) {
+ return DISPATCHER.describe(constructor);
+ }
+
+ /**
+ * Creates a new list that describes the parameters of the given {@link Method}.
+ *
+ * @param method The method for which the parameters should be described.
+ * @return A list describing the method's parameters.
+ */
+ public static ParameterList<ParameterDescription.InDefinedShape> of(Method method) {
+ return DISPATCHER.describe(method);
+ }
+
+ @Override
+ public int size() {
+ return DISPATCHER.getParameterCount(executable);
+ }
+
+ /**
+ * A dispatcher for creating descriptions of parameter lists and for evaluating the size of an {@code java.lang.reflect.Executable}'s parameters.
+ */
+ protected interface Dispatcher {
+
+ /**
+ * Returns the amount of parameters of a given executable..
+ *
+ * @param executable The executable for which the amount of parameters should be found.
+ * @return The amount of parameters of the given executable.
+ */
+ int getParameterCount(Object executable);
+
+ /**
+ * Describes a {@link Constructor}'s parameters of the given VM.
+ *
+ * @param constructor The constructor for which the parameters should be described.
+ * @return A list describing the constructor's parameters.
+ */
+ ParameterList<ParameterDescription.InDefinedShape> describe(Constructor<?> constructor);
+
+ /**
+ * Describes a {@link Method}'s parameters of the given VM.
+ *
+ * @param method The method for which the parameters should be described.
+ * @return A list describing the method's parameters.
+ */
+ ParameterList<ParameterDescription.InDefinedShape> describe(Method method);
+
+ /**
+ * A creation action for a dispatcher.
+ */
+ enum CreationAction implements PrivilegedAction<Dispatcher> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback")
+ public Dispatcher run() {
+ try {
+ return new Dispatcher.ForJava8CapableVm(Class.forName("java.lang.reflect.Executable").getMethod("getParameterCount"));
+ } catch (Exception ignored) {
+ return Dispatcher.ForLegacyVm.INSTANCE;
+ }
+ }
+ }
+
+ /**
+ * A dispatcher for a legacy VM that does not support the {@code java.lang.reflect.Parameter} type.
+ */
+ enum ForLegacyVm implements Dispatcher {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public int getParameterCount(Object executable) {
+ throw new IllegalStateException("Cannot dispatch method for java.lang.reflect.Executable");
+ }
+
+ @Override
+ public ParameterList<ParameterDescription.InDefinedShape> describe(Constructor<?> constructor) {
+ return new OfLegacyVmConstructor(constructor);
+ }
+
+ @Override
+ public ParameterList<ParameterDescription.InDefinedShape> describe(Method method) {
+ return new OfLegacyVmMethod(method);
+ }
+ }
+
+ /**
+ * A dispatcher for a legacy VM that does support the {@code java.lang.reflect.Parameter} type.
+ */
+ @EqualsAndHashCode
+ class ForJava8CapableVm implements Dispatcher {
+
+ /**
+ * The {@code java.lang.reflect.Executable#getParameterCount()} method.
+ */
+ private final Method getParameterCount;
+
+ /**
+ * Creates a new dispatcher for a modern VM.
+ *
+ * @param getParameterCount The {@code java.lang.reflect.Executable#getParameterCount()} method.
+ */
+ protected ForJava8CapableVm(Method getParameterCount) {
+ this.getParameterCount = getParameterCount;
+ }
+
+ @Override
+ public int getParameterCount(Object executable) {
+ try {
+ return (Integer) getParameterCount.invoke(executable);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.reflect.Parameter#getModifiers", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.reflect.Parameter#getModifiers", exception.getCause());
+ }
+ }
+
+ @Override
+ public ParameterList<ParameterDescription.InDefinedShape> describe(Constructor<?> constructor) {
+ return new OfConstructor(constructor);
+ }
+
+ @Override
+ public ParameterList<ParameterDescription.InDefinedShape> describe(Method method) {
+ return new OfMethod(method);
+ }
+ }
+ }
+
+ /**
+ * Describes the list of {@link Constructor} parameters on a modern VM.
+ */
+ protected static class OfConstructor extends ForLoadedExecutable<Constructor<?>> {
+
+ /**
+ * Creates a new description of the parameters of a constructor.
+ *
+ * @param constructor The constructor that is represented by this instance.
+ */
+ protected OfConstructor(Constructor<?> constructor) {
+ super(constructor);
+ }
+
+ @Override
+ public ParameterDescription.InDefinedShape get(int index) {
+ return new ParameterDescription.ForLoadedParameter.OfConstructor(executable, index);
+ }
+ }
+
+ /**
+ * Describes the list of {@link Method} parameters on a modern VM.
+ */
+ protected static class OfMethod extends ForLoadedExecutable<Method> {
+
+ /**
+ * Creates a new description of the parameters of a method.
+ *
+ * @param method The method that is represented by this instance.
+ */
+ protected OfMethod(Method method) {
+ super(method);
+ }
+
+ @Override
+ public ParameterDescription.InDefinedShape get(int index) {
+ return new ParameterDescription.ForLoadedParameter.OfMethod(executable, index);
+ }
+ }
+
+ /**
+ * Represents a list of constructor parameters on virtual machines where the {@code java.lang.reflect.Parameter}
+ * type is not available.
+ */
+ protected static class OfLegacyVmConstructor extends ParameterList.AbstractBase<ParameterDescription.InDefinedShape> {
+
+ /**
+ * The represented constructor.
+ */
+ private final Constructor<?> constructor;
+
+ /**
+ * An array of this method's parameter types.
+ */
+ private final Class<?>[] parameterType;
+
+ /**
+ * An array of all parameter annotations of the represented method.
+ */
+ private final Annotation[][] parameterAnnotation;
+
+ /**
+ * Creates a legacy representation of a constructor's parameters.
+ *
+ * @param constructor The constructor to represent.
+ */
+ public OfLegacyVmConstructor(Constructor<?> constructor) {
+ this.constructor = constructor;
+ this.parameterType = constructor.getParameterTypes();
+ this.parameterAnnotation = constructor.getParameterAnnotations();
+ }
+
+ @Override
+ public ParameterDescription.InDefinedShape get(int index) {
+ return new ParameterDescription.ForLoadedParameter.OfLegacyVmConstructor(constructor, index, parameterType, parameterAnnotation);
+ }
+
+ @Override
+ public int size() {
+ return parameterType.length;
+ }
+ }
+
+ /**
+ * Represents a list of method parameters on virtual machines where the {@code java.lang.reflect.Parameter}
+ * type is not available.
+ */
+ protected static class OfLegacyVmMethod extends ParameterList.AbstractBase<ParameterDescription.InDefinedShape> {
+
+ /**
+ * The represented method.
+ */
+ private final Method method;
+
+ /**
+ * An array of this method's parameter types.
+ */
+ private final Class<?>[] parameterType;
+
+ /**
+ * An array of all parameter annotations of the represented method.
+ */
+ private final Annotation[][] parameterAnnotation;
+
+ /**
+ * Creates a legacy representation of a method's parameters.
+ *
+ * @param method The method to represent.
+ */
+ protected OfLegacyVmMethod(Method method) {
+ this.method = method;
+ this.parameterType = method.getParameterTypes();
+ this.parameterAnnotation = method.getParameterAnnotations();
+ }
+
+ @Override
+ public ParameterDescription.InDefinedShape get(int index) {
+ return new ParameterDescription.ForLoadedParameter.OfLegacyVmMethod(method, index, parameterType, parameterAnnotation);
+ }
+
+ @Override
+ public int size() {
+ return parameterType.length;
+ }
+ }
+ }
+
+ /**
+ * A list of explicitly provided parameter descriptions.
+ *
+ * @param <S> The type of parameter descriptions represented by this list.
+ */
+ class Explicit<S extends ParameterDescription> extends AbstractBase<S> {
+
+ /**
+ * The list of parameter descriptions that are represented by this list.
+ */
+ private final List<? extends S> parameterDescriptions;
+
+ /**
+ * Creates a new list of explicit parameter descriptions.
+ *
+ * @param parameterDescription The list of parameter descriptions that are represented by this list.
+ */
+ @SuppressWarnings("unchecked")
+ public Explicit(S... parameterDescription) {
+ this(Arrays.asList(parameterDescription));
+ }
+
+ /**
+ * Creates a new list of explicit parameter descriptions.
+ *
+ * @param parameterDescriptions The list of parameter descriptions that are represented by this list.
+ */
+ public Explicit(List<? extends S> parameterDescriptions) {
+ this.parameterDescriptions = parameterDescriptions;
+ }
+
+ @Override
+ public S get(int index) {
+ return parameterDescriptions.get(index);
+ }
+
+ @Override
+ public int size() {
+ return parameterDescriptions.size();
+ }
+
+ /**
+ * A parameter list representing parameters without meta data or annotations.
+ */
+ public static class ForTypes extends ParameterList.AbstractBase<ParameterDescription.InDefinedShape> {
+
+ /**
+ * The method description that declares the parameters.
+ */
+ private final MethodDescription.InDefinedShape methodDescription;
+
+ /**
+ * A list of detached types representing the parameters.
+ */
+ private final List<? extends TypeDefinition> typeDefinitions;
+
+ /**
+ * Creates a new parameter type list.
+ *
+ * @param methodDescription The method description that declares the parameters.
+ * @param typeDefinition A list of detached types representing the parameters.
+ */
+ public ForTypes(MethodDescription.InDefinedShape methodDescription, TypeDefinition... typeDefinition) {
+ this(methodDescription, Arrays.asList(typeDefinition));
+ }
+
+ /**
+ * Creates a new parameter type list.
+ *
+ * @param methodDescription The method description that declares the parameters.
+ * @param typeDefinitions A list of detached types representing the parameters.
+ */
+ public ForTypes(MethodDescription.InDefinedShape methodDescription, List<? extends TypeDefinition> typeDefinitions) {
+ this.methodDescription = methodDescription;
+ this.typeDefinitions = typeDefinitions;
+ }
+
+ @Override
+ public ParameterDescription.InDefinedShape get(int index) {
+ int offset = methodDescription.isStatic() ? 0 : 1;
+ for (TypeDefinition typeDefinition : typeDefinitions.subList(0, index)) {
+ offset += typeDefinition.getStackSize().getSize();
+ }
+ return new ParameterDescription.Latent(methodDescription, typeDefinitions.get(index).asGenericType(), index, offset);
+ }
+
+ @Override
+ public int size() {
+ return typeDefinitions.size();
+ }
+ }
+ }
+
+ /**
+ * A list of parameter descriptions for a list of detached tokens. For the returned parameter, each token is attached to its parameter representation.
+ */
+ class ForTokens extends AbstractBase<ParameterDescription.InDefinedShape> {
+
+ /**
+ * The method that is declaring the represented token.
+ */
+ private final MethodDescription.InDefinedShape declaringMethod;
+
+ /**
+ * The list of tokens to represent.
+ */
+ private final List<? extends ParameterDescription.Token> tokens;
+
+ /**
+ * Creates a new parameter list for the provided tokens.
+ *
+ * @param declaringMethod The method that is declaring the represented token.
+ * @param tokens The list of tokens to represent.
+ */
+ public ForTokens(MethodDescription.InDefinedShape declaringMethod, List<? extends ParameterDescription.Token> tokens) {
+ this.declaringMethod = declaringMethod;
+ this.tokens = tokens;
+ }
+
+ @Override
+ public ParameterDescription.InDefinedShape get(int index) {
+ int offset = declaringMethod.isStatic() ? 0 : 1;
+ for (ParameterDescription.Token token : tokens.subList(0, index)) {
+ offset += token.getType().getStackSize().getSize();
+ }
+ return new ParameterDescription.Latent(declaringMethod, tokens.get(index), index, offset);
+ }
+
+ @Override
+ public int size() {
+ return tokens.size();
+ }
+ }
+
+ /**
+ * A list of parameter descriptions that yields {@link net.bytebuddy.description.method.ParameterDescription.TypeSubstituting}.
+ */
+ class TypeSubstituting extends AbstractBase<ParameterDescription.InGenericShape> {
+
+ /**
+ * The method that is declaring the transformed parameters.
+ */
+ private final MethodDescription.InGenericShape declaringMethod;
+
+ /**
+ * The untransformed parameters that are represented by this list.
+ */
+ private final List<? extends ParameterDescription> parameterDescriptions;
+
+ /**
+ * The visitor to apply to the parameter types before returning them.
+ */
+ private final TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor;
+
+ /**
+ * Creates a new type substituting parameter list.
+ *
+ * @param declaringMethod The method that is declaring the transformed parameters.
+ * @param parameterDescriptions The untransformed parameters that are represented by this list.
+ * @param visitor The visitor to apply to the parameter types before returning them.
+ */
+ public TypeSubstituting(MethodDescription.InGenericShape declaringMethod,
+ List<? extends ParameterDescription> parameterDescriptions,
+ TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor) {
+ this.declaringMethod = declaringMethod;
+ this.parameterDescriptions = parameterDescriptions;
+ this.visitor = visitor;
+ }
+
+ @Override
+ public ParameterDescription.InGenericShape get(int index) {
+ return new ParameterDescription.TypeSubstituting(declaringMethod, parameterDescriptions.get(index), visitor);
+ }
+
+ @Override
+ public int size() {
+ return parameterDescriptions.size();
+ }
+ }
+
+ /**
+ * An empty list of parameters.
+ *
+ * @param <S> The type of parameter descriptions represented by this list.
+ */
+ class Empty<S extends ParameterDescription> extends FilterableList.Empty<S, ParameterList<S>> implements ParameterList<S> {
+
+ @Override
+ public boolean hasExplicitMetaData() {
+ return true;
+ }
+
+ @Override
+ public TypeList.Generic asTypeList() {
+ return new TypeList.Generic.Empty();
+ }
+
+ @Override
+ public ByteCodeElement.Token.TokenList<ParameterDescription.Token> asTokenList(ElementMatcher<? super TypeDescription> matcher) {
+ return new ByteCodeElement.Token.TokenList<ParameterDescription.Token>();
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public ParameterList<ParameterDescription.InDefinedShape> asDefined() {
+ return (ParameterList<ParameterDescription.InDefinedShape>) this;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/method/package-info.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/method/package-info.java
new file mode 100644
index 0000000..5b755b5
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/method/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Contains descriptions of Java methods and constructors as well as their parameters.
+ */
+package net.bytebuddy.description.method;
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/EnumerationState.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/EnumerationState.java
new file mode 100644
index 0000000..6c28a83
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/EnumerationState.java
@@ -0,0 +1,57 @@
+package net.bytebuddy.description.modifier;
+
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Determines if a type describes an enumeration. Note that enumerations must never also be interfaces.
+ */
+public enum EnumerationState implements ModifierContributor.ForType, ModifierContributor.ForField {
+
+ /**
+ * Modifier for marking a type as a non-enumeration. (This is the default modifier.)
+ */
+ PLAIN(EMPTY_MASK),
+
+ /**
+ * Modifier for marking a type as an enumeration.
+ */
+ ENUMERATION(Opcodes.ACC_ENUM);
+
+ /**
+ * The mask of the modifier contributor.
+ */
+ private final int mask;
+
+ /**
+ * Creates a new enumeration state representation.
+ *
+ * @param mask The modifier mask of this instance.
+ */
+ EnumerationState(int mask) {
+ this.mask = mask;
+ }
+
+ @Override
+ public int getMask() {
+ return mask;
+ }
+
+ @Override
+ public int getRange() {
+ return Opcodes.ACC_ENUM;
+ }
+
+ @Override
+ public boolean isDefault() {
+ return this == PLAIN;
+ }
+
+ /**
+ * Checks if the current state describes the enum state.
+ *
+ * @return {@code true} if the current state describes an enumeration.
+ */
+ public boolean isEnumeration() {
+ return this == ENUMERATION;
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/FieldManifestation.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/FieldManifestation.java
new file mode 100644
index 0000000..000e46e
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/FieldManifestation.java
@@ -0,0 +1,80 @@
+package net.bytebuddy.description.modifier;
+
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Describes the manifestation of a class's field, i.e. if a field is final, volatile or neither.
+ */
+public enum FieldManifestation implements ModifierContributor.ForField {
+
+ /**
+ * Modifier for a non-final, non-volatile field. (This is the default modifier.)
+ */
+ PLAIN(EMPTY_MASK),
+
+ /**
+ * Modifier for a final field.
+ */
+ FINAL(Opcodes.ACC_FINAL),
+
+ /**
+ * Modifier for a volatile field.
+ */
+ VOLATILE(Opcodes.ACC_VOLATILE);
+
+ /**
+ * The mask the modifier contributor.
+ */
+ private final int mask;
+
+ /**
+ * Creates a new field manifestation.
+ *
+ * @param mask The modifier mask of this instance.
+ */
+ FieldManifestation(int mask) {
+ this.mask = mask;
+ }
+
+ @Override
+ public int getMask() {
+ return mask;
+ }
+
+ @Override
+ public int getRange() {
+ return Opcodes.ACC_FINAL | Opcodes.ACC_VOLATILE;
+ }
+
+ @Override
+ public boolean isDefault() {
+ return this == PLAIN;
+ }
+
+ /**
+ * Returns {@code true} if this manifestation represents a {@code final} type.
+ *
+ * @return {@code true} if this manifestation represents a {@code final} type.
+ */
+ public boolean isFinal() {
+ return (mask & Opcodes.ACC_FINAL) != 0;
+ }
+
+ /**
+ * Returns {@code true} if this manifestation represents a {@code volatile} type.
+ *
+ * @return {@code true} if this manifestation represents a {@code volatile} type.
+ */
+ public boolean isVolatile() {
+ return (mask & Opcodes.ACC_VOLATILE) != 0;
+ }
+
+ /**
+ * Returns {@code true} if this manifestation represents a field that is neither {@code final} or {@code volatile}.
+ *
+ * @return {@code true} if this manifestation represents a field that is neither {@code final} or {@code volatile}.
+ */
+ public boolean isPlain() {
+ return !isFinal() && !isVolatile();
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/FieldPersistence.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/FieldPersistence.java
new file mode 100644
index 0000000..20c1d18
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/FieldPersistence.java
@@ -0,0 +1,57 @@
+package net.bytebuddy.description.modifier;
+
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Describes the persistence of a field, i.e. if it is {@code transient}.
+ */
+public enum FieldPersistence implements ModifierContributor.ForField {
+
+ /**
+ * Modifier for a non-transient field. (This is the default modifier.)
+ */
+ PLAIN(EMPTY_MASK),
+
+ /**
+ * Modifier for a transient field.
+ */
+ TRANSIENT(Opcodes.ACC_TRANSIENT);
+
+ /**
+ * The modifier mask for this persistence type.
+ */
+ private final int mask;
+
+ /**
+ * Creates a new field persistence description.
+ *
+ * @param mask The modifier mask for this persistence type.
+ */
+ FieldPersistence(int mask) {
+ this.mask = mask;
+ }
+
+ @Override
+ public int getMask() {
+ return mask;
+ }
+
+ @Override
+ public int getRange() {
+ return Opcodes.ACC_TRANSIENT;
+ }
+
+ @Override
+ public boolean isDefault() {
+ return this == PLAIN;
+ }
+
+ /**
+ * Returns {@code true} if this manifestation represents a field that is {@code transient}.
+ *
+ * @return {@code true} if this manifestation represents a field that is {@code transient}.
+ */
+ public boolean isTransient() {
+ return (mask & Opcodes.ACC_TRANSIENT) != 0;
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/MethodArguments.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/MethodArguments.java
new file mode 100644
index 0000000..1e33c6a
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/MethodArguments.java
@@ -0,0 +1,57 @@
+package net.bytebuddy.description.modifier;
+
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Describes if a method allows varargs arguments.
+ */
+public enum MethodArguments implements ModifierContributor.ForMethod {
+
+ /**
+ * Describes a method that does not permit varargs.
+ */
+ PLAIN(EMPTY_MASK),
+
+ /**
+ * Describes a method that permits varargs.
+ */
+ VARARGS(Opcodes.ACC_VARARGS);
+
+ /**
+ * The mask of the modifier contributor.
+ */
+ private final int mask;
+
+ /**
+ * Creates a new method arguments representation.
+ *
+ * @param mask The mask of this instance.
+ */
+ MethodArguments(int mask) {
+ this.mask = mask;
+ }
+
+ @Override
+ public int getMask() {
+ return mask;
+ }
+
+ @Override
+ public int getRange() {
+ return Opcodes.ACC_VARARGS;
+ }
+
+ @Override
+ public boolean isDefault() {
+ return this == PLAIN;
+ }
+
+ /**
+ * Checks if the current state describes a varargs methods.
+ *
+ * @return {@code true} if the current state represents a varargs method.
+ */
+ public boolean isVarArgs() {
+ return this == VARARGS;
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/MethodManifestation.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/MethodManifestation.java
new file mode 100644
index 0000000..89652bf
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/MethodManifestation.java
@@ -0,0 +1,111 @@
+package net.bytebuddy.description.modifier;
+
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Describes the manifestation of a method, i.e. if a method is final, abstract or native.
+ * Note that an {@code abstract} method must never be static and can only be declared for an
+ * {@code abstract} type.
+ */
+public enum MethodManifestation implements ModifierContributor.ForMethod {
+
+ /**
+ * Modifier for a non-native, non-abstract, non-final method. (This is the default modifier.)
+ */
+ PLAIN(EMPTY_MASK),
+
+ /**
+ * Modifier for a native method.
+ */
+ NATIVE(Opcodes.ACC_NATIVE),
+
+ /**
+ * Modifier for an abstract method.
+ */
+ ABSTRACT(Opcodes.ACC_ABSTRACT),
+
+ /**
+ * Modifier for a final method.
+ */
+ FINAL(Opcodes.ACC_FINAL),
+
+ /**
+ * Modifier for a native and final method.
+ */
+ FINAL_NATIVE(Opcodes.ACC_FINAL | Opcodes.ACC_NATIVE),
+
+ /**
+ * Modifier for a bridge method.
+ */
+ BRIDGE(Opcodes.ACC_BRIDGE),
+
+ /**
+ * Modifier for a final bridge method.
+ */
+ FINAL_BRIDGE(Opcodes.ACC_FINAL | Opcodes.ACC_BRIDGE);
+
+ /**
+ * The mask the modifier contributor.
+ */
+ private final int mask;
+
+ /**
+ * Creates a new method manifestation.
+ *
+ * @param mask The modifier mask of this instance.
+ */
+ MethodManifestation(int mask) {
+ this.mask = mask;
+ }
+
+ @Override
+ public int getMask() {
+ return mask;
+ }
+
+ @Override
+ public int getRange() {
+ return Opcodes.ACC_NATIVE | Opcodes.ACC_ABSTRACT | Opcodes.ACC_FINAL | Opcodes.ACC_BRIDGE;
+ }
+
+ @Override
+ public boolean isDefault() {
+ return this == PLAIN;
+ }
+
+ /**
+ * Returns {@code true} if this instance represents a {@code native} method.
+ *
+ * @return {@code true} if this instance represents a {@code native} method.
+ */
+ public boolean isNative() {
+ return (mask & Opcodes.ACC_NATIVE) != 0;
+ }
+
+ /**
+ * Returns {@code true} if this instance represents a {@code abstract} method.
+ *
+ * @return {@code true} if this instance represents a {@code abstract} method.
+ */
+ public boolean isAbstract() {
+ return (mask & Opcodes.ACC_ABSTRACT) != 0;
+ }
+
+ /**
+ * Returns {@code true} if this instance represents a {@code final} method.
+ *
+ * @return {@code true} if this instance represents a {@code final} method.
+ */
+ public boolean isFinal() {
+ return (mask & Opcodes.ACC_FINAL) != 0;
+ }
+
+ /**
+ * Returns {@code true} if this instance represents a bridge method.
+ *
+ * @return {@code true} if this instance represents a bridge method.
+ */
+ public boolean isBridge() {
+ return (mask & Opcodes.ACC_BRIDGE) != 0;
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/MethodStrictness.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/MethodStrictness.java
new file mode 100644
index 0000000..fa846ad
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/MethodStrictness.java
@@ -0,0 +1,57 @@
+package net.bytebuddy.description.modifier;
+
+import org.objectweb.asm.Opcodes;
+
+/**
+ * A modifier contributor to determine the use of {@code strictfp} on a method.
+ */
+public enum MethodStrictness implements ModifierContributor.ForMethod {
+
+ /**
+ * Modifier for a non-strict method. (This is the default modifier.)
+ */
+ PLAIN(EMPTY_MASK),
+
+ /**
+ * Modifier for a method that applies strict floating-point computation.
+ */
+ STRICT(Opcodes.ACC_STRICT);
+
+ /**
+ * The modifier contributors mask.
+ */
+ private final int mask;
+
+ /**
+ * Creates a new modifier contributor for a method.
+ *
+ * @param mask The modifier contributors mask.
+ */
+ MethodStrictness(int mask) {
+ this.mask = mask;
+ }
+
+ @Override
+ public int getMask() {
+ return mask;
+ }
+
+ @Override
+ public int getRange() {
+ return Opcodes.ACC_STRICT;
+ }
+
+ @Override
+ public boolean isDefault() {
+ return this == PLAIN;
+ }
+
+ /**
+ * Returns {@code true} if this modifier contributor indicates strict floating-point computation.
+ *
+ * @return {@code true} if this modifier contributor indicates strict floating-point computation.
+ */
+ public boolean isStrict() {
+ return this == STRICT;
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/ModifierContributor.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/ModifierContributor.java
new file mode 100644
index 0000000..ce0f15e
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/ModifierContributor.java
@@ -0,0 +1,193 @@
+package net.bytebuddy.description.modifier;
+
+import lombok.EqualsAndHashCode;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+/**
+ * An element that describes a type modifier as described in the
+ * <a href="http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html">JVMS</a>.
+ * <p> </p>
+ * This allows for a more expressive and type safe alternative of defining a type's or type member's modifiers.
+ * However, note that modifier's that apply competing modifiers (such as {@code private} and {@code protected}
+ * should not be combined and will result in invalid types. An exception is thrown when built-in modifiers that
+ * cannot be combined are used together.
+ */
+public interface ModifierContributor {
+
+ /**
+ * The empty modifier.
+ */
+ int EMPTY_MASK = 0;
+
+ /**
+ * Returns the mask of this modifier.
+ *
+ * @return The modifier mask that is to be applied to the target type or type member.
+ */
+ int getMask();
+
+ /**
+ * Returns the entire range of modifiers that address this contributor's property.
+ *
+ * @return The range of this contributor's property.
+ */
+ int getRange();
+
+ /**
+ * Determines if this is the default modifier.
+ *
+ * @return {@code true} if this contributor represents the default modifier.
+ */
+ boolean isDefault();
+
+ /**
+ * A marker interface for modifiers that can be applied to types.
+ */
+ interface ForType extends ModifierContributor {
+
+ /**
+ * A mask for all legal modifiers of a Java type.
+ */
+ int MASK = Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE | Opcodes.ACC_SYNTHETIC
+ | Opcodes.ACC_ABSTRACT | Opcodes.ACC_INTERFACE | Opcodes.ACC_ANNOTATION | Opcodes.ACC_DEPRECATED
+ | Opcodes.ACC_ENUM | Opcodes.ACC_FINAL | Opcodes.ACC_STATIC;
+ }
+
+ /**
+ * A marker interface for modifiers that can be applied to fields.
+ */
+ interface ForField extends ModifierContributor {
+
+ /**
+ * A mask for all legal modifiers of a Java field.
+ */
+ int MASK = Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE | Opcodes.ACC_SYNTHETIC
+ | Opcodes.ACC_DEPRECATED | Opcodes.ACC_ENUM | Opcodes.ACC_FINAL | Opcodes.ACC_STATIC
+ | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_TRANSIENT | Opcodes.ACC_VOLATILE;
+ }
+
+ /**
+ * A marker interface for modifiers that can be applied to methods.
+ */
+ interface ForMethod extends ModifierContributor {
+
+ /**
+ * A mask for all legal modifiers of a Java method.
+ */
+ int MASK = Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE | Opcodes.ACC_SYNTHETIC
+ | Opcodes.ACC_BRIDGE | Opcodes.ACC_FINAL | Opcodes.ACC_NATIVE | Opcodes.ACC_ABSTRACT
+ | Opcodes.ACC_STATIC | Opcodes.ACC_STRICT | Opcodes.ACC_SYNCHRONIZED | Opcodes.ACC_SYNTHETIC
+ | Opcodes.ACC_VARARGS;
+ }
+
+ /**
+ * A marker interface for modifiers that can be applied to method parameters.
+ */
+ interface ForParameter extends ModifierContributor {
+
+ /**
+ * A mask for all legal modifiers of a Java parameter.
+ */
+ int MASK = Opcodes.ACC_MANDATED | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC;
+
+ }
+
+ /**
+ * A resolver for Java modifiers represented by {@link ModifierContributor}s.
+ *
+ * @param <T> The type of the {@link ModifierContributor}s being resolved.
+ */
+ @EqualsAndHashCode
+ class Resolver<T extends ModifierContributor> {
+
+ /**
+ * The modifier contributors to resolve.
+ */
+ private final Collection<? extends T> modifierContributors;
+
+ /**
+ * Creates a new resolver.
+ *
+ * @param modifierContributors The modifier contributors to resolve.
+ */
+ protected Resolver(Collection<? extends T> modifierContributors) {
+ this.modifierContributors = modifierContributors;
+ }
+
+ /**
+ * Creates a new resolver for modifier contributors to a type.
+ *
+ * @param modifierContributor The modifier contributors to resolve.
+ * @return A resolver for the provided modifier contributors.
+ */
+ public static Resolver<ForType> of(ForType... modifierContributor) {
+ return of(Arrays.asList(modifierContributor));
+ }
+
+ /**
+ * Creates a new resolver for modifier contributors to a field.
+ *
+ * @param modifierContributor The modifier contributors to resolve.
+ * @return A resolver for the provided modifier contributors.
+ */
+ public static Resolver<ForField> of(ForField... modifierContributor) {
+ return of(Arrays.asList(modifierContributor));
+ }
+
+ /**
+ * Creates a new resolver for modifier contributors to a method.
+ *
+ * @param modifierContributor The modifier contributors to resolve.
+ * @return A resolver for the provided modifier contributors.
+ */
+ public static Resolver<ForMethod> of(ForMethod... modifierContributor) {
+ return of(Arrays.asList(modifierContributor));
+ }
+
+ /**
+ * Creates a new resolver for modifier contributors to a parameter.
+ *
+ * @param modifierContributor The modifier contributors to resolve.
+ * @return A resolver for the provided modifier contributors.
+ */
+ public static Resolver<ForParameter> of(ForParameter... modifierContributor) {
+ return Resolver.of(Arrays.asList(modifierContributor));
+ }
+
+ /**
+ * Creates a new resolver for any modifier contributor of a given type.
+ *
+ * @param modifierContributors The modifier contributors to resolve.
+ * @param <S> The modifier contributors type.
+ * @return A resolver for the provided modifier contributors.
+ */
+ public static <S extends ModifierContributor> Resolver<S> of(Collection<? extends S> modifierContributors) {
+ return new Resolver<S>(modifierContributors);
+ }
+
+ /**
+ * Resolves the modifier contributors based on a zero modifier.
+ *
+ * @return The resolved modifiers.
+ */
+ public int resolve() {
+ return resolve(EMPTY_MASK);
+ }
+
+ /**
+ * Resolves the modifier contributors based on a given modifier.
+ *
+ * @param modifiers The base modifiers.
+ * @return The resolved modifiers.
+ */
+ public int resolve(int modifiers) {
+ for (T modifierContributor : modifierContributors) {
+ modifiers = (modifiers & ~modifierContributor.getRange()) | modifierContributor.getMask();
+ }
+ return modifiers;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/Ownership.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/Ownership.java
new file mode 100644
index 0000000..5694a8d
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/Ownership.java
@@ -0,0 +1,58 @@
+package net.bytebuddy.description.modifier;
+
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Determines the ownership of a field or method, i.e. if a member is defined in as {@code static}
+ * and belongs to a class or in contrast to an instance.
+ */
+public enum Ownership implements ModifierContributor.ForField, ModifierContributor.ForMethod, ModifierContributor.ForType {
+
+ /**
+ * Modifier for a instance ownership of a type member. (This is the default modifier.)
+ */
+ MEMBER(EMPTY_MASK),
+
+ /**
+ * Modifier for type ownership of a type member.
+ */
+ STATIC(Opcodes.ACC_STATIC);
+
+ /**
+ * The mask the modifier contributor.
+ */
+ private final int mask;
+
+ /**
+ * Creates a new ownership representation.
+ *
+ * @param mask The modifier mask of this instance.
+ */
+ Ownership(int mask) {
+ this.mask = mask;
+ }
+
+ @Override
+ public int getMask() {
+ return mask;
+ }
+
+ @Override
+ public int getRange() {
+ return Opcodes.ACC_STATIC;
+ }
+
+ @Override
+ public boolean isDefault() {
+ return this == MEMBER;
+ }
+
+ /**
+ * Checks if the current state describes a {@code static} member.
+ *
+ * @return {@code true} if this ownership representation represents a {@code static} member.
+ */
+ public boolean isStatic() {
+ return this == STATIC;
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/ParameterManifestation.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/ParameterManifestation.java
new file mode 100644
index 0000000..ef15923
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/ParameterManifestation.java
@@ -0,0 +1,57 @@
+package net.bytebuddy.description.modifier;
+
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Indicates whether a parameter was denoted as {@code final} or not.
+ */
+public enum ParameterManifestation implements ModifierContributor.ForParameter {
+
+ /**
+ * A non-final parameter. (This is the default modifier.)
+ */
+ PLAIN(EMPTY_MASK),
+
+ /**
+ * A final parameter.
+ */
+ FINAL(Opcodes.ACC_FINAL);
+
+ /**
+ * The mask of this parameter manifestation.
+ */
+ private final int mask;
+
+ /**
+ * Creates a new parameter.
+ *
+ * @param mask The mask of this parameter.
+ */
+ ParameterManifestation(int mask) {
+ this.mask = mask;
+ }
+
+ @Override
+ public int getMask() {
+ return mask;
+ }
+
+ @Override
+ public int getRange() {
+ return Opcodes.ACC_FINAL;
+ }
+
+ @Override
+ public boolean isDefault() {
+ return this == PLAIN;
+ }
+
+ /**
+ * Checks if this instance represents a final state.
+ *
+ * @return {@code true} if this instance represents a final state.
+ */
+ public boolean isFinal() {
+ return this == FINAL;
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/ProvisioningState.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/ProvisioningState.java
new file mode 100644
index 0000000..56375d3
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/ProvisioningState.java
@@ -0,0 +1,57 @@
+package net.bytebuddy.description.modifier;
+
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Describes if a method parameter is mandated, i.e. not explicitly specified in the source code.
+ */
+public enum ProvisioningState implements ModifierContributor.ForParameter {
+
+ /**
+ * Defines a parameter to not be mandated. (This is the default modifier.)
+ */
+ PLAIN(EMPTY_MASK),
+
+ /**
+ * Defines a parameter to be mandated.
+ */
+ MANDATED(Opcodes.ACC_MANDATED);
+
+ /**
+ * The mask of this provisioning state.
+ */
+ private final int mask;
+
+ /**
+ * Creates a new provisioning state.
+ *
+ * @param mask The mask of this provisioning state.
+ */
+ ProvisioningState(int mask) {
+ this.mask = mask;
+ }
+
+ @Override
+ public int getMask() {
+ return mask;
+ }
+
+ @Override
+ public int getRange() {
+ return Opcodes.ACC_MANDATED;
+ }
+
+ @Override
+ public boolean isDefault() {
+ return this == PLAIN;
+ }
+
+ /**
+ * Checks if this instance represents a mandated parameter or not.
+ *
+ * @return {@code true} if this instance represents a mandated parameter.
+ */
+ public boolean isMandated() {
+ return this == MANDATED;
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/SynchronizationState.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/SynchronizationState.java
new file mode 100644
index 0000000..b35305c
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/SynchronizationState.java
@@ -0,0 +1,57 @@
+package net.bytebuddy.description.modifier;
+
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Describes if a method is supposed to be synchronized.
+ */
+public enum SynchronizationState implements ModifierContributor.ForMethod {
+
+ /**
+ * Modifier for non-synchronized method. (This is the default modifier.)
+ */
+ PLAIN(EMPTY_MASK),
+
+ /**
+ * Modifier for a synchronized method.
+ */
+ SYNCHRONIZED(Opcodes.ACC_SYNCHRONIZED);
+
+ /**
+ * The mask the modifier contributor.
+ */
+ private final int mask;
+
+ /**
+ * Creates a new synchronization state representation.
+ *
+ * @param mask The modifier mask of this instance.
+ */
+ SynchronizationState(int mask) {
+ this.mask = mask;
+ }
+
+ @Override
+ public int getMask() {
+ return mask;
+ }
+
+ @Override
+ public int getRange() {
+ return Opcodes.ACC_SYNCHRONIZED;
+ }
+
+ @Override
+ public boolean isDefault() {
+ return this == PLAIN;
+ }
+
+ /**
+ * Checks if the current state describes the synchronized state.
+ *
+ * @return {@code true} if the current state is synchronized.
+ */
+ public boolean isSynchronized() {
+ return this == SYNCHRONIZED;
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/SyntheticState.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/SyntheticState.java
new file mode 100644
index 0000000..bf3c5fe
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/SyntheticState.java
@@ -0,0 +1,60 @@
+package net.bytebuddy.description.modifier;
+
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Defines if a type or member is supposed to be marked as synthetic.
+ */
+public enum SyntheticState implements ModifierContributor.ForType,
+ ModifierContributor.ForMethod,
+ ModifierContributor.ForField,
+ ModifierContributor.ForParameter {
+
+ /**
+ * Modifier for not marking a type member as synthetic. (This is the default modifier.)
+ */
+ PLAIN(EMPTY_MASK),
+
+ /**
+ * Modifier for marking a type member as synthetic.
+ */
+ SYNTHETIC(Opcodes.ACC_SYNTHETIC);
+
+ /**
+ * The mask of the modifier contributor.
+ */
+ private final int mask;
+
+ /**
+ * Creates a new synthetic state representation.
+ *
+ * @param mask The modifier mask of this instance.
+ */
+ SyntheticState(int mask) {
+ this.mask = mask;
+ }
+
+ @Override
+ public int getMask() {
+ return mask;
+ }
+
+ @Override
+ public int getRange() {
+ return Opcodes.ACC_SYNTHETIC;
+ }
+
+ @Override
+ public boolean isDefault() {
+ return this == PLAIN;
+ }
+
+ /**
+ * Checks if the current state describes the synthetic state.
+ *
+ * @return {@code true} if the current state is synthetic.
+ */
+ public boolean isSynthetic() {
+ return this == SYNTHETIC;
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/TypeManifestation.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/TypeManifestation.java
new file mode 100644
index 0000000..29c05d5
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/TypeManifestation.java
@@ -0,0 +1,99 @@
+package net.bytebuddy.description.modifier;
+
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Describes a type's manifestation, i.e. if a type is final, abstract, an interface or neither.
+ */
+public enum TypeManifestation implements ModifierContributor.ForType {
+
+ /**
+ * Modifier for a non-final, non-abstract, non-interface, non-enum type. (This is the default modifier.)
+ */
+ PLAIN(EMPTY_MASK),
+
+ /**
+ * Modifier for a final class.
+ */
+ FINAL(Opcodes.ACC_FINAL),
+
+ /**
+ * Modifier for an abstract class.
+ */
+ ABSTRACT(Opcodes.ACC_ABSTRACT),
+
+ /**
+ * Modifier for an interface.
+ */
+ INTERFACE(Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT),
+
+ /**
+ * Modifier for an annotation.
+ */
+ ANNOTATION(Opcodes.ACC_ANNOTATION | Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT);
+
+ /**
+ * The mask the modifier contributor.
+ */
+ private final int mask;
+
+ /**
+ * Creates a new type manifestation.
+ *
+ * @param mask The modifier mask of this instance.
+ */
+ TypeManifestation(int mask) {
+ this.mask = mask;
+ }
+
+ @Override
+ public int getMask() {
+ return mask;
+ }
+
+ @Override
+ public int getRange() {
+ return Opcodes.ACC_FINAL | Opcodes.ACC_ABSTRACT | Opcodes.ACC_INTERFACE | Opcodes.ACC_ANNOTATION;
+ }
+
+ @Override
+ public boolean isDefault() {
+ return this == PLAIN;
+ }
+
+ /**
+ * Returns {@code true} if a type represents a {@code final} type.
+ *
+ * @return {@code true} if a type represents a {@code final} type.
+ */
+ public boolean isFinal() {
+ return (mask & Opcodes.ACC_FINAL) != 0;
+ }
+
+ /**
+ * Returns {@code true} if a type represents an {@code abstract} type but not an interface type.
+ *
+ * @return {@code true} if a type represents an {@code abstract} type but not an interface type.
+ */
+ public boolean isAbstract() {
+ return (mask & Opcodes.ACC_ABSTRACT) != 0 && !isInterface();
+ }
+
+ /**
+ * Returns {@code true} if a type represents an interface type.
+ *
+ * @return {@code true} if a type represents an interface type.
+ */
+ public boolean isInterface() {
+ return (mask & Opcodes.ACC_INTERFACE) != 0;
+ }
+
+ /**
+ * Returns {@code true} if a type represents an annotation type.
+ *
+ * @return {@code true} if a type represents an annotation type.
+ */
+ public boolean isAnnotation() {
+ return (mask & Opcodes.ACC_ANNOTATION) != 0;
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/Visibility.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/Visibility.java
new file mode 100644
index 0000000..78ba03e
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/Visibility.java
@@ -0,0 +1,119 @@
+package net.bytebuddy.description.modifier;
+
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Describes a type's, field's or a method's visibility.
+ */
+public enum Visibility implements ModifierContributor.ForType, ModifierContributor.ForMethod, ModifierContributor.ForField {
+
+ /**
+ * A modifier contributor for {@code public} visibility.
+ */
+ PUBLIC(Opcodes.ACC_PUBLIC),
+
+ /**
+ * Modifier for a package-private visibility. (This is the default modifier.)
+ */
+ PACKAGE_PRIVATE(EMPTY_MASK),
+
+ /**
+ * A modifier contributor for {@code protected} visibility.
+ */
+ PROTECTED(Opcodes.ACC_PROTECTED),
+
+ /**
+ * A modifier contributor for {@code private} visibility.
+ */
+ PRIVATE(Opcodes.ACC_PRIVATE);
+
+ /**
+ * The mask the modifier contributor.
+ */
+ private final int mask;
+
+ /**
+ * Creates a new visibility representation.
+ *
+ * @param mask The modifier mask of this instance.
+ */
+ Visibility(int mask) {
+ this.mask = mask;
+ }
+
+ @Override
+ public int getMask() {
+ return mask;
+ }
+
+ @Override
+ public int getRange() {
+ return Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE;
+ }
+
+ @Override
+ public boolean isDefault() {
+ return this == PACKAGE_PRIVATE;
+ }
+
+ /**
+ * Returns {@code true} if this instance describes {@code public} visibility.
+ *
+ * @return {@code true} if this instance describes {@code public} visibility.
+ */
+ public boolean isPublic() {
+ return (mask & Opcodes.ACC_PUBLIC) != 0;
+ }
+
+ /**
+ * Returns {@code true} if this instance describes {@code protected} visibility.
+ *
+ * @return {@code true} if this instance describes {@code protected} visibility.
+ */
+ public boolean isProtected() {
+ return (mask & Opcodes.ACC_PROTECTED) != 0;
+ }
+
+ /**
+ * Returns {@code true} if this instance describes package-private visibility.
+ *
+ * @return {@code true} if this instance describes package-private visibility.
+ */
+ public boolean isPackagePrivate() {
+ return !(isPublic() || isPrivate() || isProtected());
+ }
+
+ /**
+ * Returns {@code true} if this instance describes {@code private} visibility.
+ *
+ * @return {@code true} if this instance describes {@code private} visibility.
+ */
+ public boolean isPrivate() {
+ return (mask & Opcodes.ACC_PRIVATE) != 0;
+ }
+
+ /**
+ * Expands the visibility to be at least as high as this visibility and the provided visibility.
+ *
+ * @param visibility A visibility to compare against.
+ * @return A visibility that is as least as high as this and the supplied visibility.
+ */
+ public Visibility expandTo(Visibility visibility) {
+ switch (visibility) {
+ case PUBLIC:
+ return PUBLIC;
+ case PROTECTED:
+ return this == PUBLIC
+ ? PUBLIC
+ : visibility;
+ case PACKAGE_PRIVATE:
+ return this == PRIVATE
+ ? PACKAGE_PRIVATE
+ : this;
+ case PRIVATE:
+ return this;
+ default:
+ throw new IllegalStateException("Unexpected visibility: " + visibility);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/package-info.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/package-info.java
new file mode 100644
index 0000000..e4cd766
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/modifier/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * The modifier package contains high-level and type-safe descriptions of Java modifiers.
+ */
+package net.bytebuddy.description.modifier;
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/package-info.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/package-info.java
new file mode 100644
index 0000000..1c22152
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * Classes of this package allow the representation of Java classes, their member and their meta data. Using the
+ * reflection API, it would be required to load classes but by using these descriptions, it is possible to
+ * represent byte code elements without pripor loading.
+ */
+package net.bytebuddy.description;
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/type/PackageDescription.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/type/PackageDescription.java
new file mode 100644
index 0000000..b73b169
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/type/PackageDescription.java
@@ -0,0 +1,134 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.description.NamedElement;
+import net.bytebuddy.description.annotation.AnnotationSource;
+import net.bytebuddy.description.annotation.AnnotationList;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * A package description represents a Java package.
+ */
+public interface PackageDescription extends NamedElement.WithRuntimeName, AnnotationSource {
+
+ /**
+ * The name of a Java class representing a package description.
+ */
+ String PACKAGE_CLASS_NAME = "package-info";
+
+ /**
+ * The modifiers of a Java class representing a package description.
+ */
+ int PACKAGE_MODIFIERS = Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT | Opcodes.ACC_SYNTHETIC;
+
+ /**
+ * Represents any undefined property of a type description that is instead represented as {@code null} in order
+ * to resemble the Java reflection API which returns {@code null} and is intuitive to many Java developers.
+ */
+ PackageDescription UNDEFINED = null;
+
+ /**
+ * Checks if this package contains the provided type.
+ *
+ * @param typeDescription The type to examine.
+ * @return {@code true} if the given type contains the provided type.
+ */
+ boolean contains(TypeDescription typeDescription);
+
+ /**
+ * An abstract base implementation of a package description.
+ */
+ abstract class AbstractBase implements PackageDescription {
+
+ @Override
+ public String getInternalName() {
+ return getName().replace('.', '/');
+ }
+
+ @Override
+ public String getActualName() {
+ return getName();
+ }
+
+ @Override
+ public boolean contains(TypeDescription typeDescription) {
+ return this.equals(typeDescription.getPackage());
+ }
+
+ @Override
+ public int hashCode() {
+ return getName().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other instanceof PackageDescription
+ && getName().equals(((PackageDescription) other).getName());
+ }
+
+ @Override
+ public String toString() {
+ return "package " + getName();
+ }
+ }
+
+ /**
+ * A simple implementation of a package without annotations.
+ */
+ class Simple extends AbstractBase {
+
+ /**
+ * The name of the package.
+ */
+ private final String name;
+
+ /**
+ * Creates a new simple package.
+ *
+ * @param name The name of the package.
+ */
+ public Simple(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return new AnnotationList.Empty();
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+ }
+
+ /**
+ * Represents a loaded {@link java.lang.Package} wrapped as a
+ * {@link PackageDescription}.
+ */
+ class ForLoadedPackage extends AbstractBase {
+
+ /**
+ * The represented package.
+ */
+ private final Package aPackage;
+
+ /**
+ * Creates a new loaded package representation.
+ *
+ * @param aPackage The represented package.
+ */
+ public ForLoadedPackage(Package aPackage) {
+ this.aPackage = aPackage;
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return new AnnotationList.ForLoadedAnnotations(aPackage.getDeclaredAnnotations());
+ }
+
+ @Override
+ public String getName() {
+ return aPackage.getName();
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/type/TypeDefinition.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/type/TypeDefinition.java
new file mode 100644
index 0000000..c72b40b
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/type/TypeDefinition.java
@@ -0,0 +1,286 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.description.ModifierReviewable;
+import net.bytebuddy.description.NamedElement;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.implementation.bytecode.StackSize;
+
+import java.lang.reflect.*;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Implementations define a type, either as a {@link TypeDescription} or as a {@link TypeDescription.Generic}.
+ */
+public interface TypeDefinition extends NamedElement, ModifierReviewable.ForTypeDefinition, Iterable<TypeDefinition> {
+
+ /**
+ * Returns this type definition as a generic type.
+ *
+ * @return This type definition represented as a generic type.
+ */
+ TypeDescription.Generic asGenericType();
+
+ /**
+ * Returns the erasure of this type. Wildcard types ({@link TypeDescription.Generic.Sort#WILDCARD})
+ * do not have a well-defined erasure and cause an {@link IllegalStateException} to be thrown.
+ *
+ * @return The erasure of this type.
+ */
+ TypeDescription asErasure();
+
+ /**
+ * Returns the super class of this type. A super type is only defined for non-generic types ({@link Sort#NON_GENERIC}),
+ * parameterized types ({@link Sort#PARAMETERIZED}) or generic array types ({@link Sort#GENERIC_ARRAY}) types. Interface types
+ * and the {@link Object} class do not define a super class where {@code null} is returned. Array types define {@link Object}
+ * as their direct super class.
+ *
+ * @return The super class of this type or {@code null} if no super class exists for this type.
+ */
+ TypeDescription.Generic getSuperClass();
+
+ /**
+ * Returns the interfaces that this type implements. A super type is only defined for non-generic types ({@link Sort#NON_GENERIC}),
+ * parameterized types ({@link Sort#PARAMETERIZED}) or generic array types ({@link Sort#GENERIC_ARRAY}) types.
+ *
+ * @return The interfaces that this type implements.
+ */
+ TypeList.Generic getInterfaces();
+
+ /**
+ * Returns the fields that this type declares. A super type is only defined for non-generic types ({@link Sort#NON_GENERIC}),
+ * parameterized types ({@link Sort#PARAMETERIZED}) or generic array types ({@link Sort#GENERIC_ARRAY}) types. Generic array
+ * types never define fields and the returned list is always empty for such types.
+ *
+ * @return The fields that this type declares. A super type is only defined for non-generic types ({@link Sort#NON_GENERIC}),
+ * parameterized types ({@link Sort#PARAMETERIZED}) or generic array types ({@link Sort#GENERIC_ARRAY}) types. Generic array
+ * types never define methods and the returned list is always empty for such types.
+ */
+ FieldList<?> getDeclaredFields();
+
+ /**
+ * Returns the methods that this type declares.
+ *
+ * @return The methods that this type declares.
+ */
+ MethodList<?> getDeclaredMethods();
+
+ /**
+ * <p>
+ * Returns the component type of this type.
+ * </p>
+ * <p>
+ * Only non-generic types ({@link TypeDescription.Generic.Sort#NON_GENERIC}) and generic array types
+ * {@link TypeDescription.Generic.Sort#GENERIC_ARRAY}) define a component type. For other
+ * types, an {@link IllegalStateException} is thrown.
+ * </p>
+ *
+ * @return The component type of this type or {@code null} if this type does not represent an array type.
+ */
+ TypeDefinition getComponentType();
+
+ /**
+ * Returns the sort of the generic type this instance represents.
+ *
+ * @return The sort of the generic type.
+ */
+ Sort getSort();
+
+ /**
+ * Returns the name of the type. For generic types, this name is their {@link Object#toString()} representations. For a non-generic
+ * type, it is the fully qualified binary name of the type.
+ *
+ * @return The name of this type.
+ */
+ String getTypeName();
+
+ /**
+ * Returns the size of the type described by this instance. Wildcard types
+ * ({@link TypeDescription.Generic.Sort#WILDCARD} do not have a well-defined a stack size and
+ * cause an {@link IllegalStateException} to be thrown.
+ *
+ * @return The size of the type described by this instance.
+ */
+ StackSize getStackSize();
+
+ /**
+ * Checks if the type described by this entity is an array.
+ *
+ * @return {@code true} if this type description represents an array.
+ */
+ boolean isArray();
+
+ /**
+ * Checks if the type described by this entity is a primitive type.
+ *
+ * @return {@code true} if this type description represents a primitive type.
+ */
+ boolean isPrimitive();
+
+ /**
+ * Checks if the type described by this instance represents {@code type}.
+ *
+ * @param type The type of interest.
+ * @return {@code true} if the type described by this instance represents {@code type}.
+ */
+ boolean represents(Type type);
+
+ /**
+ * Represents a {@link TypeDescription.Generic}'s form.
+ */
+ enum Sort {
+
+ /**
+ * Represents a non-generic type.
+ */
+ NON_GENERIC,
+
+ /**
+ * Represents a generic array type.
+ */
+ GENERIC_ARRAY,
+
+ /**
+ * Represents a parameterized type.
+ */
+ PARAMETERIZED,
+
+ /**
+ * Represents a wildcard type.
+ */
+ WILDCARD,
+
+ /**
+ * Represents a type variable that is attached to a {@link net.bytebuddy.description.TypeVariableSource}.
+ */
+ VARIABLE,
+
+ /**
+ * Represents a type variable that is merely symbolic and is not attached to a {@link net.bytebuddy.description.TypeVariableSource}
+ * and does not defined bounds.
+ */
+ VARIABLE_SYMBOLIC;
+
+ /**
+ * Describes a loaded generic type as a {@link TypeDescription.Generic}.
+ *
+ * @param type The type to describe.
+ * @return A description of the provided generic type.
+ */
+ public static TypeDescription.Generic describe(Type type) {
+ return describe(type, TypeDescription.Generic.AnnotationReader.NoOp.INSTANCE);
+ }
+
+ /**
+ * Describes the generic type while using the supplied annotation reader for resolving type annotations if this
+ * language feature is available on the current JVM.
+ *
+ * @param type The type to describe.
+ * @param annotationReader The annotation reader for extracting type annotations.
+ * @return A description of the provided generic annotated type.
+ */
+ protected static TypeDescription.Generic describe(Type type, TypeDescription.Generic.AnnotationReader annotationReader) {
+ if (type instanceof Class<?>) {
+ return new TypeDescription.Generic.OfNonGenericType.ForLoadedType((Class<?>) type, annotationReader);
+ } else if (type instanceof GenericArrayType) {
+ return new TypeDescription.Generic.OfGenericArray.ForLoadedType((GenericArrayType) type, annotationReader);
+ } else if (type instanceof ParameterizedType) {
+ return new TypeDescription.Generic.OfParameterizedType.ForLoadedType((ParameterizedType) type, annotationReader);
+ } else if (type instanceof TypeVariable) {
+ return new TypeDescription.Generic.OfTypeVariable.ForLoadedType((TypeVariable<?>) type, annotationReader);
+ } else if (type instanceof WildcardType) {
+ return new TypeDescription.Generic.OfWildcardType.ForLoadedType((WildcardType) type, annotationReader);
+ } else {
+ throw new IllegalArgumentException("Unknown type: " + type);
+ }
+ }
+
+ /**
+ * Checks if this type sort represents a non-generic type.
+ *
+ * @return {@code true} if this sort form represents a non-generic.
+ */
+ public boolean isNonGeneric() {
+ return this == NON_GENERIC;
+ }
+
+ /**
+ * Checks if this type sort represents a parameterized type.
+ *
+ * @return {@code true} if this sort form represents a parameterized type.
+ */
+ public boolean isParameterized() {
+ return this == PARAMETERIZED;
+ }
+
+ /**
+ * Checks if this type sort represents a generic array.
+ *
+ * @return {@code true} if this type sort represents a generic array.
+ */
+ public boolean isGenericArray() {
+ return this == GENERIC_ARRAY;
+ }
+
+ /**
+ * Checks if this type sort represents a wildcard.
+ *
+ * @return {@code true} if this type sort represents a wildcard.
+ */
+ public boolean isWildcard() {
+ return this == WILDCARD;
+ }
+
+ /**
+ * Checks if this type sort represents a type variable of any form.
+ *
+ * @return {@code true} if this type sort represents an attached type variable.
+ */
+ public boolean isTypeVariable() {
+ return this == VARIABLE || this == VARIABLE_SYMBOLIC;
+ }
+ }
+
+ /**
+ * An iterator that iterates over a type's class hierarchy.
+ */
+ class SuperClassIterator implements Iterator<TypeDefinition> {
+
+ /**
+ * The next class to represent.
+ */
+ private TypeDefinition nextClass;
+
+ /**
+ * Creates a new iterator.
+ *
+ * @param initialType The initial type of this iterator.
+ */
+ public SuperClassIterator(TypeDefinition initialType) {
+ nextClass = initialType;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return nextClass != null;
+ }
+
+ @Override
+ public TypeDefinition next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException("End of type hierarchy");
+ }
+ try {
+ return nextClass;
+ } finally {
+ nextClass = nextClass.getSuperClass();
+ }
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("remove");
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/type/TypeDescription.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/type/TypeDescription.java
new file mode 100644
index 0000000..0793d26
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/type/TypeDescription.java
@@ -0,0 +1,8055 @@
+package net.bytebuddy.description.type;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.description.ByteCodeElement;
+import net.bytebuddy.description.ModifierReviewable;
+import net.bytebuddy.description.TypeVariableSource;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.annotation.AnnotationSource;
+import net.bytebuddy.description.enumeration.EnumerationDescription;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.dynamic.TargetType;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.utility.CompoundList;
+import net.bytebuddy.utility.JavaType;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.signature.SignatureVisitor;
+import org.objectweb.asm.signature.SignatureWriter;
+
+import java.io.Serializable;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.reflect.*;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.*;
+
+import static net.bytebuddy.matcher.ElementMatchers.is;
+
+/**
+ * Implementations of this interface represent a Java type, i.e. a class or interface. Instances of this interface always
+ * represent non-generic types of sort {@link Generic.Sort#NON_GENERIC}.
+ */
+public interface TypeDescription extends TypeDefinition, ByteCodeElement, TypeVariableSource {
+
+ /**
+ * A representation of the {@link java.lang.Object} type.
+ */
+ TypeDescription OBJECT = new ForLoadedType(Object.class);
+
+ /**
+ * A representation of the {@link java.lang.String} type.
+ */
+ TypeDescription STRING = new ForLoadedType(String.class);
+
+ /**
+ * A representation of the {@link java.lang.Class} type.
+ */
+ TypeDescription CLASS = new ForLoadedType(Class.class);
+
+ /**
+ * A representation of the {@link java.lang.Throwable} type.
+ */
+ TypeDescription THROWABLE = new ForLoadedType(Throwable.class);
+
+ /**
+ * A representation of the {@code void} non-type.
+ */
+ TypeDescription VOID = new ForLoadedType(void.class);
+
+ /**
+ * A list of interfaces that are implicitly implemented by any array type.
+ */
+ TypeList.Generic ARRAY_INTERFACES = new TypeList.Generic.ForLoadedTypes(Cloneable.class, Serializable.class);
+
+ /**
+ * Represents any undefined property representing a type description that is instead represented as {@code null} in order
+ * to resemble the Java reflection API which returns {@code null} and is intuitive to many Java developers.
+ */
+ TypeDescription UNDEFINED = null;
+
+ @Override
+ FieldList<FieldDescription.InDefinedShape> getDeclaredFields();
+
+ @Override
+ MethodList<MethodDescription.InDefinedShape> getDeclaredMethods();
+
+ /**
+ * Checks if {@code value} is an instance of the type represented by this instance.
+ *
+ * @param value The object of interest.
+ * @return {@code true} if the object is an instance of the type described by this instance.
+ */
+ boolean isInstance(Object value);
+
+ /**
+ * Checks if this type is assignable from the type described by this instance, for example for
+ * {@code class Foo} and {@code class Bar extends Foo}, this method would return {@code true} for
+ * {@code Foo.class.isAssignableFrom(Bar.class)}.
+ *
+ * @param type The type of interest.
+ * @return {@code true} if this type is assignable from {@code type}.
+ */
+ boolean isAssignableFrom(Class<?> type);
+
+ /**
+ * Checks if this type is assignable from the type described by this instance, for example for
+ * {@code class Foo} and {@code class Bar extends Foo}, this method would return {@code true} for
+ * {@code Foo.class.isAssignableFrom(Bar.class)}.
+ * <p> </p>
+ * Implementations of this methods are allowed to delegate to
+ * {@link TypeDescription#isAssignableFrom(Class)}
+ *
+ * @param typeDescription The type of interest.
+ * @return {@code true} if this type is assignable from {@code type}.
+ */
+ boolean isAssignableFrom(TypeDescription typeDescription);
+
+ /**
+ * Checks if this type is assignable from the type described by this instance, for example for
+ * {@code class Foo} and {@code class Bar extends Foo}, this method would return {@code true} for
+ * {@code Bar.class.isAssignableTo(Foo.class)}.
+ *
+ * @param type The type of interest.
+ * @return {@code true} if this type is assignable to {@code type}.
+ */
+ boolean isAssignableTo(Class<?> type);
+
+ /**
+ * Checks if this type is assignable from the type described by this instance, for example for
+ * {@code class Foo} and {@code class Bar extends Foo}, this method would return {@code true} for
+ * {@code Bar.class.isAssignableFrom(Foo.class)}.
+ * <p> </p>
+ * Implementations of this methods are allowed to delegate to
+ * {@link TypeDescription#isAssignableTo(Class)}
+ *
+ * @param typeDescription The type of interest.
+ * @return {@code true} if this type is assignable to {@code type}.
+ */
+ boolean isAssignableTo(TypeDescription typeDescription);
+
+ @Override
+ TypeDescription getComponentType();
+
+ @Override
+ TypeDescription getDeclaringType();
+
+ /**
+ * Returns a list of types that are declared by this type excluding anonymous classes.
+ *
+ * @return A list of types that are declared within this type.
+ */
+ TypeList getDeclaredTypes();
+
+ /**
+ * Returns a description of the method that encloses this type. If this method is not enclosed by any type or is
+ * enclosed by the type initializer, {@code null} is returned by this method.
+ *
+ * @return A description of the enclosing method of this type or {@code null} if there is no such method.
+ */
+ MethodDescription getEnclosingMethod();
+
+ /**
+ * Returns a description of this type's enclosing type if any.
+ *
+ * @return A description of the enclosing type of this type or {@code null} if there is no such type.
+ */
+ TypeDescription getEnclosingType();
+
+ /**
+ * Returns the type's actual modifiers as present in the class file. For example, a type cannot be {@code private}.
+ * but it modifiers might reflect this property nevertheless if a class was defined as a private inner class. The
+ * returned modifiers take also into account if the type is marked as {@link Deprecated}. Anonymous classes that are
+ * enclosed in a static method or the type initializer are additionally marked as {@code final} as it is also done
+ * by the Java compiler.
+ *
+ * @param superFlag {@code true} if the modifier's super flag should be set.
+ * @return The type's actual modifiers.
+ */
+ int getActualModifiers(boolean superFlag);
+
+ /**
+ * Returns the simple internalName of this type.
+ *
+ * @return The simple internalName of this type.
+ */
+ String getSimpleName();
+
+ /**
+ * Returns the canonical name of this type if it exists.
+ *
+ * @return The canonical name of this type. Might be {@code null}.
+ */
+ String getCanonicalName();
+
+ /**
+ * Checks if this type description represents an anonymous type.
+ *
+ * @return {@code true} if this type description represents an anonymous type.
+ */
+ boolean isAnonymousClass();
+
+ /**
+ * Checks if this type description represents a local type.
+ *
+ * @return {@code true} if this type description represents a local type.
+ */
+ boolean isLocalClass();
+
+ /**
+ * Checks if this type description represents a member type.
+ *
+ * @return {@code true} if this type description represents a member type.
+ */
+ boolean isMemberClass();
+
+ /**
+ * Returns the package internalName of the type described by this instance.
+ *
+ * @return The package internalName of the type described by this instance.
+ */
+ PackageDescription getPackage();
+
+ /**
+ * Returns the annotations that this type declares or inherits from super types.
+ *
+ * @return A list of all inherited annotations.
+ */
+ AnnotationList getInheritedAnnotations();
+
+ /**
+ * Checks if two types are defined in the same package.
+ *
+ * @param typeDescription The type of interest.
+ * @return {@code true} if this type and the given type are in the same package.
+ */
+ boolean isSamePackage(TypeDescription typeDescription);
+
+ /**
+ * Checks if instances of this type can be stored in the constant pool of a class. Note that any primitive
+ * type that is smaller than an {@code int} cannot be stored in the constant pool as those types are represented
+ * as {@code int} values internally.
+ *
+ * @return {@code true} if instances of this type can be stored in the constant pool of a class.
+ */
+ boolean isConstantPool();
+
+ /**
+ * Checks if this type represents a wrapper type for a primitive type. The {@link java.lang.Void} type is
+ * not considered to be a wrapper type.
+ *
+ * @return {@code true} if this type represents a wrapper type.
+ */
+ boolean isPrimitiveWrapper();
+
+ /**
+ * Checks if instances of this type can be returned from an annotation method.
+ *
+ * @return {@code true} if instances of this type can be returned from an annotation method.
+ */
+ boolean isAnnotationReturnType();
+
+ /**
+ * Checks if instances of this type can be used for describing an annotation value.
+ *
+ * @return {@code true} if instances of this type can be used for describing an annotation value.
+ */
+ boolean isAnnotationValue();
+
+ /**
+ * Checks if instances of this type can be used for describing the given annotation value.
+ *
+ * @param value The value that is supposed to describe the annotation value for this instance.
+ * @return {@code true} if instances of this type can be used for describing the given annotation value..
+ */
+ boolean isAnnotationValue(Object value);
+
+ /**
+ * Checks if this type represents a class that is a place holder for a package description.
+ *
+ * @return {@code true} if this type represents a package description.
+ */
+ boolean isPackageType();
+
+ /**
+ * Returns the amount of outer classes this type defines. If this type is not an inner type of another class, {@code 0} is returned.
+ *
+ * @return The number of outer classes relatively to this type.
+ */
+ int getSegmentCount();
+
+ /**
+ * Returns a description of this type that represents this type as a boxed type for primitive types, unless its {@code void}.
+ *
+ * @return A description of this type in its boxed form.
+ */
+ TypeDescription asBoxed();
+
+ /**
+ * Returns a description of this type that represents this type as an unboxed type for boxing types, unless its {@link Void}.
+ *
+ * @return A description of this type in its unboxed form.
+ */
+ TypeDescription asUnboxed();
+
+ /**
+ * <p>
+ * Represents a generic type of the Java programming language. A non-generic {@link TypeDescription} is considered to be
+ * a specialization of a generic type.
+ * </p>
+ * <p>
+ * Note that annotations that are declared on an annotated type refer to any type annotations that are declared by this
+ * generic type. For reading annotations of the erasure type, {@link TypeDefinition#asErasure()} must be called before.
+ * </p>
+ */
+ interface Generic extends TypeDefinition, AnnotationSource {
+
+ /**
+ * A representation of the {@link Object} type.
+ */
+ Generic OBJECT = new OfNonGenericType.ForLoadedType(Object.class);
+
+ /**
+ * A representation of the {@code void} non-type.
+ */
+ Generic VOID = new OfNonGenericType.ForLoadedType(void.class);
+
+ /**
+ * A representation of the {@link Annotation} type.
+ */
+ Generic ANNOTATION = new OfNonGenericType.ForLoadedType(Annotation.class);
+
+ /**
+ * Represents any undefined property representing a generic type description that is instead represented as {@code null} in order
+ * to resemble the Java reflection API which returns {@code null} and is intuitive to many Java developers.
+ */
+ Generic UNDEFINED = null;
+
+ /**
+ * Returns this type as a raw type. This ressembles calling {@code asErasure().asGenericType()}.
+ *
+ * @return This type as a raw type.
+ */
+ Generic asRawType();
+
+ /**
+ * <p>
+ * Returns the upper bounds of this type. Any type with a well-defined upper bound is bound by at least one type. If no such
+ * type is defined, the bound is implicitly {@link Object}.
+ * </p>
+ * <p>
+ * Only non-symbolic type variables ({@link net.bytebuddy.description.type.TypeDefinition.Sort#VARIABLE}, and wildcard types
+ * ({@link net.bytebuddy.description.type.TypeDefinition.Sort#WILDCARD}) have well-defined upper bounds. For other
+ * types, an {@link IllegalStateException} is thrown.
+ * </p>
+ *
+ * @return The upper bounds of this type.
+ */
+ TypeList.Generic getUpperBounds();
+
+ /**
+ * <p>
+ * Returns the lower bounds of this type.
+ * </p>
+ * <p>
+ * Only wildcard types ({@link Sort#WILDCARD}) define a lower bound. For other
+ * types, an {@link IllegalStateException} is thrown.
+ * </p>
+ *
+ * @return The lower bounds of this type.
+ */
+ TypeList.Generic getLowerBounds();
+
+ /**
+ * <p>
+ * Returns the type arguments of this type.
+ * </p>
+ * <p>
+ * Parameters are only well-defined for parameterized types ({@link Sort#PARAMETERIZED}).
+ * For all other types, this method throws an {@link IllegalStateException}.
+ * </p>
+ *
+ * @return A list of this type's type parameters.
+ */
+ TypeList.Generic getTypeArguments();
+
+ /**
+ * <p>
+ * Returns the owner type of this type. A type's owner type describes a nested type's declaring type.
+ * If it exists, the returned type can be a non-generic or parameterized type. If a class has no
+ * declaring type, {@code null} is returned.
+ * </p>
+ * <p>
+ * An owner type is only well-defined for parameterized types ({@link Sort#PARAMETERIZED}),
+ * for non-generic types ({@link Sort#NON_GENERIC}) and for generic arrays ({@link Sort#GENERIC_ARRAY}).
+ * For all other types, this method throws an {@link IllegalStateException}.
+ * </p>
+ *
+ * @return This type's owner type or {@code null} if no owner type exists.
+ */
+ Generic getOwnerType();
+
+ /**
+ * <p>
+ * Returns the parameter binding of the supplied type variable.
+ * </p>
+ * <p>
+ * This method must only be called for parameterized types ({@link Sort#PARAMETERIZED}). For all other types,
+ * this method throws an {@link IllegalStateException}.
+ * </p>
+ *
+ * @param typeVariable The type variable for which a value should be located.
+ * @return The value that is bound to the supplied type variable or {@code null} if the type variable
+ * is not bound by this parameterized type.
+ */
+ Generic findBindingOf(Generic typeVariable);
+
+ /**
+ * Returns the source of this type variable. A type variable source is only well-defined for an attached type variable
+ * ({@link Sort#VARIABLE}. For other types, this method
+ * throws an {@link IllegalStateException}.
+ *
+ * @return This type's type variable source.
+ */
+ TypeVariableSource getTypeVariableSource();
+
+ /**
+ * Returns the symbol of this type variable. A symbol is only well-defined for type variables
+ * ({@link Sort#VARIABLE}, {@link Sort#VARIABLE_SYMBOLIC}). For other types, this method
+ * throws an {@link IllegalStateException}.
+ *
+ * @return This type's type variable symbol.
+ */
+ String getSymbol();
+
+ @Override
+ Generic getComponentType();
+
+ @Override
+ FieldList<FieldDescription.InGenericShape> getDeclaredFields();
+
+ @Override
+ MethodList<MethodDescription.InGenericShape> getDeclaredMethods();
+
+ /**
+ * Applies a visitor to this generic type description.
+ *
+ * @param visitor The visitor to apply.
+ * @param <T> The value that this visitor yields.
+ * @return The visitor's return value.
+ */
+ <T> T accept(Visitor<T> visitor);
+
+ /**
+ * A visitor that can be applied to a {@link Generic} for differentiating on the sort of the visited type.
+ *
+ * @param <T> The visitor's return value's type.
+ */
+ interface Visitor<T> {
+
+ /**
+ * Visits a generic array type ({@link Sort#GENERIC_ARRAY}).
+ *
+ * @param genericArray The generic array type.
+ * @return The visitor's return value.
+ */
+ T onGenericArray(Generic genericArray);
+
+ /**
+ * Visits a wildcard ({@link Sort#WILDCARD}).
+ *
+ * @param wildcard The wildcard.
+ * @return The visitor's return value.
+ */
+ T onWildcard(Generic wildcard);
+
+ /**
+ * Visits a parameterized type ({@link Sort#PARAMETERIZED}).
+ *
+ * @param parameterizedType The generic array type.
+ * @return The visitor's return value.
+ */
+ T onParameterizedType(Generic parameterizedType);
+
+ /**
+ * Visits a type variable ({@link Sort#VARIABLE}, {@link Sort#VARIABLE_SYMBOLIC}).
+ *
+ * @param typeVariable The generic array type.
+ * @return The visitor's return value.
+ */
+ T onTypeVariable(Generic typeVariable);
+
+ /**
+ * Visits a non-generic type ({@link Sort#NON_GENERIC}).
+ *
+ * @param typeDescription The non-generic type.
+ * @return The visitor's return value.
+ */
+ T onNonGenericType(Generic typeDescription);
+
+ /**
+ * A non-operational generic type visitor. Any visited type is returned in its existing form.
+ */
+ enum NoOp implements Visitor<Generic> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Generic onGenericArray(Generic genericArray) {
+ return genericArray;
+ }
+
+ @Override
+ public Generic onWildcard(Generic wildcard) {
+ return wildcard;
+ }
+
+ @Override
+ public Generic onParameterizedType(Generic parameterizedType) {
+ return parameterizedType;
+ }
+
+ @Override
+ public Generic onTypeVariable(Generic typeVariable) {
+ return typeVariable;
+ }
+
+ @Override
+ public Generic onNonGenericType(Generic typeDescription) {
+ return typeDescription;
+ }
+ }
+
+ /**
+ * A visitor that returns the erasure of any visited type. For wildcard types, an exception is thrown.
+ */
+ enum TypeErasing implements Visitor<Generic> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Generic onGenericArray(Generic genericArray) {
+ return genericArray.asRawType();
+ }
+
+ @Override
+ public Generic onWildcard(Generic wildcard) {
+ throw new IllegalArgumentException("Cannot erase a wildcard type: " + wildcard);
+ }
+
+ @Override
+ public Generic onParameterizedType(Generic parameterizedType) {
+ return parameterizedType.asRawType();
+ }
+
+ @Override
+ public Generic onTypeVariable(Generic typeVariable) {
+ return typeVariable.asRawType();
+ }
+
+ @Override
+ public Generic onNonGenericType(Generic typeDescription) {
+ return typeDescription.asRawType();
+ }
+ }
+
+ /**
+ * A visitor that strips all type annotations of all types.
+ */
+ enum AnnotationStripper implements Visitor<Generic> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Generic onGenericArray(Generic genericArray) {
+ return new OfGenericArray.Latent(genericArray.getComponentType().accept(this), Empty.INSTANCE);
+ }
+
+ @Override
+ public Generic onWildcard(Generic wildcard) {
+ return new OfWildcardType.Latent(wildcard.getUpperBounds().accept(this), wildcard.getLowerBounds().accept(this), Empty.INSTANCE);
+ }
+
+ @Override
+ public Generic onParameterizedType(Generic parameterizedType) {
+ Generic ownerType = parameterizedType.getOwnerType();
+ return new OfParameterizedType.Latent(parameterizedType.asErasure(),
+ ownerType == null
+ ? UNDEFINED
+ : ownerType.accept(this),
+ parameterizedType.getTypeArguments().accept(this),
+ Empty.INSTANCE);
+ }
+
+ @Override
+ public Generic onTypeVariable(Generic typeVariable) {
+ return new NonAnnotatedTypeVariable(typeVariable);
+ }
+
+ @Override
+ public Generic onNonGenericType(Generic typeDescription) {
+ return typeDescription.isArray()
+ ? new OfGenericArray.Latent(onNonGenericType(typeDescription.getComponentType()), Empty.INSTANCE)
+ : new OfNonGenericType.Latent(typeDescription.asErasure(), Empty.INSTANCE);
+ }
+
+ /**
+ * Representation of a type variable without annotations.
+ */
+ protected static class NonAnnotatedTypeVariable extends OfTypeVariable {
+
+ /**
+ * The represented type variable.
+ */
+ private final Generic typeVariable;
+
+ /**
+ * Creates a new non-annotated type variable.
+ *
+ * @param typeVariable The represented type variable.
+ */
+ protected NonAnnotatedTypeVariable(Generic typeVariable) {
+ this.typeVariable = typeVariable;
+ }
+
+ @Override
+ public TypeList.Generic getUpperBounds() {
+ return typeVariable.getUpperBounds();
+ }
+
+ @Override
+ public TypeVariableSource getTypeVariableSource() {
+ return typeVariable.getTypeVariableSource();
+ }
+
+ @Override
+ public String getSymbol() {
+ return typeVariable.getSymbol();
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return new AnnotationList.Empty();
+ }
+ }
+ }
+
+ /**
+ * A visitor that determines the direct assignability of a type to another generic type. This visitor only checks
+ * for strict assignability and does not perform any form of boxing or primitive type widening that are allowed
+ * in the Java language.
+ */
+ enum Assigner implements Visitor<Assigner.Dispatcher> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Dispatcher onGenericArray(Generic genericArray) {
+ return new Dispatcher.ForGenericArray(genericArray);
+ }
+
+ @Override
+ public Dispatcher onWildcard(Generic wildcard) {
+ throw new IllegalArgumentException("A wildcard is not a first level type: " + this);
+ }
+
+ @Override
+ public Dispatcher onParameterizedType(Generic parameterizedType) {
+ return new Dispatcher.ForParameterizedType(parameterizedType);
+ }
+
+ @Override
+ public Dispatcher onTypeVariable(Generic typeVariable) {
+ return new Dispatcher.ForTypeVariable(typeVariable);
+ }
+
+ @Override
+ public Dispatcher onNonGenericType(Generic typeDescription) {
+ return new Dispatcher.ForNonGenericType(typeDescription.asErasure());
+ }
+
+ /**
+ * A dispatcher that allows to check if the visited generic type is assignable to the supplied type.
+ */
+ public interface Dispatcher {
+
+ /**
+ * Checks if the represented type is a super type of the type that is supplied as an argument.
+ *
+ * @param typeDescription The type to check for being assignable to the represented type.
+ * @return {@code true} if the represented type is assignable to the supplied type.
+ */
+ boolean isAssignableFrom(Generic typeDescription);
+
+ /**
+ * An abstract base implementation of a dispatcher that forwards the decision to a visitor implementation.
+ */
+ abstract class AbstractBase implements Dispatcher, Visitor<Boolean> {
+
+ @Override
+ public boolean isAssignableFrom(Generic typeDescription) {
+ return typeDescription.accept(this);
+ }
+ }
+
+ /**
+ * A dispatcher for checking the assignability of a non-generic type.
+ */
+ @EqualsAndHashCode(callSuper = false)
+ class ForNonGenericType extends AbstractBase {
+
+ /**
+ * The description of the type to which another type is assigned.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new dispatcher of a non-generic type.
+ *
+ * @param typeDescription The description of the type to which another type is assigned.
+ */
+ protected ForNonGenericType(TypeDescription typeDescription) {
+ this.typeDescription = typeDescription;
+ }
+
+ @Override
+ public Boolean onGenericArray(Generic genericArray) {
+ return typeDescription.isArray()
+ ? genericArray.getComponentType().accept(new ForNonGenericType(typeDescription.getComponentType()))
+ : typeDescription.represents(Object.class) || TypeDescription.ARRAY_INTERFACES.contains(typeDescription.asGenericType());
+ }
+
+ @Override
+ public Boolean onWildcard(Generic wildcard) {
+ throw new IllegalArgumentException("A wildcard is not a first-level type: " + wildcard);
+ }
+
+ @Override
+ public Boolean onParameterizedType(Generic parameterizedType) {
+ if (typeDescription.equals(parameterizedType.asErasure())) {
+ return true;
+ }
+ Generic superClass = parameterizedType.getSuperClass();
+ if (superClass != null && isAssignableFrom(superClass)) {
+ return true;
+ }
+ for (Generic interfaceType : parameterizedType.getInterfaces()) {
+ if (isAssignableFrom(interfaceType)) {
+ return true;
+ }
+ }
+ return typeDescription.represents(Object.class);
+ }
+
+ @Override
+ public Boolean onTypeVariable(Generic typeVariable) {
+ for (Generic upperBound : typeVariable.getUpperBounds()) {
+ if (isAssignableFrom(upperBound)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public Boolean onNonGenericType(Generic typeDescription) {
+ return this.typeDescription.isAssignableFrom(typeDescription.asErasure());
+ }
+ }
+
+ /**
+ * A dispatcher for checking the assignability of a type variable.
+ */
+ @EqualsAndHashCode(callSuper = false)
+ class ForTypeVariable extends AbstractBase {
+
+ /**
+ * The description of the type variable to which another type is assigned.
+ */
+ private final Generic typeVariable;
+
+ /**
+ * Creates a new dispatcher of a type variable.
+ *
+ * @param typeVariable The description of the type variable to which another type is assigned.
+ */
+ protected ForTypeVariable(Generic typeVariable) {
+ this.typeVariable = typeVariable;
+ }
+
+ @Override
+ public Boolean onGenericArray(Generic genericArray) {
+ return false;
+ }
+
+ @Override
+ public Boolean onWildcard(Generic wildcard) {
+ throw new IllegalArgumentException("A wildcard is not a first-level type: " + wildcard);
+ }
+
+ @Override
+ public Boolean onParameterizedType(Generic parameterizedType) {
+ return false;
+ }
+
+ @Override
+ public Boolean onTypeVariable(Generic typeVariable) {
+ if (typeVariable.equals(this.typeVariable)) {
+ return true;
+ }
+ for (Generic upperBound : typeVariable.getUpperBounds()) {
+ if (isAssignableFrom(upperBound)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public Boolean onNonGenericType(Generic typeDescription) {
+ return false;
+ }
+ }
+
+ /**
+ * A dispatcher for checking the assignability of a parameterized type.
+ */
+ @EqualsAndHashCode(callSuper = false)
+ class ForParameterizedType extends AbstractBase {
+
+ /**
+ * The parameterized type to which another type is assigned.
+ */
+ private final Generic parameterizedType;
+
+ /**
+ * Creates a new dispatcher for checking the assignability of a parameterized type.
+ *
+ * @param parameterizedType The parameterized type to which another type is assigned.
+ */
+ protected ForParameterizedType(Generic parameterizedType) {
+ this.parameterizedType = parameterizedType;
+ }
+
+ @Override
+ public Boolean onGenericArray(Generic genericArray) {
+ return false;
+ }
+
+ @Override
+ public Boolean onWildcard(Generic wildcard) {
+ throw new IllegalArgumentException("A wildcard is not a first-level type: " + wildcard);
+ }
+
+ @Override
+ public Boolean onParameterizedType(Generic parameterizedType) {
+ if (this.parameterizedType.asErasure().equals(parameterizedType.asErasure())) {
+ Generic fromOwner = this.parameterizedType.getOwnerType(), toOwner = parameterizedType.getOwnerType();
+ if (fromOwner != null && toOwner != null && !fromOwner.accept(Assigner.INSTANCE).isAssignableFrom(toOwner)) {
+ return false;
+ }
+ TypeList.Generic fromArguments = this.parameterizedType.getTypeArguments(), toArguments = parameterizedType.getTypeArguments();
+ if (fromArguments.size() == toArguments.size()) {
+ for (int index = 0; index < fromArguments.size(); index++) {
+ if (!fromArguments.get(index).accept(ParameterAssigner.INSTANCE).isAssignableFrom(toArguments.get(index))) {
+ return false;
+ }
+ }
+ return true;
+ } else {
+ throw new IllegalArgumentException("Incompatible generic types: " + parameterizedType + " and " + this.parameterizedType);
+ }
+ }
+ Generic superClass = parameterizedType.getSuperClass();
+ if (superClass != null && isAssignableFrom(superClass)) {
+ return true;
+ }
+ for (Generic interfaceType : parameterizedType.getInterfaces()) {
+ if (isAssignableFrom(interfaceType)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public Boolean onTypeVariable(Generic typeVariable) {
+ for (Generic upperBound : typeVariable.getUpperBounds()) {
+ if (isAssignableFrom(upperBound)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public Boolean onNonGenericType(Generic typeDescription) {
+ if (parameterizedType.asErasure().equals(typeDescription.asErasure())) {
+ return true;
+ }
+ Generic superClass = typeDescription.getSuperClass();
+ if (superClass != null && isAssignableFrom(superClass)) {
+ return true;
+ }
+ for (Generic interfaceType : typeDescription.getInterfaces()) {
+ if (isAssignableFrom(interfaceType)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * An assigner for a parameter of a parameterized type.
+ */
+ protected enum ParameterAssigner implements Visitor<Dispatcher> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Dispatcher onGenericArray(Generic genericArray) {
+ return new InvariantBinding(genericArray);
+ }
+
+ @Override
+ public Dispatcher onWildcard(Generic wildcard) {
+ TypeList.Generic lowerBounds = wildcard.getLowerBounds();
+ return lowerBounds.isEmpty()
+ ? new CovariantBinding(wildcard.getUpperBounds().getOnly())
+ : new ContravariantBinding(lowerBounds.getOnly());
+ }
+
+ @Override
+ public Dispatcher onParameterizedType(Generic parameterizedType) {
+ return new InvariantBinding(parameterizedType);
+ }
+
+ @Override
+ public Dispatcher onTypeVariable(Generic typeVariable) {
+ return new InvariantBinding(typeVariable);
+ }
+
+ @Override
+ public Dispatcher onNonGenericType(Generic typeDescription) {
+ return new InvariantBinding(typeDescription);
+ }
+
+ /**
+ * A dispatcher for an invariant parameter of a parameterized type, i.e. a type without a wildcard.
+ */
+ @EqualsAndHashCode
+ protected static class InvariantBinding implements Dispatcher {
+
+ /**
+ * The invariant type of the parameter.
+ */
+ private final Generic typeDescription;
+
+ /**
+ * Creates a new dispatcher for an invariant parameter of a parameterized type.
+ *
+ * @param typeDescription The invariant type of the parameter.
+ */
+ protected InvariantBinding(Generic typeDescription) {
+ this.typeDescription = typeDescription;
+ }
+
+ @Override
+ public boolean isAssignableFrom(Generic typeDescription) {
+ return typeDescription.equals(this.typeDescription);
+ }
+ }
+
+ /**
+ * A dispatcher for an covariant parameter of a parameterized type, i.e. a type that is the lower bound of a wildcard.
+ */
+ @EqualsAndHashCode
+ protected static class CovariantBinding implements Dispatcher {
+
+ /**
+ * The lower bound type of a covariant parameter.
+ */
+ private final Generic upperBound;
+
+ /**
+ * Creates a new dispatcher for covariant parameter of a parameterized type.
+ *
+ * @param upperBound The upper bound type of a covariant parameter.
+ */
+ protected CovariantBinding(Generic upperBound) {
+ this.upperBound = upperBound;
+ }
+
+ @Override
+ public boolean isAssignableFrom(Generic typeDescription) {
+ if (typeDescription.getSort().isWildcard()) {
+ return typeDescription.getLowerBounds().isEmpty() && upperBound.accept(Assigner.INSTANCE)
+ .isAssignableFrom(typeDescription.getUpperBounds().getOnly());
+ } else {
+ return upperBound.accept(Assigner.INSTANCE).isAssignableFrom(typeDescription);
+ }
+ }
+ }
+
+ /**
+ * A dispatcher for an contravariant parameter of a parameterized type, i.e. a type that is the lower bound of a wildcard.
+ */
+ @EqualsAndHashCode
+ protected static class ContravariantBinding implements Dispatcher {
+
+ /**
+ * The lower bound type of a contravariant parameter.
+ */
+ private final Generic lowerBound;
+
+ /**
+ * Creates a new dispatcher for contravariant parameter of a parameterized type.
+ *
+ * @param lowerBound The lower bound type of a contravariant parameter.
+ */
+ protected ContravariantBinding(Generic lowerBound) {
+ this.lowerBound = lowerBound;
+ }
+
+ @Override
+ public boolean isAssignableFrom(Generic typeDescription) {
+ if (typeDescription.getSort().isWildcard()) {
+ TypeList.Generic lowerBounds = typeDescription.getLowerBounds();
+ return !lowerBounds.isEmpty() && lowerBounds.getOnly().accept(Assigner.INSTANCE).isAssignableFrom(lowerBound);
+ } else {
+ return typeDescription.getSort().isWildcard() || typeDescription.accept(Assigner.INSTANCE).isAssignableFrom(lowerBound);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * A dispatcher for checking the assignability of a generic array type.
+ */
+ @EqualsAndHashCode(callSuper = false)
+ class ForGenericArray extends AbstractBase {
+
+ /**
+ * The generic array type to which another type is assigned.
+ */
+ private final Generic genericArray;
+
+ /**
+ * Creates a new dispatcher for checking the assignability of a generic array type.
+ *
+ * @param genericArray The generic array type to which another type is assigned.
+ */
+ protected ForGenericArray(Generic genericArray) {
+ this.genericArray = genericArray;
+ }
+
+ @Override
+ public Boolean onGenericArray(Generic genericArray) {
+ return this.genericArray.getComponentType().accept(Assigner.INSTANCE).isAssignableFrom(genericArray.getComponentType());
+ }
+
+ @Override
+ public Boolean onWildcard(Generic wildcard) {
+ throw new IllegalArgumentException("A wildcard is not a first-level type: " + wildcard);
+ }
+
+ @Override
+ public Boolean onParameterizedType(Generic parameterizedType) {
+ return false;
+ }
+
+ @Override
+ public Boolean onTypeVariable(Generic typeVariable) {
+ return false;
+ }
+
+ @Override
+ public Boolean onNonGenericType(Generic typeDescription) {
+ return typeDescription.isArray()
+ && genericArray.getComponentType().accept(Assigner.INSTANCE).isAssignableFrom(typeDescription.getComponentType());
+ }
+ }
+ }
+ }
+
+ /**
+ * A validator for Java types that are defined for a specified type use within a Java class file.
+ */
+ enum Validator implements Visitor<Boolean> {
+
+ /**
+ * A validator for checking a type's non-null super class.
+ */
+ SUPER_CLASS(false, false, false, false) {
+ @Override
+ public Boolean onNonGenericType(Generic typeDescription) {
+ return super.onNonGenericType(typeDescription) && !typeDescription.isInterface();
+ }
+
+ @Override
+ public Boolean onParameterizedType(Generic parameterizedType) {
+ return !parameterizedType.isInterface();
+ }
+ },
+
+ /**
+ * A validator for an interface type.
+ */
+ INTERFACE(false, false, false, false) {
+ @Override
+ public Boolean onNonGenericType(Generic typeDescription) {
+ return super.onNonGenericType(typeDescription) && typeDescription.isInterface();
+ }
+
+ @Override
+ public Boolean onParameterizedType(Generic parameterizedType) {
+ return parameterizedType.isInterface();
+ }
+ },
+
+ /**
+ * A validator for a type variable.
+ */
+ TYPE_VARIABLE(false, false, true, false),
+
+ /**
+ * A validator for a field type.
+ */
+ FIELD(true, true, true, false),
+
+ /**
+ * A validator for a method return type.
+ */
+ METHOD_RETURN(true, true, true, true),
+
+ /**
+ * A validator for a method parameter type.
+ */
+ METHOD_PARAMETER(true, true, true, false),
+
+ /**
+ * A validator for a method exception type.
+ */
+ EXCEPTION(false, false, true, false) {
+ @Override
+ public Boolean onParameterizedType(Generic parameterizedType) {
+ return false;
+ }
+
+ @Override
+ public Boolean onTypeVariable(Generic typeVariable) {
+ for (TypeDescription.Generic bound : typeVariable.getUpperBounds()) {
+ if (bound.accept(this)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public Boolean onNonGenericType(Generic typeDescription) {
+ return typeDescription.asErasure().isAssignableTo(Throwable.class);
+ }
+ },
+
+ /**
+ * A validator for a method receiver type.
+ */
+ RECEIVER(false, false, false, false);
+
+ /**
+ * {@code true} if this validator accepts array types.
+ */
+ private final boolean acceptsArray;
+
+ /**
+ * {@code true} if this validator accepts primitive types.
+ */
+ private final boolean acceptsPrimitive;
+
+ /**
+ * {@code true} if this validator accepts type variables.
+ */
+ private final boolean acceptsVariable;
+
+ /**
+ * {@code true} if this validator accepts the {@code void} type.
+ */
+ private final boolean acceptsVoid;
+
+ /**
+ * Creates a new validator.
+ *
+ * @param acceptsArray {@code true} if this validator accepts array types.
+ * @param acceptsPrimitive {@code true} if this validator accepts primitive types.
+ * @param acceptsVariable {@code true} if this validator accepts type variables.
+ * @param acceptsVoid {@code true} if this validator accepts the {@code void} type.
+ */
+ Validator(boolean acceptsArray, boolean acceptsPrimitive, boolean acceptsVariable, boolean acceptsVoid) {
+ this.acceptsArray = acceptsArray;
+ this.acceptsPrimitive = acceptsPrimitive;
+ this.acceptsVariable = acceptsVariable;
+ this.acceptsVoid = acceptsVoid;
+ }
+
+ @Override
+ public Boolean onGenericArray(Generic genericArray) {
+ return acceptsArray;
+ }
+
+ @Override
+ public Boolean onWildcard(Generic wildcard) {
+ return false;
+ }
+
+ @Override
+ public Boolean onParameterizedType(Generic parameterizedType) {
+ return true;
+ }
+
+ @Override
+ public Boolean onTypeVariable(Generic typeVariable) {
+ return acceptsVariable;
+ }
+
+ @Override
+ public Boolean onNonGenericType(Generic typeDescription) {
+ return (acceptsArray || !typeDescription.isArray())
+ && (acceptsPrimitive || !typeDescription.isPrimitive())
+ && (acceptsVoid || !typeDescription.represents(void.class));
+ }
+
+ /**
+ * A type validator for checking type annotations.
+ */
+ public enum ForTypeAnnotations implements Visitor<Boolean> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ /**
+ * The {@link ElementType}'s {@code TYPE_USE} constant.
+ */
+ private final ElementType typeUse;
+
+ /**
+ * The {@link ElementType}'s {@code TYPE_PARAMETER} constant.
+ */
+ private final ElementType typeParameter;
+
+ /**
+ * Creates a new type annotation validator.
+ */
+ ForTypeAnnotations() {
+ ElementType typeUse, typeParameter;
+ try {
+ typeUse = Enum.valueOf(ElementType.class, "TYPE_USE");
+ typeParameter = Enum.valueOf(ElementType.class, "TYPE_PARAMETER");
+ } catch (IllegalArgumentException ignored) {
+ // Setting these values null results in this validator always failing for pre Java-8 VMs.
+ typeUse = null;
+ typeParameter = null;
+ }
+ this.typeUse = typeUse;
+ this.typeParameter = typeParameter;
+ }
+
+ /**
+ * Validates the type annotations on a formal type variable but not on its bounds..
+ *
+ * @param typeVariable The type variable to validate.
+ * @return {@code true} if the formal type variable declares invalid type annotations.
+ */
+ public static boolean ofFormalTypeVariable(Generic typeVariable) {
+ Set<TypeDescription> annotationTypes = new HashSet<TypeDescription>();
+ for (AnnotationDescription annotationDescription : typeVariable.getDeclaredAnnotations()) {
+ if (!annotationDescription.getElementTypes().contains(INSTANCE.typeParameter) || !annotationTypes.add(annotationDescription.getAnnotationType())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public Boolean onGenericArray(Generic genericArray) {
+ return isValid(genericArray) && genericArray.getComponentType().accept(this);
+ }
+
+ @Override
+ public Boolean onWildcard(Generic wildcard) {
+ if (!isValid(wildcard)) {
+ return false;
+ }
+ TypeList.Generic lowerBounds = wildcard.getLowerBounds();
+ return (lowerBounds.isEmpty()
+ ? wildcard.getUpperBounds()
+ : lowerBounds).getOnly().accept(this);
+ }
+
+ @Override
+ public Boolean onParameterizedType(Generic parameterizedType) {
+ if (!isValid(parameterizedType)) {
+ return false;
+ }
+ Generic ownerType = parameterizedType.getOwnerType();
+ if (ownerType != null && !ownerType.accept(this)) {
+ return false;
+ }
+ for (Generic typeArgument : parameterizedType.getTypeArguments()) {
+ if (!typeArgument.accept(this)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public Boolean onTypeVariable(Generic typeVariable) {
+ return isValid(typeVariable);
+ }
+
+ @Override
+ public Boolean onNonGenericType(Generic typeDescription) {
+ return isValid(typeDescription) && (!typeDescription.isArray() || typeDescription.getComponentType().accept(this));
+ }
+
+ /**
+ * Checks if the supplied type's type annotations are valid.
+ *
+ * @param typeDescription The type to validate.
+ * @return {@code true} if the supplied type's type annotations are valid.
+ */
+ private boolean isValid(Generic typeDescription) {
+ Set<TypeDescription> annotationTypes = new HashSet<TypeDescription>();
+ for (AnnotationDescription annotationDescription : typeDescription.getDeclaredAnnotations()) {
+ if (!annotationDescription.getElementTypes().contains(typeUse) || !annotationTypes.add(annotationDescription.getAnnotationType())) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+ }
+
+ /**
+ * A visitor that reifies type descriptions if they represent raw types.
+ */
+ enum Reifying implements Visitor<Generic> {
+
+ /**
+ * A visitor that reifies non-generic types if they represent raw types. This visitor should be applied when
+ * visiting a potential raw type.
+ */
+ INITIATING {
+ @Override
+ public Generic onParameterizedType(Generic parameterizedType) {
+ return parameterizedType;
+ }
+ },
+
+ /**
+ * A visitor that reifies non-generic types if they represent raw types or are parameterized types. This visitor
+ * should only be applied when a type was inherited from a reified type.
+ */
+ INHERITING {
+ @Override
+ public Generic onParameterizedType(Generic parameterizedType) {
+ return new OfParameterizedType.ForReifiedType(parameterizedType);
+ }
+ };
+
+ @Override
+ public Generic onGenericArray(Generic genericArray) {
+ throw new IllegalArgumentException("Cannot reify a generic array: " + genericArray);
+ }
+
+ @Override
+ public Generic onWildcard(Generic wildcard) {
+ throw new IllegalArgumentException("Cannot reify a wildcard: " + wildcard);
+ }
+
+ @Override
+ public Generic onTypeVariable(Generic typeVariable) {
+ throw new IllegalArgumentException("Cannot reify a type variable: " + typeVariable);
+ }
+
+ @Override
+ public Generic onNonGenericType(Generic typeDescription) {
+ TypeDescription erasure = typeDescription.asErasure();
+ return erasure.isGenerified()
+ ? new OfNonGenericType.ForReifiedErasure(erasure)
+ : typeDescription;
+ }
+ }
+
+ /**
+ * Visits a generic type and appends the discovered type to the supplied signature visitor.
+ */
+ @EqualsAndHashCode
+ class ForSignatureVisitor implements Visitor<SignatureVisitor> {
+
+ /**
+ * Index of a {@link String}'s only character to improve code readabilty.
+ */
+ private static final int ONLY_CHARACTER = 0;
+
+ /**
+ * The signature visitor that receives the discovered generic type.
+ */
+ protected final SignatureVisitor signatureVisitor;
+
+ /**
+ * Creates a new visitor for the given signature visitor.
+ *
+ * @param signatureVisitor The signature visitor that receives the discovered generic type.
+ */
+ public ForSignatureVisitor(SignatureVisitor signatureVisitor) {
+ this.signatureVisitor = signatureVisitor;
+ }
+
+ @Override
+ public SignatureVisitor onGenericArray(Generic genericArray) {
+ genericArray.getComponentType().accept(new ForSignatureVisitor(signatureVisitor.visitArrayType()));
+ return signatureVisitor;
+ }
+
+ @Override
+ public SignatureVisitor onWildcard(Generic wildcard) {
+ throw new IllegalStateException("Unexpected wildcard: " + wildcard);
+ }
+
+ @Override
+ public SignatureVisitor onParameterizedType(Generic parameterizedType) {
+ onOwnableType(parameterizedType);
+ signatureVisitor.visitEnd();
+ return signatureVisitor;
+ }
+
+ /**
+ * Visits a type which might define an owner type.
+ *
+ * @param ownableType The visited generic type.
+ */
+ private void onOwnableType(Generic ownableType) {
+ Generic ownerType = ownableType.getOwnerType();
+ if (ownerType != null && ownerType.getSort().isParameterized()) {
+ onOwnableType(ownerType);
+ signatureVisitor.visitInnerClassType(ownableType.asErasure().getSimpleName());
+ } else {
+ signatureVisitor.visitClassType(ownableType.asErasure().getInternalName());
+ }
+ for (Generic typeArgument : ownableType.getTypeArguments()) {
+ typeArgument.accept(new OfTypeArgument(signatureVisitor));
+ }
+ }
+
+ @Override
+ public SignatureVisitor onTypeVariable(Generic typeVariable) {
+ signatureVisitor.visitTypeVariable(typeVariable.getSymbol());
+ return signatureVisitor;
+ }
+
+ @Override
+ public SignatureVisitor onNonGenericType(Generic typeDescription) {
+ if (typeDescription.isArray()) {
+ typeDescription.getComponentType().accept(new ForSignatureVisitor(signatureVisitor.visitArrayType()));
+ } else if (typeDescription.isPrimitive()) {
+ signatureVisitor.visitBaseType(typeDescription.asErasure().getDescriptor().charAt(ONLY_CHARACTER));
+ } else {
+ signatureVisitor.visitClassType(typeDescription.asErasure().getInternalName());
+ signatureVisitor.visitEnd();
+ }
+ return signatureVisitor;
+ }
+
+ /**
+ * Visits a parameter while visiting a generic type for delegating discoveries to a signature visitor.
+ */
+ protected static class OfTypeArgument extends ForSignatureVisitor {
+
+ /**
+ * Creates a new parameter visitor.
+ *
+ * @param signatureVisitor The signature visitor which is notified over visited types.
+ */
+ protected OfTypeArgument(SignatureVisitor signatureVisitor) {
+ super(signatureVisitor);
+ }
+
+ @Override
+ public SignatureVisitor onWildcard(Generic wildcard) {
+ TypeList.Generic upperBounds = wildcard.getUpperBounds(), lowerBounds = wildcard.getLowerBounds();
+ if (lowerBounds.isEmpty() && upperBounds.getOnly().represents(Object.class)) {
+ signatureVisitor.visitTypeArgument();
+ } else if (!lowerBounds.isEmpty() /* && upperBounds.isEmpty() */) {
+ lowerBounds.getOnly().accept(new ForSignatureVisitor(signatureVisitor.visitTypeArgument(SignatureVisitor.SUPER)));
+ } else /* if (!upperBounds.isEmpty() && lowerBounds.isEmpty()) */ {
+ upperBounds.getOnly().accept(new ForSignatureVisitor(signatureVisitor.visitTypeArgument(SignatureVisitor.EXTENDS)));
+ }
+ return signatureVisitor;
+ }
+
+ @Override
+ public SignatureVisitor onGenericArray(Generic genericArray) {
+ genericArray.accept(new ForSignatureVisitor(signatureVisitor.visitTypeArgument(SignatureVisitor.INSTANCEOF)));
+ return signatureVisitor;
+ }
+
+ @Override
+ public SignatureVisitor onParameterizedType(Generic parameterizedType) {
+ parameterizedType.accept(new ForSignatureVisitor(signatureVisitor.visitTypeArgument(SignatureVisitor.INSTANCEOF)));
+ return signatureVisitor;
+ }
+
+ @Override
+ public SignatureVisitor onTypeVariable(Generic typeVariable) {
+ typeVariable.accept(new ForSignatureVisitor(signatureVisitor.visitTypeArgument(SignatureVisitor.INSTANCEOF)));
+ return signatureVisitor;
+ }
+
+ @Override
+ public SignatureVisitor onNonGenericType(Generic typeDescription) {
+ typeDescription.accept(new ForSignatureVisitor(signatureVisitor.visitTypeArgument(SignatureVisitor.INSTANCEOF)));
+ return signatureVisitor;
+ }
+ }
+ }
+
+ /**
+ * An abstract implementation of a visitor that substitutes generic types by replacing (nested)
+ * type variables and/or non-generic component types.
+ */
+ abstract class Substitutor implements Visitor<Generic> {
+
+ @Override
+ public Generic onParameterizedType(Generic parameterizedType) {
+ Generic ownerType = parameterizedType.getOwnerType();
+ List<Generic> typeArguments = new ArrayList<Generic>(parameterizedType.getTypeArguments().size());
+ for (Generic typeArgument : parameterizedType.getTypeArguments()) {
+ typeArguments.add(typeArgument.accept(this));
+ }
+ return new OfParameterizedType.Latent(parameterizedType.asRawType().accept(this).asErasure(),
+ ownerType == null
+ ? UNDEFINED
+ : ownerType.accept(this),
+ typeArguments,
+ parameterizedType);
+ }
+
+ @Override
+ public Generic onGenericArray(Generic genericArray) {
+ return new OfGenericArray.Latent(genericArray.getComponentType().accept(this), genericArray);
+ }
+
+ @Override
+ public Generic onWildcard(Generic wildcard) {
+ return new OfWildcardType.Latent(wildcard.getUpperBounds().accept(this), wildcard.getLowerBounds().accept(this), wildcard);
+ }
+
+ @Override
+ public Generic onNonGenericType(Generic typeDescription) {
+ return typeDescription.isArray()
+ ? new OfGenericArray.Latent(typeDescription.getComponentType().accept(this), typeDescription)
+ : onSimpleType(typeDescription);
+ }
+
+ /**
+ * Visits a simple, non-generic type, i.e. either a component type of an array or a non-array type.
+ *
+ * @param typeDescription The type that is visited.
+ * @return The substituted type.
+ */
+ protected abstract Generic onSimpleType(Generic typeDescription);
+
+ /**
+ * A {@link Substitutor} that only substitutes type variables but fully preserves non-generic type definitions.
+ */
+ public abstract static class WithoutTypeSubstitution extends Substitutor {
+
+ @Override
+ public Generic onNonGenericType(Generic typeDescription) {
+ return typeDescription;
+ }
+
+ @Override
+ protected Generic onSimpleType(Generic typeDescription) {
+ return typeDescription;
+ }
+ }
+
+ /**
+ * A substitutor that attaches type variables to a type variable source and replaces representations of
+ * {@link TargetType} with a given declaring type.
+ */
+ @EqualsAndHashCode(callSuper = false)
+ public static class ForAttachment extends Substitutor {
+
+ /**
+ * The declaring type which is filled in for {@link TargetType}.
+ */
+ private final TypeDescription declaringType;
+
+ /**
+ * The source which is used for locating type variables.
+ */
+ private final TypeVariableSource typeVariableSource;
+
+ /**
+ * Creates a visitor for attaching type variables.
+ *
+ * @param declaringType The declaring type which is filled in for {@link TargetType} in its erased form.
+ * @param typeVariableSource The source which is used for locating type variables.
+ */
+ protected ForAttachment(TypeDefinition declaringType, TypeVariableSource typeVariableSource) {
+ this(declaringType.asErasure(), typeVariableSource);
+ }
+
+ /**
+ * Creates a visitor for attaching type variables.
+ *
+ * @param declaringType The declaring type which is filled in for {@link TargetType}.
+ * @param typeVariableSource The source which is used for locating type variables.
+ */
+ protected ForAttachment(TypeDescription declaringType, TypeVariableSource typeVariableSource) {
+ this.declaringType = declaringType;
+ this.typeVariableSource = typeVariableSource;
+ }
+
+ /**
+ * Attaches all types to the given field description.
+ *
+ * @param fieldDescription The field description to which visited types should be attached to.
+ * @return A substitutor that attaches visited types to the given field's type context.
+ */
+ public static ForAttachment of(FieldDescription fieldDescription) {
+ return new ForAttachment(fieldDescription.getDeclaringType(), fieldDescription.getDeclaringType().asErasure());
+ }
+
+ /**
+ * Attaches all types to the given method description.
+ *
+ * @param methodDescription The method description to which visited types should be attached to.
+ * @return A substitutor that attaches visited types to the given method's type context.
+ */
+ public static ForAttachment of(MethodDescription methodDescription) {
+ return new ForAttachment(methodDescription.getDeclaringType(), methodDescription);
+ }
+
+ /**
+ * Attaches all types to the given parameter description.
+ *
+ * @param parameterDescription The parameter description to which visited types should be attached to.
+ * @return A substitutor that attaches visited types to the given parameter's type context.
+ */
+ public static ForAttachment of(ParameterDescription parameterDescription) {
+ return new ForAttachment(parameterDescription.getDeclaringMethod().getDeclaringType(), parameterDescription.getDeclaringMethod());
+ }
+
+ /**
+ * Attaches all types to the given type description.
+ *
+ * @param typeDescription The type description to which visited types should be attached to.
+ * @return A substitutor that attaches visited types to the given type's type context.
+ */
+ public static ForAttachment of(TypeDescription typeDescription) {
+ return new ForAttachment(typeDescription, typeDescription);
+ }
+
+ @Override
+ public Generic onTypeVariable(Generic typeVariable) {
+ Generic attachedVariable = typeVariableSource.findVariable(typeVariable.getSymbol());
+ if (attachedVariable == null) {
+ throw new IllegalArgumentException("Cannot attach undefined variable: " + typeVariable);
+ } else {
+ return new OfTypeVariable.WithAnnotationOverlay(attachedVariable, typeVariable);
+ }
+ }
+
+ @Override
+ protected Generic onSimpleType(Generic typeDescription) {
+ return typeDescription.represents(TargetType.class)
+ ? new OfNonGenericType.Latent(declaringType, typeDescription)
+ : typeDescription;
+ }
+ }
+
+ /**
+ * A visitor for detaching a type from its declaration context by detaching type variables. This is achieved by
+ * detaching type variables and by replacing the declaring type which is identified by a provided {@link ElementMatcher}
+ * with {@link TargetType}.
+ */
+ @EqualsAndHashCode(callSuper = false)
+ public static class ForDetachment extends Substitutor {
+
+ /**
+ * A type matcher for identifying the declaring type.
+ */
+ private final ElementMatcher<? super TypeDescription> typeMatcher;
+
+ /**
+ * Creates a visitor for detaching a type.
+ *
+ * @param typeMatcher A type matcher for identifying the declaring type.
+ */
+ public ForDetachment(ElementMatcher<? super TypeDescription> typeMatcher) {
+ this.typeMatcher = typeMatcher;
+ }
+
+ /**
+ * Returns a new detachment visitor that detaches any type matching the supplied type description.
+ *
+ * @param typeDefinition The type to detach.
+ * @return A detachment visitor for the supplied type description.
+ */
+ public static Visitor<Generic> of(TypeDefinition typeDefinition) {
+ return new ForDetachment(is(typeDefinition));
+ }
+
+ @Override
+ public Generic onTypeVariable(Generic typeVariable) {
+ return new OfTypeVariable.Symbolic(typeVariable.getSymbol(), typeVariable);
+ }
+
+ @Override
+ protected Generic onSimpleType(Generic typeDescription) {
+ return typeMatcher.matches(typeDescription.asErasure())
+ ? new OfNonGenericType.Latent(TargetType.DESCRIPTION, typeDescription.getOwnerType(), typeDescription)
+ : typeDescription;
+ }
+ }
+
+ /**
+ * A visitor for binding type variables to their values.
+ */
+ @EqualsAndHashCode(callSuper = false)
+ public static class ForTypeVariableBinding extends WithoutTypeSubstitution {
+
+ /**
+ * The parameterized type for which type variables are bound.
+ */
+ private final Generic parameterizedType;
+
+ /**
+ * Creates a new visitor for binding a parameterized type's type arguments to its type variables.
+ *
+ * @param parameterizedType The parameterized type for which type variables are bound.
+ */
+ protected ForTypeVariableBinding(Generic parameterizedType) {
+ this.parameterizedType = parameterizedType;
+ }
+
+ @Override
+ public Generic onTypeVariable(Generic typeVariable) {
+ return typeVariable.getTypeVariableSource().accept(new TypeVariableSubstitutor(typeVariable));
+ }
+
+ /**
+ * Substitutes a type variable, either with a new binding if the variable is defined by a type or with a
+ * retained type variable if the variable is defined by a method.
+ */
+ protected class TypeVariableSubstitutor implements TypeVariableSource.Visitor<Generic> {
+
+ /**
+ * The discovered type variable.
+ */
+ private final Generic typeVariable;
+
+ /**
+ * Creates a new type variable substitutor.
+ *
+ * @param typeVariable The discovered type variable.
+ */
+ protected TypeVariableSubstitutor(Generic typeVariable) {
+ this.typeVariable = typeVariable;
+ }
+
+ @Override
+ public Generic onType(TypeDescription typeDescription) {
+ // A type variable might be undeclared due to breaking inner class semantics or due to incorrect scoping by a compiler.
+ Generic typeArgument = parameterizedType.findBindingOf(typeVariable);
+ return typeArgument == null
+ ? typeVariable.asRawType()
+ : typeArgument;
+ }
+
+ @Override
+ public Generic onMethod(MethodDescription.InDefinedShape methodDescription) {
+ return new RetainedMethodTypeVariable(typeVariable);
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private ForTypeVariableBinding getOuter() {
+ return ForTypeVariableBinding.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && getOuter().equals(((TypeVariableSubstitutor) other).getOuter())
+ && typeVariable.equals(((TypeVariableSubstitutor) other).typeVariable);
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return typeVariable.hashCode();
+ }
+ }
+
+ /**
+ * Implementation of a type variable on a method that is not substituted.
+ */
+ protected class RetainedMethodTypeVariable extends OfTypeVariable {
+
+ /**
+ * The discovered type variable.
+ */
+ private final Generic typeVariable;
+
+ /**
+ * Creates a new retained type variable.
+ *
+ * @param typeVariable The discovered type variable.
+ */
+ protected RetainedMethodTypeVariable(Generic typeVariable) {
+ this.typeVariable = typeVariable;
+ }
+
+ @Override
+ public TypeList.Generic getUpperBounds() {
+ return typeVariable.getUpperBounds().accept(ForTypeVariableBinding.this);
+ }
+
+ @Override
+ public TypeVariableSource getTypeVariableSource() {
+ return typeVariable.getTypeVariableSource();
+ }
+
+ @Override
+ public String getSymbol() {
+ return typeVariable.getSymbol();
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return typeVariable.getDeclaredAnnotations();
+ }
+ }
+ }
+
+ /**
+ * A substitutor that normalizes a token to represent all {@link TargetType} by a given type and that symbolizes all type variables.
+ */
+ @EqualsAndHashCode(callSuper = false)
+ public static class ForTokenNormalization extends Substitutor {
+
+ /**
+ * The type description to substitute all {@link TargetType} representations with.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new token normalization visitor.
+ *
+ * @param typeDescription The type description to substitute all {@link TargetType}
+ */
+ public ForTokenNormalization(TypeDescription typeDescription) {
+ this.typeDescription = typeDescription;
+ }
+
+ @Override
+ protected Generic onSimpleType(Generic typeDescription) {
+ return typeDescription.represents(TargetType.class)
+ ? new OfNonGenericType.Latent(this.typeDescription, typeDescription)
+ : typeDescription;
+ }
+
+ @Override
+ public Generic onTypeVariable(Generic typeVariable) {
+ return new OfTypeVariable.Symbolic(typeVariable.getSymbol(), typeVariable);
+ }
+ }
+ }
+
+ /**
+ * A visitor that transforms any type into a raw type if declaring type is generified. If the declaring type is
+ * not generified, the original type description is returned.
+ */
+ class ForRawType implements Visitor<Generic> {
+
+ /**
+ * The type description that is potentially a raw type.
+ */
+ private final TypeDescription declaringType;
+
+ /**
+ * Creates a visitor for representing declared types of a potentially raw type.
+ *
+ * @param declaringType The type description that is potentially a raw type.
+ */
+ public ForRawType(TypeDescription declaringType) {
+ this.declaringType = declaringType;
+ }
+
+ @Override
+ public Generic onGenericArray(Generic genericArray) {
+ return declaringType.isGenerified()
+ ? new Generic.OfNonGenericType.Latent(genericArray.asErasure(), genericArray)
+ : genericArray;
+ }
+
+ @Override
+ public Generic onWildcard(Generic wildcard) {
+ throw new IllegalStateException("Did not expect wildcard on top-level: " + wildcard);
+ }
+
+ @Override
+ public Generic onParameterizedType(Generic parameterizedType) {
+ return declaringType.isGenerified()
+ ? new Generic.OfNonGenericType.Latent(parameterizedType.asErasure(), parameterizedType)
+ : parameterizedType;
+ }
+
+ @Override
+ public Generic onTypeVariable(Generic typeVariable) {
+ return declaringType.isGenerified()
+ ? new Generic.OfNonGenericType.Latent(typeVariable.asErasure(), typeVariable)
+ : typeVariable;
+ }
+
+ @Override
+ public Generic onNonGenericType(Generic typeDescription) {
+ return typeDescription;
+ }
+ }
+
+ /**
+ * A visitor that reduces a detached generic type to its erasure.
+ */
+ @EqualsAndHashCode
+ class Reducing implements Visitor<TypeDescription> {
+
+ /**
+ * The generic type's declaring type.
+ */
+ private final TypeDescription declaringType;
+
+ /**
+ * Any type variables that are directly declared by the member that declares the type being reduced.
+ */
+ private final List<? extends TypeVariableToken> typeVariableTokens;
+
+ /**
+ * Creates a new reducing type visitor.
+ *
+ * @param declaringType The generic type's declaring type.
+ */
+ public Reducing(TypeDescription declaringType) {
+ this(declaringType, Collections.<TypeVariableToken>emptyList());
+ }
+
+ /**
+ * Creates a new reducing type visitor.
+ *
+ * @param declaringType The generic type's declaring type.
+ * @param typeVariableTokens Any type variables that are directly declared by the member that declares the type being reduced.
+ */
+ public Reducing(TypeDescription declaringType, List<? extends TypeVariableToken> typeVariableTokens) {
+ this.declaringType = declaringType;
+ this.typeVariableTokens = typeVariableTokens;
+ }
+
+ @Override
+ public TypeDescription onGenericArray(Generic genericArray) {
+ return TargetType.resolve(genericArray.asErasure(), declaringType);
+ }
+
+ @Override
+ public TypeDescription onWildcard(Generic wildcard) {
+ throw new IllegalStateException("A wildcard cannot be a top-level type: " + wildcard);
+ }
+
+ @Override
+ public TypeDescription onParameterizedType(Generic parameterizedType) {
+ return TargetType.resolve(parameterizedType.asErasure(), declaringType);
+ }
+
+ @Override
+ public TypeDescription onTypeVariable(Generic typeVariable) {
+ for (TypeVariableToken typeVariableToken : typeVariableTokens) {
+ if (typeVariable.getSymbol().equals(typeVariableToken.getSymbol())) {
+ return typeVariableToken.getBounds().get(0).accept(this);
+ }
+ }
+ return TargetType.resolve(declaringType.findVariable(typeVariable.getSymbol()).asErasure(), declaringType);
+ }
+
+ @Override
+ public TypeDescription onNonGenericType(Generic typeDescription) {
+ return TargetType.resolve(typeDescription.asErasure(), declaringType);
+ }
+ }
+ }
+
+ /**
+ * An annotation reader is responsible for lazily evaluting type annotations if this language
+ * feature is available on the current JVM.
+ */
+ interface AnnotationReader {
+
+ /**
+ * The dispatcher to use.
+ */
+ Dispatcher DISPATCHER = AccessController.doPrivileged(Dispatcher.CreationAction.INSTANCE);
+
+ /**
+ * Resolves the underlying {@link AnnotatedElement}.
+ *
+ * @return The underlying annotated element.
+ */
+ AnnotatedElement resolve();
+
+ /**
+ * Returns the underlying type annotations as a list.
+ *
+ * @return The underlying type annotations as a list.
+ */
+ AnnotationList asList();
+
+ /**
+ * Returns a reader for type annotations of an represented element's wildcard upper bound.
+ *
+ * @param index The wildcard bound's index.
+ * @return An annotation reader for the underlying annotated upper bound.
+ */
+ AnnotationReader ofWildcardUpperBoundType(int index);
+
+ /**
+ * Returns a reader for type annotations of an represented element's wildcard lower bound.
+ *
+ * @param index The wildcard bound's index.
+ * @return An annotation reader for the underlying annotated lower bound.
+ */
+ AnnotationReader ofWildcardLowerBoundType(int index);
+
+ /**
+ * Returns a reader for type annotations of a type variable's bound.
+ *
+ * @param index The bound's index.
+ * @return An annotation reader for the underlying annotated bound.
+ */
+ AnnotationReader ofTypeVariableBoundType(int index);
+
+ /**
+ * Returns a reader for type annotations of a parameterized type's type argument.
+ *
+ * @param index The bound's index.
+ * @return An annotation reader for the underlying annotated bound..
+ */
+ AnnotationReader ofTypeArgument(int index);
+
+ /**
+ * <p>
+ * Returns a reader for type annotations of a parameterized type's owner type.
+ * </p>
+ * <p>
+ * <b>Important</b>: This feature is not currently implemented by the Java reflection API.
+ * </p>
+ *
+ * @return An annotation reader for the underlying owner type.
+ */
+ AnnotationReader ofOwnerType();
+
+ /**
+ * <p>
+ * Returns a reader for type annotations of an inner class type's outer type.
+ * </p>
+ * <p>
+ * <b>Important</b>: This feature is not currently implemented by the Java reflection API.
+ * </p>
+ *
+ * @return An annotation reader for the underlying owner type.
+ */
+ AnnotationReader ofOuterClass();
+
+ /**
+ * Returns a reader for type annotations of an array's component type.
+ *
+ * @return An annotation reader for the underlying component type.
+ */
+ AnnotationReader ofComponentType();
+
+ /**
+ * A dispatcher that represents the type annotation API via reflective calls if the language feature is available on the current JVM.
+ */
+ interface Dispatcher {
+
+ /**
+ * Resolves a formal type variable's type annotations.
+ *
+ * @param typeVariable The type variable to represent.
+ * @return A suitable annotation reader.
+ */
+ AnnotationReader resolveTypeVariable(TypeVariable<?> typeVariable);
+
+ /**
+ * Resolves a loaded type's super class's type annotations.
+ *
+ * @param type The type to represent.
+ * @return A suitable annotation reader.
+ */
+ AnnotationReader resolveSuperClassType(Class<?> type);
+
+ /**
+ * Resolves a loaded type's interface type's type annotations.
+ *
+ * @param type The type to represent.
+ * @param index The index of the interface.
+ * @return A suitable annotation reader.
+ */
+ AnnotationReader resolveInterfaceType(Class<?> type, int index);
+
+ /**
+ * Resolves a loaded field's type's type annotations.
+ *
+ * @param field The field to represent.
+ * @return A suitable annotation reader.
+ */
+ AnnotationReader resolveFieldType(Field field);
+
+ /**
+ * Resolves a loaded method's return type's type annotations.
+ *
+ * @param method The method to represent.
+ * @return A suitable annotation reader.
+ */
+ AnnotationReader resolveReturnType(Method method);
+
+ /**
+ * Resolves a loaded executable's type argument type's type annotations.
+ *
+ * @param executable The executable to represent.
+ * @param index The type argument's index.
+ * @return A suitable annotation reader.
+ */
+ AnnotationReader resolveParameterType(AccessibleObject executable, int index);
+
+ /**
+ * Resolves a loaded executable's exception type's type annotations.
+ *
+ * @param executable The executable to represent.
+ * @param index The type argument's index.
+ * @return A suitable annotation reader.
+ */
+ AnnotationReader resolveExceptionType(AccessibleObject executable, int index);
+
+ /**
+ * Resolves a method's or constructor's receiver type. If receiver types are not available on the executing VM,
+ * {@code null} is returned.
+ *
+ * @param executable The executable for which the receiver type should be resolved.
+ * @return The executable's receiver type or {@code null}.
+ */
+ Generic resolveReceiverType(AccessibleObject executable);
+
+ /**
+ * Resolves the annotated type as generic type description.
+ *
+ * @param annotatedType The loaded annotated type.
+ * @return A description of the supplied annotated type.
+ */
+ Generic resolve(AnnotatedElement annotatedType);
+
+ /**
+ * A creation action for a dispatcher.
+ */
+ enum CreationAction implements PrivilegedAction<Dispatcher> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Dispatcher run() {
+ try {
+ return new ForJava8CapableVm(Class.class.getMethod("getAnnotatedSuperclass"),
+ Class.class.getMethod("getAnnotatedInterfaces"),
+ Field.class.getMethod("getAnnotatedType"),
+ Method.class.getMethod("getAnnotatedReturnType"),
+ Class.forName("java.lang.reflect.Executable").getMethod("getAnnotatedParameterTypes"),
+ Class.forName("java.lang.reflect.Executable").getMethod("getAnnotatedExceptionTypes"),
+ Class.forName("java.lang.reflect.Executable").getMethod("getAnnotatedReceiverType"),
+ Class.forName("java.lang.reflect.AnnotatedType").getMethod("getType"));
+ } catch (RuntimeException exception) {
+ throw exception;
+ } catch (Exception ignored) {
+ return Dispatcher.ForLegacyVm.INSTANCE;
+ }
+ }
+ }
+
+ /**
+ * A dispatcher for {@link AnnotationReader}s on a legacy VM that does not support type annotations.
+ */
+ enum ForLegacyVm implements Dispatcher {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public AnnotationReader resolveTypeVariable(TypeVariable<?> typeVariable) {
+ return NoOp.INSTANCE;
+ }
+
+ @Override
+ public AnnotationReader resolveSuperClassType(Class<?> type) {
+ return NoOp.INSTANCE;
+ }
+
+ @Override
+ public AnnotationReader resolveInterfaceType(Class<?> type, int index) {
+ return NoOp.INSTANCE;
+ }
+
+ @Override
+ public AnnotationReader resolveFieldType(Field field) {
+ return NoOp.INSTANCE;
+ }
+
+ @Override
+ public AnnotationReader resolveReturnType(Method method) {
+ return NoOp.INSTANCE;
+ }
+
+ @Override
+ public AnnotationReader resolveParameterType(AccessibleObject executable, int index) {
+ return NoOp.INSTANCE;
+ }
+
+ @Override
+ public AnnotationReader resolveExceptionType(AccessibleObject executable, int index) {
+ return NoOp.INSTANCE;
+ }
+
+ @Override
+ public Generic resolveReceiverType(AccessibleObject executable) {
+ return UNDEFINED;
+ }
+
+ @Override
+ public Generic resolve(AnnotatedElement annotatedType) {
+ throw new IllegalStateException("Loaded annotated type cannot be represented on this VM");
+ }
+ }
+
+ /**
+ * A dispatcher for a modern JVM that supports type annotations.
+ */
+ @EqualsAndHashCode
+ class ForJava8CapableVm implements Dispatcher {
+
+ /**
+ * The {@code java.lang.Class#getAnnotatedSuperclass} method.
+ */
+ private final Method getAnnotatedSuperclass;
+
+ /**
+ * The {@code java.lang.Class#getAnnotatedInterfaces} method.
+ */
+ private final Method getAnnotatedInterfaces;
+
+ /**
+ * The {@code java.lang.reflect.Field#getAnnotatedType} method.
+ */
+ private final Method getAnnotatedType;
+
+ /**
+ * The {@code java.lang.reflect.Method#getAnnotatedReturnType} method.
+ */
+ private final Method getAnnotatedReturnType;
+
+ /**
+ * The {@code java.lang.reflect.Executable#getAnnotatedParameterTypes} method.
+ */
+ private final Method getAnnotatedParameterTypes;
+
+ /**
+ * The {@code java.lang.reflect.Executable#getAnnotatedExceptionTypes} method.
+ */
+ private final Method getAnnotatedExceptionTypes;
+
+ /**
+ * The {@code java.lang.reflect.Executable#getAnnotatedReceiverType} method.
+ */
+ private final Method getAnnotatedReceiverType;
+
+ /**
+ * The {@code java.lang.reflect.AnnotatedType#getType} method.
+ */
+ private final Method getType;
+
+ /**
+ * Creates a new dispatcher for a VM that supports type annotations.
+ *
+ * @param getAnnotatedSuperclass The {@code java.lang.Class#getAnnotatedSuperclass} method.
+ * @param getAnnotatedInterfaces The {@code java.lang.Class#getAnnotatedInterfaces} method.
+ * @param getAnnotatedType The {@code java.lang.reflect.Field#getAnnotatedType} method.
+ * @param getAnnotatedReturnType The {@code java.lang.reflect.Method#getAnnotatedReturnType} method.
+ * @param getAnnotatedParameterTypes The {@code java.lang.reflect.Executable#getAnnotatedParameterTypes} method.
+ * @param getAnnotatedExceptionTypes The {@code java.lang.reflect.Executable#getAnnotatedExceptionTypes} method.
+ * @param getAnnotatedReceiverType The {@code java.lang.reflect.Executable#getAnnotatedReceiverType} method.
+ * @param getType The {@code java.lang.reflect.AnnotatedType#getType} method.
+ */
+ protected ForJava8CapableVm(Method getAnnotatedSuperclass,
+ Method getAnnotatedInterfaces,
+ Method getAnnotatedType,
+ Method getAnnotatedReturnType,
+ Method getAnnotatedParameterTypes,
+ Method getAnnotatedExceptionTypes,
+ Method getAnnotatedReceiverType,
+ Method getType) {
+ this.getAnnotatedSuperclass = getAnnotatedSuperclass;
+ this.getAnnotatedInterfaces = getAnnotatedInterfaces;
+ this.getAnnotatedType = getAnnotatedType;
+ this.getAnnotatedReturnType = getAnnotatedReturnType;
+ this.getAnnotatedParameterTypes = getAnnotatedParameterTypes;
+ this.getAnnotatedExceptionTypes = getAnnotatedExceptionTypes;
+ this.getAnnotatedReceiverType = getAnnotatedReceiverType;
+ this.getType = getType;
+ }
+
+ @Override
+ public AnnotationReader resolveTypeVariable(TypeVariable<?> typeVariable) {
+ return new AnnotatedTypeVariableType(typeVariable);
+ }
+
+ @Override
+ public AnnotationReader resolveSuperClassType(Class<?> type) {
+ return new AnnotatedSuperClass(type);
+ }
+
+ @Override
+ public AnnotationReader resolveInterfaceType(Class<?> type, int index) {
+ return new AnnotatedInterfaceType(type, index);
+ }
+
+ @Override
+ public AnnotationReader resolveFieldType(Field field) {
+ return new AnnotatedFieldType(field);
+ }
+
+ @Override
+ public AnnotationReader resolveReturnType(Method method) {
+ return new AnnotatedReturnType(method);
+ }
+
+ @Override
+ public AnnotationReader resolveParameterType(AccessibleObject executable, int index) {
+ return new AnnotatedParameterizedType(executable, index);
+ }
+
+ @Override
+ public AnnotationReader resolveExceptionType(AccessibleObject executable, int index) {
+ return new AnnotatedExceptionType(executable, index);
+ }
+
+ @Override
+ public Generic resolveReceiverType(AccessibleObject executable) {
+ try {
+ return resolve((AnnotatedElement) getAnnotatedReceiverType.invoke(executable));
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.reflect.Executable#getAnnotatedReceiverType", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.reflect.Executable#getAnnotatedReceiverType", exception.getCause());
+ }
+ }
+
+ @Override
+ public Generic resolve(AnnotatedElement annotatedType) {
+ try {
+ return annotatedType == null
+ ? UNDEFINED
+ : Sort.describe((java.lang.reflect.Type) getType.invoke(annotatedType), new Resolved(annotatedType));
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.reflect.AnnotatedType#getType", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.reflect.AnnotatedType#getType", exception.getCause());
+ }
+ }
+
+ /**
+ * A delegator for an existing {@code java.lang.reflect.Annotatedelement}.
+ */
+ @EqualsAndHashCode(callSuper = false)
+ protected static class Resolved extends Delegator {
+
+ /**
+ * The represented annotated element.
+ */
+ private final AnnotatedElement annotatedElement;
+
+ /**
+ * Creates a new resolved delegator.
+ *
+ * @param annotatedElement The represented annotated element.
+ */
+ protected Resolved(AnnotatedElement annotatedElement) {
+ this.annotatedElement = annotatedElement;
+ }
+
+ @Override
+ public AnnotatedElement resolve() {
+ return annotatedElement;
+ }
+ }
+
+ /**
+ * A delegating annotation reader for an annotated type variable.
+ */
+ @EqualsAndHashCode(callSuper = false)
+ protected static class AnnotatedTypeVariableType extends Delegator {
+
+ /**
+ * The represented type variable.
+ */
+ private final TypeVariable<?> typeVariable;
+
+ /**
+ * Creates a new annotation reader for the given type variable.
+ *
+ * @param typeVariable The represented type variable.
+ */
+ protected AnnotatedTypeVariableType(TypeVariable<?> typeVariable) {
+ this.typeVariable = typeVariable;
+ }
+
+ @Override
+ public AnnotatedElement resolve() {
+ return (AnnotatedElement) typeVariable;
+ }
+
+ @Override
+ public AnnotationReader ofTypeVariableBoundType(int index) {
+ return new ForTypeVariableBoundType.OfFormalTypeVariable(typeVariable, index);
+ }
+ }
+
+ /**
+ * A delegating annotation reader for an annotated super type.
+ */
+ protected class AnnotatedSuperClass extends Delegator {
+
+ /**
+ * The represented type.
+ */
+ private final Class<?> type;
+
+ /**
+ * Creates a new annotation reader for an annotated super type.
+ *
+ * @param type The represented type.
+ */
+ protected AnnotatedSuperClass(Class<?> type) {
+ this.type = type;
+ }
+
+ @Override
+ public AnnotatedElement resolve() {
+ try {
+ return (AnnotatedElement) getAnnotatedSuperclass.invoke(type);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.Class#getAnnotatedSuperclass", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.Class#getAnnotatedSuperclass", exception.getCause());
+ }
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private ForJava8CapableVm getOuter() {
+ return ForJava8CapableVm.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && getOuter().equals(((AnnotatedSuperClass) other).getOuter())
+ && type.equals(((AnnotatedSuperClass) other).type);
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return getOuter().hashCode() + type.hashCode() * 31;
+ }
+ }
+
+ /**
+ * A delegating annotation reader for an annotated interface type.
+ */
+ protected class AnnotatedInterfaceType extends Delegator {
+
+ /**
+ * The represented interface type.
+ */
+ private final Class<?> type;
+
+ /**
+ * The interface type's index.
+ */
+ private final int index;
+
+ /**
+ * Creates a new annotation reader for an annotated interface type.
+ *
+ * @param type The represented interface type.
+ * @param index The interface type's index.
+ */
+ protected AnnotatedInterfaceType(Class<?> type, int index) {
+ this.type = type;
+ this.index = index;
+ }
+
+ @Override
+ public AnnotatedElement resolve() {
+ try {
+ return (AnnotatedElement) Array.get(getAnnotatedInterfaces.invoke(type), index);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.Class#getAnnotatedInterfaces", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.Class#getAnnotatedInterfaces", exception.getCause());
+ }
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private ForJava8CapableVm getOuter() {
+ return ForJava8CapableVm.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && getOuter().equals(((AnnotatedInterfaceType) other).getOuter())
+ && type.equals(((AnnotatedInterfaceType) other).type)
+ && index == ((AnnotatedInterfaceType) other).index;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return 31 * (type.hashCode() + 31 * getOuter().hashCode()) + index;
+ }
+ }
+
+ /**
+ * A delegating annotation reader for an annotated field variable.
+ */
+ protected class AnnotatedFieldType extends Delegator {
+
+ /**
+ * The represented field.
+ */
+ private final Field field;
+
+ /**
+ * Creates a new annotation reader for an annotated field type.
+ *
+ * @param field The represented field.
+ */
+ protected AnnotatedFieldType(Field field) {
+ this.field = field;
+ }
+
+ @Override
+ public AnnotatedElement resolve() {
+ try {
+ return (AnnotatedElement) getAnnotatedType.invoke(field);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.reflect.Field#getAnnotatedType", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.reflect.Field#getAnnotatedType", exception.getCause());
+ }
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private ForJava8CapableVm getOuter() {
+ return ForJava8CapableVm.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && getOuter().equals(((AnnotatedFieldType) other).getOuter())
+ && field.equals(((AnnotatedFieldType) other).field);
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return field.hashCode() + getOuter().hashCode() * 31;
+ }
+ }
+
+ /**
+ * A delegating annotation reader for an annotated return variable.
+ */
+ protected class AnnotatedReturnType extends Delegator {
+
+ /**
+ * The represented method.
+ */
+ private final Method method;
+
+ /**
+ * Creates a new annotation reader for an annotated return type.
+ *
+ * @param method The represented method.
+ */
+ protected AnnotatedReturnType(Method method) {
+ this.method = method;
+ }
+
+ @Override
+ public AnnotatedElement resolve() {
+ try {
+ return (AnnotatedElement) getAnnotatedReturnType.invoke(method);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.reflect.Method#getAnnotatedReturnType", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.reflect.Method#getAnnotatedReturnType", exception.getCause());
+ }
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private ForJava8CapableVm getOuter() {
+ return ForJava8CapableVm.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && getOuter().equals(((AnnotatedReturnType) other).getOuter())
+ && method.equals(((AnnotatedReturnType) other).method);
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return 31 * method.hashCode() + getOuter().hashCode();
+ }
+ }
+
+ /**
+ * A delegating annotation reader for an annotated parameter variable.
+ */
+ protected class AnnotatedParameterizedType extends Delegator {
+
+ /**
+ * The represented executable.
+ */
+ private final AccessibleObject executable;
+
+ /**
+ * The type argument's index.
+ */
+ private final int index;
+
+ /**
+ * Creates a new annotation reader for an annotated type argument type.
+ *
+ * @param executable The represented executable.
+ * @param index The type argument's index.
+ */
+ protected AnnotatedParameterizedType(AccessibleObject executable, int index) {
+ this.executable = executable;
+ this.index = index;
+ }
+
+ @Override
+ public AnnotatedElement resolve() {
+ try {
+ return (AnnotatedElement) Array.get(getAnnotatedParameterTypes.invoke(executable), index);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.reflect.Executable#getAnnotatedParameterTypes", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.reflect.Executable#getAnnotatedParameterTypes", exception.getCause());
+ }
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private ForJava8CapableVm getOuter() {
+ return ForJava8CapableVm.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && getOuter().equals(((AnnotatedParameterizedType) other).getOuter())
+ && executable.equals(((AnnotatedParameterizedType) other).executable)
+ && index == ((AnnotatedParameterizedType) other).index;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return 31 * (executable.hashCode() + 31 * index) + getOuter().hashCode();
+ }
+ }
+
+ /**
+ * A delegating annotation reader for an annotated exception variable.
+ */
+ protected class AnnotatedExceptionType extends Delegator {
+
+ /**
+ * The represented executable.
+ */
+ private final AccessibleObject executable;
+
+ /**
+ * The exception type's index.
+ */
+ private final int index;
+
+ /**
+ * Creates a new annotation reader for an annotated exception type.
+ *
+ * @param executable The represented executable.
+ * @param index The exception type's index.
+ */
+ protected AnnotatedExceptionType(AccessibleObject executable, int index) {
+ this.executable = executable;
+ this.index = index;
+ }
+
+ @Override
+ public AnnotatedElement resolve() {
+ try {
+ return (AnnotatedElement) Array.get(getAnnotatedExceptionTypes.invoke(executable), index);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.reflect.Executable#getAnnotatedExceptionTypes", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.reflect.Executable#getAnnotatedExceptionTypes", exception.getCause());
+ }
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private ForJava8CapableVm getOuter() {
+ return ForJava8CapableVm.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && getOuter().equals(((AnnotatedExceptionType) other).getOuter())
+ && executable.equals(((AnnotatedExceptionType) other).executable)
+ && index == ((AnnotatedExceptionType) other).index;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return 31 * (executable.hashCode() + 31 * index) + getOuter().hashCode();
+ }
+ }
+ }
+ }
+
+ /**
+ * A non-operational annotation reader.
+ */
+ enum NoOp implements AnnotationReader, AnnotatedElement {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public AnnotatedElement resolve() {
+ return this;
+ }
+
+ @Override
+ public AnnotationList asList() {
+ return new AnnotationList.Empty();
+ }
+
+ @Override
+ public AnnotationReader ofWildcardUpperBoundType(int index) {
+ return this;
+ }
+
+ @Override
+ public AnnotationReader ofWildcardLowerBoundType(int index) {
+ return this;
+ }
+
+ @Override
+ public AnnotationReader ofTypeVariableBoundType(int index) {
+ return this;
+ }
+
+ @Override
+ public AnnotationReader ofTypeArgument(int index) {
+ return this;
+ }
+
+ @Override
+ public AnnotationReader ofOwnerType() {
+ return this;
+ }
+
+ @Override
+ public AnnotationReader ofOuterClass() {
+ return this;
+ }
+
+ @Override
+ public AnnotationReader ofComponentType() {
+ return this;
+ }
+
+ @Override
+ public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
+ throw new IllegalStateException("Cannot resolve annotations for no-op reader: " + this);
+ }
+
+ @Override
+ public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
+ throw new IllegalStateException("Cannot resolve annotations for no-op reader: " + this);
+ }
+
+ @Override
+ public Annotation[] getAnnotations() {
+ throw new IllegalStateException("Cannot resolve annotations for no-op reader: " + this);
+ }
+
+ @Override
+ public Annotation[] getDeclaredAnnotations() {
+ return new Annotation[0];
+ }
+ }
+
+ /**
+ * A delegating annotation reader that delegates all invocations to an annotation reader that wraps the previous one.
+ */
+ abstract class Delegator implements AnnotationReader {
+
+ @Override
+ public AnnotationReader ofWildcardUpperBoundType(int index) {
+ return new ForWildcardUpperBoundType(this, index);
+ }
+
+ @Override
+ public AnnotationReader ofWildcardLowerBoundType(int index) {
+ return new ForWildcardLowerBoundType(this, index);
+ }
+
+ @Override
+ public AnnotationReader ofTypeVariableBoundType(int index) {
+ return new ForTypeVariableBoundType(this, index);
+ }
+
+ @Override
+ public AnnotationReader ofTypeArgument(int index) {
+ return new ForTypeArgument(this, index);
+ }
+
+ @Override
+ public AnnotationReader ofOwnerType() {
+ return ForOwnerType.of(this);
+ }
+
+ @Override
+ public AnnotationReader ofOuterClass() {
+ return ForOwnerType.of(this);
+ }
+
+ @Override
+ public AnnotationReader ofComponentType() {
+ return new ForComponentType(this);
+ }
+
+ @Override
+ public AnnotationList asList() {
+ return new AnnotationList.ForLoadedAnnotations(resolve().getDeclaredAnnotations());
+ }
+
+ /**
+ * A chained delegator that bases its result on an underlying annotation reader.
+ */
+ @EqualsAndHashCode(callSuper = false)
+ protected abstract static class Chained extends Delegator {
+
+ /**
+ * Indicates that a method is not available on the current VM.
+ */
+ protected static final Method NOT_AVAILABLE = null;
+
+ /**
+ * The underlying annotation reader.
+ */
+ protected final AnnotationReader annotationReader;
+
+ /**
+ * Creates a new chained annotation reader.
+ *
+ * @param annotationReader The underlying annotation reader.
+ */
+ protected Chained(AnnotationReader annotationReader) {
+ this.annotationReader = annotationReader;
+ }
+
+ /**
+ * Resolves the method to invoke or returns {@code null} if the method does not exist on the current VM.
+ *
+ * @param typeName The declaring type's name.
+ * @param methodName The method's name.
+ * @return The resolved method or {@code null}.
+ */
+ @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback")
+ protected static Method of(String typeName, String methodName) {
+ try {
+ return Class.forName(typeName).getMethod(methodName);
+ } catch (Exception exception) {
+ return NOT_AVAILABLE;
+ }
+ }
+
+ @Override
+ public AnnotatedElement resolve() {
+ return resolve(annotationReader.resolve());
+ }
+
+ /**
+ * Resolves the type annotations from a given annotated element into the annotated element that this instance represents.
+ *
+ * @param annotatedElement The original annotated element.
+ * @return The resolved annotated element.
+ */
+ protected abstract AnnotatedElement resolve(AnnotatedElement annotatedElement);
+ }
+ }
+
+ /**
+ * A chained annotation reader for reading a wildcard type's upper bound type.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ class ForWildcardUpperBoundType extends Delegator.Chained {
+
+ /**
+ * The {@code java.lang.reflect.AnnotatedWildcardType#getAnnotatedUpperBounds} method.
+ */
+ private static final Method GET_ANNOTATED_UPPER_BOUNDS = of("java.lang.reflect.AnnotatedWildcardType", "getAnnotatedUpperBounds");
+
+ /**
+ * The wildcard bound's index.
+ */
+ private final int index;
+
+ /**
+ * Creates a chained annotation reader for reading a upper-bound wildcard's bound type.
+ *
+ * @param annotationReader The annotation reader from which to delegate.
+ * @param index The wildcard bound's index.
+ */
+ protected ForWildcardUpperBoundType(AnnotationReader annotationReader, int index) {
+ super(annotationReader);
+ this.index = index;
+ }
+
+ @Override
+ protected AnnotatedElement resolve(AnnotatedElement annotatedElement) {
+ try {
+ Object annotatedUpperBounds = GET_ANNOTATED_UPPER_BOUNDS.invoke(annotatedElement);
+ return Array.getLength(annotatedUpperBounds) == 0 // Wildcards with a lower bound do not define annotations for their implicit upper bound.
+ ? NoOp.INSTANCE
+ : (AnnotatedElement) Array.get(annotatedUpperBounds, index);
+ } catch (ClassCastException ignored) { // To avoid bug on early releases of Java 8.
+ return NoOp.INSTANCE;
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.reflect.AnnotatedWildcardType#getAnnotatedUpperBounds", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.reflect.AnnotatedWildcardType#getAnnotatedUpperBounds", exception.getCause());
+ }
+ }
+ }
+
+ /**
+ * A chained annotation reader for reading a wildcard type's lower bound type.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ class ForWildcardLowerBoundType extends Delegator.Chained {
+
+ /**
+ * The {@code java.lang.reflect.AnnotatedWildcardType#getAnnotatedLowerBounds} method.
+ */
+ private static final Method GET_ANNOTATED_LOWER_BOUNDS = of("java.lang.reflect.AnnotatedWildcardType", "getAnnotatedLowerBounds");
+
+ /**
+ * The wildcard bound's index.
+ */
+ private final int index;
+
+ /**
+ * Creates a chained annotation reader for reading a lower-bound wildcard's bound type.
+ *
+ * @param annotationReader The annotation reader from which to delegate.
+ * @param index The wildcard bound's index.
+ */
+ protected ForWildcardLowerBoundType(AnnotationReader annotationReader, int index) {
+ super(annotationReader);
+ this.index = index;
+ }
+
+ @Override
+ protected AnnotatedElement resolve(AnnotatedElement annotatedElement) {
+ try {
+ return (AnnotatedElement) Array.get(GET_ANNOTATED_LOWER_BOUNDS.invoke(annotatedElement), index);
+ } catch (ClassCastException ignored) { // To avoid bug on early releases of Java 8.
+ return NoOp.INSTANCE;
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.reflect.AnnotatedWildcardType#getAnnotatedLowerBounds", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.reflect.AnnotatedWildcardType#getAnnotatedLowerBounds", exception.getCause());
+ }
+ }
+ }
+
+ /**
+ * A chained annotation reader for reading a type variable's type argument.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ class ForTypeVariableBoundType extends Delegator.Chained {
+
+ /**
+ * The {@code java.lang.reflect.AnnotatedTypeVariable#getAnnotatedBounds} method.
+ */
+ private static final Method GET_ANNOTATED_BOUNDS = of("java.lang.reflect.AnnotatedTypeVariable", "getAnnotatedBounds");
+
+ /**
+ * The type variable's index.
+ */
+ private final int index;
+
+ /**
+ * Creates a chained annotation reader for reading a type variable's bound type.
+ *
+ * @param annotationReader The annotation reader from which to delegate.
+ * @param index The type variable's index.
+ */
+ protected ForTypeVariableBoundType(AnnotationReader annotationReader, int index) {
+ super(annotationReader);
+ this.index = index;
+ }
+
+ @Override
+ protected AnnotatedElement resolve(AnnotatedElement annotatedElement) {
+ try {
+ return (AnnotatedElement) Array.get(GET_ANNOTATED_BOUNDS.invoke(annotatedElement), index);
+ } catch (ClassCastException ignored) { // To avoid bug on early releases of Java 8.
+ return NoOp.INSTANCE;
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.reflect.AnnotatedTypeVariable#getAnnotatedBounds", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.reflect.AnnotatedTypeVariable#getAnnotatedBounds", exception.getCause());
+ }
+ }
+
+ /**
+ * A chained annotation reader for reading a formal type variable's type argument.
+ */
+ @EqualsAndHashCode(callSuper = false)
+ protected static class OfFormalTypeVariable extends Delegator {
+
+ /**
+ * The {@code java.lang.reflect.TypeVariable#getAnnotatedBounds} method.
+ */
+ private static final Method GET_ANNOTATED_BOUNDS = of(TypeVariable.class.getName(), "getAnnotatedBounds");
+
+ /**
+ * The represented type variable.
+ */
+ private final TypeVariable<?> typeVariable;
+
+ /**
+ * The type variable's index.
+ */
+ private final int index;
+
+ /**
+ * Creates a chained annotation reader for reading a formal type variable's bound type.
+ *
+ * @param typeVariable The represented type variable.
+ * @param index The type variable's index.
+ */
+ protected OfFormalTypeVariable(TypeVariable<?> typeVariable, int index) {
+ this.typeVariable = typeVariable;
+ this.index = index;
+ }
+
+ @Override
+ public AnnotatedElement resolve() {
+ try {
+ return (AnnotatedElement) Array.get(GET_ANNOTATED_BOUNDS.invoke(typeVariable), index);
+ } catch (ClassCastException ignored) { // To avoid bug on early releases of Java 8.
+ return NoOp.INSTANCE;
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.reflect.TypeVariable#getAnnotatedBounds", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.reflect.TypeVariable#getAnnotatedBounds", exception.getCause());
+ }
+ }
+ }
+ }
+
+ /**
+ * A chained annotation reader for reading a parameterized type's type argument.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ class ForTypeArgument extends Delegator.Chained {
+
+ /**
+ * The {@code java.lang.reflect.AnnotatedParameterizedType#getAnnotatedActualTypeArguments} method.
+ */
+ private static final Method GET_ANNOTATED_ACTUAL_TYPE_ARGUMENTS = of("java.lang.reflect.AnnotatedParameterizedType", "getAnnotatedActualTypeArguments");
+
+ /**
+ * The type argument's index.
+ */
+ private final int index;
+
+ /**
+ * Creates a chained annotation reader for reading a component type.
+ *
+ * @param annotationReader The annotation reader from which to delegate.
+ * @param index The type argument's index.
+ */
+ protected ForTypeArgument(AnnotationReader annotationReader, int index) {
+ super(annotationReader);
+ this.index = index;
+ }
+
+ @Override
+ protected AnnotatedElement resolve(AnnotatedElement annotatedElement) {
+ try {
+ return (AnnotatedElement) Array.get(GET_ANNOTATED_ACTUAL_TYPE_ARGUMENTS.invoke(annotatedElement), index);
+ } catch (ClassCastException ignored) { // To avoid bug on early releases of Java 8.
+ return NoOp.INSTANCE;
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.reflect.AnnotatedParameterizedType#getAnnotatedActualTypeArguments", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.reflect.AnnotatedParameterizedType#getAnnotatedActualTypeArguments", exception.getCause());
+ }
+ }
+ }
+
+ /**
+ * A chained annotation reader for reading a component type.
+ */
+ class ForComponentType extends Delegator.Chained {
+
+ /**
+ * The {@code java.lang.reflect.AnnotatedArrayType#getAnnotatedGenericComponentType} method.
+ */
+ private static final Method GET_ANNOTATED_GENERIC_COMPONENT_TYPE = of("java.lang.reflect.AnnotatedArrayType", "getAnnotatedGenericComponentType");
+
+ /**
+ * Creates a chained annotation reader for reading a component type.
+ *
+ * @param annotationReader The annotation reader from which to delegate.
+ */
+ protected ForComponentType(AnnotationReader annotationReader) {
+ super(annotationReader);
+ }
+
+ @Override
+ protected AnnotatedElement resolve(AnnotatedElement annotatedElement) {
+ try {
+ return (AnnotatedElement) GET_ANNOTATED_GENERIC_COMPONENT_TYPE.invoke(annotatedElement);
+ } catch (ClassCastException ignored) { // To avoid bug on early releases of Java 8.
+ return NoOp.INSTANCE;
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.reflect.AnnotatedArrayType#getAnnotatedGenericComponentType", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.reflect.AnnotatedArrayType#getAnnotatedGenericComponentType", exception.getCause());
+ }
+ }
+ }
+
+ /**
+ * A chained annotation reader for reading an owner type.
+ */
+ class ForOwnerType extends Delegator.Chained {
+
+ /**
+ * The {@code java.lang.reflect.AnnotatedType#getAnnotatedOwnerType} method.
+ */
+ private static final Method GET_ANNOTATED_OWNER_TYPE = of("java.lang.reflect.AnnotatedType", "getAnnotatedOwnerType");
+
+ /**
+ * Creates a chained annotation reader for reading an owner type if it is accessible. This method checks if annotated
+ * owner types are available on the executing VM (Java 9+). If this is not the case, a non-operational annotation
+ * reader is returned.
+ *
+ * @param annotationReader The annotation reader from which to delegate.
+ * @return An annotation reader for the resolved type's owner type.
+ */
+ private static AnnotationReader of(AnnotationReader annotationReader) {
+ return GET_ANNOTATED_OWNER_TYPE == null
+ ? NoOp.INSTANCE
+ : new ForOwnerType(annotationReader);
+ }
+
+ /**
+ * Creates a chained annotation reader for reading an owner type if it is accessible.
+ *
+ * @param annotationReader The annotation reader from which to delegate.
+ */
+ protected ForOwnerType(AnnotationReader annotationReader) {
+ super(annotationReader);
+ }
+
+ @Override
+ protected AnnotatedElement resolve(AnnotatedElement annotatedElement) {
+ try {
+ AnnotatedElement annotatedOwnerType = (AnnotatedElement) GET_ANNOTATED_OWNER_TYPE.invoke(annotatedElement);
+ return annotatedOwnerType == null
+ ? NoOp.INSTANCE
+ : annotatedOwnerType;
+ } catch (ClassCastException ignored) { // To avoid bug on early releases of Java 8.
+ return NoOp.INSTANCE;
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.reflect.AnnotatedType#getAnnotatedOwnerType", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.reflect.AnnotatedType#getAnnotatedOwnerType", exception.getCause());
+ }
+ }
+ }
+ }
+
+ /**
+ * An abstract base implementation of a generic type description.
+ */
+ abstract class AbstractBase extends ModifierReviewable.AbstractBase implements Generic {
+
+ @Override
+ public int getModifiers() {
+ return asErasure().getModifiers();
+ }
+
+ @Override
+ public Generic asGenericType() {
+ return this;
+ }
+
+ @Override
+ public Generic asRawType() {
+ return asErasure().asGenericType();
+ }
+
+ @Override
+ public boolean represents(java.lang.reflect.Type type) {
+ return equals(Sort.describe(type));
+ }
+ }
+
+ /**
+ * <p>
+ * A raw type representation of a non-generic type. This raw type differs from a raw type in the Java programming language by
+ * representing a minimal erasure compared to Java's full erasure. This means that generic types are preserved as long as they
+ * do not involve a type variable. Nested type variables are erased on the deepest possible level.
+ * </p>
+ * <p>
+ * All fields, methods, interfaces and the super type that are returned from this instance represent appropriately erased types.
+ * </p>
+ */
+ abstract class OfNonGenericType extends AbstractBase {
+
+ @Override
+ public Sort getSort() {
+ return Sort.NON_GENERIC;
+ }
+
+ @Override
+ public Generic getSuperClass() {
+ TypeDescription erasure = asErasure();
+ Generic superClass = erasure.getSuperClass();
+ return superClass == null
+ ? Generic.UNDEFINED
+ : new Generic.LazyProjection.WithResolvedErasure(superClass, new Visitor.ForRawType(erasure), Empty.INSTANCE);
+ }
+
+ @Override
+ public TypeList.Generic getInterfaces() {
+ TypeDescription erasure = asErasure();
+ return new TypeList.Generic.ForDetachedTypes.WithResolvedErasure(erasure.getInterfaces(), new Visitor.ForRawType(erasure));
+ }
+
+ @Override
+ public FieldList<FieldDescription.InGenericShape> getDeclaredFields() {
+ TypeDescription erasure = asErasure();
+ return new FieldList.TypeSubstituting(this, erasure.getDeclaredFields(), new Visitor.ForRawType(erasure));
+ }
+
+ @Override
+ public MethodList<MethodDescription.InGenericShape> getDeclaredMethods() {
+ TypeDescription erasure = asErasure();
+ return new MethodList.TypeSubstituting(this, erasure.getDeclaredMethods(), new Visitor.ForRawType(erasure));
+ }
+
+ @Override
+ public TypeList.Generic getTypeArguments() {
+ throw new IllegalStateException("A non-generic type does not imply type arguments: " + this);
+ }
+
+ @Override
+ public Generic findBindingOf(Generic typeVariable) {
+ throw new IllegalStateException("A non-generic type does not imply type arguments: " + this);
+ }
+
+ @Override
+ public <T> T accept(Visitor<T> visitor) {
+ return visitor.onNonGenericType(this);
+ }
+
+ @Override
+ public String getTypeName() {
+ return asErasure().getTypeName();
+ }
+
+ @Override
+ public TypeList.Generic getUpperBounds() {
+ throw new IllegalStateException("A non-generic type does not imply upper type bounds: " + this);
+ }
+
+ @Override
+ public TypeList.Generic getLowerBounds() {
+ throw new IllegalStateException("A non-generic type does not imply lower type bounds: " + this);
+ }
+
+ @Override
+ public TypeVariableSource getTypeVariableSource() {
+ throw new IllegalStateException("A non-generic type does not imply a type variable source: " + this);
+ }
+
+ @Override
+ public String getSymbol() {
+ throw new IllegalStateException("A non-generic type does not imply a symbol: " + this);
+ }
+
+ @Override
+ public StackSize getStackSize() {
+ return asErasure().getStackSize();
+ }
+
+ @Override
+ public String getActualName() {
+ return asErasure().getActualName();
+ }
+
+ @Override
+ public boolean isArray() {
+ return asErasure().isArray();
+ }
+
+ @Override
+ public boolean isPrimitive() {
+ return asErasure().isPrimitive();
+ }
+
+ @Override
+ public boolean represents(java.lang.reflect.Type type) {
+ return asErasure().represents(type);
+ }
+
+ @Override
+ public Iterator<TypeDefinition> iterator() {
+ return new SuperClassIterator(this);
+ }
+
+ @Override
+ public int hashCode() {
+ return asErasure().hashCode();
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "EQ_CHECK_FOR_OPERAND_NOT_COMPATIBLE_WITH_THIS", justification = "Type check is performed by erasure implementation")
+ public boolean equals(Object other) {
+ return asErasure().equals(other);
+ }
+
+ @Override
+ public String toString() {
+ return asErasure().toString();
+ }
+
+ /**
+ * Represents a non-generic type for a loaded {@link Class}.
+ */
+ public static class ForLoadedType extends OfNonGenericType {
+
+ /**
+ * The type that this instance represents.
+ */
+ private final Class<?> type;
+
+ /**
+ * The annotation reader to query for the non-generic type's annotations.
+ */
+ private final AnnotationReader annotationReader;
+
+ /**
+ * Creates a new description of a generic type of a loaded type.
+ *
+ * @param type The represented type.
+ */
+ public ForLoadedType(Class<?> type) {
+ this(type, AnnotationReader.NoOp.INSTANCE);
+ }
+
+ /**
+ * /**
+ * Creates a new description of a generic type of a loaded type.
+ *
+ * @param type The represented type.
+ * @param annotationReader The annotation reader to query for the non-generic type's annotations.
+ */
+ protected ForLoadedType(Class<?> type, AnnotationReader annotationReader) {
+ this.type = type;
+ this.annotationReader = annotationReader;
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ return new TypeDescription.ForLoadedType(type);
+ }
+
+ @Override
+ public Generic getOwnerType() {
+ Class<?> declaringClass = this.type.getDeclaringClass();
+ return declaringClass == null
+ ? Generic.UNDEFINED
+ : new ForLoadedType(declaringClass, annotationReader.ofOuterClass());
+ }
+
+ @Override
+ public Generic getComponentType() {
+ Class<?> componentType = type.getComponentType();
+ return componentType == null
+ ? Generic.UNDEFINED
+ : new ForLoadedType(componentType, annotationReader.ofComponentType());
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return annotationReader.asList();
+ }
+ }
+
+ /**
+ * A type description for a type erasure. Compared to a {@link Latent} representation, this
+ * representation does not allow for the specification of any complex properties but does
+ * not require any form of navigation on the type.
+ */
+ public static class ForErasure extends OfNonGenericType {
+
+ /**
+ * The represented type erasure.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new description of a non-generic type as an erasure.
+ *
+ * @param typeDescription The represented type erasure.
+ */
+ public ForErasure(TypeDescription typeDescription) {
+ this.typeDescription = typeDescription;
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ return typeDescription;
+ }
+
+ @Override
+ public Generic getOwnerType() {
+ TypeDescription declaringType = typeDescription.getDeclaringType();
+ return declaringType == null
+ ? Generic.UNDEFINED
+ : declaringType.asGenericType();
+ }
+
+ @Override
+ public Generic getComponentType() {
+ TypeDescription componentType = typeDescription.getComponentType();
+ return componentType == null
+ ? Generic.UNDEFINED
+ : componentType.asGenericType();
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return new AnnotationList.Empty();
+ }
+ }
+
+ /**
+ * A latent description of a non-generic type.
+ */
+ public static class Latent extends OfNonGenericType {
+
+ /**
+ * The non-generic type's raw type.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * The non-generic type's declaring type.
+ */
+ private final TypeDescription.Generic declaringType;
+
+ /**
+ * The annotation source to query for the declared annotations.
+ */
+ private final AnnotationSource annotationSource;
+
+ /**
+ * Creates a non-generic type with an implicit owner type.
+ *
+ * @param typeDescription The non-generic type's raw type.
+ * @param annotationSource The annotation source to query for the declared annotations.
+ */
+ public Latent(TypeDescription typeDescription, AnnotationSource annotationSource) {
+ this(typeDescription, typeDescription.getDeclaringType(), annotationSource);
+ }
+
+ /**
+ * Creates a non-generic type with a raw owner type.
+ *
+ * @param typeDescription The non-generic type's raw type.
+ * @param declaringType The non-generic type's declaring type.
+ * @param annotationSource The annotation source to query for the declared annotations.
+ */
+ private Latent(TypeDescription typeDescription, TypeDescription declaringType, AnnotationSource annotationSource) {
+ this(typeDescription,
+ declaringType == null
+ ? Generic.UNDEFINED
+ : declaringType.asGenericType(),
+ annotationSource);
+ }
+
+ /**
+ * Creates a non-generic type.
+ *
+ * @param typeDescription The non-generic type's raw type.
+ * @param declaringType The non-generic type's declaring type.
+ * @param annotationSource The annotation source to query for the declared annotations.
+ */
+ protected Latent(TypeDescription typeDescription, Generic declaringType, AnnotationSource annotationSource) {
+ this.typeDescription = typeDescription;
+ this.declaringType = declaringType;
+ this.annotationSource = annotationSource;
+ }
+
+ @Override
+ public Generic getOwnerType() {
+ return declaringType;
+ }
+
+ @Override
+ public Generic getComponentType() {
+ TypeDescription componentType = typeDescription.getComponentType();
+ return componentType == null
+ ? Generic.UNDEFINED
+ : componentType.asGenericType();
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return annotationSource.getDeclaredAnnotations();
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ return typeDescription;
+ }
+ }
+
+ /**
+ * A representation of a raw type that preserves its generic super types' generic information with a minimum
+ * but erases all of their members' types.
+ */
+ public static class ForReifiedErasure extends OfNonGenericType {
+
+ /**
+ * The represented type erasure.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new reified non-generic type.
+ *
+ * @param typeDescription The represented type erasure.
+ */
+ protected ForReifiedErasure(TypeDescription typeDescription) {
+ this.typeDescription = typeDescription;
+ }
+
+ /**
+ * Creates a new generic type representation for an erasure where any generified type is reified.
+ *
+ * @param typeDescription The erasure to represent.
+ * @return An appropriate generic type representation where any generified type is reified.
+ */
+ protected static Generic of(TypeDescription typeDescription) {
+ return typeDescription.isGenerified()
+ ? new ForReifiedErasure(typeDescription)
+ : new ForErasure(typeDescription);
+ }
+
+ @Override
+ public Generic getSuperClass() {
+ Generic superClass = typeDescription.getSuperClass();
+ return superClass == null
+ ? Generic.UNDEFINED
+ : new LazyProjection.WithResolvedErasure(superClass, Visitor.Reifying.INHERITING);
+ }
+
+ @Override
+ public TypeList.Generic getInterfaces() {
+ return new TypeList.Generic.ForDetachedTypes.WithResolvedErasure(typeDescription.getInterfaces(), Visitor.Reifying.INHERITING);
+ }
+
+ @Override
+ public FieldList<FieldDescription.InGenericShape> getDeclaredFields() {
+ return new FieldList.TypeSubstituting(this, typeDescription.getDeclaredFields(), Visitor.TypeErasing.INSTANCE);
+ }
+
+ @Override
+ public MethodList<MethodDescription.InGenericShape> getDeclaredMethods() {
+ return new MethodList.TypeSubstituting(this, typeDescription.getDeclaredMethods(), Visitor.TypeErasing.INSTANCE);
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ return typeDescription;
+ }
+
+ @Override
+ public Generic getOwnerType() {
+ TypeDescription declaringType = typeDescription.getDeclaringType();
+ return declaringType == null
+ ? Generic.UNDEFINED
+ : of(declaringType);
+ }
+
+ @Override
+ public Generic getComponentType() {
+ TypeDescription componentType = typeDescription.getComponentType();
+ return componentType == null
+ ? Generic.UNDEFINED
+ : of(componentType);
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return new AnnotationList.Empty();
+ }
+ }
+ }
+
+ /**
+ * A base implementation of a generic type description that represents a potentially generic array. Instances represent a non-generic type
+ * if the given component type is non-generic.
+ */
+ abstract class OfGenericArray extends AbstractBase {
+
+ @Override
+ public Sort getSort() {
+ return getComponentType().getSort().isNonGeneric()
+ ? Sort.NON_GENERIC
+ : Sort.GENERIC_ARRAY;
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ return ArrayProjection.of(getComponentType().asErasure(), 1);
+ }
+
+ @Override
+ public Generic getSuperClass() {
+ return TypeDescription.Generic.OBJECT;
+ }
+
+ @Override
+ public TypeList.Generic getInterfaces() {
+ return ARRAY_INTERFACES;
+ }
+
+ @Override
+ public FieldList<FieldDescription.InGenericShape> getDeclaredFields() {
+ return new FieldList.Empty<FieldDescription.InGenericShape>();
+ }
+
+ @Override
+ public MethodList<MethodDescription.InGenericShape> getDeclaredMethods() {
+ return new MethodList.Empty<MethodDescription.InGenericShape>();
+ }
+
+ @Override
+ public TypeList.Generic getUpperBounds() {
+ throw new IllegalStateException("A generic array type does not imply upper type bounds: " + this);
+ }
+
+ @Override
+ public TypeList.Generic getLowerBounds() {
+ throw new IllegalStateException("A generic array type does not imply lower type bounds: " + this);
+ }
+
+ @Override
+ public TypeVariableSource getTypeVariableSource() {
+ throw new IllegalStateException("A generic array type does not imply a type variable source: " + this);
+ }
+
+ @Override
+ public TypeList.Generic getTypeArguments() {
+ throw new IllegalStateException("A generic array type does not imply type arguments: " + this);
+ }
+
+ @Override
+ public Generic findBindingOf(Generic typeVariable) {
+ throw new IllegalStateException("A generic array type does not imply type arguments: " + this);
+ }
+
+ @Override
+ public Generic getOwnerType() {
+ return Generic.UNDEFINED;
+ }
+
+ @Override
+ public String getSymbol() {
+ throw new IllegalStateException("A generic array type does not imply a symbol: " + this);
+ }
+
+ @Override
+ public String getTypeName() {
+ return getSort().isNonGeneric()
+ ? asErasure().getTypeName()
+ : toString();
+ }
+
+ @Override
+ public String getActualName() {
+ return getSort().isNonGeneric()
+ ? asErasure().getActualName()
+ : toString();
+ }
+
+ @Override
+ public boolean isArray() {
+ return true;
+ }
+
+ @Override
+ public boolean isPrimitive() {
+ return false;
+ }
+
+ @Override
+ public Iterator<TypeDefinition> iterator() {
+ return new SuperClassIterator(this);
+ }
+
+ @Override
+ public <T> T accept(Visitor<T> visitor) {
+ return getSort().isNonGeneric()
+ ? visitor.onNonGenericType(this)
+ : visitor.onGenericArray(this);
+ }
+
+ @Override
+ public StackSize getStackSize() {
+ return StackSize.SINGLE;
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "EQ_CHECK_FOR_OPERAND_NOT_COMPATIBLE_WITH_THIS", justification = "Type check is performed by erasure implementation")
+ public boolean equals(Object other) {
+ if (getSort().isNonGeneric()) {
+ return asErasure().equals(other);
+ }
+ if (!(other instanceof Generic)) return false;
+ Generic typeDescription = (Generic) other;
+ return typeDescription.getSort().isGenericArray() && getComponentType().equals(typeDescription.getComponentType());
+ }
+
+ @Override
+ public int hashCode() {
+ return getSort().isNonGeneric()
+ ? asErasure().hashCode()
+ : getComponentType().hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return getSort().isNonGeneric()
+ ? asErasure().toString()
+ : getComponentType().getTypeName() + "[]";
+ }
+
+ /**
+ * A description of a loaded generic array type.
+ */
+ public static class ForLoadedType extends OfGenericArray {
+
+ /**
+ * The loaded generic array type.
+ */
+ private final GenericArrayType genericArrayType;
+
+ /**
+ * The annotation reader to query for the generic array type's annotations.
+ */
+ private final AnnotationReader annotationReader;
+
+ /**
+ * Creates a type description of the given generic array type.
+ *
+ * @param genericArrayType The loaded generic array type.
+ */
+ public ForLoadedType(GenericArrayType genericArrayType) {
+ this(genericArrayType, AnnotationReader.NoOp.INSTANCE);
+ }
+
+ /**
+ * Creates a type description of the given generic array type.
+ *
+ * @param genericArrayType The loaded generic array type.
+ * @param annotationReader The annotation reader to query for the generic array type's annotations.
+ */
+ protected ForLoadedType(GenericArrayType genericArrayType, AnnotationReader annotationReader) {
+ this.genericArrayType = genericArrayType;
+ this.annotationReader = annotationReader;
+ }
+
+ @Override
+ public Generic getComponentType() {
+ return Sort.describe(genericArrayType.getGenericComponentType(), annotationReader.ofComponentType());
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return annotationReader.asList();
+ }
+ }
+
+ /**
+ * A latent implementation of a generic array type.
+ */
+ public static class Latent extends OfGenericArray {
+
+ /**
+ * The component type of the generic array.
+ */
+ private final Generic componentType;
+
+ /**
+ * The annotation source to query for the declared annotations.
+ */
+ private final AnnotationSource annotationSource;
+
+ /**
+ * Creates a latent representation of a generic array type.
+ *
+ * @param componentType The component type.
+ * @param annotationSource The annotation source to query for the declared annotations.
+ */
+ public Latent(Generic componentType, AnnotationSource annotationSource) {
+ this.componentType = componentType;
+ this.annotationSource = annotationSource;
+ }
+
+ @Override
+ public Generic getComponentType() {
+ return componentType;
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return annotationSource.getDeclaredAnnotations();
+ }
+ }
+ }
+
+ /**
+ * A base implementation of a generic type description that represents a wildcard type.
+ */
+ abstract class OfWildcardType extends AbstractBase {
+
+ /**
+ * The source code representation of a wildcard.
+ */
+ public static final String SYMBOL = "?";
+
+ @Override
+ public Sort getSort() {
+ return Sort.WILDCARD;
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ throw new IllegalStateException("A wildcard does not represent an erasable type: " + this);
+ }
+
+ @Override
+ public Generic getSuperClass() {
+ throw new IllegalStateException("A wildcard does not imply a super type definition: " + this);
+ }
+
+ @Override
+ public TypeList.Generic getInterfaces() {
+ throw new IllegalStateException("A wildcard does not imply an interface type definition: " + this);
+ }
+
+ @Override
+ public FieldList<FieldDescription.InGenericShape> getDeclaredFields() {
+ throw new IllegalStateException("A wildcard does not imply field definitions: " + this);
+ }
+
+ @Override
+ public MethodList<MethodDescription.InGenericShape> getDeclaredMethods() {
+ throw new IllegalStateException("A wildcard does not imply method definitions: " + this);
+ }
+
+ @Override
+ public Generic getComponentType() {
+ throw new IllegalStateException("A wildcard does not imply a component type: " + this);
+ }
+
+ @Override
+ public TypeVariableSource getTypeVariableSource() {
+ throw new IllegalStateException("A wildcard does not imply a type variable source: " + this);
+ }
+
+ @Override
+ public TypeList.Generic getTypeArguments() {
+ throw new IllegalStateException("A wildcard does not imply type arguments: " + this);
+ }
+
+ @Override
+ public Generic findBindingOf(Generic typeVariable) {
+ throw new IllegalStateException("A wildcard does not imply type arguments: " + this);
+ }
+
+ @Override
+ public Generic getOwnerType() {
+ throw new IllegalStateException("A wildcard does not imply an owner type: " + this);
+ }
+
+ @Override
+ public String getSymbol() {
+ throw new IllegalStateException("A wildcard does not imply a symbol: " + this);
+ }
+
+ @Override
+ public String getTypeName() {
+ return toString();
+ }
+
+ @Override
+ public String getActualName() {
+ return toString();
+ }
+
+ @Override
+ public boolean isPrimitive() {
+ return false;
+ }
+
+ @Override
+ public boolean isArray() {
+ return false;
+ }
+
+ @Override
+ public boolean represents(java.lang.reflect.Type type) {
+ return equals(Sort.describe(type));
+ }
+
+ @Override
+ public Iterator<TypeDefinition> iterator() {
+ throw new IllegalStateException("A wildcard does not imply a super type definition: " + this);
+ }
+
+ @Override
+ public <T> T accept(Visitor<T> visitor) {
+ return visitor.onWildcard(this);
+ }
+
+ @Override
+ public StackSize getStackSize() {
+ throw new IllegalStateException("A wildcard does not imply an operand stack size: " + this);
+ }
+
+ @Override
+ public int hashCode() {
+ int lowerHash = 1, upperHash = 1;
+ for (Generic lowerBound : getLowerBounds()) {
+ lowerHash = 31 * lowerHash + lowerBound.hashCode();
+ }
+ for (Generic upperBound : getUpperBounds()) {
+ upperHash = 31 * upperHash + upperBound.hashCode();
+ }
+ return lowerHash ^ upperHash;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof Generic)) return false;
+ Generic typeDescription = (Generic) other;
+ return typeDescription.getSort().isWildcard()
+ && getUpperBounds().equals(typeDescription.getUpperBounds())
+ && getLowerBounds().equals(typeDescription.getLowerBounds());
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder(SYMBOL);
+ TypeList.Generic bounds = getLowerBounds();
+ if (!bounds.isEmpty()) {
+ stringBuilder.append(" super ");
+ } else {
+ bounds = getUpperBounds();
+ if (bounds.getOnly().equals(TypeDescription.Generic.OBJECT)) {
+ return SYMBOL;
+ }
+ stringBuilder.append(" extends ");
+ }
+ return stringBuilder.append(bounds.getOnly().getTypeName()).toString();
+ }
+
+ /**
+ * Description of a loaded wildcard.
+ */
+ public static class ForLoadedType extends OfWildcardType {
+
+ /**
+ * The represented loaded wildcard type.
+ */
+ private final WildcardType wildcardType;
+
+ /**
+ * The annotation reader to query for the wildcard type's annotations.
+ */
+ private final AnnotationReader annotationReader;
+
+ /**
+ * Creates a description of a loaded wildcard.
+ *
+ * @param wildcardType The represented loaded wildcard type.
+ */
+ public ForLoadedType(WildcardType wildcardType) {
+ this(wildcardType, AnnotationReader.NoOp.INSTANCE);
+ }
+
+ /**
+ * Creates a description of a loaded wildcard.
+ *
+ * @param wildcardType The represented loaded wildcard type.
+ * @param annotationReader The annotation reader to query for the wildcard type's annotations.
+ */
+ protected ForLoadedType(WildcardType wildcardType, AnnotationReader annotationReader) {
+ this.wildcardType = wildcardType;
+ this.annotationReader = annotationReader;
+ }
+
+ @Override
+ public TypeList.Generic getUpperBounds() {
+ return new WildcardUpperBoundTypeList(wildcardType.getUpperBounds(), annotationReader);
+ }
+
+ @Override
+ public TypeList.Generic getLowerBounds() {
+ return new WildcardLowerBoundTypeList(wildcardType.getLowerBounds(), annotationReader);
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return annotationReader.asList();
+ }
+
+ /**
+ * A type list representing an upper-bound type variable's bound types.
+ */
+ protected static class WildcardUpperBoundTypeList extends TypeList.Generic.AbstractBase {
+
+ /**
+ * The represented upper bounds.
+ */
+ private final java.lang.reflect.Type[] upperBound;
+
+ /**
+ * The annotation reader to query for type annotations.
+ */
+ private final AnnotationReader annotationReader;
+
+ /**
+ * Creates a type list for a wildcard type's upper bounds.
+ *
+ * @param upperBound The represented upper bounds.
+ * @param annotationReader The annotation reader to query for type annotations.
+ */
+ protected WildcardUpperBoundTypeList(java.lang.reflect.Type[] upperBound, AnnotationReader annotationReader) {
+ this.upperBound = upperBound;
+ this.annotationReader = annotationReader;
+ }
+
+ @Override
+ public Generic get(int index) {
+ return Sort.describe(upperBound[index], annotationReader.ofWildcardUpperBoundType(index));
+ }
+
+ @Override
+ public int size() {
+ return upperBound.length;
+ }
+ }
+
+ /**
+ * A type list representing an upper-bound type variable's bound types.
+ */
+ protected static class WildcardLowerBoundTypeList extends TypeList.Generic.AbstractBase {
+
+ /**
+ * The represented lower bounds.
+ */
+ private final java.lang.reflect.Type[] lowerBound;
+
+ /**
+ * The annotation reader to query for type annotations.
+ */
+ private final AnnotationReader annotationReader;
+
+ /**
+ * Creates a type list for a wildcard type's lower bounds.
+ *
+ * @param lowerBound The represented lower bounds.
+ * @param annotationReader The annotation reader to query for type annotations.
+ */
+ protected WildcardLowerBoundTypeList(java.lang.reflect.Type[] lowerBound, AnnotationReader annotationReader) {
+ this.lowerBound = lowerBound;
+ this.annotationReader = annotationReader;
+ }
+
+ @Override
+ public Generic get(int index) {
+ return Sort.describe(lowerBound[index], annotationReader.ofWildcardLowerBoundType(index));
+ }
+
+ @Override
+ public int size() {
+ return lowerBound.length;
+ }
+ }
+ }
+
+ /**
+ * A latent description of a wildcard type.
+ */
+ public static class Latent extends OfWildcardType {
+
+ /**
+ * The wildcard's upper bounds.
+ */
+ private final List<? extends Generic> upperBounds;
+
+ /**
+ * The wildcard's lower bounds.
+ */
+ private final List<? extends Generic> lowerBounds;
+
+ /**
+ * The annotation source to query for the declared annotations.
+ */
+ private final AnnotationSource annotationSource;
+
+ /**
+ * Creates a description of a latent wildcard.
+ *
+ * @param upperBounds The wildcard's upper bounds.
+ * @param lowerBounds The wildcard's lower bounds.
+ * @param annotationSource The annotation source to query for the declared annotations.
+ */
+ protected Latent(List<? extends Generic> upperBounds, List<? extends Generic> lowerBounds, AnnotationSource annotationSource) {
+ this.upperBounds = upperBounds;
+ this.lowerBounds = lowerBounds;
+ this.annotationSource = annotationSource;
+ }
+
+ /**
+ * Creates an unbounded wildcard. Such a wildcard is implicitly bound above by the {@link Object} type.
+ *
+ * @param annotationSource The annotation source to query for the declared annotations.
+ * @return A description of an unbounded wildcard.
+ */
+ public static Generic unbounded(AnnotationSource annotationSource) {
+ return new Latent(Collections.singletonList(TypeDescription.Generic.OBJECT), Collections.<Generic>emptyList(), annotationSource);
+ }
+
+ /**
+ * Creates a wildcard with an upper bound.
+ *
+ * @param upperBound The upper bound of the wildcard.
+ * @param annotationSource The annotation source to query for the declared annotations.
+ * @return A wildcard with the given upper bound.
+ */
+ public static Generic boundedAbove(Generic upperBound, AnnotationSource annotationSource) {
+ return new Latent(Collections.singletonList(upperBound), Collections.<Generic>emptyList(), annotationSource);
+ }
+
+ /**
+ * Creates a wildcard with a lower bound. Such a wildcard is implicitly bounded above by the {@link Object} type.
+ *
+ * @param lowerBound The lower bound of the wildcard.
+ * @param annotationSource The annotation source to query for the declared annotations.
+ * @return A wildcard with the given lower bound.
+ */
+ public static Generic boundedBelow(Generic lowerBound, AnnotationSource annotationSource) {
+ return new Latent(Collections.singletonList(TypeDescription.Generic.OBJECT), Collections.singletonList(lowerBound), annotationSource);
+ }
+
+ @Override
+ public TypeList.Generic getUpperBounds() {
+ return new TypeList.Generic.Explicit(upperBounds);
+ }
+
+ @Override
+ public TypeList.Generic getLowerBounds() {
+ return new TypeList.Generic.Explicit(lowerBounds);
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return annotationSource.getDeclaredAnnotations();
+ }
+ }
+ }
+
+ /**
+ * A base implementation of a generic type description that represents a parameterized type.
+ */
+ abstract class OfParameterizedType extends AbstractBase {
+
+ @Override
+ public Sort getSort() {
+ return Sort.PARAMETERIZED;
+ }
+
+ @Override
+ public Generic getSuperClass() {
+ Generic superClass = asErasure().getSuperClass();
+ return superClass == null
+ ? Generic.UNDEFINED
+ : new LazyProjection.WithResolvedErasure(superClass, new Visitor.Substitutor.ForTypeVariableBinding(this));
+ }
+
+ @Override
+ public TypeList.Generic getInterfaces() {
+ return new TypeList.Generic.ForDetachedTypes.WithResolvedErasure(asErasure().getInterfaces(), new Visitor.Substitutor.ForTypeVariableBinding(this));
+ }
+
+ @Override
+ public FieldList<FieldDescription.InGenericShape> getDeclaredFields() {
+ return new FieldList.TypeSubstituting(this, asErasure().getDeclaredFields(), new Visitor.Substitutor.ForTypeVariableBinding(this));
+ }
+
+ @Override
+ public MethodList<MethodDescription.InGenericShape> getDeclaredMethods() {
+ return new MethodList.TypeSubstituting(this, asErasure().getDeclaredMethods(), new Visitor.Substitutor.ForTypeVariableBinding(this));
+ }
+
+ @Override
+ public Generic findBindingOf(Generic typeVariable) {
+ Generic typeDescription = this;
+ do {
+ TypeList.Generic typeArguments = typeDescription.getTypeArguments(), typeVariables = typeDescription.asErasure().getTypeVariables();
+ for (int index = 0; index < Math.min(typeArguments.size(), typeVariables.size()); index++) {
+ if (typeVariable.equals(typeVariables.get(index))) {
+ return typeArguments.get(index);
+ }
+ }
+ typeDescription = typeDescription.getOwnerType();
+ } while (typeDescription != null && typeDescription.getSort().isParameterized());
+ return Generic.UNDEFINED;
+ }
+
+ @Override
+ public TypeList.Generic getUpperBounds() {
+ throw new IllegalStateException("A parameterized type does not imply upper bounds: " + this);
+ }
+
+ @Override
+ public TypeList.Generic getLowerBounds() {
+ throw new IllegalStateException("A parameterized type does not imply lower bounds: " + this);
+ }
+
+ @Override
+ public Generic getComponentType() {
+ throw new IllegalStateException("A parameterized type does not imply a component type: " + this);
+ }
+
+ @Override
+ public TypeVariableSource getTypeVariableSource() {
+ throw new IllegalStateException("A parameterized type does not imply a type variable source: " + this);
+ }
+
+ @Override
+ public String getSymbol() {
+ throw new IllegalStateException("A parameterized type does not imply a symbol: " + this);
+ }
+
+ @Override
+ public String getTypeName() {
+ return toString();
+ }
+
+ @Override
+ public String getActualName() {
+ return toString();
+ }
+
+ @Override
+ public boolean isPrimitive() {
+ return false;
+ }
+
+ @Override
+ public boolean isArray() {
+ return false;
+ }
+
+ @Override
+ public boolean represents(java.lang.reflect.Type type) {
+ return equals(Sort.describe(type));
+ }
+
+ @Override
+ public Iterator<TypeDefinition> iterator() {
+ return new SuperClassIterator(this);
+ }
+
+ @Override
+ public <T> T accept(Visitor<T> visitor) {
+ return visitor.onParameterizedType(this);
+ }
+
+ @Override
+ public StackSize getStackSize() {
+ return StackSize.SINGLE;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 1;
+ for (Generic typeArgument : getTypeArguments()) {
+ result = 31 * result + typeArgument.hashCode();
+ }
+ Generic ownerType = getOwnerType();
+ return result ^ (ownerType == null
+ ? asErasure().hashCode()
+ : ownerType.hashCode());
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof Generic)) return false;
+ Generic typeDescription = (Generic) other;
+ if (!typeDescription.getSort().isParameterized()) return false;
+ Generic ownerType = getOwnerType(), otherOwnerType = typeDescription.getOwnerType();
+ return asErasure().equals(typeDescription.asErasure())
+ && !(ownerType == null && otherOwnerType != null) && !(ownerType != null && !ownerType.equals(otherOwnerType))
+ && getTypeArguments().equals(typeDescription.getTypeArguments());
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder();
+ Generic ownerType = getOwnerType();
+ if (ownerType != null) {
+ RenderingDelegate.CURRENT.apply(stringBuilder.append(ownerType.getTypeName()), asErasure(), ownerType);
+ } else {
+ stringBuilder.append(asErasure().getName());
+ }
+ TypeList.Generic actualTypeArguments = getTypeArguments();
+ if (!actualTypeArguments.isEmpty()) {
+ stringBuilder.append('<');
+ boolean multiple = false;
+ for (Generic typeArgument : actualTypeArguments) {
+ if (multiple) {
+ stringBuilder.append(", ");
+ }
+ stringBuilder.append(typeArgument.getTypeName());
+ multiple = true;
+ }
+ stringBuilder.append('>');
+ }
+ return stringBuilder.toString();
+ }
+
+ /**
+ * A rendering delegate for resolving a parameterized type's {@link Object#toString()} representation.
+ */
+ protected enum RenderingDelegate {
+
+ /**
+ * A rendering delegate for any VM prior to Java 9 where types are concatenated using a {@code .} character
+ * and where the fully qualified names are appended to non-parameterized types.
+ */
+ LEGACY_VM {
+ @Override
+ protected void apply(StringBuilder stringBuilder, TypeDescription typeDescription, Generic ownerType) {
+ stringBuilder.append('.').append(ownerType.getSort().isParameterized()
+ ? typeDescription.getSimpleName()
+ : typeDescription.getName());
+ }
+ },
+
+ /**
+ * A rendering delegate for any VM supporting Java 9 or newer where a type's simple name is appended.
+ */
+ JAVA_9_CAPABLE_VM {
+ @Override
+ protected void apply(StringBuilder stringBuilder, TypeDescription typeDescription, Generic ownerType) {
+ stringBuilder.append('$').append(typeDescription.getSimpleName());
+ }
+ };
+
+ /**
+ * A rendering delegate for the current VM.
+ */
+ protected static final RenderingDelegate CURRENT = ClassFileVersion.ofThisVm(ClassFileVersion.JAVA_V6).isAtLeast(ClassFileVersion.JAVA_V9)
+ ? RenderingDelegate.JAVA_9_CAPABLE_VM
+ : RenderingDelegate.LEGACY_VM;
+
+ /**
+ * Applies this rendering delegate.
+ *
+ * @param stringBuilder The string builder which is used for creating a parameterized type's string representation.
+ * @param typeDescription The rendered type's erasure.
+ * @param ownerType The rendered type's owner type.
+ */
+ protected abstract void apply(StringBuilder stringBuilder, TypeDescription typeDescription, Generic ownerType);
+ }
+
+ /**
+ * Description of a loaded parameterized type.
+ */
+ public static class ForLoadedType extends OfParameterizedType {
+
+ /**
+ * The represented parameterized type.
+ */
+ private final ParameterizedType parameterizedType;
+
+ /**
+ * The annotation reader to query for the parameterized type's annotations.
+ */
+ private final AnnotationReader annotationReader;
+
+ /**
+ * Creates a description of the loaded parameterized type.
+ *
+ * @param parameterizedType The represented parameterized type.
+ */
+ public ForLoadedType(ParameterizedType parameterizedType) {
+ this(parameterizedType, AnnotationReader.NoOp.INSTANCE);
+ }
+
+ /**
+ * Creates a description of the loaded parameterized type.
+ *
+ * @param parameterizedType The represented parameterized type.
+ * @param annotationReader The annotation reader to query for the parameterized type's annotations.
+ */
+ protected ForLoadedType(ParameterizedType parameterizedType, AnnotationReader annotationReader) {
+ this.parameterizedType = parameterizedType;
+ this.annotationReader = annotationReader;
+ }
+
+ @Override
+ public TypeList.Generic getTypeArguments() {
+ return new ParameterArgumentTypeList(parameterizedType.getActualTypeArguments(), annotationReader);
+ }
+
+ @Override
+ public Generic getOwnerType() {
+ java.lang.reflect.Type ownerType = parameterizedType.getOwnerType();
+ return ownerType == null
+ ? Generic.UNDEFINED
+ : Sort.describe(ownerType, annotationReader.ofOwnerType());
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ return new TypeDescription.ForLoadedType((Class<?>) parameterizedType.getRawType());
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return annotationReader.asList();
+ }
+
+ /**
+ * A type list that represents a loaded parameterized type's parameter types.
+ */
+ protected static class ParameterArgumentTypeList extends TypeList.Generic.AbstractBase {
+
+ /**
+ * The represented argument types.
+ */
+ private final java.lang.reflect.Type[] argumentType;
+
+ /**
+ * The annotation reader to query for type annotations.
+ */
+ private final AnnotationReader annotationReader;
+
+ /**
+ * Creates a list representing a parameterized type's type arguments.
+ *
+ * @param argumentType The represented argument types.
+ * @param annotationReader The annotation reader to query for type annotations.
+ */
+ protected ParameterArgumentTypeList(java.lang.reflect.Type[] argumentType, AnnotationReader annotationReader) {
+ this.argumentType = argumentType;
+ this.annotationReader = annotationReader;
+ }
+
+ @Override
+ public Generic get(int index) {
+ // Onfuscators sometimes render parameterized type arguments as null values.
+ return Sort.describe(argumentType[index], annotationReader.ofTypeArgument(index));
+ }
+
+ @Override
+ public int size() {
+ return argumentType.length;
+ }
+ }
+ }
+
+ /**
+ * A latent description of a parameterized type.
+ */
+ public static class Latent extends OfParameterizedType {
+
+ /**
+ * The raw type of the described parameterized type.
+ */
+ private final TypeDescription rawType;
+
+ /**
+ * This parameterized type's owner type or {@code null} if no owner type exists.
+ */
+ private final Generic ownerType;
+
+ /**
+ * The parameters of this parameterized type.
+ */
+ private final List<? extends Generic> parameters;
+
+ /**
+ * The annotation source to query for the declared annotations.
+ */
+ private final AnnotationSource annotationSource;
+
+ /**
+ * Creates a description of a latent parameterized type.
+ *
+ * @param rawType The raw type of the described parameterized type.
+ * @param ownerType This parameterized type's owner type or {@code null} if no owner type exists.
+ * @param parameters The parameters of this parameterized type.
+ * @param annotationSource The annotation source to query for the declared annotations.
+ */
+ public Latent(TypeDescription rawType,
+ Generic ownerType,
+ List<? extends Generic> parameters,
+ AnnotationSource annotationSource) {
+ this.rawType = rawType;
+ this.ownerType = ownerType;
+ this.parameters = parameters;
+ this.annotationSource = annotationSource;
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ return rawType;
+ }
+
+ @Override
+ public Generic getOwnerType() {
+ return ownerType;
+ }
+
+ @Override
+ public TypeList.Generic getTypeArguments() {
+ return new TypeList.Generic.Explicit(parameters);
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return annotationSource.getDeclaredAnnotations();
+ }
+ }
+
+ /**
+ * A representation of a parameterized type that is a super type of a raw type but preserves the minimal type information
+ * that is required for allowing creating correct erasures for overridden methods. All members' types are erased and all
+ * type arguments are reduced to their erasure.
+ */
+ public static class ForReifiedType extends OfParameterizedType {
+
+ /**
+ * The represented parameterized type.
+ */
+ private final Generic parameterizedType;
+
+ /**
+ * Creates a new reified parameterized type.
+ *
+ * @param parameterizedType The represented parameterized type.
+ */
+ protected ForReifiedType(Generic parameterizedType) {
+ this.parameterizedType = parameterizedType;
+ }
+
+ @Override
+ public Generic getSuperClass() {
+ Generic superClass = super.getSuperClass();
+ return superClass == null
+ ? Generic.UNDEFINED
+ : new LazyProjection.WithResolvedErasure(superClass, Visitor.Reifying.INHERITING);
+ }
+
+ @Override
+ public TypeList.Generic getInterfaces() {
+ return new TypeList.Generic.ForDetachedTypes.WithResolvedErasure(super.getInterfaces(), Visitor.Reifying.INHERITING);
+ }
+
+ @Override
+ public FieldList<FieldDescription.InGenericShape> getDeclaredFields() {
+ return new FieldList.TypeSubstituting(this, super.getDeclaredFields(), Visitor.TypeErasing.INSTANCE);
+ }
+
+ @Override
+ public MethodList<MethodDescription.InGenericShape> getDeclaredMethods() {
+ return new MethodList.TypeSubstituting(this, super.getDeclaredMethods(), Visitor.TypeErasing.INSTANCE);
+ }
+
+ @Override
+ public TypeList.Generic getTypeArguments() {
+ return new TypeList.Generic.ForDetachedTypes(parameterizedType.getTypeArguments(), Visitor.TypeErasing.INSTANCE);
+ }
+
+ @Override
+ public Generic getOwnerType() {
+ Generic ownerType = parameterizedType.getOwnerType();
+ return ownerType == null
+ ? Generic.UNDEFINED
+ : ownerType.accept(Visitor.Reifying.INHERITING);
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ return parameterizedType.asErasure();
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return new AnnotationList.Empty();
+ }
+ }
+
+ /**
+ * Represents an erasure as a generic type where all type variables are representing their own arguments.
+ */
+ public static class ForGenerifiedErasure extends OfParameterizedType {
+
+ /**
+ * The represented erasure.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new generified erasure.
+ *
+ * @param typeDescription The represented erasure.
+ */
+ protected ForGenerifiedErasure(TypeDescription typeDescription) {
+ this.typeDescription = typeDescription;
+ }
+
+ /**
+ * Represents the supplied type description as a generified erasure if it is generified or as a non-generic type if not so.
+ *
+ * @param typeDescription The represented erasure.
+ * @return An appropriate generic type.
+ */
+ public static Generic of(TypeDescription typeDescription) {
+ return typeDescription.isGenerified()
+ ? new ForGenerifiedErasure(typeDescription)
+ : new OfNonGenericType.ForErasure(typeDescription);
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ return typeDescription;
+ }
+
+ @Override
+ public TypeList.Generic getTypeArguments() {
+ return new TypeList.Generic.ForDetachedTypes(typeDescription.getTypeVariables(), Visitor.AnnotationStripper.INSTANCE);
+ }
+
+ @Override
+ public Generic getOwnerType() {
+ TypeDescription declaringType = typeDescription.getDeclaringType();
+ return declaringType == null
+ ? Generic.UNDEFINED
+ : of(declaringType);
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return new AnnotationList.Empty();
+ }
+ }
+ }
+
+ /**
+ * A base implementation of a generic type description that represents a type variable.
+ */
+ abstract class OfTypeVariable extends AbstractBase {
+
+ @Override
+ public Sort getSort() {
+ return Sort.VARIABLE;
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ TypeList.Generic upperBounds = getUpperBounds();
+ return upperBounds.isEmpty()
+ ? TypeDescription.OBJECT
+ : upperBounds.get(0).asErasure();
+ }
+
+ @Override
+ public Generic getSuperClass() {
+ throw new IllegalStateException("A type variable does not imply a super type definition: " + this);
+ }
+
+ @Override
+ public TypeList.Generic getInterfaces() {
+ throw new IllegalStateException("A type variable does not imply an interface type definition: " + this);
+ }
+
+ @Override
+ public FieldList<FieldDescription.InGenericShape> getDeclaredFields() {
+ throw new IllegalStateException("A type variable does not imply field definitions: " + this);
+ }
+
+ @Override
+ public MethodList<MethodDescription.InGenericShape> getDeclaredMethods() {
+ throw new IllegalStateException("A type variable does not imply method definitions: " + this);
+ }
+
+ @Override
+ public Generic getComponentType() {
+ throw new IllegalStateException("A type variable does not imply a component type: " + this);
+ }
+
+ @Override
+ public TypeList.Generic getTypeArguments() {
+ throw new IllegalStateException("A type variable does not imply type arguments: " + this);
+ }
+
+ @Override
+ public Generic findBindingOf(Generic typeVariable) {
+ throw new IllegalStateException("A type variable does not imply type arguments: " + this);
+ }
+
+ @Override
+ public TypeList.Generic getLowerBounds() {
+ throw new IllegalStateException("A type variable does not imply lower bounds: " + this);
+ }
+
+ @Override
+ public Generic getOwnerType() {
+ throw new IllegalStateException("A type variable does not imply an owner type: " + this);
+ }
+
+ @Override
+ public String getTypeName() {
+ return toString();
+ }
+
+ @Override
+ public String getActualName() {
+ return getSymbol();
+ }
+
+ @Override
+ public <T> T accept(Visitor<T> visitor) {
+ return visitor.onTypeVariable(this);
+ }
+
+ @Override
+ public StackSize getStackSize() {
+ return StackSize.SINGLE;
+ }
+
+ @Override
+ public boolean isArray() {
+ return false;
+ }
+
+ @Override
+ public boolean isPrimitive() {
+ return false;
+ }
+
+ @Override
+ public boolean represents(java.lang.reflect.Type type) {
+ return equals(Sort.describe(type));
+ }
+
+ @Override
+ public Iterator<TypeDefinition> iterator() {
+ throw new IllegalStateException("A type variable does not imply a super type definition: " + this);
+ }
+
+ @Override
+ public int hashCode() {
+ return getTypeVariableSource().hashCode() ^ getSymbol().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof Generic)) return false;
+ Generic typeDescription = (Generic) other;
+ return typeDescription.getSort().isTypeVariable()
+ && getSymbol().equals(typeDescription.getSymbol())
+ && getTypeVariableSource().equals(typeDescription.getTypeVariableSource());
+ }
+
+ @Override
+ public String toString() {
+ return getSymbol();
+ }
+
+ /**
+ * Implementation of a symbolic type variable.
+ */
+ public static class Symbolic extends Generic.AbstractBase {
+
+ /**
+ * The symbol of the symbolic type variable.
+ */
+ private final String symbol;
+
+ /**
+ * The annotation source to query for the declared annotations.
+ */
+ private final AnnotationSource annotationSource;
+
+ /**
+ * Creates a symbolic type variable.
+ *
+ * @param symbol The symbol of the symbolic type variable.
+ * @param annotationSource The annotation source to query for the declared annotations.
+ */
+ public Symbolic(String symbol, AnnotationSource annotationSource) {
+ this.symbol = symbol;
+ this.annotationSource = annotationSource;
+ }
+
+ @Override
+ public Sort getSort() {
+ return Sort.VARIABLE_SYMBOLIC;
+ }
+
+ @Override
+ public String getSymbol() {
+ return symbol;
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return annotationSource.getDeclaredAnnotations();
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ throw new IllegalStateException("A symbolic type variable does not imply an erasure: " + this);
+ }
+
+ @Override
+ public TypeList.Generic getUpperBounds() {
+ throw new IllegalStateException("A symbolic type variable does not imply an upper type bound: " + this);
+ }
+
+ @Override
+ public TypeVariableSource getTypeVariableSource() {
+ throw new IllegalStateException("A symbolic type variable does not imply a variable source: " + this);
+ }
+
+ @Override
+ public Generic getSuperClass() {
+ throw new IllegalStateException("A symbolic type variable does not imply a super type definition: " + this);
+ }
+
+ @Override
+ public TypeList.Generic getInterfaces() {
+ throw new IllegalStateException("A symbolic type variable does not imply an interface type definition: " + this);
+ }
+
+ @Override
+ public FieldList<FieldDescription.InGenericShape> getDeclaredFields() {
+ throw new IllegalStateException("A symbolic type variable does not imply field definitions: " + this);
+ }
+
+ @Override
+ public MethodList<MethodDescription.InGenericShape> getDeclaredMethods() {
+ throw new IllegalStateException("A symbolic type variable does not imply method definitions: " + this);
+ }
+
+ @Override
+ public Generic getComponentType() {
+ throw new IllegalStateException("A symbolic type variable does not imply a component type: " + this);
+ }
+
+ @Override
+ public TypeList.Generic getTypeArguments() {
+ throw new IllegalStateException("A symbolic type variable does not imply type arguments: " + this);
+ }
+
+ @Override
+ public Generic findBindingOf(Generic typeVariable) {
+ throw new IllegalStateException("A symbolic type variable does not imply type arguments: " + this);
+ }
+
+ @Override
+ public TypeList.Generic getLowerBounds() {
+ throw new IllegalStateException("A symbolic type variable does not imply lower bounds: " + this);
+ }
+
+ @Override
+ public Generic getOwnerType() {
+ throw new IllegalStateException("A symbolic type variable does not imply an owner type: " + this);
+ }
+
+ @Override
+ public String getTypeName() {
+ return toString();
+ }
+
+ @Override
+ public String getActualName() {
+ return getSymbol();
+ }
+
+ @Override
+ public <T> T accept(Visitor<T> visitor) {
+ return visitor.onTypeVariable(this);
+ }
+
+ @Override
+ public StackSize getStackSize() {
+ return StackSize.SINGLE;
+ }
+
+ @Override
+ public boolean isArray() {
+ return false;
+ }
+
+ @Override
+ public boolean isPrimitive() {
+ return false;
+ }
+
+ @Override
+ public boolean represents(java.lang.reflect.Type type) {
+ if (type == null) {
+ throw new NullPointerException();
+ }
+ return false;
+ }
+
+ @Override
+ public Iterator<TypeDefinition> iterator() {
+ throw new IllegalStateException("A symbolic type variable does not imply a super type definition: " + this);
+ }
+
+ @Override
+ public int hashCode() {
+ return symbol.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof Generic)) return false;
+ Generic typeDescription = (Generic) other;
+ return typeDescription.getSort().isTypeVariable() && getSymbol().equals(typeDescription.getSymbol());
+ }
+
+ @Override
+ public String toString() {
+ return getSymbol();
+ }
+ }
+
+ /**
+ * Description of a loaded type variable.
+ */
+ public static class ForLoadedType extends OfTypeVariable {
+
+ /**
+ * The represented type variable.
+ */
+ private final TypeVariable<?> typeVariable;
+
+ /**
+ * The annotation reader to query for the variable's annotations.
+ */
+ private final AnnotationReader annotationReader;
+
+ /**
+ * Creates a description of a loaded type variable.
+ *
+ * @param typeVariable The represented type variable.
+ */
+ public ForLoadedType(TypeVariable<?> typeVariable) {
+ this(typeVariable, AnnotationReader.NoOp.INSTANCE);
+ }
+
+ /**
+ * Creates a description of a loaded type variable with an annotation.
+ *
+ * @param typeVariable The represented type variable.
+ * @param annotationReader The annotation reader to query for the variable's annotations.
+ */
+ protected ForLoadedType(TypeVariable<?> typeVariable, AnnotationReader annotationReader) {
+ this.typeVariable = typeVariable;
+ this.annotationReader = annotationReader;
+ }
+
+ @Override
+ public TypeVariableSource getTypeVariableSource() {
+ GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration();
+ if (genericDeclaration instanceof Class) {
+ return new TypeDescription.ForLoadedType((Class<?>) genericDeclaration);
+ } else if (genericDeclaration instanceof Method) {
+ return new MethodDescription.ForLoadedMethod((Method) genericDeclaration);
+ } else if (genericDeclaration instanceof Constructor) {
+ return new MethodDescription.ForLoadedConstructor((Constructor<?>) genericDeclaration);
+ } else {
+ throw new IllegalStateException("Unknown declaration: " + genericDeclaration);
+ }
+ }
+
+ @Override
+ public TypeList.Generic getUpperBounds() {
+ return new TypeVariableBoundList(typeVariable.getBounds(), annotationReader);
+ }
+
+ @Override
+ public String getSymbol() {
+ return typeVariable.getName();
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return annotationReader.asList();
+ }
+
+ /**
+ * A list of type variable bounds for a loaded {@link TypeVariable} that resolves annotations..
+ */
+ protected static class TypeVariableBoundList extends TypeList.Generic.AbstractBase {
+
+ /**
+ * The type variable bounds.
+ */
+ private final java.lang.reflect.Type[] bound;
+
+ /**
+ * The annotation reader to query for the type bounds.
+ */
+ private final AnnotationReader annotationReader;
+
+ /**
+ * Creates a new list for a {@link TypeVariable}'s bound.
+ *
+ * @param bound The type variable bounds.
+ * @param annotationReader The annotation reader to query for the type bounds.
+ */
+ protected TypeVariableBoundList(java.lang.reflect.Type[] bound, AnnotationReader annotationReader) {
+ this.bound = bound;
+ this.annotationReader = annotationReader;
+ }
+
+ @Override
+ public Generic get(int index) {
+ return Sort.describe(bound[index], annotationReader.ofTypeVariableBoundType(index));
+ }
+
+ @Override
+ public int size() {
+ return bound.length;
+ }
+ }
+ }
+
+ /**
+ * A type variable with explicit annotations that replace the annotations that are declared by the provided type variable.
+ */
+ public static class WithAnnotationOverlay extends OfTypeVariable {
+
+ /**
+ * The type variable to represent.
+ */
+ private final Generic typeVariable;
+
+ /**
+ * The annotation source to query for the declared annotations.
+ */
+ private final AnnotationSource annotationSource;
+
+ /**
+ * Creates a new type definition for a type variable with explicit annotations.
+ *
+ * @param typeVariable The type variable to represent.
+ * @param annotationSource The annotation source to query for the declared annotations.
+ */
+ public WithAnnotationOverlay(Generic typeVariable, AnnotationSource annotationSource) {
+ this.typeVariable = typeVariable;
+ this.annotationSource = annotationSource;
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return annotationSource.getDeclaredAnnotations();
+ }
+
+ @Override
+ public TypeList.Generic getUpperBounds() {
+ return typeVariable.getUpperBounds();
+ }
+
+ @Override
+ public TypeVariableSource getTypeVariableSource() {
+ return typeVariable.getTypeVariableSource();
+ }
+
+ @Override
+ public String getSymbol() {
+ return typeVariable.getSymbol();
+ }
+ }
+ }
+
+ /**
+ * A lazy projection of a generic type. Such projections allow to only read generic type information in case it is required. This
+ * is meaningful as the Java virtual needs to process generic type information which requires extra ressources. Also, this allows
+ * the extraction of non-generic type information even if the generic type information is invalid.
+ */
+ abstract class LazyProjection extends AbstractBase {
+
+ /**
+ * Resolves the actual generic type.
+ *
+ * @return An actual description of the represented generic type.
+ */
+ protected abstract Generic resolve();
+
+ @Override
+ public Sort getSort() {
+ return resolve().getSort();
+ }
+
+ @Override
+ public FieldList<FieldDescription.InGenericShape> getDeclaredFields() {
+ return resolve().getDeclaredFields();
+ }
+
+ @Override
+ public MethodList<MethodDescription.InGenericShape> getDeclaredMethods() {
+ return resolve().getDeclaredMethods();
+ }
+
+ @Override
+ public TypeList.Generic getUpperBounds() {
+ return resolve().getUpperBounds();
+ }
+
+ @Override
+ public TypeList.Generic getLowerBounds() {
+ return resolve().getLowerBounds();
+ }
+
+ @Override
+ public Generic getComponentType() {
+ return resolve().getComponentType();
+ }
+
+ @Override
+ public TypeList.Generic getTypeArguments() {
+ return resolve().getTypeArguments();
+ }
+
+ @Override
+ public Generic findBindingOf(Generic typeVariable) {
+ return resolve().findBindingOf(typeVariable);
+ }
+
+ @Override
+ public TypeVariableSource getTypeVariableSource() {
+ return resolve().getTypeVariableSource();
+ }
+
+ @Override
+ public Generic getOwnerType() {
+ return resolve().getOwnerType();
+ }
+
+ @Override
+ public String getTypeName() {
+ return resolve().getTypeName();
+ }
+
+ @Override
+ public String getSymbol() {
+ return resolve().getSymbol();
+ }
+
+ @Override
+ public String getActualName() {
+ return resolve().getActualName();
+ }
+
+ @Override
+ public <T> T accept(Visitor<T> visitor) {
+ return resolve().accept(visitor);
+ }
+
+ @Override
+ public StackSize getStackSize() {
+ return asErasure().getStackSize();
+ }
+
+ @Override
+ public boolean isArray() {
+ return asErasure().isArray();
+ }
+
+ @Override
+ public boolean isPrimitive() {
+ return asErasure().isPrimitive();
+ }
+
+ @Override
+ public boolean represents(java.lang.reflect.Type type) {
+ return resolve().represents(type);
+ }
+
+ @Override
+ public int hashCode() {
+ return resolve().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other instanceof TypeDefinition && resolve().equals(other);
+ }
+
+ @Override
+ public String toString() {
+ return resolve().toString();
+ }
+
+ /**
+ * A lazy projection of a type with a lazy resolution of super class and interface types. A lazy navigation
+ * must only be used for describing types that are guaranteed to define a super class and interface types,
+ * i.e. non-generic types and parameterized types. Lazy navigation can also be applied to array types where
+ * the usage does however make little sense as those properties are never generic.
+ */
+ public abstract static class WithLazyNavigation extends LazyProjection {
+
+ @Override
+ public Generic getSuperClass() {
+ return LazySuperClass.of(this);
+ }
+
+ @Override
+ public TypeList.Generic getInterfaces() {
+ return LazyInterfaceList.of(this);
+ }
+
+ @Override
+ public Iterator<TypeDefinition> iterator() {
+ return new TypeDefinition.SuperClassIterator(this);
+ }
+
+ /**
+ * A lazy super class description for a lazy projection.
+ */
+ protected static class LazySuperClass extends WithLazyNavigation {
+
+ /**
+ * The lazy projection for which this description is a delegate.
+ */
+ private final LazyProjection delegate;
+
+ /**
+ * Creates a new lazy super class description.
+ *
+ * @param delegate The lazy projection for which this description is a delegate.
+ */
+ protected LazySuperClass(LazyProjection delegate) {
+ this.delegate = delegate;
+ }
+
+ /**
+ * Resolves a lazy super class description.
+ *
+ * @param delegate The lazy projection for which this description is a delegate.
+ * @return A lazy description of the super class or {@code null} if the delegate does not define a super class.
+ */
+ protected static Generic of(LazyProjection delegate) {
+ return delegate.asErasure().getSuperClass() == null
+ ? Generic.UNDEFINED
+ : new LazySuperClass(delegate);
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return resolve().getDeclaredAnnotations();
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ return delegate.asErasure().getSuperClass().asErasure();
+ }
+
+ @Override
+ protected Generic resolve() {
+ return delegate.resolve().getSuperClass();
+ }
+ }
+
+ /**
+ * A lazy interface type description for a lazy projection.
+ */
+ protected static class LazyInterfaceType extends WithLazyNavigation {
+
+ /**
+ * The lazy projection for which this description is a delegate.
+ */
+ private final LazyProjection delegate;
+
+ /**
+ * The index of the interface in question.
+ */
+ private final int index;
+
+ /**
+ * The raw interface that is declared by the erasure of the represented lazy projection.
+ */
+ private final TypeDescription.Generic rawInterface;
+
+ /**
+ * Creates a new lazy interface type.
+ *
+ * @param delegate The lazy projection for which this description is a delegate.
+ * @param index The index of the interface in question.
+ * @param rawInterface The raw interface that is declared by the erasure of the represented lazy projection.
+ */
+ protected LazyInterfaceType(LazyProjection delegate, int index, Generic rawInterface) {
+ this.delegate = delegate;
+ this.index = index;
+ this.rawInterface = rawInterface;
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return resolve().getDeclaredAnnotations();
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ return rawInterface.asErasure();
+ }
+
+ @Override
+ protected Generic resolve() {
+ return delegate.resolve().getInterfaces().get(index);
+ }
+ }
+
+ /**
+ * A lazy representation of a lazy projection's interfaces.
+ */
+ protected static class LazyInterfaceList extends TypeList.Generic.AbstractBase {
+
+ /**
+ * The lazy projection for which this description is a delegate.
+ */
+ private final LazyProjection delegate;
+
+ /**
+ * A list of raw interface types declared by the lazy projection's erasure.
+ */
+ private final TypeList.Generic rawInterfaces;
+
+ /**
+ * Creates a new lazy interface list.
+ *
+ * @param delegate The lazy projection for which this description is a delegate.
+ * @param rawInterfaces A list of raw interface types declared by the lazy projection's erasure.
+ */
+ protected LazyInterfaceList(LazyProjection delegate, TypeList.Generic rawInterfaces) {
+ this.delegate = delegate;
+ this.rawInterfaces = rawInterfaces;
+ }
+
+ /**
+ * Resolves a lazy interface list.
+ *
+ * @param delegate The delegate for which to represent interfaces.
+ * @return A lazy list representing the delegate's interfaces lazily.
+ */
+ protected static TypeList.Generic of(LazyProjection delegate) {
+ return new LazyInterfaceList(delegate, delegate.asErasure().getInterfaces());
+ }
+
+ @Override
+ public Generic get(int index) {
+ return new LazyInterfaceType(delegate, index, rawInterfaces.get(index));
+ }
+
+ @Override
+ public int size() {
+ return rawInterfaces.size();
+ }
+ }
+
+ /**
+ * A description of an annotated lazy type with lazy navigation.
+ */
+ protected abstract static class OfAnnotatedElement extends WithLazyNavigation {
+
+ /**
+ * Returns the current type's annotation reader.
+ *
+ * @return The current type's annotation reader.
+ */
+ protected abstract AnnotationReader getAnnotationReader();
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return getAnnotationReader().asList();
+ }
+ }
+ }
+
+ /**
+ * A lazy projection of a type that resolves super class and interface types eagerly.
+ */
+ public abstract static class WithEagerNavigation extends LazyProjection {
+
+ @Override
+ public Generic getSuperClass() {
+ return resolve().getSuperClass();
+ }
+
+ @Override
+ public TypeList.Generic getInterfaces() {
+ return resolve().getInterfaces();
+ }
+
+ @Override
+ public Iterator<TypeDefinition> iterator() {
+ return resolve().iterator();
+ }
+
+ /**
+ * A description of an annotated lazy type with eager navigation.
+ */
+ protected abstract static class OfAnnotatedElement extends WithEagerNavigation {
+
+ /**
+ * Returns the current type's annotation reader.
+ *
+ * @return The current type's annotation reader.
+ */
+ protected abstract AnnotationReader getAnnotationReader();
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return getAnnotationReader().asList();
+ }
+ }
+ }
+
+ /**
+ * A lazy projection of a generic super type.
+ */
+ public static class ForLoadedSuperClass extends LazyProjection.WithLazyNavigation.OfAnnotatedElement {
+
+ /**
+ * The type of which the super class is represented.
+ */
+ private final Class<?> type;
+
+ /**
+ * Creates a new lazy projection of a type's super class.
+ *
+ * @param type The type of which the super class is represented.
+ */
+ public ForLoadedSuperClass(Class<?> type) {
+ this.type = type;
+ }
+
+ @Override
+ protected Generic resolve() {
+ java.lang.reflect.Type superClass = type.getGenericSuperclass();
+ return superClass == null
+ ? Generic.UNDEFINED
+ : Sort.describe(superClass, getAnnotationReader());
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ Class<?> superClass = type.getSuperclass();
+ return superClass == null
+ ? TypeDescription.UNDEFINED
+ : new ForLoadedType(superClass);
+ }
+
+ @Override
+ protected AnnotationReader getAnnotationReader() {
+ return AnnotationReader.DISPATCHER.resolveSuperClassType(type);
+ }
+ }
+
+ /**
+ * A lazy projection of a field's type.
+ */
+ public static class ForLoadedFieldType extends LazyProjection.WithEagerNavigation.OfAnnotatedElement {
+
+ /**
+ * The field of which the type is represented.
+ */
+ private final Field field;
+
+ /**
+ * Create's a lazy projection of a field type.
+ *
+ * @param field The field of which the type is represented.
+ */
+ public ForLoadedFieldType(Field field) {
+ this.field = field;
+ }
+
+ @Override
+ protected Generic resolve() {
+ return Sort.describe(field.getGenericType(), getAnnotationReader());
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ return new ForLoadedType(field.getType());
+ }
+
+ @Override
+ protected AnnotationReader getAnnotationReader() {
+ return AnnotationReader.DISPATCHER.resolveFieldType(field);
+ }
+ }
+
+ /**
+ * A lazy projection of a method's generic return type.
+ */
+ public static class ForLoadedReturnType extends LazyProjection.WithEagerNavigation.OfAnnotatedElement {
+
+ /**
+ * The method which defines the return type.
+ */
+ private final Method method;
+
+ /**
+ * Creates a new lazy projection of a method's return type.
+ *
+ * @param method The method which defines the return type.
+ */
+ public ForLoadedReturnType(Method method) {
+ this.method = method;
+ }
+
+ @Override
+ protected Generic resolve() {
+ return Sort.describe(method.getGenericReturnType(), getAnnotationReader());
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ return new ForLoadedType(method.getReturnType());
+ }
+
+ @Override
+ protected AnnotationReader getAnnotationReader() {
+ return AnnotationReader.DISPATCHER.resolveReturnType(method);
+ }
+ }
+
+ /**
+ * A lazy projection of the parameter type of a {@link Constructor}.
+ */
+ public static class OfConstructorParameter extends LazyProjection.WithEagerNavigation.OfAnnotatedElement {
+
+ /**
+ * The constructor of which a parameter type is represented.
+ */
+ private final Constructor<?> constructor;
+
+ /**
+ * The parameter's index.
+ */
+ private final int index;
+
+ /**
+ * The erasure of the parameter type.
+ */
+ private final Class<?>[] erasure;
+
+ /**
+ * Creates a lazy projection of a constructor's parameter.
+ *
+ * @param constructor The constructor of which a parameter type is represented.
+ * @param index The parameter's index.
+ * @param erasure The erasure of the parameter type.
+ */
+ @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "The array is never exposed outside of the class")
+ public OfConstructorParameter(Constructor<?> constructor, int index, Class<?>[] erasure) {
+ this.constructor = constructor;
+ this.index = index;
+ this.erasure = erasure;
+ }
+
+ @Override
+ protected Generic resolve() {
+ java.lang.reflect.Type[] type = constructor.getGenericParameterTypes();
+ return erasure.length == type.length
+ ? Sort.describe(type[index], getAnnotationReader())
+ : new OfNonGenericType.ForLoadedType(erasure[index]);
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ return new TypeDescription.ForLoadedType(erasure[index]);
+ }
+
+ @Override
+ protected AnnotationReader getAnnotationReader() {
+ return AnnotationReader.DISPATCHER.resolveParameterType(constructor, index);
+ }
+ }
+
+ /**
+ * A lazy projection of the parameter type of a {@link Method}.
+ */
+ public static class OfMethodParameter extends LazyProjection.WithEagerNavigation.OfAnnotatedElement {
+
+ /**
+ * The method of which a parameter type is represented.
+ */
+ private final Method method;
+
+ /**
+ * The parameter's index.
+ */
+ private final int index;
+
+ /**
+ * The erasures of the method's parameter types.
+ */
+ private final Class<?>[] erasure;
+
+ /**
+ * Creates a lazy projection of a constructor's parameter.
+ *
+ * @param method The method of which a parameter type is represented.
+ * @param index The parameter's index.
+ * @param erasure The erasures of the method's parameter types.
+ */
+ @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "The array is never exposed outside of the class")
+ public OfMethodParameter(Method method, int index, Class<?>[] erasure) {
+ this.method = method;
+ this.index = index;
+ this.erasure = erasure;
+ }
+
+ @Override
+ protected Generic resolve() {
+ java.lang.reflect.Type[] type = method.getGenericParameterTypes();
+ return erasure.length == type.length
+ ? Sort.describe(type[index], getAnnotationReader())
+ : new OfNonGenericType.ForLoadedType(erasure[index]);
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ return new TypeDescription.ForLoadedType(erasure[index]);
+ }
+
+ @Override
+ protected AnnotationReader getAnnotationReader() {
+ return AnnotationReader.DISPATCHER.resolveParameterType(method, index);
+ }
+ }
+
+ /**
+ * A lazy projection that applies a visitor only when resolving the generic type but not when reading the erasure.
+ */
+ public static class WithResolvedErasure extends LazyProjection.WithEagerNavigation {
+
+ /**
+ * The unresolved generic type.
+ */
+ private final Generic delegate;
+
+ /**
+ * The visitor to apply for resolving the generic type.
+ */
+ private final Visitor<? extends Generic> visitor;
+
+ /**
+ * The annotation source to apply.
+ */
+ private final AnnotationSource annotationSource;
+
+ /**
+ * Creates a lazy projection with a resolved erasure that retains the delegates type annotations.
+ *
+ * @param delegate The unresolved generic type.
+ * @param visitor The visitor to apply for resolving the generic type.
+ */
+ public WithResolvedErasure(Generic delegate, Visitor<? extends Generic> visitor) {
+ this(delegate, visitor, delegate);
+ }
+
+ /**
+ * Creates a lazy projection with a resolved erasure.
+ *
+ * @param delegate The unresolved generic type.
+ * @param visitor The visitor to apply for resolving the generic type.
+ * @param annotationSource The annotation source representing this type's type annotations.
+ */
+ public WithResolvedErasure(Generic delegate, Visitor<? extends Generic> visitor, AnnotationSource annotationSource) {
+ this.delegate = delegate;
+ this.visitor = visitor;
+ this.annotationSource = annotationSource;
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return annotationSource.getDeclaredAnnotations();
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ return delegate.asErasure();
+ }
+
+ @Override
+ protected Generic resolve() {
+ return delegate.accept(visitor);
+ }
+ }
+ }
+
+ /**
+ * A builder for creating describing a generic type as a {@link Generic}.
+ */
+ @EqualsAndHashCode
+ abstract class Builder {
+
+ /**
+ * Represents an undefined {@link java.lang.reflect.Type} within a build step.
+ */
+ private static final java.lang.reflect.Type UNDEFINED = null;
+
+ /**
+ * The type annotations of the current annotated type.
+ */
+ protected final List<? extends AnnotationDescription> annotations;
+
+ /**
+ * Creates a new builder for a generic type description.
+ *
+ * @param annotations The type annotations of the current annotated type.
+ */
+ protected Builder(List<? extends AnnotationDescription> annotations) {
+ this.annotations = annotations;
+ }
+
+ /**
+ * Creates a raw type of a type description.
+ *
+ * @param type The type to represent as a raw type.
+ * @return A builder for creating a raw type.
+ */
+ public static Builder rawType(Class<?> type) {
+ return rawType(new ForLoadedType(type));
+ }
+
+ /**
+ * Creates a raw type of a type description.
+ *
+ * @param type The type to represent as a raw type.
+ * @return A builder for creating a raw type.
+ */
+ public static Builder rawType(TypeDescription type) {
+ return new Builder.OfNonGenericType(type);
+ }
+
+ /**
+ * Creates a raw type of a type description where the supplied owner type is used as .
+ *
+ * @param type The type to represent as a raw type.
+ * @param ownerType The raw type's (annotated) declaring type or {@code null} if no owner type should be declared.
+ * @return A builder for creating a raw type.
+ */
+ public static Builder rawType(Class<?> type, Generic ownerType) {
+ return rawType(new ForLoadedType(type), ownerType);
+ }
+
+ /**
+ * Creates a raw type of a type description.
+ *
+ * @param type The type to represent as a raw type.
+ * @param ownerType The raw type's (annotated) declaring type or {@code null} if no owner type should be declared.
+ * @return A builder for creating a raw type.
+ */
+ public static Builder rawType(TypeDescription type, Generic ownerType) {
+ TypeDescription declaringType = type.getDeclaringType();
+ if (declaringType == null && ownerType != null) {
+ throw new IllegalArgumentException(type + " does not have a declaring type: " + ownerType);
+ } else if (declaringType != null && (ownerType == null || !declaringType.equals(ownerType.asErasure()))) {
+ throw new IllegalArgumentException(ownerType + " is not the declaring type of " + type);
+ }
+ return new Builder.OfNonGenericType(type, ownerType);
+ }
+
+ /**
+ * Creates an unbound wildcard without type annotations.
+ *
+ * @return A description of an unbound wildcard without type annotations.
+ */
+ public static Generic unboundWildcard() {
+ return unboundWildcard(Collections.<AnnotationDescription>emptySet());
+ }
+
+ /**
+ * Creates an unbound wildcard.
+ *
+ * @param annotation The type annotations of the unbound wildcard.
+ * @return A description of an unbound wildcard.
+ */
+ public static Generic unboundWildcard(Annotation... annotation) {
+ return unboundWildcard(Arrays.asList(annotation));
+ }
+
+ /**
+ * Creates an unbound wildcard.
+ *
+ * @param annotations The type annotations of the unbound wildcard.
+ * @return A description of an unbound wildcard.
+ */
+ public static Generic unboundWildcard(List<? extends Annotation> annotations) {
+ return unboundWildcard(new AnnotationList.ForLoadedAnnotations(annotations));
+ }
+
+ /**
+ * Creates an unbound wildcard.
+ *
+ * @param annotation The type annotations of the unbound wildcard.
+ * @return A description of an unbound wildcard.
+ */
+ public static Generic unboundWildcard(AnnotationDescription... annotation) {
+ return unboundWildcard(Arrays.asList(annotation));
+ }
+
+ /**
+ * Creates an unbound wildcard.
+ *
+ * @param annotations The type annotations of the unbound wildcard.
+ * @return A description of an unbound wildcard.
+ */
+ public static Generic unboundWildcard(Collection<? extends AnnotationDescription> annotations) {
+ return OfWildcardType.Latent.unbounded(new Explicit(new ArrayList<AnnotationDescription>(annotations)));
+ }
+
+ /**
+ * Creates a symolic type variable of the given name.
+ *
+ * @param symbol The symbolic name of the type variable.
+ * @return A builder for creating a type variable.
+ */
+ public static Builder typeVariable(String symbol) {
+ return new OfTypeVariable(symbol);
+ }
+
+ /**
+ * Creates a parameterized type without an owner type or with a non-generic owner type.
+ *
+ * @param rawType A raw version of the type to describe as a parameterized type.
+ * @param parameter The type arguments to attach to the raw type as parameters.
+ * @return A builder for creating a parameterized type.
+ */
+ public static Builder parameterizedType(Class<?> rawType, java.lang.reflect.Type... parameter) {
+ return parameterizedType(rawType, Arrays.asList(parameter));
+ }
+
+ /**
+ * Creates a parameterized type without an owner type or with a non-generic owner type.
+ *
+ * @param rawType A raw version of the type to describe as a parameterized type.
+ * @param parameters The type arguments to attach to the raw type as parameters.
+ * @return A builder for creating a parameterized type.
+ */
+ public static Builder parameterizedType(Class<?> rawType, List<? extends java.lang.reflect.Type> parameters) {
+ return parameterizedType(rawType, UNDEFINED, parameters);
+ }
+
+ /**
+ * Creates a parameterized type.
+ *
+ * @param rawType A raw version of the type to describe as a parameterized type.
+ * @param ownerType The owner type of the parameterized type.
+ * @param parameters The type arguments to attach to the raw type as parameters.
+ * @return A builder for creating a parameterized type.
+ */
+ public static Builder parameterizedType(Class<?> rawType, java.lang.reflect.Type ownerType, List<? extends java.lang.reflect.Type> parameters) {
+ return parameterizedType(new ForLoadedType(rawType),
+ ownerType == null
+ ? null
+ : Sort.describe(ownerType),
+ new TypeList.Generic.ForLoadedTypes(parameters));
+ }
+
+ /**
+ * Creates a parameterized type without an owner type or with a non-generic owner type.
+ *
+ * @param rawType A raw version of the type to describe as a parameterized type.
+ * @param parameter The type arguments to attach to the raw type as parameters.
+ * @return A builder for creating a parameterized type.
+ */
+ public static Builder parameterizedType(TypeDescription rawType, TypeDefinition... parameter) {
+ return parameterizedType(rawType, Arrays.asList(parameter));
+ }
+
+ /**
+ * Creates a parameterized type without an owner type or with a non-generic owner type.
+ *
+ * @param rawType A raw version of the type to describe as a parameterized type.
+ * @param parameters The type arguments to attach to the raw type as parameters.
+ * @return A builder for creating a parameterized type.
+ */
+ public static Builder parameterizedType(TypeDescription rawType, Collection<? extends TypeDefinition> parameters) {
+ return parameterizedType(rawType, Generic.UNDEFINED, parameters);
+ }
+
+ /**
+ * Creates a parameterized type.
+ *
+ * @param rawType A raw version of the type to describe as a parameterized type.
+ * @param ownerType The owner type of the parameterized type.
+ * @param parameters The type arguments to attach to the raw type as parameters.
+ * @return A builder for creating a parameterized type.
+ */
+ public static Builder parameterizedType(TypeDescription rawType, Generic ownerType, Collection<? extends TypeDefinition> parameters) {
+ TypeDescription declaringType = rawType.getDeclaringType();
+ if (ownerType == null && declaringType != null && rawType.isStatic()) {
+ ownerType = declaringType.asGenericType();
+ }
+ if (!rawType.isGenerified()) {
+ throw new IllegalArgumentException(rawType + " is not a parameterized type");
+ } else if (ownerType == null && declaringType != null && !rawType.isStatic()) {
+ throw new IllegalArgumentException(rawType + " requires an owner type");
+ } else if (ownerType != null && !ownerType.asErasure().equals(declaringType)) {
+ throw new IllegalArgumentException(ownerType + " does not represent required owner for " + rawType);
+ } else if (ownerType != null && (rawType.isStatic() ^ ownerType.getSort().isNonGeneric())) {
+ throw new IllegalArgumentException(ownerType + " does not define the correct parameters for owning " + rawType);
+ } else if (rawType.getTypeVariables().size() != parameters.size()) {
+ throw new IllegalArgumentException(parameters + " does not contain number of required parameters for " + rawType);
+ }
+ return new Builder.OfParameterizedType(rawType, ownerType, new TypeList.Generic.Explicit(new ArrayList<TypeDefinition>(parameters)));
+ }
+
+ /**
+ * Transforms this type into the upper bound of a wildcard type.
+ *
+ * @return A generic type description of a wildcard type with this builder's type as an upper bound.
+ */
+ public Generic asWildcardUpperBound() {
+ return asWildcardUpperBound(Collections.<AnnotationDescription>emptySet());
+ }
+
+ /**
+ * Transforms this type into the upper bound of a wildcard type.
+ *
+ * @param annotation Type annotations to be declared by the wildcard type.
+ * @return A generic type description of a wildcard type with this builder's type as an upper bound.
+ */
+ public Generic asWildcardUpperBound(Annotation... annotation) {
+ return asWildcardUpperBound(Arrays.asList(annotation));
+ }
+
+ /**
+ * Transforms this type into the upper bound of a wildcard type.
+ *
+ * @param annotations Type annotations to be declared by the wildcard type.
+ * @return A generic type description of a wildcard type with this builder's type as an upper bound.
+ */
+ public Generic asWildcardUpperBound(List<? extends Annotation> annotations) {
+ return asWildcardUpperBound(new AnnotationList.ForLoadedAnnotations(annotations));
+ }
+
+ /**
+ * Transforms this type into the upper bound of a wildcard type.
+ *
+ * @param annotation Type annotations to be declared by the wildcard type.
+ * @return A generic type description of a wildcard type with this builder's type as an upper bound.
+ */
+ public Generic asWildcardUpperBound(AnnotationDescription... annotation) {
+ return asWildcardUpperBound(Arrays.asList(annotation));
+ }
+
+ /**
+ * Transforms this type into the upper bound of a wildcard type.
+ *
+ * @param annotations Type annotations to be declared by the wildcard type.
+ * @return A generic type description of a wildcard type with this builder's type as an upper bound.
+ */
+ public Generic asWildcardUpperBound(Collection<? extends AnnotationDescription> annotations) {
+ return OfWildcardType.Latent.boundedAbove(build(), new Explicit(new ArrayList<AnnotationDescription>(annotations)));
+ }
+
+ /**
+ * Transforms this type into the lower bound of a wildcard type.
+ *
+ * @return A generic type description of a wildcard type with this builder's type as an lower bound.
+ */
+ public Generic asWildcardLowerBound() {
+ return asWildcardLowerBound(Collections.<AnnotationDescription>emptySet());
+ }
+
+ /**
+ * Transforms this type into the lower bound of a wildcard type.
+ *
+ * @param annotation Type annotations to be declared by the wildcard type.
+ * @return A generic type description of a wildcard type with this builder's type as an lower bound.
+ */
+ public Generic asWildcardLowerBound(Annotation... annotation) {
+ return asWildcardLowerBound(Arrays.asList(annotation));
+ }
+
+ /**
+ * Transforms this type into the lower bound of a wildcard type.
+ *
+ * @param annotations Type annotations to be declared by the wildcard type.
+ * @return A generic type description of a wildcard type with this builder's type as an lower bound.
+ */
+ public Generic asWildcardLowerBound(List<? extends Annotation> annotations) {
+ return asWildcardLowerBound(new AnnotationList.ForLoadedAnnotations(annotations));
+ }
+
+ /**
+ * Transforms this type into the lower bound of a wildcard type.
+ *
+ * @param annotation Type annotations to be declared by the wildcard type.
+ * @return A generic type description of a wildcard type with this builder's type as an lower bound.
+ */
+ public Generic asWildcardLowerBound(AnnotationDescription... annotation) {
+ return asWildcardLowerBound(Arrays.asList(annotation));
+ }
+
+ /**
+ * Transforms this type into the lower bound of a wildcard type.
+ *
+ * @param annotations Type annotations to be declared by the wildcard type.
+ * @return A generic type description of a wildcard type with this builder's type as an lower bound.
+ */
+ public Generic asWildcardLowerBound(Collection<? extends AnnotationDescription> annotations) {
+ return OfWildcardType.Latent.boundedBelow(build(), new Explicit(new ArrayList<AnnotationDescription>(annotations)));
+ }
+
+ /**
+ * Represents the built type into an array.
+ *
+ * @return A builder for creating an array of the currently built type.
+ */
+ public Builder asArray() {
+ return asArray(1);
+ }
+
+ /**
+ * Represents the built type into an array.
+ *
+ * @param arity The arity of the array.
+ * @return A builder for creating an array of the currently built type.
+ */
+ public Builder asArray(int arity) {
+ if (arity < 1) {
+ throw new IllegalArgumentException("Cannot define an array of a non-positive arity: " + arity);
+ }
+ TypeDescription.Generic typeDescription = build();
+ while (--arity > 0) {
+ typeDescription = new OfGenericArray.Latent(typeDescription, Empty.INSTANCE);
+ }
+ return new Builder.OfGenericArrayType(typeDescription);
+ }
+
+ /**
+ * Defines type annotations to be declared by the current type.
+ *
+ * @param annotation Type annotations to be declared by the current type.
+ * @return A new builder where the current type declares the supplied type annotations.
+ */
+ public Builder annotate(Annotation... annotation) {
+ return annotate(Arrays.asList(annotation));
+ }
+
+ /**
+ * Defines type annotations to be declared by the current type.
+ *
+ * @param annotations Type annotations to be declared by the current type.
+ * @return A new builder where the current type declares the supplied type annotations.
+ */
+ public Builder annotate(List<? extends Annotation> annotations) {
+ return annotate(new AnnotationList.ForLoadedAnnotations(annotations));
+ }
+
+ /**
+ * Defines type annotations to be declared by the current type.
+ *
+ * @param annotation Type annotations to be declared by the current type.
+ * @return A new builder where the current type declares the supplied type annotations.
+ */
+ public Builder annotate(AnnotationDescription... annotation) {
+ return annotate(Arrays.asList(annotation));
+ }
+
+ /**
+ * Defines type annotations to be declared by the current type.
+ *
+ * @param annotations Type annotations to be declared by the current type.
+ * @return A new builder where the current type declares the supplied type annotations.
+ */
+ public Builder annotate(Collection<? extends AnnotationDescription> annotations) {
+ return doAnnotate(new ArrayList<AnnotationDescription>(annotations));
+ }
+
+ /**
+ * Creates a new builder for the current type and the spplied type annotations.
+ *
+ * @param annotations Type annotations to be declared by the current type.
+ * @return A new builder where the current type declares the supplied type annotations.
+ */
+ protected abstract Builder doAnnotate(List<? extends AnnotationDescription> annotations);
+
+ /**
+ * Finalizes the build and finalizes the created type as a generic type description.
+ *
+ * @return A generic type description of the built type.
+ */
+ public Generic build() {
+ return doBuild();
+ }
+
+ /**
+ * Finalizes the build and finalizes the created type as a generic type description.
+ *
+ * @param annotation Type annotations place for the built generic type to declare.
+ * @return A generic type description of the built type.
+ */
+ public Generic build(Annotation... annotation) {
+ return build(Arrays.asList(annotation));
+ }
+
+ /**
+ * Finalizes the build and finalizes the created type as a generic type description.
+ *
+ * @param annotations Type annotations place for the built generic type to declare.
+ * @return A generic type description of the built type.
+ */
+ public Generic build(List<? extends Annotation> annotations) {
+ return build(new AnnotationList.ForLoadedAnnotations(annotations));
+ }
+
+ /**
+ * Finalizes the build and finalizes the created type as a generic type description.
+ *
+ * @param annotation Type annotations place for the built generic type to declare.
+ * @return A generic type description of the built type.
+ */
+ public Generic build(AnnotationDescription... annotation) {
+ return build(Arrays.asList(annotation));
+ }
+
+ /**
+ * Finalizes the build and finalizes the created type as a generic type description.
+ *
+ * @param annotations Type annotations place for the built generic type to declare.
+ * @return A generic type description of the built type.
+ */
+ public Generic build(Collection<? extends AnnotationDescription> annotations) {
+ return doAnnotate(new ArrayList<AnnotationDescription>(annotations)).doBuild();
+ }
+
+ /**
+ * Builds the generic type.
+ *
+ * @return The generic type.
+ */
+ protected abstract Generic doBuild();
+
+ /**
+ * A generic type builder for building a non-generic type.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ protected static class OfNonGenericType extends Builder {
+
+ /**
+ * The type's erasure.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * The raw type's (annotated) declaring type.
+ */
+ private final Generic ownerType;
+
+ /**
+ * Creates a builder for a non-generic type.
+ *
+ * @param typeDescription The type's erasure.
+ */
+ protected OfNonGenericType(TypeDescription typeDescription) {
+ this(typeDescription, typeDescription.getDeclaringType());
+ }
+
+ /**
+ * Creates a builder for a non-generic type.
+ *
+ * @param typeDescription The type's erasure.
+ * @param ownerType The raw type's raw declaring type.
+ */
+ private OfNonGenericType(TypeDescription typeDescription, TypeDescription ownerType) {
+ this(typeDescription, ownerType == null
+ ? Generic.UNDEFINED
+ : ownerType.asGenericType());
+ }
+
+ /**
+ * Creates a builder for a non-generic type.
+ *
+ * @param typeDescription The type's erasure.
+ * @param ownerType The raw type's (annotated) declaring type.
+ */
+ protected OfNonGenericType(TypeDescription typeDescription, Generic ownerType) {
+ this(typeDescription, ownerType, Collections.<AnnotationDescription>emptyList());
+ }
+
+ /**
+ * Creates a builder for a non-generic type.
+ *
+ * @param typeDescription The type's erasure.
+ * @param ownerType The raw type's (annotated) declaring type.
+ * @param annotations The type's type annotations.
+ */
+ protected OfNonGenericType(TypeDescription typeDescription, Generic ownerType, List<? extends AnnotationDescription> annotations) {
+ super(annotations);
+ this.ownerType = ownerType;
+ this.typeDescription = typeDescription;
+ }
+
+ @Override
+ protected Builder doAnnotate(List<? extends AnnotationDescription> annotations) {
+ return new OfNonGenericType(typeDescription, ownerType, CompoundList.of(this.annotations, annotations));
+ }
+
+ @Override
+ protected Generic doBuild() {
+ if (typeDescription.represents(void.class) && !annotations.isEmpty()) {
+ throw new IllegalArgumentException("The void non-type cannot be annotated");
+ }
+ return new Generic.OfNonGenericType.Latent(typeDescription, ownerType, new Explicit(annotations));
+ }
+ }
+
+ /**
+ * A generic type builder for building a parameterized type.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ protected static class OfParameterizedType extends Builder {
+
+ /**
+ * The raw base type.
+ */
+ private final TypeDescription rawType;
+
+ /**
+ * The generic owner type.
+ */
+ private final Generic ownerType;
+
+ /**
+ * The parameter types.
+ */
+ private final List<? extends Generic> parameterTypes;
+
+ /**
+ * Creates a builder for a parameterized type.
+ *
+ * @param rawType The raw base type.
+ * @param ownerType The generic owner type.
+ * @param parameterTypes The parameter types.
+ */
+ protected OfParameterizedType(TypeDescription rawType, Generic ownerType, List<? extends Generic> parameterTypes) {
+ this(rawType, ownerType, parameterTypes, Collections.<AnnotationDescription>emptyList());
+ }
+
+ /**
+ * Creates a builder for a parameterized type.
+ *
+ * @param rawType The raw base type.
+ * @param ownerType The generic owner type.
+ * @param parameterTypes The parameter types.
+ * @param annotations The type's type annotations.
+ */
+ protected OfParameterizedType(TypeDescription rawType,
+ Generic ownerType,
+ List<? extends Generic> parameterTypes,
+ List<? extends AnnotationDescription> annotations) {
+ super(annotations);
+ this.rawType = rawType;
+ this.ownerType = ownerType;
+ this.parameterTypes = parameterTypes;
+ }
+
+ @Override
+ protected Builder doAnnotate(List<? extends AnnotationDescription> annotations) {
+ return new OfParameterizedType(rawType, ownerType, parameterTypes, CompoundList.of(this.annotations, annotations));
+ }
+
+ @Override
+ protected Generic doBuild() {
+ return new Generic.OfParameterizedType.Latent(rawType, ownerType, parameterTypes, new Explicit(annotations));
+ }
+ }
+
+ /**
+ * A generic type builder building a generic array type.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ protected static class OfGenericArrayType extends Builder {
+
+ /**
+ * The generic component type.
+ */
+ private final Generic componentType;
+
+ /**
+ * Creates a type builder for building a generic array type.
+ *
+ * @param componentType The generic component type.
+ */
+ protected OfGenericArrayType(Generic componentType) {
+ this(componentType, Collections.<AnnotationDescription>emptyList());
+ }
+
+ /**
+ * Creates a type builder for building a generic array type.
+ *
+ * @param componentType The generic component type.
+ * @param annotations The type's type annotations.
+ */
+ protected OfGenericArrayType(Generic componentType, List<? extends AnnotationDescription> annotations) {
+ super(annotations);
+ this.componentType = componentType;
+ }
+
+ @Override
+ protected Builder doAnnotate(List<? extends AnnotationDescription> annotations) {
+ return new OfGenericArrayType(componentType, CompoundList.of(this.annotations, annotations));
+ }
+
+ @Override
+ protected Generic doBuild() {
+ return new Generic.OfGenericArray.Latent(componentType, new Explicit(annotations));
+ }
+ }
+
+ /**
+ * A generic type builder building a symbolic type variable.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ protected static class OfTypeVariable extends Builder {
+
+ /**
+ * The variable's symbol.
+ */
+ private final String symbol;
+
+ /**
+ * Creates a new builder for a symbolic type variable.
+ *
+ * @param symbol The variable's symbol.
+ */
+ protected OfTypeVariable(String symbol) {
+ this(symbol, Collections.<AnnotationDescription>emptyList());
+ }
+
+ /**
+ * Creates a new builder for a symbolic type variable.
+ *
+ * @param symbol The variable's symbol.
+ * @param annotations The type's type annotations.
+ */
+ protected OfTypeVariable(String symbol, List<? extends AnnotationDescription> annotations) {
+ super(annotations);
+ this.symbol = symbol;
+ }
+
+ @Override
+ protected Builder doAnnotate(List<? extends AnnotationDescription> annotations) {
+ return new OfTypeVariable(symbol, CompoundList.of(this.annotations, annotations));
+ }
+
+ @Override
+ protected Generic doBuild() {
+ return new Generic.OfTypeVariable.Symbolic(symbol, new Explicit(annotations));
+ }
+ }
+ }
+ }
+
+ /**
+ * An abstract base implementation of a type description.
+ */
+ abstract class AbstractBase extends TypeVariableSource.AbstractBase implements TypeDescription {
+
+ /**
+ * Checks if a specific type is assignable to another type where the source type must be a super
+ * type of the target type.
+ *
+ * @param sourceType The source type to which another type is to be assigned to.
+ * @param targetType The target type that is to be assigned to the source type.
+ * @return {@code true} if the target type is assignable to the source type.
+ */
+ private static boolean isAssignable(TypeDescription sourceType, TypeDescription targetType) {
+ // Means that '[sourceType] var = ([targetType]) val;' is a valid assignment. This is true, if:
+ // (1) Both types are equal (implies primitive types.)
+ if (sourceType.equals(targetType)) {
+ return true;
+ }
+ // (3) For arrays, there are special assignment rules.
+ if (targetType.isArray()) {
+ return sourceType.isArray()
+ ? isAssignable(sourceType.getComponentType(), targetType.getComponentType())
+ : sourceType.represents(Object.class) || ARRAY_INTERFACES.contains(sourceType.asGenericType());
+ }
+ // (2) Interfaces do not extend the Object type but are assignable to the Object type.
+ if (sourceType.represents(Object.class)) {
+ return !targetType.isPrimitive();
+ }
+ // (4) The sub type has a super type and this super type is assignable to the super type.
+ Generic superClass = targetType.getSuperClass();
+ if (superClass != null && sourceType.isAssignableFrom(superClass.asErasure())) {
+ return true;
+ }
+ // (5) If the target type is an interface, any of this type's interfaces might be assignable to it.
+ if (sourceType.isInterface()) {
+ for (TypeDescription interfaceType : targetType.getInterfaces().asErasures()) {
+ if (sourceType.isAssignableFrom(interfaceType)) {
+ return true;
+ }
+ }
+ }
+ // (6) None of these criteria are true, i.e. the types are not assignable.
+ return false;
+ }
+
+ @Override
+ public boolean isAssignableFrom(Class<?> type) {
+ return isAssignableFrom(new ForLoadedType(type));
+ }
+
+ @Override
+ public boolean isAssignableFrom(TypeDescription typeDescription) {
+ return isAssignable(this, typeDescription);
+ }
+
+ @Override
+ public boolean isAssignableTo(Class<?> type) {
+ return isAssignableTo(new ForLoadedType(type));
+ }
+
+ @Override
+ public boolean isAssignableTo(TypeDescription typeDescription) {
+ return isAssignable(typeDescription, this);
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ return this;
+ }
+
+ @Override
+ public Generic asGenericType() {
+ return new Generic.OfNonGenericType.ForErasure(this);
+ }
+
+ @Override
+ public Sort getSort() {
+ return Sort.NON_GENERIC;
+ }
+
+ @Override
+ public boolean isInstance(Object value) {
+ return isAssignableFrom(value.getClass());
+ }
+
+ @Override
+ public boolean isAnnotationValue(Object value) {
+ if ((represents(Class.class) && value instanceof TypeDescription)
+ || (value instanceof AnnotationDescription && ((AnnotationDescription) value).getAnnotationType().equals(this))
+ || (value instanceof EnumerationDescription && ((EnumerationDescription) value).getEnumerationType().equals(this))
+ || (represents(String.class) && value instanceof String)
+ || (represents(boolean.class) && value instanceof Boolean)
+ || (represents(byte.class) && value instanceof Byte)
+ || (represents(short.class) && value instanceof Short)
+ || (represents(char.class) && value instanceof Character)
+ || (represents(int.class) && value instanceof Integer)
+ || (represents(long.class) && value instanceof Long)
+ || (represents(float.class) && value instanceof Float)
+ || (represents(double.class) && value instanceof Double)
+ || (represents(String[].class) && value instanceof String[])
+ || (represents(boolean[].class) && value instanceof boolean[])
+ || (represents(byte[].class) && value instanceof byte[])
+ || (represents(short[].class) && value instanceof short[])
+ || (represents(char[].class) && value instanceof char[])
+ || (represents(int[].class) && value instanceof int[])
+ || (represents(long[].class) && value instanceof long[])
+ || (represents(float[].class) && value instanceof float[])
+ || (represents(double[].class) && value instanceof double[])
+ || (represents(Class[].class) && value instanceof TypeDescription[])) {
+ return true;
+ } else if (isAssignableTo(Annotation[].class) && value instanceof AnnotationDescription[]) {
+ for (AnnotationDescription annotationDescription : (AnnotationDescription[]) value) {
+ if (!annotationDescription.getAnnotationType().equals(getComponentType())) {
+ return false;
+ }
+ }
+ return true;
+ } else if (isAssignableTo(Enum[].class) && value instanceof EnumerationDescription[]) {
+ for (EnumerationDescription enumerationDescription : (EnumerationDescription[]) value) {
+ if (!enumerationDescription.getEnumerationType().equals(getComponentType())) {
+ return false;
+ }
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public String getInternalName() {
+ return getName().replace('.', '/');
+ }
+
+ @Override
+ public int getActualModifiers(boolean superFlag) {
+ int actualModifiers = getModifiers() | (getDeclaredAnnotations().isAnnotationPresent(Deprecated.class)
+ ? Opcodes.ACC_DEPRECATED
+ : EMPTY_MASK);
+ if (isPrivate()) {
+ actualModifiers = actualModifiers & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC);
+ } else if (isProtected()) {
+ actualModifiers = actualModifiers & ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_STATIC) | Opcodes.ACC_PUBLIC;
+ } else {
+ actualModifiers = actualModifiers & ~Opcodes.ACC_STATIC;
+ }
+ return superFlag ? (actualModifiers | Opcodes.ACC_SUPER) : actualModifiers;
+ }
+
+ @Override
+ public String getGenericSignature() {
+ try {
+ SignatureWriter signatureWriter = new SignatureWriter();
+ boolean generic = false;
+ for (Generic typeVariable : getTypeVariables()) {
+ signatureWriter.visitFormalTypeParameter(typeVariable.getSymbol());
+ for (Generic upperBound : typeVariable.getUpperBounds()) {
+ upperBound.accept(new Generic.Visitor.ForSignatureVisitor(upperBound.asErasure().isInterface()
+ ? signatureWriter.visitInterfaceBound()
+ : signatureWriter.visitClassBound()));
+ }
+ generic = true;
+ }
+ Generic superClass = getSuperClass();
+ // The object type itself is non generic and implicitly returns a non-generic signature
+ if (superClass == null) {
+ superClass = TypeDescription.Generic.OBJECT;
+ }
+ superClass.accept(new Generic.Visitor.ForSignatureVisitor(signatureWriter.visitSuperclass()));
+ generic = generic || !superClass.getSort().isNonGeneric();
+ for (Generic interfaceType : getInterfaces()) {
+ interfaceType.accept(new Generic.Visitor.ForSignatureVisitor(signatureWriter.visitInterface()));
+ generic = generic || !interfaceType.getSort().isNonGeneric();
+ }
+ return generic
+ ? signatureWriter.toString()
+ : NON_GENERIC_SIGNATURE;
+ } catch (GenericSignatureFormatError ignored) {
+ return NON_GENERIC_SIGNATURE;
+ }
+ }
+
+ @Override
+ public boolean isSamePackage(TypeDescription typeDescription) {
+ PackageDescription thisPackage = getPackage(), otherPackage = typeDescription.getPackage();
+ return thisPackage == null || otherPackage == null
+ ? thisPackage == otherPackage
+ : thisPackage.equals(otherPackage);
+ }
+
+ @Override
+ public boolean isVisibleTo(TypeDescription typeDescription) {
+ return isPrimitive() || (isArray()
+ ? getComponentType().isVisibleTo(typeDescription)
+ : isPublic() || isProtected() || isSamePackage(typeDescription)/* || equals(typeDescription.asErasure()) */);
+ }
+
+ @Override
+ public boolean isAccessibleTo(TypeDescription typeDescription) {
+ return isPrimitive() || (isArray()
+ ? getComponentType().isVisibleTo(typeDescription)
+ : isPublic() || isSamePackage(typeDescription)/* || equals(typeDescription.asErasure()) */);
+ }
+
+ @Override
+ public AnnotationList getInheritedAnnotations() {
+ Generic superClass = getSuperClass();
+ AnnotationList declaredAnnotations = getDeclaredAnnotations();
+ if (superClass == null) {
+ return declaredAnnotations;
+ } else {
+ Set<TypeDescription> annotationTypes = new HashSet<TypeDescription>();
+ for (AnnotationDescription annotationDescription : declaredAnnotations) {
+ annotationTypes.add(annotationDescription.getAnnotationType());
+ }
+ return new AnnotationList.Explicit(CompoundList.of(declaredAnnotations, superClass.asErasure().getInheritedAnnotations().inherited(annotationTypes)));
+ }
+ }
+
+ @Override
+ public String getActualName() {
+ if (isArray()) {
+ TypeDescription typeDescription = this;
+ int dimensions = 0;
+ do {
+ dimensions++;
+ typeDescription = typeDescription.getComponentType();
+ } while (typeDescription.isArray());
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append(typeDescription.getActualName());
+ for (int i = 0; i < dimensions; i++) {
+ stringBuilder.append("[]");
+ }
+ return stringBuilder.toString();
+ } else {
+ return getName();
+ }
+ }
+
+ @Override
+ public boolean isConstantPool() {
+ return represents(int.class)
+ || represents(long.class)
+ || represents(float.class)
+ || represents(double.class)
+ || represents(String.class)
+ || represents(Class.class)
+ || JavaType.METHOD_HANDLE.getTypeStub().equals(this)
+ || JavaType.METHOD_TYPE.getTypeStub().equals(this);
+ }
+
+ @Override
+ public boolean isPrimitiveWrapper() {
+ return represents(Boolean.class)
+ || represents(Byte.class)
+ || represents(Short.class)
+ || represents(Character.class)
+ || represents(Integer.class)
+ || represents(Long.class)
+ || represents(Float.class)
+ || represents(Double.class);
+ }
+
+ @Override
+ public boolean isAnnotationReturnType() {
+ return isPrimitive()
+ || represents(String.class)
+ || (isAssignableTo(Enum.class) && !represents(Enum.class))
+ || (isAssignableTo(Annotation.class) && !represents(Annotation.class))
+ || represents(Class.class)
+ || (isArray() && !getComponentType().isArray() && getComponentType().isAnnotationReturnType());
+ }
+
+ @Override
+ public boolean isAnnotationValue() {
+ return isPrimitive()
+ || represents(String.class)
+ || isAssignableTo(TypeDescription.class)
+ || isAssignableTo(AnnotationDescription.class)
+ || isAssignableTo(EnumerationDescription.class)
+ || (isArray() && !getComponentType().isArray() && getComponentType().isAnnotationValue());
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "EC_UNRELATED_CLASS_AND_INTERFACE", justification = "Fits equality contract for type definitions")
+ public boolean represents(java.lang.reflect.Type type) {
+ return equals(Sort.describe(type));
+ }
+
+ @Override
+ public String getTypeName() {
+ return getName();
+ }
+
+ @Override
+ public TypeVariableSource getEnclosingSource() {
+ MethodDescription enclosingMethod = getEnclosingMethod();
+ return enclosingMethod == null
+ ? (isStatic() ? TypeVariableSource.UNDEFINED : getEnclosingType()) // Top-level classes (non-static) have no enclosing type.
+ : enclosingMethod;
+ }
+
+ @Override
+ public <T> T accept(TypeVariableSource.Visitor<T> visitor) {
+ return visitor.onType(this);
+ }
+
+ @Override
+ public boolean isPackageType() {
+ return getSimpleName().equals(PackageDescription.PACKAGE_CLASS_NAME);
+ }
+
+ @Override
+ public boolean isGenerified() {
+ if (!getTypeVariables().isEmpty()) {
+ return true;
+ } else if (isStatic()) {
+ return false;
+ }
+ TypeDescription declaringType = getDeclaringType();
+ return declaringType != null && declaringType.isGenerified();
+ }
+
+ @Override
+ public int getSegmentCount() {
+ if (isStatic()) {
+ return 0;
+ }
+ TypeDescription declaringType = getDeclaringType();
+ return declaringType == null
+ ? 0
+ : declaringType.getSegmentCount() + 1;
+ }
+
+ @Override
+ public TypeDescription asBoxed() {
+ if (represents(boolean.class)) {
+ return new ForLoadedType(Boolean.class);
+ } else if (represents(byte.class)) {
+ return new ForLoadedType(Byte.class);
+ } else if (represents(short.class)) {
+ return new ForLoadedType(Short.class);
+ } else if (represents(char.class)) {
+ return new ForLoadedType(Character.class);
+ } else if (represents(int.class)) {
+ return new ForLoadedType(Integer.class);
+ } else if (represents(long.class)) {
+ return new ForLoadedType(Long.class);
+ } else if (represents(float.class)) {
+ return new ForLoadedType(Float.class);
+ } else if (represents(double.class)) {
+ return new ForLoadedType(Double.class);
+ } else {
+ return this;
+ }
+ }
+
+ @Override
+ public TypeDescription asUnboxed() {
+ if (represents(Boolean.class)) {
+ return new ForLoadedType(boolean.class);
+ } else if (represents(Byte.class)) {
+ return new ForLoadedType(byte.class);
+ } else if (represents(Short.class)) {
+ return new ForLoadedType(short.class);
+ } else if (represents(Character.class)) {
+ return new ForLoadedType(char.class);
+ } else if (represents(Integer.class)) {
+ return new ForLoadedType(int.class);
+ } else if (represents(Long.class)) {
+ return new ForLoadedType(long.class);
+ } else if (represents(Float.class)) {
+ return new ForLoadedType(float.class);
+ } else if (represents(Double.class)) {
+ return new ForLoadedType(double.class);
+ } else {
+ return this;
+ }
+ }
+
+ @Override
+ public Iterator<TypeDefinition> iterator() {
+ return new SuperClassIterator(this);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this || other instanceof TypeDefinition
+ && ((TypeDefinition) other).getSort().isNonGeneric()
+ && getName().equals(((TypeDefinition) other).asErasure().getName());
+ }
+
+ @Override
+ public int hashCode() {
+ return getName().hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return (isPrimitive() ? "" : (isInterface() ? "interface" : "class") + " ") + getName();
+ }
+
+ /**
+ * An adapter implementation of a {@link TypeDescription} that
+ * describes any type that is not an array or a primitive type.
+ */
+ public abstract static class OfSimpleType extends TypeDescription.AbstractBase {
+
+ @Override
+ public boolean isPrimitive() {
+ return false;
+ }
+
+ @Override
+ public boolean isArray() {
+ return false;
+ }
+
+ @Override
+ public TypeDescription getComponentType() {
+ return TypeDescription.UNDEFINED;
+ }
+
+ @Override
+ public String getDescriptor() {
+ return "L" + getInternalName() + ";";
+ }
+
+ @Override
+ public String getCanonicalName() {
+ return isAnonymousClass() || isLocalClass()
+ ? NO_NAME
+ : getName().replace('$', '.');
+ }
+
+ @Override
+ public String getSimpleName() {
+ String internalName = getInternalName();
+ int simpleNameIndex = internalName.lastIndexOf('$');
+ simpleNameIndex = simpleNameIndex == -1
+ ? internalName.lastIndexOf('/')
+ : simpleNameIndex;
+ if (simpleNameIndex == -1) {
+ return internalName;
+ } else {
+ while (simpleNameIndex < internalName.length() && !Character.isLetter(internalName.charAt(simpleNameIndex))) {
+ simpleNameIndex += 1;
+ }
+ return internalName.substring(simpleNameIndex);
+ }
+ }
+
+ @Override
+ public StackSize getStackSize() {
+ return StackSize.SINGLE;
+ }
+
+ /**
+ * An implementation of a type description that delegates all properties but the type's name to a delegate.
+ */
+ public abstract static class WithDelegation extends OfSimpleType {
+
+ /**
+ * Returns the delegate type description to this type instance.
+ *
+ * @return The delegate type description.
+ */
+ protected abstract TypeDescription delegate();
+
+ @Override
+ public Generic getSuperClass() {
+ return delegate().getSuperClass();
+ }
+
+ @Override
+ public TypeList.Generic getInterfaces() {
+ return delegate().getInterfaces();
+ }
+
+ @Override
+ public FieldList<FieldDescription.InDefinedShape> getDeclaredFields() {
+ return delegate().getDeclaredFields();
+ }
+
+ @Override
+ public MethodList<MethodDescription.InDefinedShape> getDeclaredMethods() {
+ return delegate().getDeclaredMethods();
+ }
+
+ @Override
+ public TypeDescription getDeclaringType() {
+ return delegate().getDeclaringType();
+ }
+
+ @Override
+ public MethodDescription getEnclosingMethod() {
+ return delegate().getEnclosingMethod();
+ }
+
+ @Override
+ public TypeDescription getEnclosingType() {
+ return delegate().getEnclosingType();
+ }
+
+ @Override
+ public TypeList getDeclaredTypes() {
+ return delegate().getDeclaredTypes();
+ }
+
+ @Override
+ public boolean isAnonymousClass() {
+ return delegate().isAnonymousClass();
+ }
+
+ @Override
+ public boolean isLocalClass() {
+ return delegate().isLocalClass();
+ }
+
+ @Override
+ public boolean isMemberClass() {
+ return delegate().isMemberClass();
+ }
+
+ @Override
+ public PackageDescription getPackage() {
+ return delegate().getPackage();
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return delegate().getDeclaredAnnotations();
+ }
+
+ @Override
+ public TypeList.Generic getTypeVariables() {
+ return delegate().getTypeVariables();
+ }
+
+ @Override
+ public int getModifiers() {
+ return delegate().getModifiers();
+ }
+
+ @Override
+ public String getGenericSignature() {
+ // Embrace use of native generic signature by direct delegation.
+ return delegate().getGenericSignature();
+ }
+
+ @Override
+ public int getActualModifiers(boolean superFlag) {
+ // Embrace use of native actual modifiers by direct delegation.
+ return delegate().getActualModifiers(superFlag);
+ }
+ }
+ }
+ }
+
+ /**
+ * A type description implementation that represents a loaded type.
+ */
+ class ForLoadedType extends AbstractBase implements Serializable {
+
+ /**
+ * The class's serial version UID.
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * The loaded type this instance represents.
+ */
+ private final Class<?> type;
+
+ /**
+ * Creates a new immutable type description for a loaded type.
+ *
+ * @param type The type to be represented by this type description.
+ */
+ public ForLoadedType(Class<?> type) {
+ this.type = type;
+ }
+
+ /**
+ * Returns the type's actual name where it is taken into consideration that this type might be loaded anonymously.
+ * In this case, the remainder of the types name is suffixed by {@code /<id>} which is removed when using this method
+ * but is retained when calling {@link Class#getName()}.
+ *
+ * @param type The type for which to resolve its name.
+ * @return The type's actual name.
+ */
+ public static String getName(Class<?> type) {
+ String name = type.getName();
+ int anonymousLoaderIndex = name.indexOf('/');
+ return anonymousLoaderIndex == -1
+ ? name
+ : name.substring(0, anonymousLoaderIndex);
+ }
+
+ @Override
+ public boolean isAssignableFrom(Class<?> type) {
+ // The JVM conducts more efficient assignability lookups of loaded types what is attempted first.
+ return this.type.isAssignableFrom(type) || super.isAssignableFrom(type);
+ }
+
+ @Override
+ public boolean isAssignableTo(Class<?> type) {
+ // The JVM conducts more efficient assignability lookups of loaded types what is attempted first.
+ return type.isAssignableFrom(this.type) || super.isAssignableTo(type);
+ }
+
+ @Override
+ public boolean represents(java.lang.reflect.Type type) {
+ // The JVM conducts more efficient assignability lookups of loaded types what is attempted first.
+ return type == this.type || super.represents(type);
+ }
+
+ @Override
+ public TypeDescription getComponentType() {
+ Class<?> componentType = type.getComponentType();
+ return componentType == null
+ ? TypeDescription.UNDEFINED
+ : new ForLoadedType(componentType);
+ }
+
+ @Override
+ public boolean isArray() {
+ return type.isArray();
+ }
+
+ @Override
+ public boolean isPrimitive() {
+ return type.isPrimitive();
+ }
+
+ @Override
+ public boolean isAnnotation() {
+ return type.isAnnotation();
+ }
+
+ @Override
+ public Generic getSuperClass() {
+ return type.getSuperclass() == null
+ ? TypeDescription.Generic.UNDEFINED
+ : new Generic.LazyProjection.ForLoadedSuperClass(type);
+ }
+
+ @Override
+ public TypeList.Generic getInterfaces() {
+ return isArray()
+ ? ARRAY_INTERFACES
+ : new TypeList.Generic.OfLoadedInterfaceTypes(type);
+ }
+
+ @Override
+ public TypeDescription getDeclaringType() {
+ Class<?> declaringType = type.getDeclaringClass();
+ return declaringType == null
+ ? TypeDescription.UNDEFINED
+ : new ForLoadedType(declaringType);
+ }
+
+ @Override
+ public MethodDescription getEnclosingMethod() {
+ Method enclosingMethod = type.getEnclosingMethod();
+ Constructor<?> enclosingConstructor = type.getEnclosingConstructor();
+ if (enclosingMethod != null) {
+ return new MethodDescription.ForLoadedMethod(enclosingMethod);
+ } else if (enclosingConstructor != null) {
+ return new MethodDescription.ForLoadedConstructor(enclosingConstructor);
+ } else {
+ return MethodDescription.UNDEFINED;
+ }
+ }
+
+ @Override
+ public TypeDescription getEnclosingType() {
+ Class<?> enclosingType = type.getEnclosingClass();
+ return enclosingType == null
+ ? TypeDescription.UNDEFINED
+ : new ForLoadedType(enclosingType);
+ }
+
+ @Override
+ public TypeList getDeclaredTypes() {
+ return new TypeList.ForLoadedTypes(type.getDeclaredClasses());
+ }
+
+ @Override
+ public String getSimpleName() {
+ String simpleName = type.getSimpleName();
+ int anonymousLoaderIndex = simpleName.indexOf('/');
+ if (anonymousLoaderIndex == -1) {
+ return simpleName;
+ } else {
+ StringBuilder normalized = new StringBuilder(simpleName.substring(0, anonymousLoaderIndex));
+ Class<?> type = this.type;
+ while (type.isArray()) {
+ normalized.append("[]");
+ type = type.getComponentType();
+ }
+ return normalized.toString();
+ }
+ }
+
+ @Override
+ public boolean isAnonymousClass() {
+ return type.isAnonymousClass();
+ }
+
+ @Override
+ public boolean isLocalClass() {
+ return type.isLocalClass();
+ }
+
+ @Override
+ public boolean isMemberClass() {
+ return type.isMemberClass();
+ }
+
+ @Override
+ public FieldList<FieldDescription.InDefinedShape> getDeclaredFields() {
+ return new FieldList.ForLoadedFields(type.getDeclaredFields());
+ }
+
+ @Override
+ public MethodList<MethodDescription.InDefinedShape> getDeclaredMethods() {
+ return new MethodList.ForLoadedMethods(type);
+ }
+
+ @Override
+ public PackageDescription getPackage() {
+ Package aPackage = type.getPackage();
+ return aPackage == null
+ ? PackageDescription.UNDEFINED
+ : new PackageDescription.ForLoadedPackage(aPackage);
+ }
+
+ @Override
+ public StackSize getStackSize() {
+ return StackSize.of(type);
+ }
+
+ @Override
+ public String getName() {
+ return getName(type);
+ }
+
+ @Override
+ public String getCanonicalName() {
+ String canonicalName = type.getCanonicalName();
+ if (canonicalName == null) {
+ return NO_NAME;
+ }
+ int anonymousLoaderIndex = canonicalName.indexOf('/');
+ if (anonymousLoaderIndex == -1) {
+ return canonicalName;
+ } else {
+ StringBuilder normalized = new StringBuilder(canonicalName.substring(0, anonymousLoaderIndex));
+ Class<?> type = this.type;
+ while (type.isArray()) {
+ normalized.append("[]");
+ type = type.getComponentType();
+ }
+ return normalized.toString();
+ }
+ }
+
+ @Override
+ public String getDescriptor() {
+ String name = type.getName();
+ int anonymousLoaderIndex = name.indexOf('/');
+ return anonymousLoaderIndex == -1
+ ? Type.getDescriptor(type)
+ : "L" + name.substring(0, anonymousLoaderIndex).replace('.', '/') + ";";
+ }
+
+ @Override
+ public int getModifiers() {
+ return type.getModifiers();
+ }
+
+ @Override
+ public TypeList.Generic getTypeVariables() {
+ return TypeList.Generic.ForLoadedTypes.OfTypeVariables.of(type);
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return new AnnotationList.ForLoadedAnnotations(type.getDeclaredAnnotations());
+ }
+ }
+
+ /**
+ * A projection for an array type based on an existing {@link TypeDescription}.
+ */
+ class ArrayProjection extends AbstractBase {
+
+ /**
+ * Modifiers that every array in Java implies.
+ */
+ private static final int ARRAY_IMPLIED = Opcodes.ACC_FINAL | Opcodes.ACC_ABSTRACT;
+
+ /**
+ * Modifiers that no array in Java displays.
+ */
+ private static final int ARRAY_EXCLUDED = Opcodes.ACC_INTERFACE | Opcodes.ACC_ANNOTATION | Opcodes.ACC_STATIC;
+
+ /**
+ * The base component type which is itself not an array.
+ */
+ private final TypeDescription componentType;
+
+ /**
+ * The arity of this array.
+ */
+ private final int arity;
+
+ /**
+ * Crrates a new array projection.
+ *
+ * @param componentType The base component type of the array which is itself not an array.
+ * @param arity The arity of this array.
+ */
+ protected ArrayProjection(TypeDescription componentType, int arity) {
+ this.componentType = componentType;
+ this.arity = arity;
+ }
+
+ /**
+ * Creates an array projection of an arrity of one.
+ *
+ * @param componentType The component type of the array.
+ * @return A projection of the component type as an array of the given value with an arity of one.
+ */
+ public static TypeDescription of(TypeDescription componentType) {
+ return of(componentType, 1);
+ }
+
+ /**
+ * Creates an array projection.
+ *
+ * @param componentType The component type of the array.
+ * @param arity The arity of this array.
+ * @return A projection of the component type as an array of the given value with the supplied arity.
+ */
+ public static TypeDescription of(TypeDescription componentType, int arity) {
+ if (arity < 0) {
+ throw new IllegalArgumentException("Arrays cannot have a negative arity");
+ }
+ while (componentType.isArray()) {
+ componentType = componentType.getComponentType();
+ arity++;
+ }
+ return arity == 0
+ ? componentType
+ : new ArrayProjection(componentType, arity);
+ }
+
+ @Override
+ public boolean isArray() {
+ return true;
+ }
+
+ @Override
+ public TypeDescription getComponentType() {
+ return arity == 1
+ ? componentType
+ : new ArrayProjection(componentType, arity - 1);
+ }
+
+ @Override
+ public boolean isPrimitive() {
+ return false;
+ }
+
+ @Override
+ public Generic getSuperClass() {
+ return TypeDescription.Generic.OBJECT;
+ }
+
+ @Override
+ public TypeList.Generic getInterfaces() {
+ return ARRAY_INTERFACES;
+ }
+
+ @Override
+ public MethodDescription getEnclosingMethod() {
+ return MethodDescription.UNDEFINED;
+ }
+
+ @Override
+ public TypeDescription getEnclosingType() {
+ return TypeDescription.UNDEFINED;
+ }
+
+ @Override
+ public TypeList getDeclaredTypes() {
+ return new TypeList.Empty();
+ }
+
+ @Override
+ public String getSimpleName() {
+ StringBuilder stringBuilder = new StringBuilder(componentType.getSimpleName());
+ for (int i = 0; i < arity; i++) {
+ stringBuilder.append("[]");
+ }
+ return stringBuilder.toString();
+ }
+
+ @Override
+ public String getCanonicalName() {
+ String canonicalName = componentType.getCanonicalName();
+ if (canonicalName == null) {
+ return NO_NAME;
+ }
+ StringBuilder stringBuilder = new StringBuilder(canonicalName);
+ for (int i = 0; i < arity; i++) {
+ stringBuilder.append("[]");
+ }
+ return stringBuilder.toString();
+ }
+
+ @Override
+ public boolean isAnonymousClass() {
+ return false;
+ }
+
+ @Override
+ public boolean isLocalClass() {
+ return false;
+ }
+
+ @Override
+ public boolean isMemberClass() {
+ return false;
+ }
+
+ @Override
+ public FieldList<FieldDescription.InDefinedShape> getDeclaredFields() {
+ return new FieldList.Empty<FieldDescription.InDefinedShape>();
+ }
+
+ @Override
+ public MethodList<MethodDescription.InDefinedShape> getDeclaredMethods() {
+ return new MethodList.Empty<MethodDescription.InDefinedShape>();
+ }
+
+ @Override
+ public StackSize getStackSize() {
+ return StackSize.SINGLE;
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return new AnnotationList.Empty();
+ }
+
+ @Override
+ public AnnotationList getInheritedAnnotations() {
+ return new AnnotationList.Empty();
+ }
+
+ @Override
+ public PackageDescription getPackage() {
+ return PackageDescription.UNDEFINED;
+ }
+
+ @Override
+ public String getName() {
+ StringBuilder stringBuilder = new StringBuilder();
+ for (int i = 0; i < arity; i++) {
+ stringBuilder.append('[');
+ }
+ return stringBuilder.append(componentType.getDescriptor().replace('/', '.')).toString();
+ }
+
+ @Override
+ public String getDescriptor() {
+ StringBuilder stringBuilder = new StringBuilder();
+ for (int i = 0; i < arity; i++) {
+ stringBuilder.append('[');
+ }
+ return stringBuilder.append(componentType.getDescriptor()).toString();
+ }
+
+ @Override
+ public TypeDescription getDeclaringType() {
+ return TypeDescription.UNDEFINED;
+ }
+
+ @Override
+ public int getModifiers() {
+ return (getComponentType().getModifiers() & ~ARRAY_EXCLUDED) | ARRAY_IMPLIED;
+ }
+
+ @Override
+ public TypeList.Generic getTypeVariables() {
+ return new TypeList.Generic.Empty();
+ }
+ }
+
+ /**
+ * A latent type description for a type without methods or fields.
+ */
+ class Latent extends AbstractBase.OfSimpleType {
+
+ /**
+ * The name of the type.
+ */
+ private final String name;
+
+ /**
+ * The modifiers of the type.
+ */
+ private final int modifiers;
+
+ /**
+ * The super type or {@code null} if no such type exists.
+ */
+ private final Generic superClass;
+
+ /**
+ * The interfaces that this type implements.
+ */
+ private final List<? extends Generic> interfaces;
+
+ /**
+ * Creates a new latent type.
+ *
+ * @param name The name of the type.
+ * @param modifiers The modifiers of the type.
+ * @param superClass The super type or {@code null} if no such type exists.
+ * @param interfaces The interfaces that this type implements.
+ */
+ public Latent(String name, int modifiers, Generic superClass, List<? extends Generic> interfaces) {
+ this.name = name;
+ this.modifiers = modifiers;
+ this.superClass = superClass;
+ this.interfaces = interfaces;
+ }
+
+ @Override
+ public Generic getSuperClass() {
+ return superClass;
+ }
+
+ @Override
+ public TypeList.Generic getInterfaces() {
+ return new TypeList.Generic.Explicit(interfaces);
+ }
+
+ @Override
+ public MethodDescription getEnclosingMethod() {
+ throw new IllegalStateException("Cannot resolve enclosing method of a latent type description: " + this);
+ }
+
+ @Override
+ public TypeDescription getEnclosingType() {
+ throw new IllegalStateException("Cannot resolve enclosing type of a latent type description: " + this);
+ }
+
+ @Override
+ public TypeList getDeclaredTypes() {
+ throw new IllegalStateException("Cannot resolve inner types of a latent type description: " + this);
+ }
+
+ @Override
+ public boolean isAnonymousClass() {
+ throw new IllegalStateException("Cannot resolve anonymous type property of a latent type description: " + this);
+ }
+
+ @Override
+ public boolean isLocalClass() {
+ throw new IllegalStateException("Cannot resolve local class property of a latent type description: " + this);
+ }
+
+ @Override
+ public boolean isMemberClass() {
+ throw new IllegalStateException("Cannot resolve member class property of a latent type description: " + this);
+ }
+
+ @Override
+ public FieldList<FieldDescription.InDefinedShape> getDeclaredFields() {
+ throw new IllegalStateException("Cannot resolve declared fields of a latent type description: " + this);
+ }
+
+ @Override
+ public MethodList<MethodDescription.InDefinedShape> getDeclaredMethods() {
+ throw new IllegalStateException("Cannot resolve declared methods of a latent type description: " + this);
+ }
+
+ @Override
+ public PackageDescription getPackage() {
+ String name = getName();
+ int index = name.lastIndexOf('.');
+ return index == -1
+ ? PackageDescription.UNDEFINED
+ : new PackageDescription.Simple(name.substring(0, index));
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ throw new IllegalStateException("Cannot resolve declared annotations of a latent type description: " + this);
+ }
+
+ @Override
+ public TypeDescription getDeclaringType() {
+ throw new IllegalStateException("Cannot resolve declared type of a latent type description: " + this);
+ }
+
+ @Override
+ public int getModifiers() {
+ return modifiers;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public TypeList.Generic getTypeVariables() {
+ throw new IllegalStateException("Cannot resolve type variables of a latent type description: " + this);
+ }
+ }
+
+ /**
+ * A type representation of a package description.
+ */
+ class ForPackageDescription extends AbstractBase.OfSimpleType {
+
+ /**
+ * The package to be described as a type.
+ */
+ private final PackageDescription packageDescription;
+
+ /**
+ * Creates a new type description of a package description.
+ *
+ * @param packageDescription The package to be described as a type.
+ */
+ public ForPackageDescription(PackageDescription packageDescription) {
+ this.packageDescription = packageDescription;
+ }
+
+ @Override
+ public Generic getSuperClass() {
+ return TypeDescription.Generic.OBJECT;
+ }
+
+ @Override
+ public TypeList.Generic getInterfaces() {
+ return new TypeList.Generic.Empty();
+ }
+
+ @Override
+ public MethodDescription getEnclosingMethod() {
+ return MethodDescription.UNDEFINED;
+ }
+
+ @Override
+ public TypeDescription getEnclosingType() {
+ return TypeDescription.UNDEFINED;
+ }
+
+ @Override
+ public boolean isAnonymousClass() {
+ return false;
+ }
+
+ @Override
+ public boolean isLocalClass() {
+ return false;
+ }
+
+ @Override
+ public boolean isMemberClass() {
+ return false;
+ }
+
+ @Override
+ public TypeList getDeclaredTypes() {
+ return new TypeList.Empty();
+ }
+
+ @Override
+ public FieldList<FieldDescription.InDefinedShape> getDeclaredFields() {
+ return new FieldList.Empty<FieldDescription.InDefinedShape>();
+ }
+
+ @Override
+ public MethodList<MethodDescription.InDefinedShape> getDeclaredMethods() {
+ return new MethodList.Empty<MethodDescription.InDefinedShape>();
+ }
+
+ @Override
+ public PackageDescription getPackage() {
+ return packageDescription;
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return packageDescription.getDeclaredAnnotations();
+ }
+
+ @Override
+ public TypeDescription getDeclaringType() {
+ return TypeDescription.UNDEFINED;
+ }
+
+ @Override
+ public TypeList.Generic getTypeVariables() {
+ return new TypeList.Generic.Empty();
+ }
+
+ @Override
+ public int getModifiers() {
+ return PackageDescription.PACKAGE_MODIFIERS;
+ }
+
+ @Override
+ public String getName() {
+ return packageDescription.getName() + "." + PackageDescription.PACKAGE_CLASS_NAME;
+ }
+ }
+
+ /**
+ * A delegating type description that always attempts to load the super types of a delegate type.
+ */
+ class SuperTypeLoading extends AbstractBase {
+
+ /**
+ * The delegate type description.
+ */
+ private final TypeDescription delegate;
+
+ /**
+ * The class loader to use for loading a super type.
+ */
+ private final ClassLoader classLoader;
+
+ /**
+ * A delegate for loading a type.
+ */
+ private final ClassLoadingDelegate classLoadingDelegate;
+
+ /**
+ * Creates a super type loading type description.
+ *
+ * @param delegate The delegate type description.
+ * @param classLoader The class loader to use for loading a super type.
+ */
+ public SuperTypeLoading(TypeDescription delegate, ClassLoader classLoader) {
+ this(delegate, classLoader, ClassLoadingDelegate.Simple.INSTANCE);
+ }
+
+ /**
+ * Creates a super type loading type description.
+ *
+ * @param delegate The delegate type description.
+ * @param classLoader The class loader to use for loading a super type.
+ * @param classLoadingDelegate A delegate for loading a type.
+ */
+ public SuperTypeLoading(TypeDescription delegate, ClassLoader classLoader, ClassLoadingDelegate classLoadingDelegate) {
+ this.delegate = delegate;
+ this.classLoader = classLoader;
+ this.classLoadingDelegate = classLoadingDelegate;
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return delegate.getDeclaredAnnotations();
+ }
+
+ @Override
+ public int getModifiers() {
+ return delegate.getModifiers();
+ }
+
+ @Override
+ public TypeList.Generic getTypeVariables() {
+ return delegate.getTypeVariables();
+ }
+
+ @Override
+ public String getDescriptor() {
+ return delegate.getDescriptor();
+ }
+
+ @Override
+ public String getName() {
+ return delegate.getName();
+ }
+
+ @Override
+ public Generic getSuperClass() {
+ Generic superClass = delegate.getSuperClass();
+ return superClass == null
+ ? Generic.UNDEFINED
+ : new ClassLoadingTypeProjection(superClass, classLoader, classLoadingDelegate);
+ }
+
+ @Override
+ public TypeList.Generic getInterfaces() {
+ return new ClassLoadingTypeList(delegate.getInterfaces(), classLoader, classLoadingDelegate);
+ }
+
+ @Override
+ public FieldList<FieldDescription.InDefinedShape> getDeclaredFields() {
+ return delegate.getDeclaredFields();
+ }
+
+ @Override
+ public MethodList<MethodDescription.InDefinedShape> getDeclaredMethods() {
+ return delegate.getDeclaredMethods();
+ }
+
+ @Override
+ public StackSize getStackSize() {
+ return delegate.getStackSize();
+ }
+
+ @Override
+ public boolean isArray() {
+ return delegate.isArray();
+ }
+
+ @Override
+ public boolean isPrimitive() {
+ return delegate.isPrimitive();
+ }
+
+ @Override
+ public TypeDescription getComponentType() {
+ return delegate.getComponentType();
+ }
+
+ @Override
+ public TypeDescription getDeclaringType() {
+ return delegate.getDeclaringType();
+ }
+
+ @Override
+ public TypeList getDeclaredTypes() {
+ return delegate.getDeclaredTypes();
+ }
+
+ @Override
+ public MethodDescription getEnclosingMethod() {
+ return delegate.getEnclosingMethod();
+ }
+
+ @Override
+ public TypeDescription getEnclosingType() {
+ return delegate.getEnclosingType();
+ }
+
+ @Override
+ public String getSimpleName() {
+ return delegate.getSimpleName();
+ }
+
+ @Override
+ public String getCanonicalName() {
+ return delegate.getCanonicalName();
+ }
+
+ @Override
+ public boolean isAnonymousClass() {
+ return delegate.isAnonymousClass();
+ }
+
+ @Override
+ public boolean isLocalClass() {
+ return delegate.isLocalClass();
+ }
+
+ @Override
+ public boolean isMemberClass() {
+ return delegate.isMemberClass();
+ }
+
+ @Override
+ public PackageDescription getPackage() {
+ return delegate.getPackage();
+ }
+
+ /**
+ * A class loading delegate is responsible for resolving a type given a class loader and a type name.
+ */
+ public interface ClassLoadingDelegate {
+
+ /**
+ * Loads a type.
+ *
+ * @param name The type's name,
+ * @param classLoader The class loader to load the type from which might be {@code null} to represent the bootstrap class loader.
+ * @return The loaded type.
+ * @throws ClassNotFoundException If the type could not be found.
+ */
+ Class<?> load(String name, ClassLoader classLoader) throws ClassNotFoundException;
+
+ /**
+ * A simple class loading delegate that simply loads a type.
+ */
+ enum Simple implements ClassLoadingDelegate {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Class<?> load(String name, ClassLoader classLoader) throws ClassNotFoundException {
+ return Class.forName(name, false, classLoader);
+ }
+ }
+ }
+
+ /**
+ * A type projection that attempts to load any super type of the delegate type.
+ */
+ protected static class ClassLoadingTypeProjection extends TypeDescription.Generic.LazyProjection {
+
+ /**
+ * The delegate type description.
+ */
+ private final Generic delegate;
+
+ /**
+ * The class loader to use for loading types which might be {@code null} to represent the bootstrap class loader.
+ */
+ private final ClassLoader classLoader;
+
+ /**
+ * A delegate for loading a type.
+ */
+ private final ClassLoadingDelegate classLoadingDelegate;
+
+ /**
+ * Creates a class loading type description.
+ *
+ * @param delegate The delegate type description.
+ * @param classLoader The class loader to use for loading types which might be {@code null} to represent the bootstrap class loader.
+ * @param classLoadingDelegate A delegate for loading a type.
+ */
+ protected ClassLoadingTypeProjection(Generic delegate, ClassLoader classLoader, ClassLoadingDelegate classLoadingDelegate) {
+ this.delegate = delegate;
+ this.classLoader = classLoader;
+ this.classLoadingDelegate = classLoadingDelegate;
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return delegate.getDeclaredAnnotations();
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ try {
+ return new ForLoadedType(classLoadingDelegate.load(delegate.asErasure().getName(), classLoader));
+ } catch (ClassNotFoundException ignored) {
+ return delegate.asErasure();
+ }
+ }
+
+ @Override
+ protected Generic resolve() {
+ return delegate;
+ }
+
+ @Override
+ public Generic getSuperClass() {
+ Generic superClass = delegate.getSuperClass();
+ if (superClass == null) {
+ return Generic.UNDEFINED;
+ } else {
+ try {
+ return new ClassLoadingTypeProjection(superClass,
+ classLoadingDelegate.load(delegate.asErasure().getName(), classLoader).getClassLoader(),
+ classLoadingDelegate);
+ } catch (ClassNotFoundException ignored) {
+ return superClass;
+ }
+ }
+ }
+
+ @Override
+ public TypeList.Generic getInterfaces() {
+ TypeList.Generic interfaces = delegate.getInterfaces();
+ try {
+ return new ClassLoadingTypeList(interfaces,
+ classLoadingDelegate.load(delegate.asErasure().getName(), classLoader).getClassLoader(),
+ classLoadingDelegate);
+ } catch (ClassNotFoundException ignored) {
+ return interfaces;
+ }
+ }
+
+ @Override
+ public Iterator<TypeDefinition> iterator() {
+ return new SuperClassIterator(this);
+ }
+ }
+
+ /**
+ * A type list that attempts loading any type.
+ */
+ protected static class ClassLoadingTypeList extends TypeList.Generic.AbstractBase {
+
+ /**
+ * The delegate type list.
+ */
+ private final TypeList.Generic delegate;
+
+ /**
+ * The class loader to use for loading types which might be {@code null} to represent the bootstrap class loader.
+ */
+ private final ClassLoader classLoader;
+
+ /**
+ * A delegate for loading a type.
+ */
+ private final ClassLoadingDelegate classLoadingDelegate;
+
+ /**
+ * Creates a class loading type list.
+ *
+ * @param delegate The delegate type list.
+ * @param classLoader The class loader to use for loading types which might be {@code null} to represent the bootstrap class loader.
+ * @param classLoadingDelegate A delegate for loading a type.
+ */
+ protected ClassLoadingTypeList(TypeList.Generic delegate, ClassLoader classLoader, ClassLoadingDelegate classLoadingDelegate) {
+ this.delegate = delegate;
+ this.classLoader = classLoader;
+ this.classLoadingDelegate = classLoadingDelegate;
+ }
+
+ @Override
+ public Generic get(int index) {
+ return new ClassLoadingTypeProjection(delegate.get(index), classLoader, classLoadingDelegate);
+ }
+
+ @Override
+ public int size() {
+ return delegate.size();
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/type/TypeList.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/type/TypeList.java
new file mode 100644
index 0000000..b1d1538
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/type/TypeList.java
@@ -0,0 +1,945 @@
+package net.bytebuddy.description.type;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import net.bytebuddy.description.ByteCodeElement;
+import net.bytebuddy.description.TypeVariableSource;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.matcher.FilterableList;
+import org.objectweb.asm.Type;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.GenericDeclaration;
+import java.lang.reflect.Method;
+import java.lang.reflect.TypeVariable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Implementations represent a list of type descriptions.
+ */
+public interface TypeList extends FilterableList<TypeDescription, TypeList> {
+
+ /**
+ * Represents that a type list does not contain any values for ASM interoperability which is represented by {@code null}.
+ */
+ @SuppressFBWarnings(value = {"MS_MUTABLE_ARRAY", "MS_OOI_PKGPROTECT"}, justification = "Value is null")
+ String[] NO_INTERFACES = null;
+
+ /**
+ * Returns a list of internal names of all types represented by this list.
+ *
+ * @return An array of all internal names or {@code null} if the list is empty.
+ */
+ String[] toInternalNames();
+
+ /**
+ * Returns the sum of the size of all types contained in this list.
+ *
+ * @return The sum of the size of all types contained in this list.
+ */
+ int getStackSize();
+
+ /**
+ * An abstract base implementation of a type list.
+ */
+ abstract class AbstractBase extends FilterableList.AbstractBase<TypeDescription, TypeList> implements TypeList {
+
+ @Override
+ protected TypeList wrap(List<TypeDescription> values) {
+ return new Explicit(values);
+ }
+ }
+
+ /**
+ * Implementation of a type list for an array of loaded types.
+ */
+ class ForLoadedTypes extends AbstractBase {
+
+ /**
+ * The loaded types this type list represents.
+ */
+ private final List<? extends Class<?>> types;
+
+ /**
+ * Creates a new type list for an array of loaded types.
+ *
+ * @param type The types to be represented by this list.
+ */
+ public ForLoadedTypes(Class<?>... type) {
+ this(Arrays.asList(type));
+ }
+
+ /**
+ * Creates a new type list for an array of loaded types.
+ *
+ * @param types The types to be represented by this list.
+ */
+ public ForLoadedTypes(List<? extends Class<?>> types) {
+ this.types = types;
+ }
+
+ @Override
+ public TypeDescription get(int index) {
+ return new TypeDescription.ForLoadedType(types.get(index));
+ }
+
+ @Override
+ public int size() {
+ return types.size();
+ }
+
+ @Override
+ public String[] toInternalNames() {
+ String[] internalNames = new String[types.size()];
+ int i = 0;
+ for (Class<?> type : types) {
+ internalNames[i++] = Type.getInternalName(type);
+ }
+ return internalNames.length == 0
+ ? NO_INTERFACES
+ : internalNames;
+ }
+
+ @Override
+ public int getStackSize() {
+ return StackSize.sizeOf(types);
+ }
+ }
+
+ /**
+ * A wrapper implementation of an explicit list of types.
+ */
+ class Explicit extends AbstractBase {
+
+ /**
+ * The list of type descriptions this list represents.
+ */
+ private final List<? extends TypeDescription> typeDescriptions;
+
+ /**
+ * Creates an immutable wrapper.
+ *
+ * @param typeDescription The list of types to be represented by this wrapper.
+ */
+ public Explicit(TypeDescription... typeDescription) {
+ this(Arrays.asList(typeDescription));
+ }
+
+ /**
+ * Creates an immutable wrapper.
+ *
+ * @param typeDescriptions The list of types to be represented by this wrapper.
+ */
+ public Explicit(List<? extends TypeDescription> typeDescriptions) {
+ this.typeDescriptions = typeDescriptions;
+ }
+
+ @Override
+ public TypeDescription get(int index) {
+ return typeDescriptions.get(index);
+ }
+
+ @Override
+ public int size() {
+ return typeDescriptions.size();
+ }
+
+ @Override
+ public String[] toInternalNames() {
+ String[] internalNames = new String[typeDescriptions.size()];
+ int i = 0;
+ for (TypeDescription typeDescription : typeDescriptions) {
+ internalNames[i++] = typeDescription.getInternalName();
+ }
+ return internalNames.length == 0
+ ? NO_INTERFACES
+ : internalNames;
+ }
+
+ @Override
+ public int getStackSize() {
+ int stackSize = 0;
+ for (TypeDescription typeDescription : typeDescriptions) {
+ stackSize += typeDescription.getStackSize().getSize();
+ }
+ return stackSize;
+ }
+ }
+
+ /**
+ * An implementation of an empty type list.
+ */
+ class Empty extends FilterableList.Empty<TypeDescription, TypeList> implements TypeList {
+
+ @Override
+ @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "Value is null")
+ public String[] toInternalNames() {
+ return NO_INTERFACES;
+ }
+
+ @Override
+ public int getStackSize() {
+ return 0;
+ }
+ }
+
+ /**
+ * A list containing descriptions of generic types.
+ */
+ interface Generic extends FilterableList<TypeDescription.Generic, Generic> {
+
+ /**
+ * Returns a list of the generic types' erasures.
+ *
+ * @return A list of the generic types' erasures.
+ */
+ TypeList asErasures();
+
+ /**
+ * Returns a list of the generic types' raw types.
+ *
+ * @return A list of the generic types' raw types.
+ */
+ Generic asRawTypes();
+
+ /**
+ * Transforms a list of attached type variables into their tokenized form. Calling this method throws an {@link IllegalStateException}
+ * if any type in this list does not represent a type variable ({@link net.bytebuddy.description.type.TypeDefinition.Sort#VARIABLE}).
+ *
+ * @param visitor The visitor to use for detaching the type variable's bounds.
+ * @return A list of tokens representing the type variables contained in this list.
+ */
+ ByteCodeElement.Token.TokenList<TypeVariableToken> asTokenList(ElementMatcher<? super TypeDescription> visitor);
+
+ /**
+ * Transforms the generic types by applying the supplied visitor to each of them.
+ *
+ * @param visitor The visitor to apply to each type.
+ * @return A list of the types returned by the supplied visitor.
+ */
+ Generic accept(TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor);
+
+ /**
+ * Returns the sum of the size of all types contained in this list.
+ *
+ * @return The sum of the size of all types contained in this list.
+ */
+ int getStackSize();
+
+ /**
+ * An abstract base implementation of a generic type list.
+ */
+ abstract class AbstractBase extends FilterableList.AbstractBase<TypeDescription.Generic, Generic> implements Generic {
+
+ @Override
+ protected Generic wrap(List<TypeDescription.Generic> values) {
+ return new Explicit(values);
+ }
+
+ @Override
+ public Generic accept(TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor) {
+ List<TypeDescription.Generic> visited = new ArrayList<TypeDescription.Generic>(size());
+ for (TypeDescription.Generic typeDescription : this) {
+ visited.add(typeDescription.accept(visitor));
+ }
+ return new Explicit(visited);
+ }
+
+ @Override
+ public ByteCodeElement.Token.TokenList<TypeVariableToken> asTokenList(ElementMatcher<? super TypeDescription> matcher) {
+ List<TypeVariableToken> tokens = new ArrayList<TypeVariableToken>(size());
+ for (TypeDescription.Generic typeVariable : this) {
+ tokens.add(TypeVariableToken.of(typeVariable, matcher));
+ }
+ return new ByteCodeElement.Token.TokenList<TypeVariableToken>(tokens);
+ }
+
+ @Override
+ public int getStackSize() {
+ int stackSize = 0;
+ for (TypeDescription.Generic typeDescription : this) {
+ stackSize += typeDescription.getStackSize().getSize();
+ }
+ return stackSize;
+ }
+
+ @Override
+ public TypeList asErasures() {
+ List<TypeDescription> typeDescriptions = new ArrayList<TypeDescription>(size());
+ for (TypeDescription.Generic typeDescription : this) {
+ typeDescriptions.add(typeDescription.asErasure());
+ }
+ return new TypeList.Explicit(typeDescriptions);
+ }
+
+ @Override
+ public Generic asRawTypes() {
+ List<TypeDescription.Generic> typeDescriptions = new ArrayList<TypeDescription.Generic>(size());
+ for (TypeDescription.Generic typeDescription : this) {
+ typeDescriptions.add(typeDescription.asRawType());
+ }
+ return new Explicit(typeDescriptions);
+ }
+ }
+
+ /**
+ * An explicit list of generic types.
+ */
+ class Explicit extends AbstractBase {
+
+ /**
+ * The generic types represented by this list.
+ */
+ private final List<? extends TypeDefinition> typeDefinitions;
+
+ /**
+ * Creates a new explicit list of generic types.
+ *
+ * @param typeDefinition The generic types represented by this list.
+ */
+ public Explicit(TypeDefinition... typeDefinition) {
+ this(Arrays.asList(typeDefinition));
+ }
+
+ /**
+ * Creates a new explicit list of generic types.
+ *
+ * @param typeDefinitions The generic types represented by this list.
+ */
+ public Explicit(List<? extends TypeDefinition> typeDefinitions) {
+ this.typeDefinitions = typeDefinitions;
+ }
+
+ @Override
+ public TypeDescription.Generic get(int index) {
+ return typeDefinitions.get(index).asGenericType();
+ }
+
+ @Override
+ public int size() {
+ return typeDefinitions.size();
+ }
+ }
+
+ /**
+ * A list of loaded generic types.
+ */
+ class ForLoadedTypes extends AbstractBase {
+
+ /**
+ * The loaded types this list represents.
+ */
+ private final List<? extends java.lang.reflect.Type> types;
+
+ /**
+ * Creates a list of loaded generic types.
+ *
+ * @param type The loaded types this list represents.
+ */
+ public ForLoadedTypes(java.lang.reflect.Type... type) {
+ this(Arrays.asList(type));
+ }
+
+ /**
+ * Creates a list of loaded generic types.
+ *
+ * @param types The loaded types this list represents.
+ */
+ public ForLoadedTypes(List<? extends java.lang.reflect.Type> types) {
+ this.types = types;
+ }
+
+ @Override
+ public TypeDescription.Generic get(int index) {
+ return TypeDefinition.Sort.describe(types.get(index));
+ }
+
+ @Override
+ public int size() {
+ return types.size();
+ }
+
+ /**
+ * A type list that represents loaded type variables.
+ */
+ public static class OfTypeVariables extends Generic.AbstractBase {
+
+ /**
+ * The type variables this list represents.
+ */
+ private final List<TypeVariable<?>> typeVariables;
+
+ /**
+ * Creates a new type list for loaded type variables.
+ *
+ * @param typeVariable The type variables this list represents.
+ */
+ protected OfTypeVariables(TypeVariable<?>... typeVariable) {
+ this(Arrays.asList(typeVariable));
+ }
+
+ /**
+ * Creates a new type list for loaded type variables.
+ *
+ * @param typeVariables The type variables this list represents.
+ */
+ protected OfTypeVariables(List<TypeVariable<?>> typeVariables) {
+ this.typeVariables = typeVariables;
+ }
+
+ /**
+ * Creates a list of the type variables of the supplied generic declaration.
+ *
+ * @param genericDeclaration The generic declaration to represent.
+ * @return A genric type list for the returned generic declaration.
+ */
+ public static Generic of(GenericDeclaration genericDeclaration) {
+ return new OfTypeVariables(genericDeclaration.getTypeParameters());
+ }
+
+ @Override
+ public TypeDescription.Generic get(int index) {
+ TypeVariable<?> typeVariable = typeVariables.get(index);
+ return TypeDefinition.Sort.describe(typeVariable, TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(typeVariable));
+ }
+
+ @Override
+ public int size() {
+ return typeVariables.size();
+ }
+ }
+ }
+
+ /**
+ * A list of detached types that are attached on reception.
+ */
+ class ForDetachedTypes extends AbstractBase {
+
+ /**
+ * The detached types this list represents.
+ */
+ private final List<? extends TypeDescription.Generic> detachedTypes;
+
+ /**
+ * The visitor to use for attaching the detached types.
+ */
+ private final TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor;
+
+ /**
+ * Creates a list of detached types that are attached on reception.
+ *
+ * @param detachedTypes The detached types this list represents.
+ * @param visitor The visitor to use for attaching the detached types.
+ */
+ public ForDetachedTypes(List<? extends TypeDescription.Generic> detachedTypes,
+ TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor) {
+ this.detachedTypes = detachedTypes;
+ this.visitor = visitor;
+ }
+
+ /**
+ * Creates a list of type variables that are attached to the provided type.
+ *
+ * @param typeDescription The type to which the type variables are to be attached to.
+ * @param detachedTypeVariables A mapping of type variable symbols to their detached type variable bounds.
+ * @return A type list representing the symbolic type variables in their attached state to the given type description.
+ */
+ public static Generic attachVariables(TypeDescription typeDescription, List<? extends TypeVariableToken> detachedTypeVariables) {
+ return new OfTypeVariables(typeDescription, detachedTypeVariables, TypeDescription.Generic.Visitor.Substitutor.ForAttachment.of(typeDescription));
+ }
+
+ /**
+ * Creates a list of types that are attached to the provided field.
+ *
+ * @param fieldDescription The field to which the detached variables are attached to.
+ * @param detachedTypes The detached types.
+ * @return A type list representing the detached types being attached to the provided field description.
+ */
+ public static Generic attach(FieldDescription fieldDescription, List<? extends TypeDescription.Generic> detachedTypes) {
+ return new ForDetachedTypes(detachedTypes, TypeDescription.Generic.Visitor.Substitutor.ForAttachment.of(fieldDescription));
+ }
+
+ /**
+ * Creates a list of types that are attached to the provided method.
+ *
+ * @param methodDescription The method to which the detached variables are attached to.
+ * @param detachedTypes The detached types.
+ * @return A type list representing the detached types being attached to the provided method description.
+ */
+ public static Generic attach(MethodDescription methodDescription, List<? extends TypeDescription.Generic> detachedTypes) {
+ return new ForDetachedTypes(detachedTypes, TypeDescription.Generic.Visitor.Substitutor.ForAttachment.of(methodDescription));
+ }
+
+ /**
+ * Creates a list of type variables that are attached to the provided method.
+ *
+ * @param methodDescription The method to which the type variables are to be attached to.
+ * @param detachedTypeVariables A mapping of type variable symbols to their detached type variable bounds.
+ * @return A type list representing the symbolic type variables in their attached state to the given method description.
+ */
+ public static Generic attachVariables(MethodDescription methodDescription, List<? extends TypeVariableToken> detachedTypeVariables) {
+ return new OfTypeVariables(methodDescription, detachedTypeVariables, TypeDescription.Generic.Visitor.Substitutor.ForAttachment.of(methodDescription));
+ }
+
+ /**
+ * Creates a list of types that are attached to the provided parameter.
+ *
+ * @param parameterDescription The parameter to which the detached variables are attached to.
+ * @param detachedTypes The detached types.
+ * @return A type list representing the detached types being attached to the provided parameter description.
+ */
+ public static Generic attach(ParameterDescription parameterDescription, List<? extends TypeDescription.Generic> detachedTypes) {
+ return new ForDetachedTypes(detachedTypes, TypeDescription.Generic.Visitor.Substitutor.ForAttachment.of(parameterDescription));
+ }
+
+ @Override
+ public TypeDescription.Generic get(int index) {
+ return detachedTypes.get(index).accept(visitor);
+ }
+
+ @Override
+ public int size() {
+ return detachedTypes.size();
+ }
+
+ /**
+ * A list of detached types that are attached on reception but not when computing an erasure.
+ */
+ public static class WithResolvedErasure extends Generic.AbstractBase {
+
+ /**
+ * The detached types this list represents.
+ */
+ private final List<? extends TypeDescription.Generic> detachedTypes;
+
+ /**
+ * The visitor to use for attaching the detached types.
+ */
+ private final TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor;
+
+ /**
+ * Creates a list of generic type descriptions that are resolved lazily, i.e. type variables are not resolved
+ * when computing an erausre.
+ *
+ * @param detachedTypes The detached types this list represents.
+ * @param visitor The visitor to use for attaching the detached types.
+ */
+ public WithResolvedErasure(List<? extends TypeDescription.Generic> detachedTypes,
+ TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor) {
+ this.detachedTypes = detachedTypes;
+ this.visitor = visitor;
+ }
+
+ @Override
+ public TypeDescription.Generic get(int index) {
+ return new TypeDescription.Generic.LazyProjection.WithResolvedErasure(detachedTypes.get(index), visitor);
+ }
+
+ @Override
+ public int size() {
+ return detachedTypes.size();
+ }
+ }
+
+ /**
+ * A list of attached type variables represented by a list of type variable tokens.
+ */
+ public static class OfTypeVariables extends Generic.AbstractBase {
+
+ /**
+ * The type variable's source.
+ */
+ private final TypeVariableSource typeVariableSource;
+
+ /**
+ * A token representing the type variable in its detached state.
+ */
+ private final List<? extends TypeVariableToken> detachedTypeVariables;
+
+ /**
+ * A visitor for attaching the type variable's bounds.
+ */
+ private final TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor;
+
+ /**
+ * Creates a new list of attached type variables representing a list of type variable tokens.
+ *
+ * @param typeVariableSource The type variable's source.
+ * @param detachedTypeVariables A token representing the type variable in its detached state.
+ * @param visitor A visitor for attaching the type variable's bounds.
+ */
+ public OfTypeVariables(TypeVariableSource typeVariableSource,
+ List<? extends TypeVariableToken> detachedTypeVariables,
+ TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor) {
+ this.typeVariableSource = typeVariableSource;
+ this.detachedTypeVariables = detachedTypeVariables;
+ this.visitor = visitor;
+ }
+
+ @Override
+ public TypeDescription.Generic get(int index) {
+ return new AttachedTypeVariable(typeVariableSource, detachedTypeVariables.get(index), visitor);
+ }
+
+ @Override
+ public int size() {
+ return detachedTypeVariables.size();
+ }
+
+ /**
+ * A wrapper for representing a type variable in its attached state.
+ */
+ protected static class AttachedTypeVariable extends TypeDescription.Generic.OfTypeVariable {
+
+ /**
+ * The type variable's source.
+ */
+ private final TypeVariableSource typeVariableSource;
+
+ /**
+ * A token representing the type variable in its detached state.
+ */
+ private final TypeVariableToken typeVariableToken;
+
+ /**
+ * A visitor for attaching the type variable's bounds.
+ */
+ private final TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor;
+
+ /**
+ * Creates a new attached type variable.
+ *
+ * @param typeVariableSource The type variable's source.
+ * @param typeVariableToken A token representing the type variable in its detached state.
+ * @param visitor A visitor for attaching the type variable's bounds.
+ */
+ protected AttachedTypeVariable(TypeVariableSource typeVariableSource,
+ TypeVariableToken typeVariableToken,
+ TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor) {
+ this.typeVariableSource = typeVariableSource;
+ this.typeVariableToken = typeVariableToken;
+ this.visitor = visitor;
+ }
+
+ @Override
+ public Generic getUpperBounds() {
+ return typeVariableToken.getBounds().accept(visitor);
+ }
+
+ @Override
+ public TypeVariableSource getTypeVariableSource() {
+ return typeVariableSource;
+ }
+
+ @Override
+ public String getSymbol() {
+ return typeVariableToken.getSymbol();
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return typeVariableToken.getAnnotations();
+ }
+ }
+ }
+ }
+
+ /**
+ * A lazy projection of a type's generic interface types.
+ */
+ class OfLoadedInterfaceTypes extends AbstractBase {
+
+ /**
+ * The type of which the interface types are represented by this list.
+ */
+ private final Class<?> type;
+
+ /**
+ * Creates a lazy projection of interface types.
+ *
+ * @param type The type of which the interface types are represented by this list.
+ */
+ public OfLoadedInterfaceTypes(Class<?> type) {
+ this.type = type;
+ }
+
+ @Override
+ public TypeDescription.Generic get(int index) {
+ return new OfLoadedInterfaceTypes.TypeProjection(type, index, type.getInterfaces());
+ }
+
+ @Override
+ public int size() {
+ return type.getInterfaces().length;
+ }
+
+ @Override
+ public TypeList asErasures() {
+ return new TypeList.ForLoadedTypes(type.getInterfaces());
+ }
+
+ /**
+ * A type projection of an interface type.
+ */
+ private static class TypeProjection extends TypeDescription.Generic.LazyProjection.WithLazyNavigation.OfAnnotatedElement {
+
+ /**
+ * The type of which an interface type is represented.
+ */
+ private final Class<?> type;
+
+ /**
+ * The index of the generic interface type that is represented.
+ */
+ private final int index;
+
+ /**
+ * The erasures of the represented type's interface types.
+ */
+ private final Class<?>[] erasure;
+
+ /**
+ * Creates a new lazy type projection of a generic interface type.
+ *
+ * @param type The type of which an interface type is represented.
+ * @param index The index of the generic interface type that is represented.
+ * @param erasure The erasures of the represented type's interface types.
+ */
+ private TypeProjection(Class<?> type, int index, Class<?>[] erasure) {
+ this.type = type;
+ this.index = index;
+ this.erasure = erasure;
+ }
+
+ @Override
+ protected TypeDescription.Generic resolve() {
+ java.lang.reflect.Type[] type = this.type.getGenericInterfaces();
+ return erasure.length == type.length
+ ? Sort.describe(type[index], getAnnotationReader())
+ : asRawType();
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ return new TypeDescription.ForLoadedType(erasure[index]);
+ }
+
+ @Override
+ protected AnnotationReader getAnnotationReader() {
+ return AnnotationReader.DISPATCHER.resolveInterfaceType(type, index);
+ }
+ }
+ }
+
+ /**
+ * A lazy projection of a constructor's exception types.
+ */
+ class OfConstructorExceptionTypes extends AbstractBase {
+
+ /**
+ * The constructor of which the exception types are represented.
+ */
+ private final Constructor<?> constructor;
+
+ /**
+ * Creates a new lazy projection of a constructor's exception types.
+ *
+ * @param constructor The constructor of which the exception types are represented.
+ */
+ public OfConstructorExceptionTypes(Constructor<?> constructor) {
+ this.constructor = constructor;
+ }
+
+ @Override
+ public TypeDescription.Generic get(int index) {
+ return new OfConstructorExceptionTypes.TypeProjection(constructor, index, constructor.getExceptionTypes());
+ }
+
+ @Override
+ public int size() {
+ return constructor.getExceptionTypes().length;
+ }
+
+ @Override
+ public TypeList asErasures() {
+ return new TypeList.ForLoadedTypes(constructor.getExceptionTypes());
+ }
+
+ /**
+ * A projection of a specific exception type.
+ */
+ private static class TypeProjection extends TypeDescription.Generic.LazyProjection.WithEagerNavigation.OfAnnotatedElement {
+
+ /**
+ * The constructor of which the exception types are represented.
+ */
+ private final Constructor<?> constructor;
+
+ /**
+ * The index of the exception type.
+ */
+ private final int index;
+
+ /**
+ * The erasures of the represented constructor's exception types.
+ */
+ private final Class<?>[] erasure;
+
+ /**
+ * Creates a lazy type projection of a constructor's exception type.
+ *
+ * @param constructor The constructor of which the exception types are represented.
+ * @param index The index of the exception type.
+ * @param erasure The erasures of the represented constructor's exception types.
+ */
+ private TypeProjection(Constructor<?> constructor, int index, Class<?>[] erasure) {
+ this.constructor = constructor;
+ this.index = index;
+ this.erasure = erasure;
+ }
+
+ @Override
+ protected TypeDescription.Generic resolve() {
+ java.lang.reflect.Type[] type = constructor.getGenericExceptionTypes();
+ return erasure.length == type.length
+ ? Sort.describe(type[index], getAnnotationReader())
+ : asRawType();
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ return new TypeDescription.ForLoadedType(erasure[index]);
+ }
+
+ @Override
+ protected AnnotationReader getAnnotationReader() {
+ return AnnotationReader.DISPATCHER.resolveExceptionType(constructor, index);
+ }
+ }
+ }
+
+ /**
+ * A lazy projection of a method's exception types.
+ */
+ class OfMethodExceptionTypes extends AbstractBase {
+
+ /**
+ * The method of which the exception types are represented.
+ */
+ private final Method method;
+
+ /**
+ * Creates a new lazy projection of a constructor's exception types.
+ *
+ * @param method The method of which the exception types are represented.
+ */
+ public OfMethodExceptionTypes(Method method) {
+ this.method = method;
+ }
+
+ @Override
+ public TypeDescription.Generic get(int index) {
+ return new OfMethodExceptionTypes.TypeProjection(method, index, method.getExceptionTypes());
+ }
+
+ @Override
+ public int size() {
+ return method.getExceptionTypes().length;
+ }
+
+ @Override
+ public TypeList asErasures() {
+ return new TypeList.ForLoadedTypes(method.getExceptionTypes());
+ }
+
+ /**
+ * A projection of a specific exception type.
+ */
+ private static class TypeProjection extends TypeDescription.Generic.LazyProjection.WithEagerNavigation.OfAnnotatedElement {
+
+ /**
+ * The method of which the exception types are represented.
+ */
+ private final Method method;
+
+ /**
+ * The index of the exception type.
+ */
+ private final int index;
+
+ /**
+ * The erasures of the represented type's interface type.
+ */
+ private final Class<?>[] erasure;
+
+ /**
+ * Creates a lazy type projection of a constructor's exception type.
+ *
+ * @param method The method of which the exception types are represented.
+ * @param index The index of the exception type.
+ * @param erasure The erasures of the represented type's interface type.
+ */
+ public TypeProjection(Method method, int index, Class<?>[] erasure) {
+ this.method = method;
+ this.index = index;
+ this.erasure = erasure;
+ }
+
+ @Override
+ protected TypeDescription.Generic resolve() {
+ java.lang.reflect.Type[] type = method.getGenericExceptionTypes();
+ return erasure.length == type.length
+ ? Sort.describe(type[index], getAnnotationReader())
+ : asRawType();
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ return new TypeDescription.ForLoadedType(erasure[index]);
+ }
+
+ @Override
+ protected AnnotationReader getAnnotationReader() {
+ return AnnotationReader.DISPATCHER.resolveExceptionType(method, index);
+ }
+ }
+ }
+
+ /**
+ * An empty list of generic types.
+ */
+ class Empty extends FilterableList.Empty<TypeDescription.Generic, Generic> implements Generic {
+
+ @Override
+ public TypeList asErasures() {
+ return new TypeList.Empty();
+ }
+
+ @Override
+ public Generic asRawTypes() {
+ return this;
+ }
+
+ @Override
+ public Generic accept(TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor) {
+ return new Generic.Empty();
+ }
+
+ @Override
+ public ByteCodeElement.Token.TokenList<TypeVariableToken> asTokenList(ElementMatcher<? super TypeDescription> matcher) {
+ return new ByteCodeElement.Token.TokenList<TypeVariableToken>();
+ }
+
+ @Override
+ public int getStackSize() {
+ return 0;
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/type/TypeVariableToken.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/type/TypeVariableToken.java
new file mode 100644
index 0000000..5b02b6e
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/type/TypeVariableToken.java
@@ -0,0 +1,121 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.description.ByteCodeElement;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.matcher.ElementMatcher;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A tokenized representation of a type variable.
+ */
+public class TypeVariableToken implements ByteCodeElement.Token<TypeVariableToken> {
+
+ /**
+ * The type variable's symbol.
+ */
+ private final String symbol;
+
+ /**
+ * The type variable's upper bounds.
+ */
+ private final List<? extends TypeDescription.Generic> bounds;
+
+ /**
+ * The annotations of the type variable.
+ */
+ private final List<? extends AnnotationDescription> annotations;
+
+ /**
+ * Creates a new type variable token without annotations.
+ *
+ * @param symbol The type variable's symbol.
+ * @param bounds The type variable's upper bounds.
+ */
+ public TypeVariableToken(String symbol, List<? extends TypeDescription.Generic> bounds) {
+ this(symbol, bounds, Collections.<AnnotationDescription>emptyList());
+ }
+
+ /**
+ * Creates a new type variable token.
+ *
+ * @param symbol The type variable's symbol.
+ * @param bounds The type variable's upper bounds.
+ * @param annotations The annotations of the type variable.
+ */
+ public TypeVariableToken(String symbol, List<? extends TypeDescription.Generic> bounds, List<? extends AnnotationDescription> annotations) {
+ this.symbol = symbol;
+ this.bounds = bounds;
+ this.annotations = annotations;
+ }
+
+ /**
+ * Transforms a type variable into a type variable token with its bounds detached.
+ *
+ * @param typeVariable A type variable in its attached state.
+ * @param matcher A matcher that identifies types to detach from the upper bound types.
+ * @return A token representing the detached type variable.
+ */
+ public static TypeVariableToken of(TypeDescription.Generic typeVariable, ElementMatcher<? super TypeDescription> matcher) {
+ return new TypeVariableToken(typeVariable.getSymbol(),
+ typeVariable.getUpperBounds().accept(new TypeDescription.Generic.Visitor.Substitutor.ForDetachment(matcher)),
+ typeVariable.getDeclaredAnnotations());
+ }
+
+ /**
+ * Returns the type variable's symbol.
+ *
+ * @return The type variable's symbol.
+ */
+ public String getSymbol() {
+ return symbol;
+ }
+
+ /**
+ * Returns the type variable's upper bounds.
+ *
+ * @return The type variable's upper bounds.
+ */
+ public TypeList.Generic getBounds() {
+ return new TypeList.Generic.Explicit(bounds);
+ }
+
+ /**
+ * Returns the annotations on this type variable.
+ *
+ * @return The annotations on this variable.
+ */
+ public AnnotationList getAnnotations() {
+ return new AnnotationList.Explicit(annotations);
+ }
+
+ @Override
+ public TypeVariableToken accept(TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor) {
+ return new TypeVariableToken(symbol, getBounds().accept(visitor), annotations);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (!(other instanceof TypeVariableToken)) return false;
+ TypeVariableToken that = (TypeVariableToken) other;
+ return symbol.equals(that.symbol)
+ && bounds.equals(that.bounds)
+ && annotations.equals(that.annotations);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = symbol.hashCode();
+ result = 31 * result + bounds.hashCode();
+ result = 31 * result + annotations.hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return symbol;
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/description/type/package-info.java b/byte-buddy-dep/src/main/java/net/bytebuddy/description/type/package-info.java
new file mode 100644
index 0000000..89fb324
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/description/type/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Contains descriptions of Java types and packages.
+ */
+package net.bytebuddy.description.type;
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/ClassFileLocator.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/ClassFileLocator.java
new file mode 100644
index 0000000..fa44e5f
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/ClassFileLocator.java
@@ -0,0 +1,1492 @@
+package net.bytebuddy.dynamic;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.NamedElement;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.utility.JavaModule;
+import net.bytebuddy.utility.JavaType;
+import net.bytebuddy.utility.StreamDrainer;
+
+import java.io.*;
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.Instrumentation;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.ProtectionDomain;
+import java.util.*;
+import java.util.jar.JarFile;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import static net.bytebuddy.matcher.ElementMatchers.isChildOf;
+
+/**
+ * Locates a class file or its byte array representation when it is given its type description.
+ */
+public interface ClassFileLocator extends Closeable {
+
+ /**
+ * The file extension for a Java class file.
+ */
+ String CLASS_FILE_EXTENSION = ".class";
+
+ /**
+ * Locates the class file for a given type and returns the binary data of the class file.
+ *
+ * @param typeName The name of the type to locate a class file representation for.
+ * @return Any binary representation of the type which might be illegal.
+ * @throws java.io.IOException If reading a class file causes an error.
+ */
+ Resolution locate(String typeName) throws IOException;
+
+ /**
+ * Represents a class file as binary data.
+ */
+ interface Resolution {
+
+ /**
+ * Checks if this binary representation is valid.
+ *
+ * @return {@code true} if this binary representation is valid.
+ */
+ boolean isResolved();
+
+ /**
+ * Finds the data of this binary representation. Calling this method is only legal for resolved instances.
+ * For non-resolved instances, an exception is thrown.
+ *
+ * @return The requested binary data. The returned array must not be altered.
+ */
+ byte[] resolve();
+
+ /**
+ * A canonical representation of an illegal binary representation.
+ */
+ @EqualsAndHashCode
+ class Illegal implements Resolution {
+
+ /**
+ * The name of the unresolved class file.
+ */
+ private final String typeName;
+
+ /**
+ * Creates an illegal resolution for a class file.
+ *
+ * @param typeName The name of the unresolved class file.
+ */
+ public Illegal(String typeName) {
+ this.typeName = typeName;
+ }
+
+ @Override
+ public boolean isResolved() {
+ return false;
+ }
+
+ @Override
+ public byte[] resolve() {
+ throw new IllegalStateException("Could not locate class file for " + typeName);
+ }
+ }
+
+ /**
+ * Represents a byte array as binary data.
+ */
+ @EqualsAndHashCode
+ class Explicit implements Resolution {
+
+ /**
+ * The represented data.
+ */
+ private final byte[] binaryRepresentation;
+
+ /**
+ * Creates a new explicit resolution of a given array of binary data.
+ *
+ * @param binaryRepresentation The binary data to represent. The array must not be modified.
+ */
+ @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "The array is not to be modified by contract")
+ public Explicit(byte[] binaryRepresentation) {
+ this.binaryRepresentation = binaryRepresentation;
+ }
+
+ @Override
+ public boolean isResolved() {
+ return true;
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "The array is not to be modified by contract")
+ public byte[] resolve() {
+ return binaryRepresentation;
+ }
+ }
+ }
+
+ /**
+ * A class file locator that cannot locate any class files.
+ */
+ enum NoOp implements ClassFileLocator {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Resolution locate(String typeName) {
+ return new Resolution.Illegal(typeName);
+ }
+
+ @Override
+ public void close() throws IOException {
+ /* do nothing */
+ }
+ }
+
+ /**
+ * A simple class file locator that returns class files from a selection of given types.
+ */
+ @EqualsAndHashCode
+ class Simple implements ClassFileLocator {
+
+ /**
+ * The class files that are known to this class file locator mapped by their type name.
+ */
+ private final Map<String, byte[]> classFiles;
+
+ /**
+ * Creates a new simple class file locator.
+ *
+ * @param classFiles The class files that are known to this class file locator mapped by their type name.
+ */
+ public Simple(Map<String, byte[]> classFiles) {
+ this.classFiles = classFiles;
+ }
+
+ /**
+ * Creates a class file locator for a single known type.
+ *
+ * @param typeName The name of the type.
+ * @param binaryRepresentation The binary representation of the type.
+ * @return An appropriate class file locator.
+ */
+ public static ClassFileLocator of(String typeName, byte[] binaryRepresentation) {
+ return new Simple(Collections.singletonMap(typeName, binaryRepresentation));
+ }
+
+ /**
+ * Creates a class file locator for a single known type with an additional fallback locator.
+ *
+ * @param typeName The name of the type.
+ * @param binaryRepresentation The binary representation of the type.
+ * @param fallback The class file locator to query in case that a lookup triggers any other type.
+ * @return An appropriate class file locator.
+ */
+ public static ClassFileLocator of(String typeName, byte[] binaryRepresentation, ClassFileLocator fallback) {
+ return new Compound(new Simple(Collections.singletonMap(typeName, binaryRepresentation)), fallback);
+ }
+
+ /**
+ * Creates a class file locator that represents all types of a dynamic type.
+ *
+ * @param dynamicType The dynamic type to represent.
+ * @return A class file locator representing the dynamic type's types.
+ */
+ public static ClassFileLocator of(DynamicType dynamicType) {
+ return of(dynamicType.getAllTypes());
+ }
+
+ /**
+ * Creates a class file locator that represents all types of a dynamic type.
+ *
+ * @param binaryRepresentations The binary representation of all types.
+ * @return A class file locator representing the dynamic type's types.
+ */
+ public static ClassFileLocator of(Map<TypeDescription, byte[]> binaryRepresentations) {
+ Map<String, byte[]> classFiles = new HashMap<String, byte[]>();
+ for (Map.Entry<TypeDescription, byte[]> entry : binaryRepresentations.entrySet()) {
+ classFiles.put(entry.getKey().getName(), entry.getValue());
+ }
+ return new Simple(classFiles);
+ }
+
+ @Override
+ public Resolution locate(String typeName) {
+ byte[] binaryRepresentation = classFiles.get(typeName);
+ return binaryRepresentation == null
+ ? new Resolution.Illegal(typeName)
+ : new Resolution.Explicit(binaryRepresentation);
+ }
+
+ @Override
+ public void close() {
+ /* do nothing */
+ }
+ }
+
+ /**
+ * <p>
+ * A class file locator that queries a class loader for binary representations of class files.
+ * </p>
+ * <p>
+ * <b>Important</b>: Even when calling {@link Closeable#close()} on this class file locator, no underlying
+ * class loader is closed if it implements the {@link Closeable} interface as this is typically not intended.
+ * </p>
+ */
+ @EqualsAndHashCode
+ class ForClassLoader implements ClassFileLocator {
+
+ /**
+ * The class loader to query.
+ */
+ private final ClassLoader classLoader;
+
+ /**
+ * Creates a new class file locator for the given class loader.
+ *
+ * @param classLoader The class loader to query which must not be the bootstrap class loader, i.e. {@code null}.
+ */
+ protected ForClassLoader(ClassLoader classLoader) {
+ this.classLoader = classLoader;
+ }
+
+ /**
+ * Creates a class file locator that queries the system class loader.
+ *
+ * @return A class file locator that queries the system class loader.
+ */
+ public static ClassFileLocator ofClassPath() {
+ return new ForClassLoader(ClassLoader.getSystemClassLoader());
+ }
+
+ /**
+ * Creates a class file locator for a given class loader.
+ *
+ * @param classLoader The class loader to be used. If this class loader represents the bootstrap class
+ * loader which is represented by the {@code null} value, this system class loader
+ * is used instead.
+ * @return A corresponding source locator.
+ */
+ public static ClassFileLocator of(ClassLoader classLoader) {
+ return new ForClassLoader(classLoader == null
+ ? ClassLoader.getSystemClassLoader()
+ : classLoader);
+ }
+
+ /**
+ * Attempts to create a binary representation of a loaded type by requesting data from its
+ * {@link java.lang.ClassLoader}.
+ *
+ * @param type The type of interest.
+ * @return The binary data to this type which might be illegal.
+ */
+ public static Resolution read(Class<?> type) {
+ try {
+ ClassLoader classLoader = type.getClassLoader();
+ return locate(classLoader == null
+ ? ClassLoader.getSystemClassLoader()
+ : classLoader, TypeDescription.ForLoadedType.getName(type));
+ } catch (IOException exception) {
+ throw new IllegalStateException("Cannot read class file for " + type, exception);
+ }
+ }
+
+ @Override
+ public Resolution locate(String typeName) throws IOException {
+ return locate(classLoader, typeName);
+ }
+
+ @Override
+ public void close() throws IOException {
+ /* do nothing */
+ }
+
+ /**
+ * Locates the class file for the supplied type by requesting a resource from the class loader.
+ *
+ * @param classLoader The class loader to query for the resource.
+ * @param typeName The name of the type for which to locate a class file.
+ * @return A resolution for the class file.
+ * @throws IOException If reading the class file causes an exception.
+ */
+ protected static Resolution locate(ClassLoader classLoader, String typeName) throws IOException {
+ InputStream inputStream = classLoader.getResourceAsStream(typeName.replace('.', '/') + CLASS_FILE_EXTENSION);
+ if (inputStream != null) {
+ try {
+ return new Resolution.Explicit(StreamDrainer.DEFAULT.drain(inputStream));
+ } finally {
+ inputStream.close();
+ }
+ } else {
+ return new Resolution.Illegal(typeName);
+ }
+ }
+
+ /**
+ * <p>
+ * A class file locator that queries a class loader for binary representations of class files.
+ * The class loader is only weakly referenced.
+ * </p>
+ * <p>
+ * <b>Important</b>: Even when calling {@link Closeable#close()} on this class file locator, no underlying
+ * class loader is closed if it implements the {@link Closeable} interface as this is typically not intended.
+ * </p>
+ */
+ public static class WeaklyReferenced extends WeakReference<ClassLoader> implements ClassFileLocator {
+
+ /**
+ * The represented class loader's hash code.
+ */
+ private final int hashCode;
+
+ /**
+ * Creates a class file locator for a class loader that is weakly referenced.
+ *
+ * @param classLoader The class loader to represent.
+ */
+ protected WeaklyReferenced(ClassLoader classLoader) {
+ super(classLoader);
+ hashCode = System.identityHashCode(classLoader);
+ }
+
+ /**
+ * Creates a class file locator for a given class loader. If the class loader is not the bootstrap
+ * class loader or the system class loader which cannot be collected, the class loader is only weakly
+ * referenced.
+ *
+ * @param classLoader The class loader to be used. If this class loader represents the bootstrap class
+ * loader which is represented by the {@code null} value, this system class loader
+ * is used instead.
+ * @return A corresponding source locator.
+ */
+ public static ClassFileLocator of(ClassLoader classLoader) {
+ return classLoader == null || classLoader == ClassLoader.getSystemClassLoader() || classLoader == ClassLoader.getSystemClassLoader().getParent()
+ ? ForClassLoader.of(classLoader)
+ : new WeaklyReferenced(classLoader);
+ }
+
+ @Override
+ public Resolution locate(String typeName) throws IOException {
+ ClassLoader classLoader = get();
+ return classLoader == null
+ ? new Resolution.Illegal(typeName)
+ : ForClassLoader.locate(classLoader, typeName);
+ }
+
+ @Override
+ public void close() throws IOException {
+ /* do nothing */
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (this == object) return true;
+ if (object == null || getClass() != object.getClass()) return false;
+ WeaklyReferenced that = (WeaklyReferenced) object;
+ ClassLoader classLoader = that.get();
+ return classLoader != null && get() == classLoader;
+ }
+ }
+ }
+
+ /**
+ * <p>
+ * A class file locator that locates class files by querying a Java module's {@code getResourceAsStream} method.
+ * </p>
+ * <p>
+ * <b>Important</b>: Even when calling {@link Closeable#close()} on this class file locator, no underlying
+ * class loader is closed if it implements the {@link Closeable} interface as this is typically not intended.
+ * </p>
+ */
+ @EqualsAndHashCode
+ class ForModule implements ClassFileLocator {
+
+ /**
+ * The represented Java module.
+ */
+ private final JavaModule module;
+
+ /**
+ * Creates a new class file locator for a Java module.
+ *
+ * @param module The represented Java module.
+ */
+ protected ForModule(JavaModule module) {
+ this.module = module;
+ }
+
+ /**
+ * Returns a class file locator that exposes all class files of the boot module layer. This class file locator is only available
+ * on virtual machines of version 9 or later. On earlier versions, the returned class file locator does not locate any resources.
+ *
+ * @return A class file locator that locates classes of the boot layer.
+ */
+ @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception is supposed to be rethrown")
+ public static ClassFileLocator ofBootLayer() {
+ try {
+ Map<String, ClassFileLocator> bootModules = new HashMap<String, ClassFileLocator>();
+ Class<?> layerType = Class.forName("java.lang.ModuleLayer");
+ Method getPackages = JavaType.MODULE.load().getMethod("getPackages");
+ for (Object rawModule : (Set<?>) layerType.getMethod("modules").invoke(layerType.getMethod("boot").invoke(null))) {
+ ClassFileLocator classFileLocator = ForModule.of(JavaModule.of(rawModule));
+ for (String packageName : (String[]) getPackages.invoke(rawModule)) {
+ bootModules.put(packageName, classFileLocator);
+ }
+ }
+ return new PackageDiscriminating(bootModules);
+ } catch (Exception exception) {
+ throw new IllegalStateException("Cannot process boot layer", exception);
+ }
+ }
+
+ /**
+ * Returns a class file locator for the provided module. If the provided module is not named, class files are located via this
+ * unnamed module's class loader.
+ *
+ * @param module The module to create a class file locator for.
+ * @return An appropriate class file locator.
+ */
+ public static ClassFileLocator of(JavaModule module) {
+ return module.isNamed()
+ ? new ForModule(module)
+ : ForClassLoader.of(module.getClassLoader());
+ }
+
+ @Override
+ public Resolution locate(String typeName) throws IOException {
+ return locate(module, typeName);
+ }
+
+ /**
+ * Creates a resolution for a Java module's class files.
+ *
+ * @param module The Java module to query.
+ * @param typeName The name of the type being queried.
+ * @return A resolution for the query.
+ * @throws IOException If an I/O exception was thrown.
+ */
+ protected static Resolution locate(JavaModule module, String typeName) throws IOException {
+ InputStream inputStream = module.getResourceAsStream(typeName.replace('.', '/') + CLASS_FILE_EXTENSION);
+ if (inputStream != null) {
+ try {
+ return new Resolution.Explicit(StreamDrainer.DEFAULT.drain(inputStream));
+ } finally {
+ inputStream.close();
+ }
+ } else {
+ return new Resolution.Illegal(typeName);
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ /* do nothing */
+ }
+
+ /**
+ * <p>
+ * A class file locator for a Java module that only references this module weakly. If a module was garbage collected,
+ * this class file locator only returns unresolved resolutions.
+ * </p>
+ * <p>
+ * <b>Important</b>: Even when calling {@link Closeable#close()} on this class file locator, no underlying
+ * class loader is closed if it implements the {@link Closeable} interface as this is typically not intended.
+ * </p>
+ */
+ public static class WeaklyReferenced extends WeakReference<Object> implements ClassFileLocator {
+
+ /**
+ * The represented module's hash code.
+ */
+ private final int hashCode;
+
+ /**
+ * Creates a class file locator for a Java module that is weakly referenced.
+ *
+ * @param module The raw Java module to represent.
+ */
+ protected WeaklyReferenced(Object module) {
+ super(module);
+ hashCode = System.identityHashCode(module);
+ }
+
+ /**
+ * Creates a class file locator for a Java module where the module is referenced weakly. If the module is not named, the module's class loader
+ * is represented instead. Module's of the boot layer are not referenced weakly.
+ *
+ * @param module The Java module to represent.
+ * @return A suitable class file locator.
+ */
+ public static ClassFileLocator of(JavaModule module) {
+ if (module.isNamed()) {
+ return module.getClassLoader() == null || module.getClassLoader() == ClassLoader.getSystemClassLoader() || module.getClassLoader() == ClassLoader.getSystemClassLoader().getParent()
+ ? new ForModule(module)
+ : new WeaklyReferenced(module.unwrap());
+ } else {
+ return ForClassLoader.WeaklyReferenced.of(module.getClassLoader());
+ }
+ }
+
+ @Override
+ public Resolution locate(String typeName) throws IOException {
+ Object module = get();
+ return module == null
+ ? new Resolution.Illegal(typeName)
+ : ForModule.locate(JavaModule.of(module), typeName);
+ }
+
+ @Override
+ public void close() throws IOException {
+ /* do nothing */
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (this == object) return true;
+ if (object == null || getClass() != object.getClass()) return false;
+ WeaklyReferenced that = (WeaklyReferenced) object;
+ Object module = that.get();
+ return module != null && get() == module;
+ }
+ }
+ }
+
+ /**
+ * A class file locator that locates classes within a Java <i>jar</i> file.
+ */
+ @EqualsAndHashCode
+ class ForJarFile implements ClassFileLocator {
+
+ /**
+ * A list of potential locations of the runtime jar for different platforms.
+ */
+ private static final List<String> RUNTIME_LOCATIONS = Arrays.asList("lib/rt.jar", "../lib/rt.jar", "../Classes/classes.jar");
+
+ /**
+ * The jar file to read from.
+ */
+ private final JarFile jarFile;
+
+ /**
+ * Creates a new class file locator for the given jar file.
+ *
+ * @param jarFile The jar file to read from.
+ */
+ public ForJarFile(JarFile jarFile) {
+ this.jarFile = jarFile;
+ }
+
+ /**
+ * Creates a new class file locator for the given jar file.
+ *
+ * @param file The jar file to read from.
+ * @return A class file locator for the jar file.
+ * @throws IOException If an I/O exception is thrown.
+ */
+ public static ClassFileLocator of(File file) throws IOException {
+ return new ForJarFile(new JarFile(file));
+ }
+
+ /**
+ * Resolves a class file locator for the class path that reads class files directly from the file system. The resulting
+ * class file locator does not imply classes on the boot path.
+ *
+ * @return A class file locator for the class path.
+ * @throws IOException If an I/O exception occurs.
+ */
+ public static ClassFileLocator ofClassPath() throws IOException {
+ return ofClassPath(System.getProperty("java.class.path"));
+ }
+
+ /**
+ * <p>
+ * Resolves a class file locator for the class path that reads class files directly from the file system.
+ * </p>
+ * <p>
+ * <b>Note</b>: The resulting class file locator does not include classes of the bootstrap class loader.
+ * </p>
+ *
+ * @param classPath The class path to scan with the elements separated by {@code path.separator}.
+ * @return A class file locator for the class path.
+ * @throws IOException If an I/O exception occurs.
+ */
+ public static ClassFileLocator ofClassPath(String classPath) throws IOException {
+ List<ClassFileLocator> classFileLocators = new ArrayList<ClassFileLocator>();
+ for (String element : Pattern.compile(System.getProperty("path.separator"), Pattern.LITERAL).split(classPath)) {
+ File file = new File(element);
+ if (file.isDirectory()) {
+ classFileLocators.add(new ForFolder(file));
+ } else if (file.isFile()) {
+ classFileLocators.add(of(file));
+ }
+ }
+ return new Compound(classFileLocators);
+ }
+
+ /**
+ * Resolves a class file locator for the runtime jar. If such a file does not exist or cannot be located, a runtime exception is thrown.
+ *
+ * @return A class file locator for the runtime jar, if available.
+ * @throws IOException If an I/O exception occurs.
+ */
+ public static ClassFileLocator ofRuntimeJar() throws IOException {
+ String javaHome = System.getProperty("java.home").replace('\\', '/');
+ File runtimeJar = null;
+ for (String location : RUNTIME_LOCATIONS) {
+ File candidate = new File(javaHome, location);
+ if (candidate.isFile()) {
+ runtimeJar = candidate;
+ break;
+ }
+ }
+ if (runtimeJar == null) {
+ throw new IllegalStateException("Runtime jar does not exist in " + javaHome + " for any of " + RUNTIME_LOCATIONS);
+ }
+ return of(runtimeJar);
+ }
+
+ @Override
+ public Resolution locate(String typeName) throws IOException {
+ ZipEntry zipEntry = jarFile.getEntry(typeName.replace('.', '/') + CLASS_FILE_EXTENSION);
+ if (zipEntry == null) {
+ return new Resolution.Illegal(typeName);
+ } else {
+ InputStream inputStream = jarFile.getInputStream(zipEntry);
+ try {
+ return new Resolution.Explicit(StreamDrainer.DEFAULT.drain(inputStream));
+ } finally {
+ inputStream.close();
+ }
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ jarFile.close();
+ }
+ }
+
+ /**
+ * A class file locator that locates classes within a Java <i>jmod</i> file. This class file locator should not be used
+ * for reading modular jar files for which {@link ForJarFile} is appropriate.
+ */
+ @EqualsAndHashCode
+ class ForModuleFile implements ClassFileLocator {
+
+ /**
+ * The file extension of a modular Java package.
+ */
+ private static final String JMOD_FILE_EXTENSION = ".jmod";
+
+ /**
+ * A list of potential locations of the boot path for different platforms.
+ */
+ private static final List<String> BOOT_LOCATIONS = Arrays.asList("jmods", "../jmods");
+
+ /**
+ * The represented jmod file.
+ */
+ private final ZipFile zipFile;
+
+ /**
+ * Creates a new class file locator for a jmod file.
+ *
+ * @param zipFile The represented jmod file.
+ */
+ public ForModuleFile(ZipFile zipFile) {
+ this.zipFile = zipFile;
+ }
+
+ /**
+ * Creates a new class file locator for this VM's boot module path.
+ *
+ * @return A class file locator for this VM's boot module path.
+ * @throws IOException If an I/O error occurs.
+ */
+ public static ClassFileLocator ofBootPath() throws IOException {
+ String javaHome = System.getProperty("java.home").replace('\\', '/');
+ File bootPath = null;
+ for (String location : BOOT_LOCATIONS) {
+ File candidate = new File(javaHome, location);
+ if (candidate.isDirectory()) {
+ bootPath = candidate;
+ break;
+ }
+ }
+ if (bootPath == null) {
+ throw new IllegalStateException("Boot modules do not exist in " + javaHome + " for any of " + BOOT_LOCATIONS);
+ }
+ return ofBootPath(bootPath);
+ }
+
+ /**
+ * Creates a new class file locator for a Java boot module path.
+ *
+ * @param bootPath The boot path folder.
+ * @return A class file locator for this VMs boot module path.
+ * @throws IOException If an I/O error occurs.
+ */
+ public static ClassFileLocator ofBootPath(File bootPath) throws IOException {
+ File[] module = bootPath.listFiles();
+ if (module == null) {
+ return NoOp.INSTANCE;
+ }
+ List<ClassFileLocator> classFileLocators = new ArrayList<ClassFileLocator>(module.length);
+ for (File aModule : module) {
+ if (aModule.isFile()) {
+ classFileLocators.add(of(aModule));
+ }
+ }
+ return new Compound(classFileLocators);
+ }
+
+ /**
+ * <p>
+ * Resolves a class file locator for this VM's Java module path that reads class files directly from the file system.
+ * </p>
+ * <p>
+ * <b>Note</b>: The resulting class file locator does not include classes of the bootstrap class loader.
+ * </p>
+ *
+ * @return A class file locator for the class path.
+ * @throws IOException If an I/O exception occurs.
+ */
+ public static ClassFileLocator ofModulePath() throws IOException {
+ String modulePath = System.getProperty("jdk.module.path");
+ return modulePath == null
+ ? NoOp.INSTANCE
+ : ofModulePath(modulePath);
+ }
+
+ /**
+ * <p>
+ * Resolves a class file locator for a Java module path that reads class files directly from the file system. All
+ * elements of the module path are resolved releatively to this VM's {@code user.dir}.
+ * </p>
+ * <p>
+ * <b>Note</b>: The resulting class file locator does not include classes of the bootstrap class loader.
+ * </p>
+ *
+ * @param modulePath The module path to scan with the elements separated by {@code path.separator}.
+ * @return A class file locator for the class path.
+ * @throws IOException If an I/O exception occurs.
+ */
+ public static ClassFileLocator ofModulePath(String modulePath) throws IOException {
+ return ofModulePath(modulePath, System.getProperty("user.dir"));
+ }
+
+ /**
+ * <p>
+ * Resolves a class file locator for a Java module path that reads class files directly from the file system.
+ * </p>
+ * <p>
+ * <b>Note</b>: The resulting class file locator does not include classes of the bootstrap class loader.
+ * </p>
+ *
+ * @param modulePath The module path to scan with the elements separated by {@code path.separator}.
+ * @param baseFolder The relative location of the elements on the module path.
+ * @return A class file locator for the class path.
+ * @throws IOException If an I/O exception occurs.
+ */
+ public static ClassFileLocator ofModulePath(String modulePath, String baseFolder) throws IOException {
+ List<ClassFileLocator> classFileLocators = new ArrayList<ClassFileLocator>();
+ for (String element : Pattern.compile(System.getProperty("path.separator"), Pattern.LITERAL).split(modulePath)) {
+ File file = new File(baseFolder, element);
+ if (file.isDirectory()) {
+ File[] module = file.listFiles();
+ if (module != null) {
+ for (File aModule : module) {
+ if (aModule.isDirectory()) {
+ classFileLocators.add(new ForFolder(aModule));
+ } else if (aModule.isFile()) {
+ classFileLocators.add(aModule.getName().endsWith(JMOD_FILE_EXTENSION)
+ ? of(aModule)
+ : ForJarFile.of(aModule));
+ }
+ }
+ }
+ } else if (file.isFile()) {
+ classFileLocators.add(file.getName().endsWith(JMOD_FILE_EXTENSION)
+ ? of(file)
+ : ForJarFile.of(file));
+ }
+ }
+ return new Compound(classFileLocators);
+ }
+
+ /**
+ * Returns a class file locator for the given module file.
+ *
+ * @param file The module file.
+ * @return A class file locator for the given module
+ * @throws IOException If an I/O error occurs.
+ */
+ public static ClassFileLocator of(File file) throws IOException {
+ return new ForModuleFile(new ZipFile(file));
+ }
+
+ @Override
+ public Resolution locate(String typeName) throws IOException {
+ ZipEntry zipEntry = zipFile.getEntry("classes/" + typeName.replace('.', '/') + CLASS_FILE_EXTENSION);
+ if (zipEntry == null) {
+ return new Resolution.Illegal(typeName);
+ } else {
+ InputStream inputStream = zipFile.getInputStream(zipEntry);
+ try {
+ return new Resolution.Explicit(StreamDrainer.DEFAULT.drain(inputStream));
+ } finally {
+ inputStream.close();
+ }
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ zipFile.close();
+ }
+ }
+
+ /**
+ * A class file locator that finds files from a standardized Java folder structure with
+ * folders donating packages and class files being saved as {@code <classname>.class} files
+ * within their package folder.
+ */
+ @EqualsAndHashCode
+ class ForFolder implements ClassFileLocator {
+
+ /**
+ * The base folder of the package structure.
+ */
+ private final File folder;
+
+ /**
+ * Creates a new class file locator for a folder structure of class files.
+ *
+ * @param folder The base folder of the package structure.
+ */
+ public ForFolder(File folder) {
+ this.folder = folder;
+ }
+
+ @Override
+ public Resolution locate(String typeName) throws IOException {
+ File file = new File(folder, typeName.replace('.', File.separatorChar) + CLASS_FILE_EXTENSION);
+ if (file.exists()) {
+ InputStream inputStream = new FileInputStream(file);
+ try {
+ return new Resolution.Explicit(StreamDrainer.DEFAULT.drain(inputStream));
+ } finally {
+ inputStream.close();
+ }
+ } else {
+ return new Resolution.Illegal(typeName);
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ /* do nothing */
+ }
+ }
+
+ /**
+ * A Java agent that allows the location of class files by emulating a retransformation. Note that this class file
+ * locator causes a class to be loaded in order to look up its class file. Also, this locator does deliberately not
+ * support the look-up of classes that represent lambda expressions.
+ */
+ @EqualsAndHashCode
+ class AgentBased implements ClassFileLocator {
+
+ /**
+ * The name of the Byte Buddy {@code net.bytebuddy.agent.Installer} class.
+ */
+ private static final String INSTALLER_TYPE = "net.bytebuddy.agent.Installer";
+
+ /**
+ * The name of the {@code net.bytebuddy.agent.Installer} getter for reading an installed {@link Instrumentation}.
+ */
+ private static final String INSTRUMENTATION_GETTER = "getInstrumentation";
+
+ /**
+ * Indicator for access to a static member via reflection to make the code more readable.
+ */
+ private static final Object STATIC_MEMBER = null;
+
+ /**
+ * The instrumentation instance to use for looking up the binary format of a type.
+ */
+ private final Instrumentation instrumentation;
+
+ /**
+ * The delegate to load a class by its name.
+ */
+ private final ClassLoadingDelegate classLoadingDelegate;
+
+ /**
+ * Creates an agent-based class file locator.
+ *
+ * @param instrumentation The instrumentation to be used.
+ * @param classLoader The class loader to read a class from.
+ */
+ public AgentBased(Instrumentation instrumentation, ClassLoader classLoader) {
+ this(instrumentation, ClassLoadingDelegate.Default.of(classLoader));
+ }
+
+ /**
+ * Creates an agent-based class file locator.
+ *
+ * @param instrumentation The instrumentation to be used.
+ * @param classLoadingDelegate The delegate responsible for class loading.
+ */
+ public AgentBased(Instrumentation instrumentation, ClassLoadingDelegate classLoadingDelegate) {
+ if (!instrumentation.isRetransformClassesSupported()) {
+ throw new IllegalArgumentException(instrumentation + " does not support retransformation");
+ }
+ this.instrumentation = instrumentation;
+ this.classLoadingDelegate = classLoadingDelegate;
+ }
+
+ /**
+ * Returns an agent-based class file locator for the given class loader and an already installed
+ * Byte Buddy-agent.
+ *
+ * @param classLoader The class loader that is expected to load the looked-up a class.
+ * @return A class file locator for the given class loader based on a Byte Buddy agent.
+ */
+ public static ClassFileLocator fromInstalledAgent(ClassLoader classLoader) {
+ try {
+ return new AgentBased((Instrumentation) ClassLoader.getSystemClassLoader()
+ .loadClass(INSTALLER_TYPE)
+ .getMethod(INSTRUMENTATION_GETTER)
+ .invoke(STATIC_MEMBER), classLoader);
+ } catch (RuntimeException exception) {
+ throw exception;
+ } catch (Exception exception) {
+ throw new IllegalStateException("The Byte Buddy agent is not installed or not accessible", exception);
+ }
+ }
+
+ /**
+ * Returns a class file locator that is capable of locating a class file for the given type using the given instrumentation instance.
+ *
+ * @param instrumentation The instrumentation instance to query for a retransformation.
+ * @param type The locatable type which class loader is used as a fallback.
+ * @return A class file locator for locating the class file of the given type.
+ */
+ public static ClassFileLocator of(Instrumentation instrumentation, Class<?> type) {
+ return new AgentBased(instrumentation, ClassLoadingDelegate.Explicit.of(type));
+ }
+
+ @Override
+ public Resolution locate(String typeName) {
+ try {
+ ExtractionClassFileTransformer classFileTransformer = new ExtractionClassFileTransformer(classLoadingDelegate.getClassLoader(), typeName);
+ instrumentation.addTransformer(classFileTransformer, true);
+ try {
+ instrumentation.retransformClasses(classLoadingDelegate.locate(typeName));
+ byte[] binaryRepresentation = classFileTransformer.getBinaryRepresentation();
+ return binaryRepresentation == null
+ ? new Resolution.Illegal(typeName)
+ : new Resolution.Explicit(binaryRepresentation);
+ } finally {
+ instrumentation.removeTransformer(classFileTransformer);
+ }
+ } catch (RuntimeException exception) {
+ throw exception;
+ } catch (Exception ignored) {
+ return new Resolution.Illegal(typeName);
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ /* do nothing */
+ }
+
+ /**
+ * A delegate that is queried for loading a class.
+ */
+ public interface ClassLoadingDelegate {
+
+ /**
+ * Loads a class by its name.
+ *
+ * @param name The name of the type.
+ * @return The class with the given name.
+ * @throws ClassNotFoundException If a class cannot be found.
+ */
+ Class<?> locate(String name) throws ClassNotFoundException;
+
+ /**
+ * Returns the underlying class loader.
+ *
+ * @return The underlying class loader.
+ */
+ ClassLoader getClassLoader();
+
+ /**
+ * A default implementation of a class loading delegate.
+ */
+ @EqualsAndHashCode
+ class Default implements ClassLoadingDelegate {
+
+ /**
+ * The underlying class loader.
+ */
+ protected final ClassLoader classLoader;
+
+ /**
+ * Creates a default class loading delegate.
+ *
+ * @param classLoader The class loader to be queried.
+ */
+ protected Default(ClassLoader classLoader) {
+ this.classLoader = classLoader;
+ }
+
+ /**
+ * Creates a class loading delegate for the given class loader.
+ *
+ * @param classLoader The class loader for which to create a delegate.
+ * @return The class loading delegate for the provided class loader.
+ */
+ public static ClassLoadingDelegate of(ClassLoader classLoader) {
+ return ForDelegatingClassLoader.isDelegating(classLoader)
+ ? new ForDelegatingClassLoader(classLoader)
+ : new Default(classLoader == null ? ClassLoader.getSystemClassLoader() : classLoader);
+ }
+
+ @Override
+ public Class<?> locate(String name) throws ClassNotFoundException {
+ return classLoader.loadClass(name);
+ }
+
+ @Override
+ public ClassLoader getClassLoader() {
+ return classLoader;
+ }
+ }
+
+ /**
+ * A class loading delegate that accounts for a {@code sun.reflect.DelegatingClassLoader} which
+ * cannot load its own classes by name.
+ */
+ class ForDelegatingClassLoader extends Default {
+
+ /**
+ * The name of the delegating class loader.
+ */
+ private static final String DELEGATING_CLASS_LOADER_NAME = "sun.reflect.DelegatingClassLoader";
+
+ /**
+ * An index indicating the first element of a collection.
+ */
+ private static final int ONLY = 0;
+
+ /**
+ * A dispatcher for extracting a class loader's loaded classes.
+ */
+ private static final Dispatcher.Initializable DISPATCHER = AccessController.doPrivileged(Dispatcher.CreationAction.INSTANCE);
+
+ /**
+ * Creates a class loading delegate for a delegating class loader.
+ *
+ * @param classLoader The delegating class loader.
+ */
+ protected ForDelegatingClassLoader(ClassLoader classLoader) {
+ super(classLoader);
+ }
+
+ /**
+ * Checks if a class loader is a delegating class loader.
+ *
+ * @param classLoader The class loader to inspect.
+ * @return {@code true} if the class loader is a delegating class loader.
+ */
+ protected static boolean isDelegating(ClassLoader classLoader) {
+ return classLoader != null && classLoader.getClass().getName().equals(DELEGATING_CLASS_LOADER_NAME);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Class<?> locate(String name) throws ClassNotFoundException {
+ Vector<Class<?>> classes;
+ try {
+ classes = DISPATCHER.initialize().extract(classLoader);
+ } catch (RuntimeException ignored) {
+ return super.locate(name);
+ }
+ if (classes.size() != 1) {
+ return super.locate(name);
+ }
+ Class<?> type = classes.get(ONLY);
+ return TypeDescription.ForLoadedType.getName(type).equals(name)
+ ? type
+ : super.locate(name);
+ }
+
+ /**
+ * Representation of a Java {@link java.lang.reflect.Field}.
+ */
+ protected interface Dispatcher {
+
+ /**
+ * Reads the classes of the represented collection.
+ *
+ * @param classLoader The class loader to read from.
+ * @return The class loader's loaded classes.
+ */
+ Vector<Class<?>> extract(ClassLoader classLoader);
+
+ /**
+ * An unitialized version of a dispatcher for extracting a class loader's loaded classes.
+ */
+ interface Initializable {
+
+ /**
+ * Initializes the dispatcher.
+ *
+ * @return An initialized dispatcher.
+ */
+ Dispatcher initialize();
+ }
+
+ /**
+ * An action for creating a dispatcher.
+ */
+ enum CreationAction implements PrivilegedAction<Initializable> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Initializable run() {
+ try {
+ return new Dispatcher.Resolved(ClassLoader.class.getDeclaredField("classes"));
+ } catch (Exception exception) {
+ return new Dispatcher.Unresolved(exception);
+ }
+ }
+ }
+
+ /**
+ * Represents a field that could be located.
+ */
+ @EqualsAndHashCode
+ class Resolved implements Dispatcher, Initializable, PrivilegedAction<Dispatcher> {
+
+ /**
+ * The represented field.
+ */
+ private final Field field;
+
+ /**
+ * Creates a new resolved field.
+ *
+ * @param field the represented field.l
+ */
+ public Resolved(Field field) {
+ this.field = field;
+ }
+
+ @Override
+ public Dispatcher initialize() {
+ return AccessController.doPrivileged(this);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Vector<Class<?>> extract(ClassLoader classLoader) {
+ try {
+ return (Vector<Class<?>>) field.get(classLoader);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access field", exception);
+ }
+ }
+
+ @Override
+ public Dispatcher run() {
+ field.setAccessible(true);
+ return this;
+ }
+ }
+
+ /**
+ * Represents a field that could not be located.
+ */
+ @EqualsAndHashCode
+ class Unresolved implements Initializable {
+
+ /**
+ * The exception that occurred when attempting to locate the field.
+ */
+ private final Exception exception;
+
+ /**
+ * Creates a representation of a non-resolved field.
+ *
+ * @param exception The exception that occurred when attempting to locate the field.
+ */
+ public Unresolved(Exception exception) {
+ this.exception = exception;
+ }
+
+ @Override
+ public Dispatcher initialize() {
+ throw new IllegalStateException("Could not locate classes vector", exception);
+ }
+ }
+ }
+ }
+
+ /**
+ * A class loading delegate that allows the location of explicitly registered classes that cannot
+ * be located by a class loader directly. This allows for locating classes that are loaded by
+ * an anonymous class loader which does not register its classes in a system dictionary.
+ */
+ @EqualsAndHashCode
+ class Explicit implements ClassLoadingDelegate {
+
+ /**
+ * A class loading delegate that is queried for classes that are not registered explicitly.
+ */
+ private final ClassLoadingDelegate fallbackDelegate;
+
+ /**
+ * The map of registered classes mapped by their name.
+ */
+ private final Map<String, Class<?>> types;
+
+ /**
+ * Creates a new class loading delegate with a possibility of looking up explicitly
+ * registered classes.
+ *
+ * @param classLoader The class loader to be used for looking up classes.
+ * @param types A collection of classes that cannot be looked up explicitly.
+ */
+ public Explicit(ClassLoader classLoader, Collection<? extends Class<?>> types) {
+ this(Default.of(classLoader), types);
+ }
+
+ /**
+ * Creates a new class loading delegate with a possibility of looking up explicitly
+ * registered classes.
+ *
+ * @param fallbackDelegate The class loading delegate to query for any class that is not
+ * registered explicitly.
+ * @param types A collection of classes that cannot be looked up explicitly.
+ */
+ public Explicit(ClassLoadingDelegate fallbackDelegate, Collection<? extends Class<?>> types) {
+ this.fallbackDelegate = fallbackDelegate;
+ this.types = new HashMap<String, Class<?>>();
+ for (Class<?> type : types) {
+ this.types.put(TypeDescription.ForLoadedType.getName(type), type);
+ }
+ }
+
+ /**
+ * Creates an explicit class loading delegate for the given type.
+ *
+ * @param type The type that is explicitly locatable.
+ * @return A suitable class loading delegate.
+ */
+ public static ClassLoadingDelegate of(Class<?> type) {
+ return new Explicit(type.getClassLoader(), Collections.singleton(type));
+ }
+
+ @Override
+ public Class<?> locate(String name) throws ClassNotFoundException {
+ Class<?> type = types.get(name);
+ return type == null
+ ? fallbackDelegate.locate(name)
+ : type;
+ }
+
+ @Override
+ public ClassLoader getClassLoader() {
+ return fallbackDelegate.getClassLoader();
+ }
+ }
+ }
+
+ /**
+ * A non-operational class file transformer that remembers the binary format of a given class.
+ */
+ protected static class ExtractionClassFileTransformer implements ClassFileTransformer {
+
+ /**
+ * An indicator that an attempted class file transformation did not alter the handed class file.
+ */
+ private static final byte[] DO_NOT_TRANSFORM = null;
+
+ /**
+ * The class loader that is expected to have loaded the looked-up a class.
+ */
+ private final ClassLoader classLoader;
+
+ /**
+ * The name of the type to look up.
+ */
+ private final String typeName;
+
+ /**
+ * The binary representation of the looked-up class.
+ */
+ @SuppressFBWarnings(value = "VO_VOLATILE_REFERENCE_TO_ARRAY", justification = "The array is not to be modified by contract")
+ private volatile byte[] binaryRepresentation;
+
+ /**
+ * Creates a class file transformer for the purpose of extraction.
+ *
+ * @param classLoader The class loader that is expected to have loaded the looked-up a class.
+ * @param typeName The name of the type to look up.
+ */
+ protected ExtractionClassFileTransformer(ClassLoader classLoader, String typeName) {
+ this.classLoader = classLoader;
+ this.typeName = typeName;
+ }
+
+ @Override
+ @SuppressFBWarnings(value = {"EI_EXPOSE_REP", "EI_EXPOSE_REP2"}, justification = "The array is not to be modified by contract")
+ public byte[] transform(ClassLoader classLoader,
+ String internalName,
+ Class<?> redefinedType,
+ ProtectionDomain protectionDomain,
+ byte[] binaryRepresentation) {
+ if (internalName != null && isChildOf(this.classLoader).matches(classLoader) && typeName.equals(internalName.replace('/', '.'))) {
+ this.binaryRepresentation = binaryRepresentation.clone();
+ }
+ return DO_NOT_TRANSFORM;
+ }
+
+ /**
+ * Returns the binary representation of the class file that was looked up. The returned array must never be modified.
+ *
+ * @return The binary representation of the class file or {@code null} if no such class file could
+ * be located.
+ */
+ @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "The array is not to be modified by contract")
+ protected byte[] getBinaryRepresentation() {
+ return binaryRepresentation;
+ }
+ }
+ }
+
+ /**
+ * A class file locator that discriminates by a type's package.
+ */
+ @EqualsAndHashCode
+ class PackageDiscriminating implements ClassFileLocator {
+
+ /**
+ * A mapping of package names to class file locators.
+ */
+ private final Map<String, ClassFileLocator> classFileLocators;
+
+ /**
+ * Creates a new package-discriminating class file locator.
+ *
+ * @param classFileLocators A mapping of package names to class file locators where an empty string donates the default package.
+ */
+ public PackageDiscriminating(Map<String, ClassFileLocator> classFileLocators) {
+ this.classFileLocators = classFileLocators;
+ }
+
+ @Override
+ public Resolution locate(String typeName) throws IOException {
+ int packageIndex = typeName.lastIndexOf('.');
+ ClassFileLocator classFileLocator = classFileLocators.get(packageIndex == -1
+ ? NamedElement.EMPTY_NAME
+ : typeName.substring(0, packageIndex));
+ return classFileLocator == null
+ ? new Resolution.Illegal(typeName)
+ : classFileLocator.locate(typeName);
+ }
+
+ @Override
+ public void close() throws IOException {
+ for (ClassFileLocator classFileLocator : classFileLocators.values()) {
+ classFileLocator.close();
+ }
+ }
+ }
+
+ /**
+ * A compound {@link ClassFileLocator} that chains several locators.
+ * Any class file locator is queried in the supplied order until one locator is able to provide an input
+ * stream of the class file.
+ */
+ @EqualsAndHashCode
+ class Compound implements ClassFileLocator, Closeable {
+
+ /**
+ * The {@link ClassFileLocator}s which are represented by this compound
+ * class file locator in the order of their application.
+ */
+ private final List<ClassFileLocator> classFileLocators;
+
+ /**
+ * Creates a new compound class file locator.
+ *
+ * @param classFileLocator The {@link ClassFileLocator}s to be
+ * represented by this compound class file locator in the order of their application.
+ */
+ public Compound(ClassFileLocator... classFileLocator) {
+ this(Arrays.asList(classFileLocator));
+ }
+
+ /**
+ * Creates a new compound class file locator.
+ *
+ * @param classFileLocators The {@link ClassFileLocator}s to be represented by this compound class file locator in
+ * the order of their application.
+ */
+ public Compound(List<? extends ClassFileLocator> classFileLocators) {
+ this.classFileLocators = new ArrayList<ClassFileLocator>();
+ for (ClassFileLocator classFileLocator : classFileLocators) {
+ if (classFileLocator instanceof Compound) {
+ this.classFileLocators.addAll(((Compound) classFileLocator).classFileLocators);
+ } else if (!(classFileLocator instanceof NoOp)) {
+ this.classFileLocators.add(classFileLocator);
+ }
+ }
+ }
+
+ @Override
+ public Resolution locate(String typeName) throws IOException {
+ for (ClassFileLocator classFileLocator : classFileLocators) {
+ Resolution resolution = classFileLocator.locate(typeName);
+ if (resolution.isResolved()) {
+ return resolution;
+ }
+ }
+ return new Resolution.Illegal(typeName);
+ }
+
+ @Override
+ public void close() throws IOException {
+ for (ClassFileLocator classFileLocator : classFileLocators) {
+ classFileLocator.close();
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/DynamicType.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/DynamicType.java
new file mode 100644
index 0000000..d1a9b51
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/DynamicType.java
@@ -0,0 +1,4464 @@
+package net.bytebuddy.dynamic;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.asm.AsmVisitorWrapper;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.annotation.AnnotationValue;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.modifier.*;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.description.type.TypeVariableToken;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.dynamic.loading.InjectionClassLoader;
+import net.bytebuddy.dynamic.scaffold.*;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.LoadedTypeInitializer;
+import net.bytebuddy.implementation.attribute.*;
+import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.matcher.LatentMatcher;
+import net.bytebuddy.pool.TypePool;
+import net.bytebuddy.utility.CompoundList;
+
+import java.io.*;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.jar.*;
+
+import static net.bytebuddy.matcher.ElementMatchers.*;
+
+/**
+ * A dynamic type that is created at runtime, usually as the result of applying a
+ * {@link net.bytebuddy.dynamic.DynamicType.Builder} or as the result of an
+ * {@link net.bytebuddy.implementation.auxiliary.AuxiliaryType}.
+ * <p> </p>
+ * Note that the {@link TypeDescription}s will represent their
+ * unloaded forms and therefore differ from the loaded types, especially with regards to annotations.
+ */
+public interface DynamicType {
+
+ /**
+ * <p>
+ * Returns a description of this dynamic type.
+ * </p>
+ * <p>
+ * <b>Note</b>: This description will most likely differ from the binary representation of this type. Normally,
+ * annotations and intercepted methods are not added to this type description.
+ * </p>
+ *
+ * @return A description of this dynamic type.
+ */
+ TypeDescription getTypeDescription();
+
+ /**
+ * Returns a byte array representing this dynamic type. This byte array might be reused by this dynamic type and
+ * must therefore not be altered.
+ *
+ * @return A byte array of the type's binary representation.
+ */
+ byte[] getBytes();
+
+ /**
+ * <p>
+ * Returns a map of all auxiliary types that are required for making use of the main type.
+ * </p>
+ * <p>
+ * <b>Note</b>: The type descriptions will most likely differ from the binary representation of this type.
+ * Normally, annotations and intercepted methods are not added to the type descriptions of auxiliary types.
+ * </p>
+ *
+ * @return A map of all auxiliary types by their descriptions to their binary representation.
+ */
+ Map<TypeDescription, byte[]> getAuxiliaryTypes();
+
+ /**
+ * Returns all types that are implied by this dynamic type.
+ *
+ * @return A mapping from all type descriptions, the actual type and its auxiliary types to their binary
+ * representation
+ */
+ Map<TypeDescription, byte[]> getAllTypes();
+
+ /**
+ * <p>
+ * Returns a map of all loaded type initializers for the main type and all auxiliary types, if any.
+ * </p>
+ * <p>
+ * <b>Note</b>: The type descriptions will most likely differ from the binary representation of this type.
+ * Normally, annotations and intercepted methods are not added to the type descriptions of auxiliary types.
+ * </p>
+ *
+ * @return A mapping of all types' descriptions to their loaded type initializers.
+ */
+ Map<TypeDescription, LoadedTypeInitializer> getLoadedTypeInitializers();
+
+ /**
+ * Checks if a dynamic type requires some form of explicit type initialization, either for itself or for one
+ * of its auxiliary types, if any. This is the case when this dynamic type was defined to delegate method calls
+ * to a specific instance which is stored in a field of the created type. If this class serialized, it could not
+ * be used without its loaded type initializers since the field value represents a specific runtime context.
+ *
+ * @return {@code true} if this type requires explicit type initialization.
+ */
+ boolean hasAliveLoadedTypeInitializers();
+
+ /**
+ * <p>
+ * Saves a dynamic type in a given folder using the Java class file format while respecting the naming conventions
+ * for saving compiled Java classes. All auxiliary types, if any, are saved in the same directory. The resulting
+ * folder structure will resemble the structure that is required for Java run times, i.e. each folder representing
+ * a segment of the package name. If the specified {@code folder} does not yet exist, it is created during the
+ * call of this method.
+ * </p>
+ * <p>
+ * <b>Note</b>: The type descriptions will most likely differ from the binary representation of this type.
+ * Normally, annotations and intercepted methods are not added to the type descriptions of auxiliary types.
+ * </p>
+ *
+ * @param folder The base target folder for storing this dynamic type and its auxiliary types, if any.
+ * @return A map of type descriptions pointing to files with their stored binary representations within {@code folder}.
+ * @throws IOException Thrown if the underlying file operations cause an {@code IOException}.
+ */
+ Map<TypeDescription, File> saveIn(File folder) throws IOException;
+
+ /**
+ * Injects the types of this dynamic type into a given <i>jar</i> file. Any pre-existent type with the same name
+ * is overridden during injection. The {@code target} file's folder must exist prior to calling this method. The
+ * file itself is overwritten or created depending on its prior existence.
+ *
+ * @param sourceJar The original jar file.
+ * @param targetJar The {@code source} jar file with the injected contents.
+ * @return The {@code target} jar file.
+ * @throws IOException If an I/O exception occurs while injecting from the source into the target.
+ */
+ File inject(File sourceJar, File targetJar) throws IOException;
+
+ /**
+ * Injects the types of this dynamic type into a given <i>jar</i> file. Any pre-existent type with the same name
+ * is overridden during injection.
+ *
+ * @param jar The jar file to replace with an injected version.
+ * @return The {@code jar} file.
+ * @throws IOException If an I/O exception occurs while injecting into the jar.
+ */
+ File inject(File jar) throws IOException;
+
+ /**
+ * Saves the contents of this dynamic type inside a <i>jar</i> file. The folder of the given {@code file} must
+ * exist prior to calling this method. The jar file is created with a simple manifest that only contains a version
+ * number.
+ *
+ * @param file The target file to which the <i>jar</i> is written to.
+ * @return The given {@code file}.
+ * @throws IOException If an I/O exception occurs while writing the file.
+ */
+ File toJar(File file) throws IOException;
+
+ /**
+ * Saves the contents of this dynamic type inside a <i>jar</i> file. The folder of the given {@code file} must
+ * exist prior to calling this method.
+ *
+ * @param file The target file to which the <i>jar</i> is written to.
+ * @param manifest The manifest of the created <i>jar</i>.
+ * @return The given {@code file}.
+ * @throws IOException If an I/O exception occurs while writing the file.
+ */
+ File toJar(File file, Manifest manifest) throws IOException;
+
+ /**
+ * A builder for creating a dynamic type.
+ *
+ * @param <T> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ interface Builder<T> {
+
+ /**
+ * Applies the supplied {@link AsmVisitorWrapper} onto the {@link org.objectweb.asm.ClassVisitor} during building a dynamic type.
+ * Using an ASM visitor, it is possible to manipulate byte code directly. Byte Buddy does not validate directly created byte code
+ * and it remains the responsibility of the visitor's implementor to generate legal byte code. If several ASM visitor wrappers
+ * are registered, they are applied on top of another in their registration order.
+ *
+ * @param asmVisitorWrapper The ASM visitor wrapper to apply during
+ * @return A new builder that is equal to this builder and applies the ASM visitor wrapper.
+ */
+ Builder<T> visit(AsmVisitorWrapper asmVisitorWrapper);
+
+ /**
+ * Names the dynamic type by the supplied name. The name needs to be fully qualified and in the binary format (packages separated
+ * by dots: {@code foo.Bar}). A type's package determines what other types are visible to the instrumented type and what methods
+ * can be overridden or be represented in method signatures or as field types.
+ *
+ * @param name The fully qualified name of the generated class in a binary format.
+ * @return A new builder that is equal to this builder but with the instrumented type named by the supplied name.
+ */
+ Builder<T> name(String name);
+
+ /**
+ * Defines the supplied modifiers as the modifiers of the instrumented type.
+ *
+ * @param modifierContributor The modifiers of the instrumented type.
+ * @return A new builder that is equal to this builder but with the supplied modifiers applied onto the instrumented type.
+ */
+ Builder<T> modifiers(ModifierContributor.ForType... modifierContributor);
+
+ /**
+ * Defines the supplied modifiers as the modifiers of the instrumented type.
+ *
+ * @param modifierContributors The modifiers of the instrumented type.
+ * @return A new builder that is equal to this builder but with the supplied modifiers applied onto the instrumented type.
+ */
+ Builder<T> modifiers(Collection<? extends ModifierContributor.ForType> modifierContributors);
+
+ /**
+ * Defines the supplied modifiers as the modifiers of the instrumented type.
+ *
+ * @param modifiers The modifiers of the instrumented type.
+ * @return A new builder that is equal to this builder but with the supplied modifiers applied onto the instrumented type.
+ */
+ Builder<T> modifiers(int modifiers);
+
+ /**
+ * Merges the supplied modifier contributors with the modifiers of the instrumented type and defines them as the instrumented
+ * type's new modifiers.
+ *
+ * @param modifierContributor The modifiers of the instrumented type.
+ * @return A new builder that is equal to this builder but with the supplied modifiers merged into the instrumented type's modifiers.
+ */
+ Builder<T> merge(ModifierContributor.ForType... modifierContributor);
+
+ /**
+ * Merges the supplied modifier contributors with the modifiers of the instrumented type and defines them as the instrumented
+ * type's new modifiers.
+ *
+ * @param modifierContributors The modifiers of the instrumented type.
+ * @return A new builder that is equal to this builder but with the supplied modifiers merged into the instrumented type's modifiers.
+ */
+ Builder<T> merge(Collection<? extends ModifierContributor.ForType> modifierContributors);
+
+ /**
+ * Applies the given type attribute appender onto the instrumented type. Using a type attribute appender, it is possible to append
+ * any type of meta data to a type, not only Java {@link Annotation}s.
+ *
+ * @param typeAttributeAppender The type attribute appender to apply.
+ * @return A new builder that is equal to this builder but with the supplied type attribute appender applied to the instrumented type.
+ */
+ Builder<T> attribute(TypeAttributeAppender typeAttributeAppender);
+
+ /**
+ * Annotates the instrumented type with the supplied annotations.
+ *
+ * @param annotation The annotations to add to the instrumented type.
+ * @return A new builder that is equal to this builder but with the annotations added to the instrumented type.
+ */
+ Builder<T> annotateType(Annotation... annotation);
+
+ /**
+ * Annotates the instrumented type with the supplied annotations.
+ *
+ * @param annotations The annotations to add to the instrumented type.
+ * @return A new builder that is equal to this builder but with the annotations added to the instrumented type.
+ */
+ Builder<T> annotateType(List<? extends Annotation> annotations);
+
+ /**
+ * Annotates the instrumented type with the supplied annotations.
+ *
+ * @param annotation The annotations to add to the instrumented type.
+ * @return A new builder that is equal to this builder but with the annotations added to the instrumented type.
+ */
+ Builder<T> annotateType(AnnotationDescription... annotation);
+
+ /**
+ * Annotates the instrumented type with the supplied annotations.
+ *
+ * @param annotations The annotations to add to the instrumented type.
+ * @return A new builder that is equal to this builder but with the annotations added to the instrumented type.
+ */
+ Builder<T> annotateType(Collection<? extends AnnotationDescription> annotations);
+
+ /**
+ * <p>
+ * Implements the supplied interfaces for the instrumented type. Optionally, it is possible to define the
+ * methods that are defined by the interfaces or the interfaces' super interfaces. This excludes methods that
+ * are explicitly ignored.
+ * </p>
+ * <p>
+ * <b>Note</b>: This methods implements the supplied types <i>as is</i>, i.e. any {@link Class} values are implemented
+ * as raw types if they declare type variables or an owner type.
+ * </p>
+ *
+ * @param interfaceType The interface types to implement.
+ * @return A new builder that is equal to this builder but with the interfaces implemented by the instrumented type.
+ */
+ MethodDefinition.ImplementationDefinition.Optional<T> implement(Type... interfaceType);
+
+ /**
+ * <p>
+ * Implements the supplied interfaces for the instrumented type. Optionally, it is possible to define the
+ * methods that are defined by the interfaces or the interfaces' super interfaces. This excludes methods that
+ * are explicitly ignored.
+ * </p>
+ * <p>
+ * <b>Note</b>: This methods implements the supplied types <i>as is</i>, i.e. any {@link Class} values are implemented
+ * as raw types if they declare type variables or an owner type.
+ * </p>
+ *
+ * @param interfaceTypes The interface types to implement.
+ * @return A new builder that is equal to this builder but with the interfaces implemented by the instrumented type.
+ */
+ MethodDefinition.ImplementationDefinition.Optional<T> implement(List<? extends Type> interfaceTypes);
+
+ /**
+ * <p>
+ * Implements the supplied interfaces for the instrumented type. Optionally, it is possible to define the
+ * methods that are defined by the interfaces or the interfaces' super interfaces. This excludes methods that
+ * are explicitly ignored.
+ * </p>
+ * <p>
+ * <b>Note</b>: This methods implements the supplied types <i>as is</i>, i.e. any {@link TypeDescription} values are
+ * implemented as raw types if they declare type variables or an owner type.
+ * </p>
+ *
+ * @param interfaceType The interface types to implement.
+ * @return A new builder that is equal to this builder but with the interfaces implemented by the instrumented type.
+ */
+ MethodDefinition.ImplementationDefinition.Optional<T> implement(TypeDefinition... interfaceType);
+
+ /**
+ * <p>
+ * Implements the supplied interfaces for the instrumented type. Optionally, it is possible to define the
+ * methods that are defined by the interfaces or the interfaces' super interfaces. This excludes methods that
+ * are explicitly ignored.
+ * </p>
+ * <p>
+ * <b>Note</b>: This methods implements the supplied types <i>as is</i>, i.e. any {@link TypeDescription} values are
+ * implemented as raw types if they declare type variables or an owner type.
+ * </p>
+ *
+ * @param interfaceTypes The interface types to implement.
+ * @return A new builder that is equal to this builder but with the interfaces implemented by the instrumented type.
+ */
+ MethodDefinition.ImplementationDefinition.Optional<T> implement(Collection<? extends TypeDefinition> interfaceTypes);
+
+ /**
+ * <p>
+ * Executes the supplied byte code appender within the beginning of the instrumented type's type initializer. The
+ * supplied byte code appender <b>must not return</b> from the method. If several byte code appenders are supplied,
+ * they are executed within their application order.
+ * </p>
+ * <p>
+ * This method should only be used for preparing an instrumented type with a specific configuration. Normally,
+ * a byte code appender is applied via Byte Buddy's standard API by invoking {@link Builder#invokable(ElementMatcher)}
+ * using the {@link net.bytebuddy.matcher.ElementMatchers#isTypeInitializer()} matcher.
+ * </p>
+ *
+ * @param byteCodeAppender The byte code appender to execute within the instrumented type's type initializer.
+ * @return A new builder that is equal to this builder but with the supplied byte code appender being executed within
+ * the instrumented type's type initializer.
+ */
+ Builder<T> initializer(ByteCodeAppender byteCodeAppender);
+
+ /**
+ * Executes the supplied loaded type initializer when loading the created instrumented type. If several loaded
+ * type initializers are supplied, each loaded type initializer is executed in its registration order.
+ *
+ * @param loadedTypeInitializer The loaded type initializer to execute upon loading the instrumented type.
+ * @return A new builder that is equal to this builder but with the supplied loaded type initializer executed upon
+ * loading the instrumented type.
+ */
+ Builder<T> initializer(LoadedTypeInitializer loadedTypeInitializer);
+
+ /**
+ * Defines the supplied type variable without any bounds as a type variable of the instrumented type.
+ *
+ * @param symbol The type variable's symbol.
+ * @return A new builder that is equal to this builder but with the given type variable defined for the instrumented type.
+ */
+ TypeVariableDefinition<T> typeVariable(String symbol);
+
+ /**
+ * Defines the supplied type variable with the given bound as a type variable of the instrumented type.
+ *
+ * @param symbol The type variable's symbol.
+ * @param bound The type variable's upper bounds. Can also be {@link net.bytebuddy.dynamic.TargetType} if the bound type
+ * should be equal to the currently instrumented type.
+ * @return A new builder that is equal to this builder but with the given type variable defined for the instrumented type.
+ */
+ TypeVariableDefinition<T> typeVariable(String symbol, Type... bound);
+
+ /**
+ * Defines the supplied type variable with the given bound as a type variable of the instrumented type.
+ *
+ * @param symbol The type variable's symbol.
+ * @param bounds The type variable's upper bounds. Can also be {@link net.bytebuddy.dynamic.TargetType} if the bound type
+ * should be equal to the currently instrumented type.
+ * @return A new builder that is equal to this builder but with the given type variable defined for the instrumented type.
+ */
+ TypeVariableDefinition<T> typeVariable(String symbol, List<? extends Type> bounds);
+
+ /**
+ * Defines the supplied type variable with the given bound as a type variable of the instrumented type.
+ *
+ * @param symbol The type variable's symbol.
+ * @param bound The type variable's upper bounds. Can also be {@link net.bytebuddy.dynamic.TargetType} if the bound type
+ * should be equal to the currently instrumented type.
+ * @return A new builder that is equal to this builder but with the given type variable defined for the instrumented type.
+ */
+ TypeVariableDefinition<T> typeVariable(String symbol, TypeDefinition... bound);
+
+ /**
+ * Defines the supplied type variable with the given bound as a type variable of the instrumented type.
+ *
+ * @param symbol The type variable's symbol.
+ * @param bounds The type variable's upper bounds. Can also be {@link net.bytebuddy.dynamic.TargetType} if the bound type
+ * should be equal to the currently instrumented type.
+ * @return A new builder that is equal to this builder but with the given type variable defined for the instrumented type.
+ */
+ TypeVariableDefinition<T> typeVariable(String symbol, Collection<? extends TypeDefinition> bounds);
+
+ /**
+ * Transforms any type variable that is defined by this type if it is matched by the supplied matcher.
+ *
+ * @param matcher The matcher to decide what type variables to transform.
+ * @param transformer The transformer to apply to the matched type variables.
+ * @return A new builder that is equal to this builder but with the supplied transformer applied to all type varaibles.
+ */
+ Builder<T> transform(ElementMatcher<? super TypeDescription.Generic> matcher, Transformer<TypeVariableToken> transformer);
+
+ /**
+ * Defines the specified field as a field of the built dynamic type.
+ *
+ * @param name The name of the field.
+ * @param type The type of the field. Can also be {@link net.bytebuddy.dynamic.TargetType} if the field type
+ * should be equal to the currently instrumented type.
+ * @param modifierContributor The modifiers of the field.
+ * @return A new builder that is equal to this builder but with the given field defined for the instrumented type.
+ * Furthermore, it is possible to optionally define a value, annotations or custom attributes for the field.
+ */
+ FieldDefinition.Optional.Valuable<T> defineField(String name, Type type, ModifierContributor.ForField... modifierContributor);
+
+ /**
+ * Defines the specified field as a field of the built dynamic type.
+ *
+ * @param name The name of the field.
+ * @param type The type of the field. Can also be {@link net.bytebuddy.dynamic.TargetType} if the field type
+ * should be equal to the currently instrumented type.
+ * @param modifierContributors The modifiers of the field.
+ * @return A new builder that is equal to this builder but with the given field defined for the instrumented type.
+ * Furthermore, it is possible to optionally define a value, annotations or custom attributes for the field.
+ */
+ FieldDefinition.Optional.Valuable<T> defineField(String name, Type type, Collection<? extends ModifierContributor.ForField> modifierContributors);
+
+ /**
+ * Defines the specified field as a field of the built dynamic type.
+ *
+ * @param name The name of the field.
+ * @param type The type of the field. Can also be {@link net.bytebuddy.dynamic.TargetType} if the field type
+ * should be equal to the currently instrumented type.
+ * @param modifiers The modifiers of the field.
+ * @return A new builder that is equal to this builder but with the given field defined for the instrumented type.
+ * Furthermore, it is possible to optionally define a value, annotations or custom attributes for the field.
+ */
+ FieldDefinition.Optional.Valuable<T> defineField(String name, Type type, int modifiers);
+
+ /**
+ * Defines the specified field as a field of the built dynamic type.
+ *
+ * @param name The name of the field.
+ * @param type The type of the field. Can also be {@link net.bytebuddy.dynamic.TargetType} if the field type
+ * should be equal to the currently instrumented type.
+ * @param modifierContributor The modifiers of the field.
+ * @return A new builder that is equal to this builder but with the given field defined for the instrumented type.
+ * Furthermore, it is possible to optionally define a value, annotations or custom attributes for the field.
+ */
+ FieldDefinition.Optional.Valuable<T> defineField(String name, TypeDefinition type, ModifierContributor.ForField... modifierContributor);
+
+ /**
+ * Defines the specified field as a field of the built dynamic type.
+ *
+ * @param name The name of the field.
+ * @param type The type of the field. Can also be {@link net.bytebuddy.dynamic.TargetType} if the field type
+ * should be equal to the currently instrumented type.
+ * @param modifierContributors The modifiers of the field.
+ * @return A new builder that is equal to this builder but with the given field defined for the instrumented type.
+ * Furthermore, it is possible to optionally define a value, annotations or custom attributes for the field.
+ */
+ FieldDefinition.Optional.Valuable<T> defineField(String name, TypeDefinition type, Collection<? extends ModifierContributor.ForField> modifierContributors);
+
+ /**
+ * Defines the specified field as a field of the built dynamic type.
+ *
+ * @param name The name of the field.
+ * @param type The type of the field. Can also be {@link net.bytebuddy.dynamic.TargetType} if the field type
+ * should be equal to the currently instrumented type.
+ * @param modifiers The modifiers of the field.
+ * @return A new builder that is equal to this builder but with the given field defined for the instrumented type.
+ * Furthermore, it is possible to optionally define a value, annotations or custom attributes for the field.
+ */
+ FieldDefinition.Optional.Valuable<T> defineField(String name, TypeDefinition type, int modifiers);
+
+ /**
+ * Defines a field that is similar to the supplied field but without copying any annotations on the field.
+ *
+ * @param field The field to imitate as a field of the instrumented type.
+ * @return A new builder that is equal to this builder but with the given field defined for the instrumented type.
+ * Furthermore, it is possible to optionally define a value, annotations or custom attributes for the field.
+ */
+ FieldDefinition.Optional.Valuable<T> define(Field field);
+
+ /**
+ * Defines a field that is similar to the supplied field but without copying any annotations on the field.
+ *
+ * @param field The field to imitate as a field of the instrumented type.
+ * @return A new builder that is equal to this builder but with the given field defined for the instrumented type.
+ * Furthermore, it is possible to optionally define a value, annotations or custom attributes for the field.
+ */
+ FieldDefinition.Optional.Valuable<T> define(FieldDescription field);
+
+ /**
+ * Defines a private, static, final field for a serial version UID of the given value.
+ *
+ * @param serialVersionUid The serial version UID to define as a value.
+ * @return A new builder that is equal to this builder but with the given type variable defined for the instrumented type.
+ * Furthermore, it is possible to optionally define a value, annotations or custom attributes for the field.
+ */
+ FieldDefinition.Optional<T> serialVersionUid(long serialVersionUid);
+
+ /**
+ * <p>
+ * Matches a field that is already declared by the instrumented type. This gives opportunity to change that field's
+ * default value, annotations or custom attributes.
+ * </p>
+ * <p>
+ * When a type is redefined or rebased, any annotations that the field declared previously is preserved
+ * <i>as it is</i> if Byte Buddy is configured to retain such annotations by
+ * {@link net.bytebuddy.implementation.attribute.AnnotationRetention#ENABLED}. If any existing annotations should be
+ * altered, annotation retention must be disabled.
+ * </p>
+ * <p>
+ * If a field is already matched by a previously specified field matcher, the new field definition gets precedence
+ * over the previous definition, i.e. the previous field definition is no longer applied.
+ * </p>
+ *
+ * @param matcher The matcher that determines what declared fields are affected by the subsequent specification.
+ * @return A builder that allows for changing a field's definition.
+ */
+ FieldDefinition.Valuable<T> field(ElementMatcher<? super FieldDescription> matcher);
+
+ /**
+ * <p>
+ * Matches a field that is already declared by the instrumented type. This gives opportunity to change that field's
+ * default value, annotations or custom attributes. Using a latent matcher gives opportunity to resolve an
+ * {@link ElementMatcher} based on the instrumented type before applying the matcher.
+ * </p>
+ * <p>
+ * When a type is redefined or rebased, any annotations that the field declared previously is preserved
+ * <i>as it is</i> if Byte Buddy is configured to retain such annotations by
+ * {@link net.bytebuddy.implementation.attribute.AnnotationRetention#ENABLED}. If any existing annotations should be
+ * altered, annotation retention must be disabled.
+ * </p>
+ * <p>
+ * If a field is already matched by a previously specified field matcher, the new field definition gets precedence
+ * over the previous definition, i.e. the previous field definition is no longer applied.
+ * </p>
+ *
+ * @param matcher The matcher that determines what declared fields are affected by the subsequent specification.
+ * @return A builder that allows for changing a field's definition.
+ */
+ FieldDefinition.Valuable<T> field(LatentMatcher<? super FieldDescription> matcher);
+
+ /**
+ * <p>
+ * Specifies to exclude any method that is matched by the supplied matcher from instrumentation. Previously supplied matchers
+ * remain valid after supplying a new matcher, i.e. any method that is matched by a previously supplied matcher is always ignored.
+ * </p>
+ * <p>
+ * When ignoring a type, previously registered matchers are applied before this matcher. If a previous matcher indicates that a type
+ * is to be ignored, this matcher is no longer executed.
+ * </p>
+ *
+ * @param ignoredMethods The matcher for determining what methods to exclude from instrumentation.
+ * @return A new builder that is equal to this builder but that is excluding any method that is matched by the supplied matcher from
+ * instrumentation.
+ */
+ Builder<T> ignoreAlso(ElementMatcher<? super MethodDescription> ignoredMethods);
+
+ /**
+ * <p>
+ * Specifies to exclude any method that is matched by the supplied matcher from instrumentation. Previously supplied matchers
+ * remain valid after supplying a new matcher, i.e. any method that is matched by a previously supplied matcher is always ignored.
+ * Using a latent matcher gives opportunity to resolve an {@link ElementMatcher} based on the instrumented type before applying the
+ * matcher.
+ * </p>
+ * <p>
+ * When ignoring a type, previously registered matchers are applied before this matcher. If a previous matcher indicates that a type
+ * is to be ignored, this matcher is no longer executed.
+ * </p>
+ *
+ * @param ignoredMethods The matcher for determining what methods to exclude from instrumentation.
+ * @return A new builder that is equal to this builder but that is excluding any method that is matched by the supplied matcher from
+ * instrumentation.
+ */
+ Builder<T> ignoreAlso(LatentMatcher<? super MethodDescription> ignoredMethods);
+
+ /**
+ * Defines the specified method to be declared by the instrumented type. Method parameters or parameter types, declared exceptions and
+ * type variables can be defined in subsequent steps.
+ *
+ * @param name The name of the method.
+ * @param returnType The method's return type. Can also be {@link net.bytebuddy.dynamic.TargetType} if the return type
+ * should be equal to the currently instrumented type.
+ * @param modifierContributor The method's modifiers.
+ * @return A builder that allows for further defining the method, either by adding more properties or by defining an implementation.
+ */
+ MethodDefinition.ParameterDefinition.Initial<T> defineMethod(String name, Type returnType, ModifierContributor.ForMethod... modifierContributor);
+
+ /**
+ * Defines the specified method to be declared by the instrumented type. Method parameters or parameter types, declared exceptions and
+ * type variables can be defined in subsequent steps.
+ *
+ * @param name The name of the method.
+ * @param returnType The method's return type. Can also be {@link net.bytebuddy.dynamic.TargetType} if the return type
+ * should be equal to the currently instrumented type.
+ * @param modifierContributors The method's modifiers.
+ * @return A builder that allows for further defining the method, either by adding more properties or by defining an implementation.
+ */
+ MethodDefinition.ParameterDefinition.Initial<T> defineMethod(String name, Type returnType, Collection<? extends ModifierContributor.ForMethod> modifierContributors);
+
+ /**
+ * Defines the specified method to be declared by the instrumented type. Method parameters or parameter types, declared exceptions and
+ * type variables can be defined in subsequent steps.
+ *
+ * @param name The name of the method.
+ * @param returnType The method's return type. Can also be {@link net.bytebuddy.dynamic.TargetType} if the return type
+ * should be equal to the currently instrumented type.
+ * @param modifiers The method's modifiers.
+ * @return A builder that allows for further defining the method, either by adding more properties or by defining an implementation.
+ */
+ MethodDefinition.ParameterDefinition.Initial<T> defineMethod(String name, Type returnType, int modifiers);
+
+ /**
+ * Defines the specified method to be declared by the instrumented type. Method parameters or parameter types, declared exceptions and
+ * type variables can be defined in subsequent steps.
+ *
+ * @param name The name of the method.
+ * @param returnType The method's return type. Can also be {@link net.bytebuddy.dynamic.TargetType} if the return type
+ * should be equal to the currently instrumented type.
+ * @param modifierContributor The method's modifiers.
+ * @return A builder that allows for further defining the method, either by adding more properties or by defining an implementation.
+ */
+ MethodDefinition.ParameterDefinition.Initial<T> defineMethod(String name, TypeDefinition returnType, ModifierContributor.ForMethod... modifierContributor);
+
+ /**
+ * Defines the specified method to be declared by the instrumented type. Method parameters or parameter types, declared exceptions and
+ * type variables can be defined in subsequent steps.
+ *
+ * @param name The name of the method.
+ * @param returnType The method's return type. Can also be {@link net.bytebuddy.dynamic.TargetType} if the return type
+ * should be equal to the currently instrumented type.
+ * @param modifierContributors The method's modifiers.
+ * @return A builder that allows for further defining the method, either by adding more properties or by defining an implementation.
+ */
+ MethodDefinition.ParameterDefinition.Initial<T> defineMethod(String name, TypeDefinition returnType, Collection<? extends ModifierContributor.ForMethod> modifierContributors);
+
+ /**
+ * Defines the specified method to be declared by the instrumented type. Method parameters or parameter types, declared exceptions and
+ * type variables can be defined in subsequent steps.
+ *
+ * @param name The name of the method.
+ * @param returnType The method's return type. Can also be {@link net.bytebuddy.dynamic.TargetType} if the return type
+ * should be equal to the currently instrumented type.
+ * @param modifiers The method's modifiers.
+ * @return A builder that allows for further defining the method, either by adding more properties or by defining an implementation.
+ */
+ MethodDefinition.ParameterDefinition.Initial<T> defineMethod(String name, TypeDefinition returnType, int modifiers);
+
+ /**
+ * Defines the specified constructor to be declared by the instrumented type. Method parameters or parameter types, declared exceptions and
+ * type variables can be defined in subsequent steps.
+ *
+ * @param modifierContributor The constructor's modifiers.
+ * @return A builder that allows for further defining the constructor, either by adding more properties or by defining an implementation.
+ */
+ MethodDefinition.ParameterDefinition.Initial<T> defineConstructor(ModifierContributor.ForMethod... modifierContributor);
+
+ /**
+ * Defines the specified constructor to be declared by the instrumented type. Method parameters or parameter types, declared exceptions and
+ * type variables can be defined in subsequent steps.
+ *
+ * @param modifierContributors The constructor's modifiers.
+ * @return A builder that allows for further defining the constructor, either by adding more properties or by defining an implementation.
+ */
+ MethodDefinition.ParameterDefinition.Initial<T> defineConstructor(Collection<? extends ModifierContributor.ForMethod> modifierContributors);
+
+ /**
+ * Defines the specified constructor to be declared by the instrumented type. Method parameters or parameter types, declared exceptions and
+ * type variables can be defined in subsequent steps.
+ *
+ * @param modifiers The constructor's modifiers.
+ * @return A builder that allows for further defining the constructor, either by adding more properties or by defining an implementation.
+ */
+ MethodDefinition.ParameterDefinition.Initial<T> defineConstructor(int modifiers);
+
+ /**
+ * Defines a method that is similar to the supplied method but without copying any annotations of the method or method parameters.
+ *
+ * @param method The method to imitate as a method of the instrumented type.
+ * @return A builder that allows for defining an implementation for the method.
+ */
+ MethodDefinition.ImplementationDefinition<T> define(Method method);
+
+ /**
+ * Defines a constructor that is similar to the supplied constructor but without copying any annotations of the constructor or
+ * constructor parameters.
+ *
+ * @param constructor The constructor to imitate as a method of the instrumented type.
+ * @return A builder that allows for defining an implementation for the constructor.
+ */
+ MethodDefinition.ImplementationDefinition<T> define(Constructor<?> constructor);
+
+ /**
+ * Defines a method or constructor that is similar to the supplied method description but without copying any annotations of
+ * the method/constructor or method/constructor parameters.
+ *
+ * @param methodDescription The method description to imitate as a method or constructor of the instrumented type.
+ * @return A builder that allows for defining an implementation for the method or constructor.
+ */
+ MethodDefinition.ImplementationDefinition<T> define(MethodDescription methodDescription);
+
+ /**
+ * <p>
+ * Matches a method that is already declared or inherited by the instrumented type. This gives opportunity to change or to
+ * override that method's implementation, default value, annotations or custom attributes. It is also possible to make
+ * a method abstract.
+ * </p>
+ * <p>
+ * When a type is redefined or rebased, any annotations that the method declared previously is preserved
+ * <i>as it is</i> if Byte Buddy is configured to retain such annotations by
+ * {@link net.bytebuddy.implementation.attribute.AnnotationRetention#ENABLED}. If any existing annotations should be
+ * altered, annotation retention must be disabled.
+ * </p>
+ * <p>
+ * If a method is already matched by a previously specified matcher, the new method definition gets precedence
+ * over the previous definition, i.e. the previous method definition is no longer applied.
+ * </p>
+ * <p>
+ * Note that the specified definition does never apply for methods that are explicitly ignored.
+ * </p>
+ *
+ * @param matcher The matcher that determines what methods are affected by the subsequent specification.
+ * @return A builder that allows for changing a method's or constructor's definition.
+ */
+ MethodDefinition.ImplementationDefinition<T> method(ElementMatcher<? super MethodDescription> matcher);
+
+ /**
+ * <p>
+ * Matches a constructor that is already declared by the instrumented type. This gives opportunity to change that constructor's
+ * implementation, default value, annotations or custom attributes.
+ * </p>
+ * <p>
+ * When a type is redefined or rebased, any annotations that the constructor declared previously is preserved
+ * <i>as it is</i> if Byte Buddy is configured to retain such annotations by
+ * {@link net.bytebuddy.implementation.attribute.AnnotationRetention#ENABLED}. If any existing annotations should be
+ * altered, annotation retention must be disabled.
+ * </p>
+ * <p>
+ * If a constructor is already matched by a previously specified matcher, the new constructor definition gets precedence
+ * over the previous definition, i.e. the previous constructor definition is no longer applied.
+ * </p>
+ * <p>
+ * Note that the specified definition does never apply for methods that are explicitly ignored.
+ * </p>
+ *
+ * @param matcher The matcher that determines what constructors are affected by the subsequent specification.
+ * @return A builder that allows for changing a method's or constructor's definition.
+ */
+ MethodDefinition.ImplementationDefinition<T> constructor(ElementMatcher<? super MethodDescription> matcher);
+
+ /**
+ * <p>
+ * Matches a method or constructor that is already declared or inherited by the instrumented type. This gives
+ * opportunity to change or to override that method's or constructor's implementation, default value, annotations
+ * or custom attributes. It is also possible to make a method abstract.
+ * </p>
+ * <p>
+ * When a type is redefined or rebased, any annotations that the method or constructor declared previously is preserved
+ * <i>as it is</i> if Byte Buddy is configured to retain such annotations by
+ * {@link net.bytebuddy.implementation.attribute.AnnotationRetention#ENABLED}. If any existing annotations should be
+ * altered, annotation retention must be disabled.
+ * </p>
+ * <p>
+ * If a method or constructor is already matched by a previously specified matcher, the new definition gets precedence
+ * over the previous definition, i.e. the previous definition is no longer applied.
+ * </p>
+ * <p>
+ * Note that the specified definition does never apply for methods that are explicitly ignored.
+ * </p>
+ * <p>
+ * <b>Important</b>: It is possible to instrument the dynamic type's initializer. Depending on the used {@link TypeResolutionStrategy},
+ * the type initializer might be run <b>before</b> Byte Buddy could apply any {@link LoadedTypeInitializer}s which are
+ * responsible for preparing the instrumented type prior to the initializer's execution. For preparing the type prior to
+ * executing the initializer, an {@link TypeResolutionStrategy.Active} resolver must be chosen.
+ * </p>
+ *
+ * @param matcher The matcher that determines what methods or constructors are affected by the subsequent specification.
+ * @return A builder that allows for changing a method's or constructor's definition.
+ */
+ MethodDefinition.ImplementationDefinition<T> invokable(ElementMatcher<? super MethodDescription> matcher);
+
+ /**
+ * <p>
+ * Matches a method or constructor that is already declared or inherited by the instrumented type. This gives
+ * opportunity to change or to override that method's or constructor's implementation, default value, annotations
+ * or custom attributes. It is also possible to make a method abstract. Using a latent matcher gives opportunity
+ * to resolve an {@link ElementMatcher} based on the instrumented type before applying the matcher.
+ * </p>
+ * <p>
+ * When a type is redefined or rebased, any annotations that the method or constructor declared previously is preserved
+ * <i>as it is</i> if Byte Buddy is configured to retain such annotations by
+ * {@link net.bytebuddy.implementation.attribute.AnnotationRetention#ENABLED}. If any existing annotations should be
+ * altered, annotation retention must be disabled.
+ * </p>
+ * <p>
+ * If a method or constructor is already matched by a previously specified matcher, the new definition gets precedence
+ * over the previous definition, i.e. the previous definition is no longer applied.
+ * </p>
+ * <p>
+ * Note that the specified definition does never apply for methods that are explicitly ignored.
+ * </p>
+ * <p>
+ * <b>Important</b>: It is possible to instrument the dynamic type's initializer. Depending on the used {@link TypeResolutionStrategy},
+ * the type initializer might be run <b>before</b> Byte Buddy could apply any {@link LoadedTypeInitializer}s which are
+ * responsible for preparing the instrumented type prior to the initializer's execution. For preparing the type prior to
+ * executing the initializer, an {@link TypeResolutionStrategy.Active} resolver must be chosen.
+ * </p>
+ *
+ * @param matcher The matcher that determines what declared methods or constructors are affected by the subsequent specification.
+ * @return A builder that allows for changing a method's or constructor's definition.
+ */
+ MethodDefinition.ImplementationDefinition<T> invokable(LatentMatcher<? super MethodDescription> matcher);
+
+ /**
+ * <p>
+ * Creates the dynamic type this builder represents. If the specified dynamic type is not legal, an {@link IllegalStateException} is thrown.
+ * </p>
+ * <p>
+ * Other than {@link DynamicType.Builder#make(TypePool)}, this method supplies a context-dependant type pool to the underlying class writer.
+ * Supplying a type pool only makes sense if custom byte code is created by adding a custom {@link AsmVisitorWrapper} where ASM might be
+ * required to compute stack map frames by processing information over any mentioned type's class hierarchy.
+ * </p>
+ * <p>
+ * The dynamic type is initialized using a {@link TypeResolutionStrategy.Passive} strategy. Using this strategy, no
+ * {@link LoadedTypeInitializer} is run during the execution of the type's initializer such that no {@link Implementation} used for
+ * executing the initializer must rely on such an initializer.
+ * </p>
+ *
+ * @return An unloaded dynamic type representing the type specified by this builder.
+ */
+ DynamicType.Unloaded<T> make();
+
+ /**
+ * <p>
+ * Creates the dynamic type this builder represents. If the specified dynamic type is not legal, an {@link IllegalStateException} is thrown.
+ * </p>
+ * <p>
+ * The dynamic type is initialized using a {@link TypeResolutionStrategy.Passive} strategy. Using this strategy, no
+ * {@link LoadedTypeInitializer} is run during the execution of the type's initializer such that no {@link Implementation} used for
+ * executing the initializer must rely on such an initializer.
+ * </p>
+ *
+ * @param typeResolutionStrategy The type resolution strategy to use for the created type's initialization.
+ * @return An unloaded dynamic type representing the type specified by this builder.
+ */
+ DynamicType.Unloaded<T> make(TypeResolutionStrategy typeResolutionStrategy);
+
+ /**
+ * <p>
+ * Creates the dynamic type this builder represents. If the specified dynamic type is not legal, an {@link IllegalStateException} is thrown.
+ * </p>
+ * <p>
+ * The dynamic type is initialized using a {@link TypeResolutionStrategy.Passive} strategy. Using this strategy, no
+ * {@link LoadedTypeInitializer} is run during the execution of the type's initializer such that no {@link Implementation} used for
+ * executing the initializer must rely on such an initializer.
+ * </p>
+ *
+ * @param typePool A type pool that is used for computing stack map frames by the underlying class writer, if required.
+ * @return An unloaded dynamic type representing the type specified by this builder.
+ */
+ DynamicType.Unloaded<T> make(TypePool typePool);
+
+ /**
+ * Creates the dynamic type this builder represents. If the specified dynamic type is not legal, an {@link IllegalStateException} is thrown.
+ *
+ * @param typeResolutionStrategy The type resolution strategy to use for the created type's initialization.
+ * @param typePool A type pool that is used for computing stack map frames by the underlying class writer, if required.
+ * @return An unloaded dynamic type representing the type specified by this builder.
+ */
+ DynamicType.Unloaded<T> make(TypeResolutionStrategy typeResolutionStrategy, TypePool typePool);
+
+ /**
+ * A builder for a type variable definition.
+ *
+ * @param <S> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ interface TypeVariableDefinition<S> extends Builder<S> {
+
+ /**
+ * Annotates the previously defined type variable with the supplied annotations.
+ *
+ * @param annotation The annotations to declare on the previously defined type variable.
+ * @return A new builder that is equal to this builder but with the given annotations declared
+ * on the previously defined type variable.
+ */
+ TypeVariableDefinition<S> annotateTypeVariable(Annotation... annotation);
+
+ /**
+ * Annotates the previously defined type variable with the supplied annotations.
+ *
+ * @param annotations The annotations to declare on the previously defined type variable.
+ * @return A new builder that is equal to this builder but with the given annotations declared
+ * on the previously defined type variable.
+ */
+ TypeVariableDefinition<S> annotateTypeVariable(List<? extends Annotation> annotations);
+
+ /**
+ * Annotates the previously defined type variable with the supplied annotations.
+ *
+ * @param annotation The annotations to declare on the previously defined type variable.
+ * @return A new builder that is equal to this builder but with the given annotations declared
+ * on the previously defined type variable.
+ */
+ TypeVariableDefinition<S> annotateTypeVariable(AnnotationDescription... annotation);
+
+ /**
+ * Annotates the previously defined type variable with the supplied annotations.
+ *
+ * @param annotations The annotations to declare on the previously defined type variable.
+ * @return A new builder that is equal to this builder but with the given annotations declared
+ * on the previously defined type variable.
+ */
+ TypeVariableDefinition<S> annotateTypeVariable(Collection<? extends AnnotationDescription> annotations);
+
+ /**
+ * An abstract base implementation of a type variable definition.
+ *
+ * @param <U> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ abstract class AbstractBase<U> extends Builder.AbstractBase.Delegator<U> implements TypeVariableDefinition<U> {
+
+ @Override
+ public TypeVariableDefinition<U> annotateTypeVariable(Annotation... annotation) {
+ return annotateTypeVariable(Arrays.asList(annotation));
+ }
+
+ @Override
+ public TypeVariableDefinition<U> annotateTypeVariable(List<? extends Annotation> annotations) {
+ return annotateTypeVariable(new AnnotationList.ForLoadedAnnotations(annotations));
+ }
+
+ @Override
+ public TypeVariableDefinition<U> annotateTypeVariable(AnnotationDescription... annotation) {
+ return annotateTypeVariable(Arrays.asList(annotation));
+ }
+ }
+ }
+
+ /**
+ * A builder for a field definition.
+ *
+ * @param <S> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ interface FieldDefinition<S> {
+
+ /**
+ * Annotates the previously defined or matched field with the supplied annotations.
+ *
+ * @param annotation The annotations to declare on the previously defined or matched field.
+ * @return A new builder that is equal to this builder but with the given annotations declared
+ * on the previously defined or matched field.
+ */
+ FieldDefinition.Optional<S> annotateField(Annotation... annotation);
+
+ /**
+ * Annotates the previously defined or matched field with the supplied annotations.
+ *
+ * @param annotations The annotations to declare on the previously defined or matched field.
+ * @return A new builder that is equal to this builder but with the given annotations declared
+ * on the previously defined or matched field.
+ */
+ FieldDefinition.Optional<S> annotateField(List<? extends Annotation> annotations);
+
+ /**
+ * Annotates the previously defined or matched field with the supplied annotations.
+ *
+ * @param annotation The annotations to declare on the previously defined or matched field.
+ * @return A new builder that is equal to this builder but with the given annotations declared
+ * on the previously defined or matched field.
+ */
+ FieldDefinition.Optional<S> annotateField(AnnotationDescription... annotation);
+
+ /**
+ * Annotates the previously defined or matched field with the supplied annotations.
+ *
+ * @param annotations The annotations to declare on the previously defined or matched field.
+ * @return A new builder that is equal to this builder but with the given annotations declared
+ * on the previously defined or matched field.
+ */
+ FieldDefinition.Optional<S> annotateField(Collection<? extends AnnotationDescription> annotations);
+
+ /**
+ * Applies the supplied attribute appender factory onto the previously defined or matched field.
+ *
+ * @param fieldAttributeAppenderFactory The field attribute appender factory that should be applied on the
+ * previously defined or matched field.
+ * @return A new builder that is equal to this builder but with the supplied field attribute appender factory
+ * applied to the previously defined or matched field.
+ */
+ FieldDefinition.Optional<S> attribute(FieldAttributeAppender.Factory fieldAttributeAppenderFactory);
+
+ /**
+ * Applies the supplied transformer onto the previously defined or matched field. The transformed
+ * field is written <i>as it is</i> and it not subject to any validations.
+ *
+ * @param transformer The transformer to apply to the previously defined or matched field.
+ * @return A new builder that is equal to this builder but with the supplied field transformer
+ * applied to the previously defined or matched field.
+ */
+ FieldDefinition.Optional<S> transform(Transformer<FieldDescription> transformer);
+
+ /**
+ * A builder for a field definition that allows for defining a value.
+ *
+ * @param <U> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ interface Valuable<U> extends FieldDefinition<U> {
+
+ /**
+ * <p>
+ * Defines the supplied {@code boolean} value as a default value of the previously defined or matched field. The value can only
+ * be set for numeric fields of type {@code boolean}, {@code byte}, {@code short}, {@code char} or {@code int}. For non-boolean
+ * fields, the field's value is set to {@code 0} for {@code false} or {@code 1} for {@code true}.
+ * </p>
+ * <p>
+ * <b>Important</b>: A default value in a Java class file defines a field's value prior to the class's initialization. This value
+ * is only visible to code if the field is declared {@code static}. A default value can also be set for non-static fields where
+ * the value is not visible to code. The Java compiler only defines such values for {@code final} fields.
+ * </p>
+ *
+ * @param value The value to define as a default value of the defined field.
+ * @return A new builder that is equal to this builder but with the given default value declared for the
+ * previously defined or matched field.
+ */
+ FieldDefinition.Optional<U> value(boolean value);
+
+ /**
+ * <p>
+ * Defines the supplied {@code int} value as a default value of the previously defined or matched field. The value can only
+ * be set for numeric fields of type {@code boolean}, {@code byte}, {@code short}, {@code char} or {@code int} where the
+ * value must be within the numeric type's range. The {@code boolean} type is regarded as a numeric type with the possible
+ * values of {@code 0} and {@code 1} representing {@code false} and {@code true}.
+ * </p>
+ * <p>
+ * <b>Important</b>: A default value in a Java class file defines a field's value prior to the class's initialization. This value
+ * is only visible to code if the field is declared {@code static}. A default value can also be set for non-static fields where
+ * the value is not visible to code. The Java compiler only defines such values for {@code final} fields.
+ * </p>
+ *
+ * @param value The value to define as a default value of the defined field.
+ * @return A new builder that is equal to this builder but with the given default value declared for the
+ * previously defined or matched field.
+ */
+ FieldDefinition.Optional<U> value(int value);
+
+ /**
+ * <p>
+ * Defines the supplied {@code long} value as a default value of the previously defined or matched field.
+ * </p>
+ * <p>
+ * <b>Important</b>: A default value in a Java class file defines a field's value prior to the class's initialization. This value
+ * is only visible to code if the field is declared {@code static}. A default value can also be set for non-static fields where
+ * the value is not visible to code. The Java compiler only defines such values for {@code final} fields.
+ * </p>
+ *
+ * @param value The value to define as a default value of the defined field.
+ * @return A new builder that is equal to this builder but with the given default value declared for the
+ * previously defined or matched field.
+ */
+ FieldDefinition.Optional<U> value(long value);
+
+ /**
+ * <p>
+ * Defines the supplied {@code float} value as a default value of the previously defined or matched field.
+ * </p>
+ * <p>
+ * <b>Important</b>: A default value in a Java class file defines a field's value prior to the class's initialization. This value
+ * is only visible to code if the field is declared {@code static}. A default value can also be set for non-static fields where
+ * the value is not visible to code. The Java compiler only defines such values for {@code final} fields.
+ * </p>
+ *
+ * @param value The value to define as a default value of the defined field.
+ * @return A new builder that is equal to this builder but with the given default value declared for the
+ * previously defined or matched field.
+ */
+ FieldDefinition.Optional<U> value(float value);
+
+ /**
+ * <p>
+ * Defines the supplied {@code double} value as a default value of the previously defined or matched field.
+ * </p>
+ * <p>
+ * <b>Important</b>: A default value in a Java class file defines a field's value prior to the class's initialization. This value
+ * is only visible to code if the field is declared {@code static}. A default value can also be set for non-static fields where
+ * the value is not visible to code. The Java compiler only defines such values for {@code final} fields.
+ * </p>
+ *
+ * @param value The value to define as a default value of the defined field.
+ * @return A new builder that is equal to this builder but with the given default value declared for the
+ * previously defined or matched field.
+ */
+ FieldDefinition.Optional<U> value(double value);
+
+ /**
+ * <p>
+ * Defines the supplied {@link String} value as a default value of the previously defined or matched field.
+ * </p>
+ * <p>
+ * <b>Important</b>: A default value in a Java class file defines a field's value prior to the class's initialization. This value
+ * is only visible to code if the field is declared {@code static}. A default value can also be set for non-static fields where
+ * the value is not visible to code. The Java compiler only defines such values for {@code final} fields.
+ * </p>
+ *
+ * @param value The value to define as a default value of the defined field.
+ * @return A new builder that is equal to this builder but with the given default value declared for the
+ * previously defined or matched field.
+ */
+ FieldDefinition.Optional<U> value(String value);
+ }
+
+ /**
+ * A builder for an optional field definition.
+ *
+ * @param <U> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ interface Optional<U> extends FieldDefinition<U>, Builder<U> {
+
+ /**
+ * A builder for an optional field definition that allows for defining a value.
+ *
+ * @param <V> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ interface Valuable<V> extends FieldDefinition.Valuable<V>, Optional<V> {
+
+ /**
+ * An abstract base implementation of an optional field definition that allows for defining a value.
+ *
+ * @param <U> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ abstract class AbstractBase<U> extends Optional.AbstractBase<U> implements Optional.Valuable<U> {
+
+ @Override
+ public FieldDefinition.Optional<U> value(boolean value) {
+ return defaultValue(value ? 1 : 0);
+ }
+
+ @Override
+ public FieldDefinition.Optional<U> value(int value) {
+ return defaultValue(value);
+ }
+
+ @Override
+ public FieldDefinition.Optional<U> value(long value) {
+ return defaultValue(value);
+ }
+
+ @Override
+ public FieldDefinition.Optional<U> value(float value) {
+ return defaultValue(value);
+ }
+
+ @Override
+ public FieldDefinition.Optional<U> value(double value) {
+ return defaultValue(value);
+ }
+
+ @Override
+ public FieldDefinition.Optional<U> value(String value) {
+ if (value == null) {
+ throw new IllegalArgumentException("Cannot set null as a default value");
+ }
+ return defaultValue(value);
+ }
+
+ /**
+ * Defines the supplied value as a default value of the previously defined or matched field.
+ *
+ * @param defaultValue The value to define as a default value of the defined field.
+ * @return A new builder that is equal to this builder but with the given default value declared for the
+ * previously defined or matched field.
+ */
+ protected abstract FieldDefinition.Optional<U> defaultValue(Object defaultValue);
+
+ /**
+ * An adapter for an optional field definition that allows for defining a value.
+ *
+ * @param <V> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ @EqualsAndHashCode(callSuper = false)
+ protected abstract static class Adapter<V> extends Optional.Valuable.AbstractBase<V> {
+
+ /**
+ * The field attribute appender factory to apply.
+ */
+ protected final FieldAttributeAppender.Factory fieldAttributeAppenderFactory;
+
+ /**
+ * The field transformer to apply.
+ */
+ protected final Transformer<FieldDescription> transformer;
+
+ /**
+ * The field's default value or {@code null} if no value is to be defined.
+ */
+ protected final Object defaultValue;
+
+ /**
+ * Creates a new field adapter.
+ *
+ * @param fieldAttributeAppenderFactory The field attribute appender factory to apply.
+ * @param transformer The field transformer to apply.
+ * @param defaultValue The field's default value or {@code null} if no value is to be defined.
+ */
+ protected Adapter(FieldAttributeAppender.Factory fieldAttributeAppenderFactory,
+ Transformer<FieldDescription> transformer,
+ Object defaultValue) {
+ this.fieldAttributeAppenderFactory = fieldAttributeAppenderFactory;
+ this.transformer = transformer;
+ this.defaultValue = defaultValue;
+ }
+
+ @Override
+ public FieldDefinition.Optional<V> attribute(FieldAttributeAppender.Factory fieldAttributeAppenderFactory) {
+ return materialize(new FieldAttributeAppender.Factory.Compound(this.fieldAttributeAppenderFactory, fieldAttributeAppenderFactory), transformer, defaultValue);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked") // In absence of @SafeVarargs for Java 6
+ public FieldDefinition.Optional<V> transform(Transformer<FieldDescription> transformer) {
+ return materialize(fieldAttributeAppenderFactory, new Transformer.Compound<FieldDescription>(this.transformer, transformer), defaultValue);
+ }
+
+ @Override
+ protected FieldDefinition.Optional<V> defaultValue(Object defaultValue) {
+ return materialize(fieldAttributeAppenderFactory, transformer, defaultValue);
+ }
+
+ /**
+ * Creates a new optional field definition for which all of the supplied values are represented.
+ *
+ * @param fieldAttributeAppenderFactory The field attribute appender factory to apply.
+ * @param transformer The field transformer to apply.
+ * @param defaultValue The field's default value or {@code null} if no value is to be defined.
+ * @return A new field definition that represents the supplied values.
+ */
+ protected abstract FieldDefinition.Optional<V> materialize(FieldAttributeAppender.Factory fieldAttributeAppenderFactory,
+ Transformer<FieldDescription> transformer,
+ Object defaultValue);
+ }
+ }
+ }
+
+ /**
+ * An abstract base implementation for an optional field definition.
+ *
+ * @param <U> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ abstract class AbstractBase<U> extends Builder.AbstractBase.Delegator<U> implements FieldDefinition.Optional<U> {
+
+ @Override
+ public FieldDefinition.Optional<U> annotateField(Annotation... annotation) {
+ return annotateField(Arrays.asList(annotation));
+ }
+
+ @Override
+ public FieldDefinition.Optional<U> annotateField(List<? extends Annotation> annotations) {
+ return annotateField(new AnnotationList.ForLoadedAnnotations(annotations));
+ }
+
+ @Override
+ public FieldDefinition.Optional<U> annotateField(AnnotationDescription... annotation) {
+ return annotateField(Arrays.asList(annotation));
+ }
+ }
+ }
+ }
+
+ /**
+ * A builder for a method definition.
+ *
+ * @param <S> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ interface MethodDefinition<S> extends Builder<S> {
+
+ /**
+ * Annotates the previously defined or matched method with the supplied annotations.
+ *
+ * @param annotation The annotations to declare on the previously defined or matched method.
+ * @return A new builder that is equal to this builder but with the given annotations declared
+ * on the previously defined or matched method.
+ */
+ MethodDefinition<S> annotateMethod(Annotation... annotation);
+
+ /**
+ * Annotates the previously defined or matched method with the supplied annotations.
+ *
+ * @param annotations The annotations to declare on the previously defined or matched method.
+ * @return A new builder that is equal to this builder but with the given annotations declared
+ * on the previously defined or matched method.
+ */
+ MethodDefinition<S> annotateMethod(List<? extends Annotation> annotations);
+
+ /**
+ * Annotates the previously defined or matched method with the supplied annotations.
+ *
+ * @param annotation The annotations to declare on the previously defined or matched method.
+ * @return A new builder that is equal to this builder but with the given annotations declared
+ * on the previously defined or matched method.
+ */
+ MethodDefinition<S> annotateMethod(AnnotationDescription... annotation);
+
+ /**
+ * Annotates the previously defined or matched method with the supplied annotations.
+ *
+ * @param annotations The annotations to declare on the previously defined or matched method.
+ * @return A new builder that is equal to this builder but with the given annotations declared
+ * on the previously defined or matched method.
+ */
+ MethodDefinition<S> annotateMethod(Collection<? extends AnnotationDescription> annotations);
+
+ /**
+ * Annotates the parameter of the given index of the previously defined or matched method with the supplied annotations.
+ *
+ * @param index The parameter's index.
+ * @param annotation The annotations to declare on the previously defined or matched method.
+ * @return A new builder that is equal to this builder but with the given annotations declared
+ * on the previously defined or matched method's parameter of the given index.
+ */
+ MethodDefinition<S> annotateParameter(int index, Annotation... annotation);
+
+ /**
+ * Annotates the parameter of the given index of the previously defined or matched method with the supplied annotations.
+ *
+ * @param index The parameter's index.
+ * @param annotations The annotations to declare on the previously defined or matched method.
+ * @return A new builder that is equal to this builder but with the given annotations declared
+ * on the previously defined or matched method's parameter of the given index.
+ */
+ MethodDefinition<S> annotateParameter(int index, List<? extends Annotation> annotations);
+
+ /**
+ * Annotates the parameter of the given index of the previously defined or matched method with the supplied annotations.
+ *
+ * @param index The parameter's index.
+ * @param annotation The annotations to declare on the previously defined or matched method.
+ * @return A new builder that is equal to this builder but with the given annotations declared
+ * on the previously defined or matched method's parameter of the given index.
+ */
+ MethodDefinition<S> annotateParameter(int index, AnnotationDescription... annotation);
+
+ /**
+ * Annotates the parameter of the given index of the previously defined or matched method with the supplied annotations.
+ *
+ * @param index The parameter's index.
+ * @param annotations The annotations to declare on the previously defined or matched method.
+ * @return A new builder that is equal to this builder but with the given annotations declared
+ * on the previously defined or matched method's parameter of the given index.
+ */
+ MethodDefinition<S> annotateParameter(int index, Collection<? extends AnnotationDescription> annotations);
+
+ /**
+ * Applies the supplied method attribute appender factory onto the previously defined or matched method.
+ *
+ * @param methodAttributeAppenderFactory The method attribute appender factory that should be applied on the
+ * previously defined or matched method.
+ * @return A new builder that is equal to this builder but with the supplied method attribute appender factory
+ * applied to the previously defined or matched method.
+ */
+ MethodDefinition<S> attribute(MethodAttributeAppender.Factory methodAttributeAppenderFactory);
+
+ /**
+ * Applies the supplied transformer onto the previously defined or matched method. The transformed
+ * method is written <i>as it is</i> and it not subject to any validations.
+ *
+ * @param transformer The transformer to apply to the previously defined or matched method.
+ * @return A new builder that is equal to this builder but with the supplied transformer
+ * applied to the previously defined or matched method.
+ */
+ MethodDefinition<S> transform(Transformer<MethodDescription> transformer);
+
+ /**
+ * A builder for a method definition with a receiver type.
+ *
+ * @param <U> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ interface ReceiverTypeDefinition<U> extends MethodDefinition<U> {
+
+ /**
+ * Defines the supplied (annotated) receiver type for the previously defined or matched method.
+ *
+ * @param receiverType The receiver type to define on the previously defined or matched method.
+ * @return A new builder that is equal to this builder but with the given type defined as the
+ * receiver on the previously defined or matched method.
+ */
+ MethodDefinition<U> receiverType(AnnotatedElement receiverType);
+
+ /**
+ * Defines the supplied (annotated) receiver type for the previously defined or matched method.
+ *
+ * @param receiverType The receiver type to define on the previously defined or matched method.
+ * @return A new builder that is equal to this builder but with the given type defined as the
+ * receiver on the previously defined or matched method.
+ */
+ MethodDefinition<U> receiverType(TypeDescription.Generic receiverType);
+
+ /**
+ * An abstract base implementation of a method definition that can accept a receiver type.
+ *
+ * @param <V> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ abstract class AbstractBase<V> extends MethodDefinition.AbstractBase<V> implements ReceiverTypeDefinition<V> {
+
+ @Override
+ public MethodDefinition<V> receiverType(AnnotatedElement receiverType) {
+ return receiverType(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolve(receiverType));
+ }
+ }
+ }
+
+ /**
+ * A builder for defining an implementation of a method.
+ *
+ * @param <U> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ interface ImplementationDefinition<U> {
+
+ /**
+ * Implements the previously defined or matched method by the supplied implementation. A method interception
+ * is typically implemented in one of the following ways:
+ * <ol>
+ * <li>If a method is declared by the instrumented type and the type builder creates a subclass or redefinition,
+ * any preexisting method is replaced by the given implementation. Any previously defined implementation is lost.</li>
+ * <li>If a method is declared by the instrumented type and the type builder creates a rebased version of the
+ * instrumented type, the original method is preserved within a private, synthetic method within the instrumented
+ * type. The original method therefore remains invokeable and is treated as the direct super method of the new
+ * method. When rebasing a type, it therefore becomes possible to invoke a non-virtual method's super method
+ * when a preexisting method body is replaced.</li>
+ * <li>If a virtual method is inherited from a super type, it is overridden. The overridden method is available
+ * for super method invocation.</li>
+ * </ol>
+ *
+ * @param implementation The implementation for implementing the previously defined or matched method.
+ * @return A new builder where the previously defined or matched method is implemented by the
+ * supplied implementation.
+ */
+ MethodDefinition.ReceiverTypeDefinition<U> intercept(Implementation implementation);
+
+ /**
+ * Defines the previously defined or matched method to be {@code abstract}.
+ *
+ * @return A new builder where the previously defined or matched method is implemented to be abstract.
+ */
+ MethodDefinition.ReceiverTypeDefinition<U> withoutCode();
+
+ /**
+ * Defines the previously defined or matched method to return the supplied value as an annotation default value. The
+ * value must be supplied in its unloaded state, i.e. enumerations as {@link net.bytebuddy.description.enumeration.EnumerationDescription},
+ * types as {@link TypeDescription} and annotations as {@link AnnotationDescription}. For supplying loaded types, use
+ * {@link ImplementationDefinition#defaultValue(Object, Class)} must be used.
+ *
+ * @param annotationValue The value to be defined as a default value.
+ * @return A builder where the previously defined or matched method is implemented to return an annotation default value.
+ */
+ MethodDefinition.ReceiverTypeDefinition<U> defaultValue(AnnotationValue<?, ?> annotationValue);
+
+ /**
+ * Defines the previously defined or matched method to return the supplied value as an annotation default value. The
+ * value must be supplied in its loaded state paired with the property type of the value.
+ *
+ * @param value The value to be defined as a default value.
+ * @param type The type of the annotation property.
+ * @param <W> The type of the annotation property.
+ * @return A builder where the previously defined or matched method is implemented to return an annotation default value.
+ */
+ <W> MethodDefinition.ReceiverTypeDefinition<U> defaultValue(W value, Class<? extends W> type);
+
+ /**
+ * A builder for optionally defining an implementation of a method.
+ *
+ * @param <V> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ interface Optional<V> extends ImplementationDefinition<V>, Builder<V> {
+ /* union type */
+ }
+
+ /**
+ * An abstract base implementation for a builder optionally defining an implementation of a method.
+ *
+ * @param <V> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ abstract class AbstractBase<V> implements ImplementationDefinition<V> {
+
+ @Override
+ public <W> MethodDefinition.ReceiverTypeDefinition<V> defaultValue(W value, Class<? extends W> type) {
+ return defaultValue(AnnotationDescription.ForLoadedAnnotation.asValue(value, type));
+ }
+ }
+ }
+
+ /**
+ * A builder for defining an implementation of a method and optionally defining a type variable.
+ *
+ * @param <U> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ interface TypeVariableDefinition<U> extends ImplementationDefinition<U> {
+
+ /**
+ * Defines a method variable to be declared by the currently defined method. The defined method variable does not define any bounds.
+ *
+ * @param symbol The symbol of the type variable.
+ * @return A new builder that is equal to the current builder but where the currently defined method declares the specified type variable.
+ */
+ Annotatable<U> typeVariable(String symbol);
+
+ /**
+ * Defines a method variable to be declared by the currently defined method.
+ *
+ * @param symbol The symbol of the type variable.
+ * @param bound The bounds of the type variables. Can also be {@link net.bytebuddy.dynamic.TargetType} for any type
+ * if a bound type should be equal to the currently instrumented type.
+ * @return A new builder that is equal to the current builder but where the currently defined method declares the specified type variable.
+ */
+ Annotatable<U> typeVariable(String symbol, Type... bound);
+
+ /**
+ * Defines a method variable to be declared by the currently defined method.
+ *
+ * @param symbol The symbol of the type variable.
+ * @param bounds The bounds of the type variables. Can also be {@link net.bytebuddy.dynamic.TargetType} for any type
+ * if a bound type should be equal to the currently instrumented type.
+ * @return A new builder that is equal to the current builder but where the currently defined method declares the specified type variable.
+ */
+ Annotatable<U> typeVariable(String symbol, List<? extends Type> bounds);
+
+ /**
+ * Defines a method variable to be declared by the currently defined method.
+ *
+ * @param symbol The symbol of the type variable.
+ * @param bound The bounds of the type variables. Can also be {@link net.bytebuddy.dynamic.TargetType} for any type
+ * if a bound type should be equal to the currently instrumented type.
+ * @return A new builder that is equal to the current builder but where the currently defined method declares the specified type variable.
+ */
+ Annotatable<U> typeVariable(String symbol, TypeDefinition... bound);
+
+ /**
+ * Defines a method variable to be declared by the currently defined method.
+ *
+ * @param symbol The symbol of the type variable.
+ * @param bounds The bounds of the type variables. Can also be {@link net.bytebuddy.dynamic.TargetType} for any type
+ * if a bound type should be equal to the currently instrumented type.
+ * @return A new builder that is equal to the current builder but where the currently defined method declares the specified type variable.
+ */
+ Annotatable<U> typeVariable(String symbol, Collection<? extends TypeDefinition> bounds);
+
+ /**
+ * A builder for optionally defining an annotation for a type variable.
+ *
+ * @param <V> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ interface Annotatable<V> extends TypeVariableDefinition<V> {
+
+ /**
+ * Annotates the previously defined type variable with the supplied annotations.
+ *
+ * @param annotation The annotations to declare on the previously defined type variable.
+ * @return A new builder that is equal to this builder but with the given annotations declared
+ * on the previously defined type variable.
+ */
+ Annotatable<V> annotateTypeVariable(Annotation... annotation);
+
+ /**
+ * Annotates the previously defined type variable with the supplied annotations.
+ *
+ * @param annotations The annotations to declare on the previously defined type variable.
+ * @return A new builder that is equal to this builder but with the given annotations declared
+ * on the previously defined type variable.
+ */
+ Annotatable<V> annotateTypeVariable(List<? extends Annotation> annotations);
+
+ /**
+ * Annotates the previously defined type variable with the supplied annotations.
+ *
+ * @param annotation The annotations to declare on the previously defined type variable.
+ * @return A new builder that is equal to this builder but with the given annotations declared
+ * on the previously defined type variable.
+ */
+ Annotatable<V> annotateTypeVariable(AnnotationDescription... annotation);
+
+ /**
+ * Annotates the previously defined type variable with the supplied annotations.
+ *
+ * @param annotations The annotations to declare on the previously defined type variable.
+ * @return A new builder that is equal to this builder but with the given annotations declared
+ * on the previously defined type variable.
+ */
+ Annotatable<V> annotateTypeVariable(Collection<? extends AnnotationDescription> annotations);
+
+ /**
+ * An abstract base implementation for defining an annotation on a parameter.
+ *
+ * @param <W> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ abstract class AbstractBase<W> extends TypeVariableDefinition.AbstractBase<W> implements Annotatable<W> {
+
+ @Override
+ public TypeVariableDefinition.Annotatable<W> annotateTypeVariable(Annotation... annotation) {
+ return annotateTypeVariable(Arrays.asList(annotation));
+ }
+
+ @Override
+ public TypeVariableDefinition.Annotatable<W> annotateTypeVariable(List<? extends Annotation> annotations) {
+ return annotateTypeVariable(new AnnotationList.ForLoadedAnnotations(annotations));
+ }
+
+ @Override
+ public TypeVariableDefinition.Annotatable<W> annotateTypeVariable(AnnotationDescription... annotation) {
+ return annotateTypeVariable(Arrays.asList(annotation));
+ }
+
+ /**
+ * An adapter implementation for an annotatable type variable definition.
+ *
+ * @param <X> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ protected abstract static class Adapter<X> extends TypeVariableDefinition.Annotatable.AbstractBase<X> {
+
+ @Override
+ public TypeVariableDefinition.Annotatable<X> typeVariable(String symbol, Collection<? extends TypeDefinition> bounds) {
+ return materialize().typeVariable(symbol, bounds);
+ }
+
+ @Override
+ public MethodDefinition.ReceiverTypeDefinition<X> intercept(Implementation implementation) {
+ return materialize().intercept(implementation);
+ }
+
+ @Override
+ public MethodDefinition.ReceiverTypeDefinition<X> withoutCode() {
+ return materialize().withoutCode();
+ }
+
+ @Override
+ public MethodDefinition.ReceiverTypeDefinition<X> defaultValue(AnnotationValue<?, ?> annotationValue) {
+ return materialize().defaultValue(annotationValue);
+ }
+
+ @Override
+ public <V> MethodDefinition.ReceiverTypeDefinition<X> defaultValue(V value, Class<? extends V> type) {
+ return materialize().defaultValue(value, type);
+ }
+
+ /**
+ * Materializes this instance as a parameter definition with the currently defined properties.
+ *
+ * @return A parameter definition with the currently defined properties.
+ */
+ protected abstract MethodDefinition.ParameterDefinition<X> materialize();
+ }
+
+ }
+ }
+
+ /**
+ * An abstract base implementation for defining an implementation of a method and optionally definign a type variable.
+ *
+ * @param <V> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ abstract class AbstractBase<V> extends ImplementationDefinition.AbstractBase<V> implements TypeVariableDefinition<V> {
+
+ @Override
+ public Annotatable<V> typeVariable(String symbol) {
+ return typeVariable(symbol, Collections.singletonList(Object.class));
+ }
+
+ @Override
+ public Annotatable<V> typeVariable(String symbol, Type... bound) {
+ return typeVariable(symbol, Arrays.asList(bound));
+ }
+
+ @Override
+ public Annotatable<V> typeVariable(String symbol, List<? extends Type> bounds) {
+ return typeVariable(symbol, new TypeList.Generic.ForLoadedTypes(bounds));
+ }
+
+ @Override
+ public Annotatable<V> typeVariable(String symbol, TypeDefinition... bound) {
+ return typeVariable(symbol, Arrays.asList(bound));
+ }
+ }
+ }
+
+ /**
+ * A builder for defining an implementation of a method and optionally defining a type variable or thrown exception.
+ *
+ * @param <U> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ interface ExceptionDefinition<U> extends TypeVariableDefinition<U> {
+
+ /**
+ * Defines a method variable to be declared by the currently defined method.
+ *
+ * @param type The type of the exception being declared by the currently defined method.
+ * @return A new builder that is equal to the current builder but where the currently defined method declares the specified exception type.
+ */
+ ExceptionDefinition<U> throwing(Type... type);
+
+ /**
+ * Defines a method variable to be declared by the currently defined method.
+ *
+ * @param types The type of the exception being declared by the currently defined method.
+ * @return A new builder that is equal to the current builder but where the currently defined method declares the specified exception type.
+ */
+ ExceptionDefinition<U> throwing(List<? extends Type> types);
+
+ /**
+ * Defines a method variable to be declared by the currently defined method.
+ *
+ * @param type The type of the exception being declared by the currently defined method.
+ * @return A new builder that is equal to the current builder but where the currently defined method declares the specified exception type.
+ */
+ ExceptionDefinition<U> throwing(TypeDefinition... type);
+
+ /**
+ * Defines a method variable to be declared by the currently defined method.
+ *
+ * @param types The type of the exception being declared by the currently defined method.
+ * @return A new builder that is equal to the current builder but where the currently defined method declares the specified exception type.
+ */
+ ExceptionDefinition<U> throwing(Collection<? extends TypeDefinition> types);
+
+ /**
+ * An abstract base implementation for defining an implementation of a method and optionally definign a type variable or thrown exception.
+ *
+ * @param <V> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ abstract class AbstractBase<V> extends TypeVariableDefinition.AbstractBase<V> implements ExceptionDefinition<V> {
+
+ @Override
+ public ExceptionDefinition<V> throwing(Type... type) {
+ return throwing(Arrays.asList(type));
+ }
+
+ @Override
+ public ExceptionDefinition<V> throwing(List<? extends Type> types) {
+ return throwing(new TypeList.Generic.ForLoadedTypes(types));
+ }
+
+ @Override
+ public ExceptionDefinition<V> throwing(TypeDefinition... type) {
+ return throwing(Arrays.asList(type));
+ }
+ }
+ }
+
+ /**
+ * A builder for defining an implementation of a method and optionally defining a type variable, thrown exception or method parameter.
+ *
+ * @param <U> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ interface ParameterDefinition<U> extends ExceptionDefinition<U> {
+
+ /**
+ * Defines the specified parameter for the currently defined method as the last parameter of the currently defined method.
+ *
+ * @param type The parameter's type. Can also be {@link net.bytebuddy.dynamic.TargetType} if the parameter type
+ * should be equal to the currently instrumented type.
+ * @param name The parameter's name.
+ * @param modifierContributor The parameter's modifiers.
+ * @return A new builder that is equal to the current builder but where the currently defined method appends the specified parameter.
+ */
+ Annotatable<U> withParameter(Type type, String name, ModifierContributor.ForParameter... modifierContributor);
+
+ /**
+ * Defines the specified parameter for the currently defined method as the last parameter of the currently defined method.
+ *
+ * @param type The parameter's type. Can also be {@link net.bytebuddy.dynamic.TargetType} if the parameter type
+ * should be equal to the currently instrumented type.
+ * @param name The parameter's name.
+ * @param modifierContributors The parameter's modifiers.
+ * @return A new builder that is equal to the current builder but where the currently defined method appends the specified parameter.
+ */
+ Annotatable<U> withParameter(Type type, String name, Collection<? extends ModifierContributor.ForParameter> modifierContributors);
+
+ /**
+ * Defines the specified parameter for the currently defined method as the last parameter of the currently defined method.
+ *
+ * @param type The parameter's type. Can also be {@link net.bytebuddy.dynamic.TargetType} if the parameter type
+ * should be equal to the currently instrumented type.
+ * @param name The parameter's name.
+ * @param modifiers The parameter's modifiers.
+ * @return A new builder that is equal to the current builder but where the currently defined method appends the specified parameter.
+ */
+ Annotatable<U> withParameter(Type type, String name, int modifiers);
+
+ /**
+ * Defines the specified parameter for the currently defined method as the last parameter of the currently defined method.
+ *
+ * @param type The parameter's type. Can also be {@link net.bytebuddy.dynamic.TargetType} if the parameter type
+ * should be equal to the currently instrumented type.
+ * @param name The parameter's name.
+ * @param modifierContributor The parameter's modifiers.
+ * @return A new builder that is equal to the current builder but where the currently defined method appends the specified parameter.
+ */
+ Annotatable<U> withParameter(TypeDefinition type, String name, ModifierContributor.ForParameter... modifierContributor);
+
+ /**
+ * Defines the specified parameter for the currently defined method as the last parameter of the currently defined method.
+ *
+ * @param type The parameter's type. Can also be {@link net.bytebuddy.dynamic.TargetType} if the parameter type
+ * should be equal to the currently instrumented type.
+ * @param name The parameter's name.
+ * @param modifierContributors The parameter's modifiers.
+ * @return A new builder that is equal to the current builder but where the currently defined method appends the specified parameter.
+ */
+ Annotatable<U> withParameter(TypeDefinition type, String name, Collection<? extends ModifierContributor.ForParameter> modifierContributors);
+
+ /**
+ * Defines the specified parameter for the currently defined method as the last parameter of the currently defined method.
+ *
+ * @param type The parameter's type. Can also be {@link net.bytebuddy.dynamic.TargetType} if the parameter type
+ * should be equal to the currently instrumented type.
+ * @param name The parameter's name.
+ * @param modifiers The parameter's modifiers.
+ * @return A new builder that is equal to the current builder but where the currently defined method appends the specified parameter.
+ */
+ Annotatable<U> withParameter(TypeDefinition type, String name, int modifiers);
+
+ /**
+ * A builder for optionally defining an annotation on a parameter.
+ *
+ * @param <V> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ interface Annotatable<V> extends ParameterDefinition<V> {
+
+ /**
+ * Annotates the previously defined parameter with the specifed annotations.
+ *
+ * @param annotation The annotations to declare on the previously defined parameter.
+ * @return A new builder that is equal to this builder but with the previously defined parameter annotated with
+ * the specified annotations.
+ */
+ Annotatable<V> annotateParameter(Annotation... annotation);
+
+ /**
+ * Annotates the previously defined parameter with the specifed annotations.
+ *
+ * @param annotations The annotations to declare on the previously defined parameter.
+ * @return A new builder that is equal to this builder but with the previously defined parameter annotated with
+ * the specified annotations.
+ */
+ Annotatable<V> annotateParameter(List<? extends Annotation> annotations);
+
+ /**
+ * Annotates the previously defined parameter with the specifed annotations.
+ *
+ * @param annotation The annotations to declare on the previously defined parameter.
+ * @return A new builder that is equal to this builder but with the previously defined parameter annotated with
+ * the specified annotations.
+ */
+ Annotatable<V> annotateParameter(AnnotationDescription... annotation);
+
+ /**
+ * Annotates the previously defined parameter with the specifed annotations.
+ *
+ * @param annotations The annotations to declare on the previously defined parameter.
+ * @return A new builder that is equal to this builder but with the previously defined parameter annotated with
+ * the specified annotations.
+ */
+ Annotatable<V> annotateParameter(Collection<? extends AnnotationDescription> annotations);
+
+ /**
+ * An abstract base implementation for defining an annotation on a parameter.
+ *
+ * @param <W> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ abstract class AbstractBase<W> extends ParameterDefinition.AbstractBase<W> implements Annotatable<W> {
+
+ @Override
+ public ParameterDefinition.Annotatable<W> annotateParameter(Annotation... annotation) {
+ return annotateParameter(Arrays.asList(annotation));
+ }
+
+ @Override
+ public ParameterDefinition.Annotatable<W> annotateParameter(List<? extends Annotation> annotations) {
+ return annotateParameter(new AnnotationList.ForLoadedAnnotations(annotations));
+ }
+
+ @Override
+ public ParameterDefinition.Annotatable<W> annotateParameter(AnnotationDescription... annotation) {
+ return annotateParameter(Arrays.asList(annotation));
+ }
+
+ /**
+ * An adapter implementation for defining an annotation on a parameter.
+ *
+ * @param <X> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ protected abstract static class Adapter<X> extends ParameterDefinition.Annotatable.AbstractBase<X> {
+
+ @Override
+ public ParameterDefinition.Annotatable<X> withParameter(TypeDefinition type, String name, int modifiers) {
+ return materialize().withParameter(type, name, modifiers);
+ }
+
+ @Override
+ public ExceptionDefinition<X> throwing(Collection<? extends TypeDefinition> types) {
+ return materialize().throwing(types);
+ }
+
+ @Override
+ public TypeVariableDefinition.Annotatable<X> typeVariable(String symbol, Collection<? extends TypeDefinition> bounds) {
+ return materialize().typeVariable(symbol, bounds);
+ }
+
+ @Override
+ public MethodDefinition.ReceiverTypeDefinition<X> intercept(Implementation implementation) {
+ return materialize().intercept(implementation);
+ }
+
+ @Override
+ public MethodDefinition.ReceiverTypeDefinition<X> withoutCode() {
+ return materialize().withoutCode();
+ }
+
+ @Override
+ public MethodDefinition.ReceiverTypeDefinition<X> defaultValue(AnnotationValue<?, ?> annotationValue) {
+ return materialize().defaultValue(annotationValue);
+ }
+
+ @Override
+ public <V> MethodDefinition.ReceiverTypeDefinition<X> defaultValue(V value, Class<? extends V> type) {
+ return materialize().defaultValue(value, type);
+ }
+
+ /**
+ * Materializes this instance as a parameter definition with the currently defined properties.
+ *
+ * @return A parameter definition with the currently defined properties.
+ */
+ protected abstract MethodDefinition.ParameterDefinition<X> materialize();
+ }
+ }
+ }
+
+ /**
+ * A builder for defining an implementation of a method and optionally defining a type variable, thrown exception or a parameter type.
+ *
+ * @param <V> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ interface Simple<V> extends ExceptionDefinition<V> {
+
+ /**
+ * Defines the specified parameter for the currently defined method as the last parameter of the currently defined method.
+ *
+ * @param type The parameter's type. Can also be {@link net.bytebuddy.dynamic.TargetType} if the parameter type
+ * should be equal to the currently instrumented type.
+ * @return A new builder that is equal to the current builder but where the currently defined method appends the specified parameter.
+ */
+ Annotatable<V> withParameter(Type type);
+
+ /**
+ * Defines the specified parameter for the currently defined method as the last parameter of the currently defined method.
+ *
+ * @param type The parameter's type. Can also be {@link net.bytebuddy.dynamic.TargetType} if the parameter type
+ * should be equal to the currently instrumented type.
+ * @return A new builder that is equal to the current builder but where the currently defined method appends the specified parameter.
+ */
+ Annotatable<V> withParameter(TypeDefinition type);
+
+ /**
+ * A builder for optionally defining an annotation on a parameter.
+ *
+ * @param <V> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ interface Annotatable<V> extends Simple<V> {
+
+ /**
+ * Annotates the previously defined parameter with the specifed annotations.
+ *
+ * @param annotation The annotations to declare on the previously defined parameter.
+ * @return A new builder that is equal to this builder but with the previously defined parameter annotated with
+ * the specified annotations.
+ */
+ Annotatable<V> annotateParameter(Annotation... annotation);
+
+ /**
+ * Annotates the previously defined parameter with the specifed annotations.
+ *
+ * @param annotations The annotations to declare on the previously defined parameter.
+ * @return A new builder that is equal to this builder but with the previously defined parameter annotated with
+ * the specified annotations.
+ */
+ Annotatable<V> annotateParameter(List<? extends Annotation> annotations);
+
+ /**
+ * Annotates the previously defined parameter with the specifed annotations.
+ *
+ * @param annotation The annotations to declare on the previously defined parameter.
+ * @return A new builder that is equal to this builder but with the previously defined parameter annotated with
+ * the specified annotations.
+ */
+ Annotatable<V> annotateParameter(AnnotationDescription... annotation);
+
+ /**
+ * Annotates the previously defined parameter with the specifed annotations.
+ *
+ * @param annotations The annotations to declare on the previously defined parameter.
+ * @return A new builder that is equal to this builder but with the previously defined parameter annotated with
+ * the specified annotations.
+ */
+ Annotatable<V> annotateParameter(Collection<? extends AnnotationDescription> annotations);
+
+ /**
+ * An abstract base implementation of a simple parameter definition.
+ *
+ * @param <W> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ abstract class AbstractBase<W> extends Simple.AbstractBase<W> implements Annotatable<W> {
+
+ @Override
+ public Simple.Annotatable<W> annotateParameter(Annotation... annotation) {
+ return annotateParameter(Arrays.asList(annotation));
+ }
+
+ @Override
+ public Simple.Annotatable<W> annotateParameter(List<? extends Annotation> annotations) {
+ return annotateParameter(new AnnotationList.ForLoadedAnnotations(annotations));
+ }
+
+ @Override
+ public Simple.Annotatable<W> annotateParameter(AnnotationDescription... annotation) {
+ return annotateParameter(Arrays.asList(annotation));
+ }
+
+ /**
+ * An adapter implementation of a simple parameter definition.
+ *
+ * @param <X> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ protected abstract static class Adapter<X> extends Simple.Annotatable.AbstractBase<X> {
+
+ @Override
+ public Simple.Annotatable<X> withParameter(TypeDefinition type) {
+ return materialize().withParameter(type);
+ }
+
+ @Override
+ public ExceptionDefinition<X> throwing(Collection<? extends TypeDefinition> types) {
+ return materialize().throwing(types);
+ }
+
+ @Override
+ public TypeVariableDefinition.Annotatable<X> typeVariable(String symbol, Collection<? extends TypeDefinition> bounds) {
+ return materialize().typeVariable(symbol, bounds);
+ }
+
+ @Override
+ public MethodDefinition.ReceiverTypeDefinition<X> intercept(Implementation implementation) {
+ return materialize().intercept(implementation);
+ }
+
+ @Override
+ public MethodDefinition.ReceiverTypeDefinition<X> withoutCode() {
+ return materialize().withoutCode();
+ }
+
+ @Override
+ public MethodDefinition.ReceiverTypeDefinition<X> defaultValue(AnnotationValue<?, ?> annotationValue) {
+ return materialize().defaultValue(annotationValue);
+ }
+
+ @Override
+ public <V> MethodDefinition.ReceiverTypeDefinition<X> defaultValue(V value, Class<? extends V> type) {
+ return materialize().defaultValue(value, type);
+ }
+
+ /**
+ * Materializes this instance as a simple parameter definition with the currently defined properties.
+ *
+ * @return A simple parameter definition with the currently defined properties.
+ */
+ protected abstract MethodDefinition.ParameterDefinition.Simple<X> materialize();
+ }
+ }
+ }
+
+ /**
+ * An abstract base implementation of an exception definition.
+ *
+ * @param <W> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ abstract class AbstractBase<W> extends ExceptionDefinition.AbstractBase<W> implements Simple<W> {
+
+ @Override
+ public Simple.Annotatable<W> withParameter(Type type) {
+ return withParameter(TypeDefinition.Sort.describe(type));
+ }
+ }
+ }
+
+ /**
+ * A builder for defining an implementation of a method and optionally defining a type variable, thrown exception or method parameter.
+ * Implementations allow for the <i>one-by-one</i> definition of parameters what gives opportunity to annotate parameters in a fluent
+ * style. Doing so, it is optionally possible to define parameter names and modifiers. This can be done for either all or no parameters.
+ * Alternatively, parameters without annotations, names or modifiers can be defined by a single step.
+ *
+ * @param <V> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ interface Initial<V> extends ParameterDefinition<V>, Simple<V> {
+
+ /**
+ * Defines the specified parameters for the currently defined method.
+ *
+ * @param type The parameter types. Any type can also be {@link net.bytebuddy.dynamic.TargetType} if the parameter type
+ * should be equal to the currently instrumented type.
+ * @return A new builder that is equal to the current builder but where the currently defined method appends the specified parameters.
+ */
+ ExceptionDefinition<V> withParameters(Type... type);
+
+ /**
+ * Defines the specified parameters for the currently defined method.
+ *
+ * @param types The parameter types. Any type can also be {@link net.bytebuddy.dynamic.TargetType} if the parameter type
+ * should be equal to the currently instrumented type.
+ * @return A new builder that is equal to the current builder but where the currently defined method appends the specified parameters.
+ */
+ ExceptionDefinition<V> withParameters(List<? extends Type> types);
+
+ /**
+ * Defines the specified parameters for the currently defined method.
+ *
+ * @param type The parameter types. Any type can also be {@link net.bytebuddy.dynamic.TargetType} if the parameter type
+ * should be equal to the currently instrumented type.
+ * @return A new builder that is equal to the current builder but where the currently defined method appends the specified parameters.
+ */
+ ExceptionDefinition<V> withParameters(TypeDefinition... type);
+
+ /**
+ * Defines the specified parameters for the currently defined method.
+ *
+ * @param types The parameter types. Any type can also be {@link net.bytebuddy.dynamic.TargetType} if the parameter type
+ * should be equal to the currently instrumented type.
+ * @return A new builder that is equal to the current builder but where the currently defined method appends the specified parameters.
+ */
+ ExceptionDefinition<V> withParameters(Collection<? extends TypeDefinition> types);
+
+ /**
+ * An abstract base implementation for an initial parameter definition.
+ *
+ * @param <W> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ abstract class AbstractBase<W> extends ParameterDefinition.AbstractBase<W> implements Initial<W> {
+
+ @Override
+ public Simple.Annotatable<W> withParameter(Type type) {
+ return withParameter(TypeDefinition.Sort.describe(type));
+ }
+
+ @Override
+ public ExceptionDefinition<W> withParameters(Type... type) {
+ return withParameters(Arrays.asList(type));
+ }
+
+ @Override
+ public ExceptionDefinition<W> withParameters(List<? extends Type> types) {
+ return withParameters(new TypeList.Generic.ForLoadedTypes(types));
+ }
+
+ @Override
+ public ExceptionDefinition<W> withParameters(TypeDefinition... type) {
+ return withParameters(Arrays.asList(type));
+ }
+
+ @Override
+ public ExceptionDefinition<W> withParameters(Collection<? extends TypeDefinition> types) {
+ ParameterDefinition.Simple<W> parameterDefinition = this;
+ for (TypeDefinition type : types) {
+ parameterDefinition = parameterDefinition.withParameter(type);
+ }
+ return parameterDefinition;
+ }
+ }
+ }
+
+ /**
+ * An abstract base implementation for defining an implementation of a method and optionally definign a type variable, thrown exception or parameter type.
+ *
+ * @param <V> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ abstract class AbstractBase<V> extends ExceptionDefinition.AbstractBase<V> implements ParameterDefinition<V> {
+
+ @Override
+ public ParameterDefinition.Annotatable<V> withParameter(Type type, String name, ModifierContributor.ForParameter... modifierContributor) {
+ return withParameter(type, name, Arrays.asList(modifierContributor));
+ }
+
+ @Override
+ public ParameterDefinition.Annotatable<V> withParameter(Type type, String name, Collection<? extends ModifierContributor.ForParameter> modifierContributors) {
+ return withParameter(type, name, ModifierContributor.Resolver.of(modifierContributors).resolve());
+ }
+
+ @Override
+ public ParameterDefinition.Annotatable<V> withParameter(Type type, String name, int modifiers) {
+ return withParameter(TypeDefinition.Sort.describe(type), name, modifiers);
+ }
+
+ @Override
+ public ParameterDefinition.Annotatable<V> withParameter(TypeDefinition type, String name, ModifierContributor.ForParameter... modifierContributor) {
+ return withParameter(type, name, Arrays.asList(modifierContributor));
+ }
+
+ @Override
+ public ParameterDefinition.Annotatable<V> withParameter(TypeDefinition type, String name, Collection<? extends ModifierContributor.ForParameter> modifierContributors) {
+ return withParameter(type, name, ModifierContributor.Resolver.of(modifierContributors).resolve());
+ }
+ }
+ }
+
+ /**
+ * An abstract base implementation of a method definition.
+ *
+ * @param <U> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ abstract class AbstractBase<U> extends Builder.AbstractBase.Delegator<U> implements MethodDefinition<U> {
+
+ @Override
+ public MethodDefinition<U> annotateMethod(Annotation... annotation) {
+ return annotateMethod(Arrays.asList(annotation));
+ }
+
+ @Override
+ public MethodDefinition<U> annotateMethod(List<? extends Annotation> annotations) {
+ return annotateMethod(new AnnotationList.ForLoadedAnnotations(annotations));
+ }
+
+ @Override
+ public MethodDefinition<U> annotateMethod(AnnotationDescription... annotation) {
+ return annotateMethod(Arrays.asList(annotation));
+ }
+
+ @Override
+ public MethodDefinition<U> annotateParameter(int index, Annotation... annotation) {
+ return annotateParameter(index, Arrays.asList(annotation));
+ }
+
+ @Override
+ public MethodDefinition<U> annotateParameter(int index, List<? extends Annotation> annotations) {
+ return annotateParameter(index, new AnnotationList.ForLoadedAnnotations(annotations));
+ }
+
+ @Override
+ public MethodDefinition<U> annotateParameter(int index, AnnotationDescription... annotation) {
+ return annotateParameter(index, Arrays.asList(annotation));
+ }
+
+ /**
+ * An adapter implementation of a method definition.
+ *
+ * @param <V> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ @EqualsAndHashCode(callSuper = false)
+ protected abstract static class Adapter<V> extends MethodDefinition.ReceiverTypeDefinition.AbstractBase<V> {
+
+ /**
+ * The handler that determines how a method is implemented.
+ */
+ protected final MethodRegistry.Handler handler;
+
+ /**
+ * The method attribute appender factory to apply onto the method that is currently being implemented.
+ */
+ protected final MethodAttributeAppender.Factory methodAttributeAppenderFactory;
+
+ /**
+ * The transformer to apply onto the method that is currently being implemented.
+ */
+ protected final Transformer<MethodDescription> transformer;
+
+ /**
+ * Creates a new adapter for a method definition.
+ *
+ * @param handler The handler that determines how a method is implemented.
+ * @param methodAttributeAppenderFactory The method attribute appender factory to apply onto the method that is currently being implemented.
+ * @param transformer The transformer to apply onto the method that is currently being implemented.
+ */
+ protected Adapter(MethodRegistry.Handler handler,
+ MethodAttributeAppender.Factory methodAttributeAppenderFactory,
+ Transformer<MethodDescription> transformer) {
+ this.handler = handler;
+ this.methodAttributeAppenderFactory = methodAttributeAppenderFactory;
+ this.transformer = transformer;
+ }
+
+ @Override
+ public MethodDefinition<V> attribute(MethodAttributeAppender.Factory methodAttributeAppenderFactory) {
+ return materialize(handler, new MethodAttributeAppender.Factory.Compound(this.methodAttributeAppenderFactory, methodAttributeAppenderFactory), transformer);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked") // In absence of @SafeVarargs for Java 6
+ public MethodDefinition<V> transform(Transformer<MethodDescription> transformer) {
+ return materialize(handler, methodAttributeAppenderFactory, new Transformer.Compound<MethodDescription>(this.transformer, transformer));
+ }
+
+ /**
+ * Materializes the current builder as a method definition.
+ *
+ * @param handler The handler that determines how a method is implemented.
+ * @param methodAttributeAppenderFactory The method attribute appender factory to apply onto the method that is currently being implemented.
+ * @param transformer The method transformer to apply onto the method that is currently being implemented.
+ * @return Returns a method definition for the supplied properties.
+ */
+ protected abstract MethodDefinition<V> materialize(MethodRegistry.Handler handler,
+ MethodAttributeAppender.Factory methodAttributeAppenderFactory,
+ Transformer<MethodDescription> transformer);
+ }
+ }
+ }
+
+ /**
+ * An abstract base implementation of a dynamic type builder.
+ *
+ * @param <S> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ abstract class AbstractBase<S> implements Builder<S> {
+
+ @Override
+ public Builder<S> annotateType(Annotation... annotation) {
+ return annotateType(Arrays.asList(annotation));
+ }
+
+ @Override
+ public Builder<S> annotateType(List<? extends Annotation> annotations) {
+ return annotateType(new AnnotationList.ForLoadedAnnotations(annotations));
+ }
+
+ @Override
+ public Builder<S> annotateType(AnnotationDescription... annotation) {
+ return annotateType(Arrays.asList(annotation));
+ }
+
+ @Override
+ public Builder<S> modifiers(ModifierContributor.ForType... modifierContributor) {
+ return modifiers(Arrays.asList(modifierContributor));
+ }
+
+ @Override
+ public Builder<S> modifiers(Collection<? extends ModifierContributor.ForType> modifierContributors) {
+ return modifiers(ModifierContributor.Resolver.of(modifierContributors).resolve());
+ }
+
+ @Override
+ public Builder<S> merge(ModifierContributor.ForType... modifierContributor) {
+ return merge(Arrays.asList(modifierContributor));
+ }
+
+ @Override
+ public MethodDefinition.ImplementationDefinition.Optional<S> implement(Type... interfaceType) {
+ return implement(Arrays.asList(interfaceType));
+ }
+
+ @Override
+ public MethodDefinition.ImplementationDefinition.Optional<S> implement(List<? extends Type> interfaceTypes) {
+ return implement(new TypeList.Generic.ForLoadedTypes(interfaceTypes));
+ }
+
+ @Override
+ public MethodDefinition.ImplementationDefinition.Optional<S> implement(TypeDefinition... interfaceType) {
+ return implement(Arrays.asList(interfaceType));
+ }
+
+ @Override
+ public TypeVariableDefinition<S> typeVariable(String symbol) {
+ return typeVariable(symbol, TypeDescription.Generic.OBJECT);
+ }
+
+ @Override
+ public TypeVariableDefinition<S> typeVariable(String symbol, Type... bound) {
+ return typeVariable(symbol, Arrays.asList(bound));
+ }
+
+ @Override
+ public TypeVariableDefinition<S> typeVariable(String symbol, List<? extends Type> bounds) {
+ return typeVariable(symbol, new TypeList.Generic.ForLoadedTypes(bounds));
+ }
+
+ @Override
+ public TypeVariableDefinition<S> typeVariable(String symbol, TypeDefinition... bound) {
+ return typeVariable(symbol, Arrays.asList(bound));
+ }
+
+ @Override
+ public FieldDefinition.Optional.Valuable<S> defineField(String name, Type type, ModifierContributor.ForField... modifierContributor) {
+ return defineField(name, type, Arrays.asList(modifierContributor));
+ }
+
+ @Override
+ public FieldDefinition.Optional.Valuable<S> defineField(String name, Type type, Collection<? extends ModifierContributor.ForField> modifierContributors) {
+ return defineField(name, type, ModifierContributor.Resolver.of(modifierContributors).resolve());
+ }
+
+ @Override
+ public FieldDefinition.Optional.Valuable<S> defineField(String name, Type type, int modifiers) {
+ return defineField(name, TypeDefinition.Sort.describe(type), modifiers);
+ }
+
+ @Override
+ public FieldDefinition.Optional.Valuable<S> defineField(String name, TypeDefinition type, ModifierContributor.ForField... modifierContributor) {
+ return defineField(name, type, Arrays.asList(modifierContributor));
+ }
+
+ @Override
+ public FieldDefinition.Optional.Valuable<S> defineField(String name, TypeDefinition type, Collection<? extends ModifierContributor.ForField> modifierContributors) {
+ return defineField(name, type, ModifierContributor.Resolver.of(modifierContributors).resolve());
+ }
+
+ @Override
+ public FieldDefinition.Optional.Valuable<S> define(Field field) {
+ return define(new FieldDescription.ForLoadedField(field));
+ }
+
+ @Override
+ public FieldDefinition.Optional.Valuable<S> define(FieldDescription field) {
+ return defineField(field.getName(), field.getType(), field.getModifiers());
+ }
+
+ @Override
+ public FieldDefinition.Optional<S> serialVersionUid(long serialVersionUid) {
+ return defineField("serialVersionUID", long.class, Visibility.PRIVATE, FieldManifestation.FINAL, Ownership.STATIC).value(serialVersionUid);
+ }
+
+ @Override
+ public FieldDefinition.Valuable<S> field(ElementMatcher<? super FieldDescription> matcher) {
+ return field(new LatentMatcher.Resolved<FieldDescription>(matcher));
+ }
+
+ @Override
+ public Builder<S> ignoreAlso(ElementMatcher<? super MethodDescription> ignoredMethods) {
+ return ignoreAlso(new LatentMatcher.Resolved<MethodDescription>(ignoredMethods));
+ }
+
+ @Override
+ public MethodDefinition.ParameterDefinition.Initial<S> defineMethod(String name, Type returnType, ModifierContributor.ForMethod... modifierContributor) {
+ return defineMethod(name, returnType, Arrays.asList(modifierContributor));
+ }
+
+ @Override
+ public MethodDefinition.ParameterDefinition.Initial<S> defineMethod(String name, Type returnType, Collection<? extends ModifierContributor.ForMethod> modifierContributors) {
+ return defineMethod(name, returnType, ModifierContributor.Resolver.of(modifierContributors).resolve());
+ }
+
+ @Override
+ public MethodDefinition.ParameterDefinition.Initial<S> defineMethod(String name, Type returnType, int modifiers) {
+ return defineMethod(name, TypeDefinition.Sort.describe(returnType), modifiers);
+ }
+
+ @Override
+ public MethodDefinition.ParameterDefinition.Initial<S> defineMethod(String name, TypeDefinition returnType, ModifierContributor.ForMethod... modifierContributor) {
+ return defineMethod(name, returnType, Arrays.asList(modifierContributor));
+ }
+
+ @Override
+ public MethodDefinition.ParameterDefinition.Initial<S> defineMethod(String name, TypeDefinition returnType, Collection<? extends ModifierContributor.ForMethod> modifierContributors) {
+ return defineMethod(name, returnType, ModifierContributor.Resolver.of(modifierContributors).resolve());
+ }
+
+ @Override
+ public MethodDefinition.ParameterDefinition.Initial<S> defineConstructor(ModifierContributor.ForMethod... modifierContributor) {
+ return defineConstructor(Arrays.asList(modifierContributor));
+ }
+
+ @Override
+ public MethodDefinition.ParameterDefinition.Initial<S> defineConstructor(Collection<? extends ModifierContributor.ForMethod> modifierContributors) {
+ return defineConstructor(ModifierContributor.Resolver.of(modifierContributors).resolve());
+ }
+
+ @Override
+ public MethodDefinition.ImplementationDefinition<S> define(Method method) {
+ return define(new MethodDescription.ForLoadedMethod(method));
+ }
+
+ @Override
+ public MethodDefinition.ImplementationDefinition<S> define(Constructor<?> constructor) {
+ return define(new MethodDescription.ForLoadedConstructor(constructor));
+ }
+
+ @Override
+ public MethodDefinition.ImplementationDefinition<S> define(MethodDescription methodDescription) {
+ MethodDefinition.ParameterDefinition.Initial<S> initialParameterDefinition = methodDescription.isConstructor()
+ ? defineConstructor(methodDescription.getModifiers())
+ : defineMethod(methodDescription.getInternalName(), methodDescription.getReturnType(), methodDescription.getModifiers());
+ ParameterList<?> parameterList = methodDescription.getParameters();
+ MethodDefinition.ExceptionDefinition<S> exceptionDefinition;
+ if (parameterList.hasExplicitMetaData()) {
+ MethodDefinition.ParameterDefinition<S> parameterDefinition = initialParameterDefinition;
+ for (ParameterDescription parameter : parameterList) {
+ parameterDefinition = parameterDefinition.withParameter(parameter.getType(), parameter.getName(), parameter.getModifiers());
+ }
+ exceptionDefinition = parameterDefinition;
+ } else {
+ exceptionDefinition = initialParameterDefinition.withParameters(parameterList.asTypeList());
+ }
+ MethodDefinition.TypeVariableDefinition<S> typeVariableDefinition = exceptionDefinition.throwing(methodDescription.getExceptionTypes());
+ for (TypeDescription.Generic typeVariable : methodDescription.getTypeVariables()) {
+ typeVariableDefinition = typeVariableDefinition.typeVariable(typeVariable.getSymbol(), typeVariable.getUpperBounds());
+ }
+ return typeVariableDefinition;
+ }
+
+ @Override
+ public MethodDefinition.ImplementationDefinition<S> method(ElementMatcher<? super MethodDescription> matcher) {
+ return invokable(isMethod().and(matcher));
+ }
+
+ @Override
+ public MethodDefinition.ImplementationDefinition<S> constructor(ElementMatcher<? super MethodDescription> matcher) {
+ return invokable(isConstructor().and(matcher));
+ }
+
+ @Override
+ public MethodDefinition.ImplementationDefinition<S> invokable(ElementMatcher<? super MethodDescription> matcher) {
+ return invokable(new LatentMatcher.Resolved<MethodDescription>(matcher));
+ }
+
+ @Override
+ public Unloaded<S> make(TypePool typePool) {
+ return make(TypeResolutionStrategy.Passive.INSTANCE, typePool);
+ }
+
+ @Override
+ public Unloaded<S> make() {
+ return make(TypeResolutionStrategy.Passive.INSTANCE);
+ }
+
+ /**
+ * A delegator for a dynamic type builder delegating all invocations to another dynamic type builder.
+ *
+ * @param <U> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ public abstract static class Delegator<U> extends AbstractBase<U> {
+
+ @Override
+ public Builder<U> visit(AsmVisitorWrapper asmVisitorWrapper) {
+ return materialize().visit(asmVisitorWrapper);
+ }
+
+ @Override
+ public Builder<U> initializer(LoadedTypeInitializer loadedTypeInitializer) {
+ return materialize().initializer(loadedTypeInitializer);
+ }
+
+ @Override
+ public Builder<U> annotateType(Collection<? extends AnnotationDescription> annotations) {
+ return materialize().annotateType(annotations);
+ }
+
+ @Override
+ public Builder<U> attribute(TypeAttributeAppender typeAttributeAppender) {
+ return materialize().attribute(typeAttributeAppender);
+ }
+
+ @Override
+ public Builder<U> modifiers(int modifiers) {
+ return materialize().modifiers(modifiers);
+ }
+
+ @Override
+ public Builder<U> merge(Collection<? extends ModifierContributor.ForType> modifierContributors) {
+ return materialize().merge(modifierContributors);
+ }
+
+ @Override
+ public Builder<U> name(String name) {
+ return materialize().name(name);
+ }
+
+ @Override
+ public MethodDefinition.ImplementationDefinition.Optional<U> implement(Collection<? extends TypeDefinition> interfaceTypes) {
+ return materialize().implement(interfaceTypes);
+ }
+
+ @Override
+ public Builder<U> initializer(ByteCodeAppender byteCodeAppender) {
+ return materialize().initializer(byteCodeAppender);
+ }
+
+ @Override
+ public Builder<U> ignoreAlso(ElementMatcher<? super MethodDescription> ignoredMethods) {
+ return materialize().ignoreAlso(ignoredMethods);
+ }
+
+ @Override
+ public Builder<U> ignoreAlso(LatentMatcher<? super MethodDescription> ignoredMethods) {
+ return materialize().ignoreAlso(ignoredMethods);
+ }
+
+ @Override
+ public TypeVariableDefinition<U> typeVariable(String symbol, Collection<? extends TypeDefinition> bounds) {
+ return materialize().typeVariable(symbol, bounds);
+ }
+
+ @Override
+ public Builder<U> transform(ElementMatcher<? super TypeDescription.Generic> matcher, Transformer<TypeVariableToken> transformer) {
+ return materialize().transform(matcher, transformer);
+ }
+
+ @Override
+ public FieldDefinition.Optional.Valuable<U> defineField(String name, TypeDefinition type, int modifiers) {
+ return materialize().defineField(name, type, modifiers);
+ }
+
+ @Override
+ public FieldDefinition.Valuable<U> field(LatentMatcher<? super FieldDescription> matcher) {
+ return materialize().field(matcher);
+ }
+
+ @Override
+ public MethodDefinition.ParameterDefinition.Initial<U> defineMethod(String name, TypeDefinition returnType, int modifiers) {
+ return materialize().defineMethod(name, returnType, modifiers);
+ }
+
+ @Override
+ public MethodDefinition.ParameterDefinition.Initial<U> defineConstructor(int modifiers) {
+ return materialize().defineConstructor(modifiers);
+ }
+
+ @Override
+ public MethodDefinition.ImplementationDefinition<U> invokable(LatentMatcher<? super MethodDescription> matcher) {
+ return materialize().invokable(matcher);
+ }
+
+ @Override
+ public DynamicType.Unloaded<U> make() {
+ return materialize().make();
+ }
+
+ @Override
+ public Unloaded<U> make(TypeResolutionStrategy typeResolutionStrategy) {
+ return materialize().make(typeResolutionStrategy);
+ }
+
+ @Override
+ public Unloaded<U> make(TypePool typePool) {
+ return materialize().make(typePool);
+ }
+
+ @Override
+ public Unloaded<U> make(TypeResolutionStrategy typeResolutionStrategy, TypePool typePool) {
+ return materialize().make(typeResolutionStrategy, typePool);
+ }
+
+ /**
+ * Creates a new builder that realizes the current state of the builder.
+ *
+ * @return A new builder that realizes the current state of the builder.
+ */
+ protected abstract Builder<U> materialize();
+ }
+
+ /**
+ * An adapter implementation of a dynamic type builder.
+ *
+ * @param <U> A loaded type that the built type is guaranteed to be a subclass of.
+ */
+ @EqualsAndHashCode(callSuper = false)
+ public abstract static class Adapter<U> extends AbstractBase<U> {
+
+ /**
+ * The instrumented type to be created.
+ */
+ protected final InstrumentedType.WithFlexibleName instrumentedType;
+
+ /**
+ * The current field registry.
+ */
+ protected final FieldRegistry fieldRegistry;
+
+ /**
+ * The current method registry.
+ */
+ protected final MethodRegistry methodRegistry;
+
+ /**
+ * The type attribute appender to apply onto the instrumented type.
+ */
+ protected final TypeAttributeAppender typeAttributeAppender;
+
+ /**
+ * The ASM visitor wrapper to apply onto the class writer.
+ */
+ protected final AsmVisitorWrapper asmVisitorWrapper;
+
+ /**
+ * The class file version to define auxiliary types in.
+ */
+ protected final ClassFileVersion classFileVersion;
+
+ /**
+ * The naming strategy for auxiliary types to apply.
+ */
+ protected final AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy;
+
+ /**
+ * The annotation value filter factory to apply.
+ */
+ protected final AnnotationValueFilter.Factory annotationValueFilterFactory;
+
+ /**
+ * The annotation retention to apply.
+ */
+ protected final AnnotationRetention annotationRetention;
+
+ /**
+ * The implementation context factory to apply.
+ */
+ protected final Implementation.Context.Factory implementationContextFactory;
+
+ /**
+ * The method graph compiler to use.
+ */
+ protected final MethodGraph.Compiler methodGraphCompiler;
+
+ /**
+ * Determines if a type should be explicitly validated.
+ */
+ protected final TypeValidation typeValidation;
+
+ /**
+ * A matcher for identifying methods that should be excluded from instrumentation.
+ */
+ protected final LatentMatcher<? super MethodDescription> ignoredMethods;
+
+ /**
+ * Creates a new default type writer for creating a new type that is not based on an existing class file.
+ *
+ * @param instrumentedType The instrumented type to be created.
+ * @param fieldRegistry The current field registry.
+ * @param methodRegistry The current method registry.
+ * @param typeAttributeAppender The type attribute appender to apply onto the instrumented type.
+ * @param asmVisitorWrapper The ASM visitor wrapper to apply onto the class writer.
+ * @param classFileVersion The class file version to define auxiliary types in.
+ * @param auxiliaryTypeNamingStrategy The naming strategy for auxiliary types to apply.
+ * @param annotationValueFilterFactory The annotation value filter factory to apply.
+ * @param annotationRetention The annotation retention to apply.
+ * @param implementationContextFactory The implementation context factory to apply.
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @param typeValidation Determines if a type should be explicitly validated.
+ * @param ignoredMethods A matcher for identifying methods that should be excluded from instrumentation.
+ */
+ protected Adapter(InstrumentedType.WithFlexibleName instrumentedType,
+ FieldRegistry fieldRegistry,
+ MethodRegistry methodRegistry,
+ TypeAttributeAppender typeAttributeAppender,
+ AsmVisitorWrapper asmVisitorWrapper,
+ ClassFileVersion classFileVersion,
+ AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
+ AnnotationValueFilter.Factory annotationValueFilterFactory,
+ AnnotationRetention annotationRetention,
+ Implementation.Context.Factory implementationContextFactory,
+ MethodGraph.Compiler methodGraphCompiler,
+ TypeValidation typeValidation,
+ LatentMatcher<? super MethodDescription> ignoredMethods) {
+ this.instrumentedType = instrumentedType;
+ this.fieldRegistry = fieldRegistry;
+ this.methodRegistry = methodRegistry;
+ this.typeAttributeAppender = typeAttributeAppender;
+ this.asmVisitorWrapper = asmVisitorWrapper;
+ this.classFileVersion = classFileVersion;
+ this.auxiliaryTypeNamingStrategy = auxiliaryTypeNamingStrategy;
+ this.annotationValueFilterFactory = annotationValueFilterFactory;
+ this.annotationRetention = annotationRetention;
+ this.implementationContextFactory = implementationContextFactory;
+ this.methodGraphCompiler = methodGraphCompiler;
+ this.typeValidation = typeValidation;
+ this.ignoredMethods = ignoredMethods;
+ }
+
+ @Override
+ public FieldDefinition.Optional.Valuable<U> defineField(String name, TypeDefinition type, int modifiers) {
+ return new FieldDefinitionAdapter(new FieldDescription.Token(name, modifiers, type.asGenericType()));
+ }
+
+ @Override
+ public FieldDefinition.Valuable<U> field(LatentMatcher<? super FieldDescription> matcher) {
+ return new FieldMatchAdapter(matcher);
+ }
+
+ @Override
+ public MethodDefinition.ParameterDefinition.Initial<U> defineMethod(String name, TypeDefinition returnType, int modifiers) {
+ return new MethodDefinitionAdapter(new MethodDescription.Token(name, modifiers, returnType.asGenericType()));
+ }
+
+ @Override
+ public MethodDefinition.ParameterDefinition.Initial<U> defineConstructor(int modifiers) {
+ return new MethodDefinitionAdapter(new MethodDescription.Token(modifiers));
+ }
+
+ @Override
+ public MethodDefinition.ImplementationDefinition<U> invokable(LatentMatcher<? super MethodDescription> matcher) {
+ return new MethodMatchAdapter(matcher);
+ }
+
+ @Override
+ public MethodDefinition.ImplementationDefinition.Optional<U> implement(Collection<? extends TypeDefinition> interfaceTypes) {
+ return new OptionalMethodMatchAdapter(new TypeList.Generic.Explicit(new ArrayList<TypeDefinition>(interfaceTypes)));
+ }
+
+ @Override
+ @SuppressWarnings("unchecked") // In absence of @SafeVarargs for Java 6
+ public Builder<U> ignoreAlso(LatentMatcher<? super MethodDescription> ignoredMethods) {
+ return materialize(instrumentedType,
+ fieldRegistry,
+ methodRegistry,
+ typeAttributeAppender,
+ asmVisitorWrapper,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ typeValidation,
+ new LatentMatcher.Disjunction<MethodDescription>(this.ignoredMethods, ignoredMethods));
+ }
+
+ @Override
+ public Builder<U> initializer(ByteCodeAppender byteCodeAppender) {
+ return materialize(instrumentedType.withInitializer(byteCodeAppender),
+ fieldRegistry,
+ methodRegistry,
+ typeAttributeAppender,
+ asmVisitorWrapper,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ typeValidation,
+ ignoredMethods);
+ }
+
+ @Override
+ public Builder<U> initializer(LoadedTypeInitializer loadedTypeInitializer) {
+ return materialize(instrumentedType.withInitializer(loadedTypeInitializer),
+ fieldRegistry,
+ methodRegistry,
+ typeAttributeAppender,
+ asmVisitorWrapper,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ typeValidation,
+ ignoredMethods);
+ }
+
+ @Override
+ public Builder<U> name(String name) {
+ return materialize(instrumentedType.withName(name),
+ fieldRegistry,
+ methodRegistry,
+ typeAttributeAppender,
+ asmVisitorWrapper,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ typeValidation,
+ ignoredMethods);
+ }
+
+ @Override
+ public Builder<U> modifiers(int modifiers) {
+ return materialize(instrumentedType.withModifiers(modifiers),
+ fieldRegistry,
+ methodRegistry,
+ typeAttributeAppender,
+ asmVisitorWrapper,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ typeValidation,
+ ignoredMethods);
+ }
+
+ @Override
+ public Builder<U> merge(Collection<? extends ModifierContributor.ForType> modifierContributors) {
+ return materialize(instrumentedType.withModifiers(ModifierContributor.Resolver.of(modifierContributors).resolve(instrumentedType.getModifiers())),
+ fieldRegistry,
+ methodRegistry,
+ typeAttributeAppender,
+ asmVisitorWrapper,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ typeValidation,
+ ignoredMethods);
+ }
+
+ @Override
+ public TypeVariableDefinition<U> typeVariable(String symbol, Collection<? extends TypeDefinition> bounds) {
+ return new TypeVariableDefinitionAdapter(new TypeVariableToken(symbol, new TypeList.Generic.Explicit(new ArrayList<TypeDefinition>(bounds))));
+ }
+
+ @Override
+ public Builder<U> transform(ElementMatcher<? super TypeDescription.Generic> matcher, Transformer<TypeVariableToken> transformer) {
+ return materialize(instrumentedType.withTypeVariables(matcher, transformer),
+ fieldRegistry,
+ methodRegistry,
+ typeAttributeAppender,
+ asmVisitorWrapper,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ typeValidation,
+ ignoredMethods);
+ }
+
+ @Override
+ public Builder<U> attribute(TypeAttributeAppender typeAttributeAppender) {
+ return materialize(instrumentedType,
+ fieldRegistry,
+ methodRegistry,
+ new TypeAttributeAppender.Compound(this.typeAttributeAppender, typeAttributeAppender),
+ asmVisitorWrapper,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ typeValidation,
+ ignoredMethods);
+ }
+
+ @Override
+ public Builder<U> annotateType(Collection<? extends AnnotationDescription> annotations) {
+ return materialize(instrumentedType.withAnnotations(new ArrayList<AnnotationDescription>(annotations)),
+ fieldRegistry,
+ methodRegistry,
+ typeAttributeAppender,
+ asmVisitorWrapper,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ typeValidation,
+ ignoredMethods);
+ }
+
+ @Override
+ public Builder<U> visit(AsmVisitorWrapper asmVisitorWrapper) {
+ return materialize(instrumentedType,
+ fieldRegistry,
+ methodRegistry,
+ typeAttributeAppender,
+ new AsmVisitorWrapper.Compound(this.asmVisitorWrapper, asmVisitorWrapper),
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ typeValidation,
+ ignoredMethods);
+ }
+
+ /**
+ * Materializes the supplied state of a dynamic type builder.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param fieldRegistry The current field registry.
+ * @param methodRegistry The current method registry.
+ * @param typeAttributeAppender The type attribute appender to apply onto the instrumented type.
+ * @param asmVisitorWrapper The ASM visitor wrapper to apply onto the class writer.
+ * @param classFileVersion The class file version to define auxiliary types in.
+ * @param auxiliaryTypeNamingStrategy The naming strategy for auxiliary types to apply.
+ * @param annotationValueFilterFactory The annotation value filter factory to apply.
+ * @param annotationRetention The annotation retention to apply.
+ * @param implementationContextFactory The implementation context factory to apply.
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @param typeValidation The type validation state.
+ * @param ignoredMethods A matcher for identifying methods that should be excluded from instrumentation.
+ * @return A type builder that represents the supplied arguments.
+ */
+ protected abstract Builder<U> materialize(InstrumentedType.WithFlexibleName instrumentedType,
+ FieldRegistry fieldRegistry,
+ MethodRegistry methodRegistry,
+ TypeAttributeAppender typeAttributeAppender,
+ AsmVisitorWrapper asmVisitorWrapper,
+ ClassFileVersion classFileVersion,
+ AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
+ AnnotationValueFilter.Factory annotationValueFilterFactory,
+ AnnotationRetention annotationRetention,
+ Implementation.Context.Factory implementationContextFactory,
+ MethodGraph.Compiler methodGraphCompiler,
+ TypeValidation typeValidation,
+ LatentMatcher<? super MethodDescription> ignoredMethods);
+
+ /**
+ * An adapter for defining a new type variable for the instrumented type.
+ */
+ protected class TypeVariableDefinitionAdapter extends TypeVariableDefinition.AbstractBase<U> {
+
+ /**
+ * The current definition of the type variable.
+ */
+ private final TypeVariableToken token;
+
+ /**
+ * Creates a new type variable definition adapter.
+ *
+ * @param token The current definition of the type variable.
+ */
+ protected TypeVariableDefinitionAdapter(TypeVariableToken token) {
+ this.token = token;
+ }
+
+ @Override
+ public TypeVariableDefinition<U> annotateTypeVariable(Collection<? extends AnnotationDescription> annotations) {
+ return new TypeVariableDefinitionAdapter(new TypeVariableToken(token.getSymbol(),
+ token.getBounds(),
+ CompoundList.of(token.getAnnotations(), new ArrayList<AnnotationDescription>(annotations))));
+ }
+
+ @Override
+ protected Builder<U> materialize() {
+ return Adapter.this.materialize(instrumentedType.withTypeVariable(token),
+ fieldRegistry,
+ methodRegistry,
+ typeAttributeAppender,
+ asmVisitorWrapper,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ typeValidation,
+ ignoredMethods);
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private Builder.AbstractBase.Adapter<?> getOuter() {
+ return Builder.AbstractBase.Adapter.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ @SuppressWarnings("unchecked")
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && getOuter().equals(((TypeVariableDefinitionAdapter) other).getOuter())
+ && token.equals(((TypeVariableDefinitionAdapter) other).token);
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ int result = getOuter().hashCode();
+ result = 31 * result + token.hashCode();
+ return result;
+ }
+ }
+
+ /**
+ * An adapter for defining a new field.
+ */
+ protected class FieldDefinitionAdapter extends FieldDefinition.Optional.Valuable.AbstractBase.Adapter<U> {
+
+ /**
+ * The token representing the current field definition.
+ */
+ private final FieldDescription.Token token;
+
+ /**
+ * Creates a new field definition adapter.
+ *
+ * @param token The token representing the current field definition.
+ */
+ protected FieldDefinitionAdapter(FieldDescription.Token token) {
+ this(FieldAttributeAppender.ForInstrumentedField.INSTANCE,
+ Transformer.NoOp.<FieldDescription>make(),
+ FieldDescription.NO_DEFAULT_VALUE,
+ token);
+ }
+
+ /**
+ * Creates a new field definition adapter.
+ *
+ * @param fieldAttributeAppenderFactory The field attribute appender factory to apply.
+ * @param transformer The field transformer to apply.
+ * @param defaultValue The field's default value or {@code null} if no value is to be defined.
+ * @param token The token representing the current field definition.
+ */
+ protected FieldDefinitionAdapter(FieldAttributeAppender.Factory fieldAttributeAppenderFactory,
+ Transformer<FieldDescription> transformer,
+ Object defaultValue,
+ FieldDescription.Token token) {
+ super(fieldAttributeAppenderFactory, transformer, defaultValue);
+ this.token = token;
+ }
+
+ @Override
+ public Optional<U> annotateField(Collection<? extends AnnotationDescription> annotations) {
+ return new FieldDefinitionAdapter(fieldAttributeAppenderFactory, transformer, defaultValue, new FieldDescription.Token(token.getName(),
+ token.getModifiers(),
+ token.getType(),
+ CompoundList.of(token.getAnnotations(), new ArrayList<AnnotationDescription>(annotations))));
+ }
+
+ @Override
+ protected Builder<U> materialize() {
+ return Builder.AbstractBase.Adapter.this.materialize(instrumentedType.withField(token),
+ fieldRegistry.prepend(new LatentMatcher.ForFieldToken(token), fieldAttributeAppenderFactory, defaultValue, transformer),
+ methodRegistry,
+ typeAttributeAppender,
+ asmVisitorWrapper,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ typeValidation,
+ ignoredMethods);
+ }
+
+ @Override
+ protected Optional<U> materialize(FieldAttributeAppender.Factory fieldAttributeAppenderFactory,
+ Transformer<FieldDescription> transformer,
+ Object defaultValue) {
+ return new FieldDefinitionAdapter(fieldAttributeAppenderFactory, transformer, defaultValue, token);
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private Builder.AbstractBase.Adapter<?> getOuter() {
+ return Builder.AbstractBase.Adapter.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ @SuppressWarnings("unchecked")
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && super.equals(other)
+ && getOuter().equals(((FieldDefinitionAdapter) other).getOuter())
+ && token.equals(((FieldDefinitionAdapter) other).token);
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + getOuter().hashCode();
+ result = 31 * result + token.hashCode();
+ return result;
+ }
+ }
+
+ /**
+ * An adapter for matching an existing field.
+ */
+ protected class FieldMatchAdapter extends FieldDefinition.Optional.Valuable.AbstractBase.Adapter<U> {
+
+ /**
+ * The matcher for any fields to apply this matcher to.
+ */
+ private final LatentMatcher<? super FieldDescription> matcher;
+
+ /**
+ * Creates a new field match adapter.
+ *
+ * @param matcher The matcher for any fields to apply this matcher to.
+ */
+ protected FieldMatchAdapter(LatentMatcher<? super FieldDescription> matcher) {
+ this(FieldAttributeAppender.NoOp.INSTANCE,
+ Transformer.NoOp.<FieldDescription>make(),
+ FieldDescription.NO_DEFAULT_VALUE,
+ matcher);
+ }
+
+ /**
+ * Creates a new field match adapter.
+ *
+ * @param fieldAttributeAppenderFactory The field attribute appender factory to apply.
+ * @param transformer The field transformer to apply.
+ * @param defaultValue The field's default value or {@code null} if no value is to be defined.
+ * @param matcher The matcher for any fields to apply this matcher to.
+ */
+ protected FieldMatchAdapter(FieldAttributeAppender.Factory fieldAttributeAppenderFactory,
+ Transformer<FieldDescription> transformer,
+ Object defaultValue,
+ LatentMatcher<? super FieldDescription> matcher) {
+ super(fieldAttributeAppenderFactory, transformer, defaultValue);
+ this.matcher = matcher;
+ }
+
+ @Override
+ public Optional<U> annotateField(Collection<? extends AnnotationDescription> annotations) {
+ return attribute(new FieldAttributeAppender.Explicit(new ArrayList<AnnotationDescription>(annotations)));
+ }
+
+ @Override
+ protected Builder<U> materialize() {
+ return Builder.AbstractBase.Adapter.this.materialize(instrumentedType,
+ fieldRegistry.prepend(matcher, fieldAttributeAppenderFactory, defaultValue, transformer),
+ methodRegistry,
+ typeAttributeAppender,
+ asmVisitorWrapper,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ typeValidation,
+ ignoredMethods);
+ }
+
+ @Override
+ protected Optional<U> materialize(FieldAttributeAppender.Factory fieldAttributeAppenderFactory,
+ Transformer<FieldDescription> transformer,
+ Object defaultValue) {
+ return new FieldMatchAdapter(fieldAttributeAppenderFactory, transformer, defaultValue, matcher);
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private Builder.AbstractBase.Adapter<?> getOuter() {
+ return Builder.AbstractBase.Adapter.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ @SuppressWarnings("unchecked")
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && super.equals(other)
+ && getOuter().equals(((FieldMatchAdapter) other).getOuter())
+ && matcher.equals(((FieldMatchAdapter) other).matcher);
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + getOuter().hashCode();
+ result = 31 * result + matcher.hashCode();
+ return result;
+ }
+ }
+
+ /**
+ * An adapter for defining a new method.
+ */
+ protected class MethodDefinitionAdapter extends MethodDefinition.ParameterDefinition.Initial.AbstractBase<U> {
+
+ /**
+ * A token representing the currently defined method.
+ */
+ private final MethodDescription.Token token;
+
+ /**
+ * Creates a new method definition adapter.
+ *
+ * @param token A token representing the currently defined method.
+ */
+ protected MethodDefinitionAdapter(MethodDescription.Token token) {
+ this.token = token;
+ }
+
+ @Override
+ public MethodDefinition.ParameterDefinition.Annotatable<U> withParameter(TypeDefinition type, String name, int modifiers) {
+ return new ParameterAnnotationAdapter(new ParameterDescription.Token(type.asGenericType(), name, modifiers));
+ }
+
+ @Override
+ public Simple.Annotatable<U> withParameter(TypeDefinition type) {
+ return new SimpleParameterAnnotationAdapter(new ParameterDescription.Token(type.asGenericType()));
+ }
+
+ @Override
+ public MethodDefinition.ExceptionDefinition<U> throwing(Collection<? extends TypeDefinition> types) {
+ return new MethodDefinitionAdapter(new MethodDescription.Token(token.getName(),
+ token.getModifiers(),
+ token.getTypeVariableTokens(),
+ token.getReturnType(),
+ token.getParameterTokens(),
+ CompoundList.of(token.getExceptionTypes(), new TypeList.Generic.Explicit(new ArrayList<TypeDefinition>(types))),
+ token.getAnnotations(),
+ token.getDefaultValue(),
+ token.getReceiverType()));
+ }
+
+ @Override
+ public MethodDefinition.TypeVariableDefinition.Annotatable<U> typeVariable(String symbol, Collection<? extends TypeDefinition> bounds) {
+ return new TypeVariableAnnotationAdapter(new TypeVariableToken(symbol, new TypeList.Generic.Explicit(new ArrayList<TypeDefinition>(bounds))));
+ }
+
+ @Override
+ public MethodDefinition.ReceiverTypeDefinition<U> intercept(Implementation implementation) {
+ return materialize(new MethodRegistry.Handler.ForImplementation(implementation));
+ }
+
+ @Override
+ public MethodDefinition.ReceiverTypeDefinition<U> withoutCode() {
+ return new MethodDefinitionAdapter(new MethodDescription.Token(token.getName(),
+ ModifierContributor.Resolver.of(MethodManifestation.ABSTRACT).resolve(token.getModifiers()),
+ token.getTypeVariableTokens(),
+ token.getReturnType(),
+ token.getParameterTokens(),
+ token.getExceptionTypes(),
+ token.getAnnotations(),
+ token.getDefaultValue(),
+ token.getReceiverType())).materialize(MethodRegistry.Handler.ForAbstractMethod.INSTANCE);
+ }
+
+ @Override
+ public MethodDefinition.ReceiverTypeDefinition<U> defaultValue(AnnotationValue<?, ?> annotationValue) {
+ return new MethodDefinitionAdapter(new MethodDescription.Token(token.getName(),
+ ModifierContributor.Resolver.of(MethodManifestation.ABSTRACT).resolve(token.getModifiers()),
+ token.getTypeVariableTokens(),
+ token.getReturnType(),
+ token.getParameterTokens(),
+ token.getExceptionTypes(),
+ token.getAnnotations(),
+ annotationValue,
+ token.getReceiverType())).materialize(new MethodRegistry.Handler.ForAnnotationValue(annotationValue));
+ }
+
+ /**
+ * Materializes the given handler as the implementation.
+ *
+ * @param handler The handler for implementing the method.
+ * @return A method definition for the given handler.
+ */
+ private MethodDefinition.ReceiverTypeDefinition<U> materialize(MethodRegistry.Handler handler) {
+ return new AnnotationAdapter(handler);
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private Adapter<?> getOuter() {
+ return Adapter.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ @SuppressWarnings("unchecked")
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && token.equals(((MethodDefinitionAdapter) other).token)
+ && getOuter().equals(((MethodDefinitionAdapter) other).getOuter());
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return 31 * getOuter().hashCode() + token.hashCode();
+ }
+
+ /**
+ * An adapter for defining a new type variable for the currently defined method.
+ */
+ protected class TypeVariableAnnotationAdapter extends MethodDefinition.TypeVariableDefinition.Annotatable.AbstractBase.Adapter<U> {
+
+ /**
+ * The currently defined type variable.
+ */
+ private final TypeVariableToken token;
+
+ /**
+ * Creates a new type variable annotation adapter.
+ *
+ * @param token The currently defined type variable.
+ */
+ protected TypeVariableAnnotationAdapter(TypeVariableToken token) {
+ this.token = token;
+ }
+
+ @Override
+ protected MethodDefinition.ParameterDefinition<U> materialize() {
+ return new MethodDefinitionAdapter(new MethodDescription.Token(MethodDefinitionAdapter.this.token.getName(),
+ MethodDefinitionAdapter.this.token.getModifiers(),
+ CompoundList.of(MethodDefinitionAdapter.this.token.getTypeVariableTokens(), token),
+ MethodDefinitionAdapter.this.token.getReturnType(),
+ MethodDefinitionAdapter.this.token.getParameterTokens(),
+ MethodDefinitionAdapter.this.token.getExceptionTypes(),
+ MethodDefinitionAdapter.this.token.getAnnotations(),
+ MethodDefinitionAdapter.this.token.getDefaultValue(),
+ MethodDefinitionAdapter.this.token.getReceiverType()));
+ }
+
+ @Override
+ public Annotatable<U> annotateTypeVariable(Collection<? extends AnnotationDescription> annotations) {
+ return new TypeVariableAnnotationAdapter(new TypeVariableToken(token.getSymbol(),
+ token.getBounds(),
+ CompoundList.of(token.getAnnotations(), new ArrayList<AnnotationDescription>(annotations))));
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private MethodDefinitionAdapter getOuter() {
+ return MethodDefinitionAdapter.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ @SuppressWarnings("unchecked")
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && token.equals(((TypeVariableAnnotationAdapter) other).token)
+ && getOuter().equals(((TypeVariableAnnotationAdapter) other).getOuter());
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return 31 * getOuter().hashCode() + token.hashCode();
+ }
+ }
+
+ /**
+ * An annotation adapter for a parameter definition.
+ */
+ protected class ParameterAnnotationAdapter extends MethodDefinition.ParameterDefinition.Annotatable.AbstractBase.Adapter<U> {
+
+ /**
+ * The token of the currently defined parameter.
+ */
+ private final ParameterDescription.Token token;
+
+ /**
+ * Creates a new parameter annotation adapter.
+ *
+ * @param token The token of the currently defined parameter.
+ */
+ protected ParameterAnnotationAdapter(ParameterDescription.Token token) {
+ this.token = token;
+ }
+
+ @Override
+ public MethodDefinition.ParameterDefinition.Annotatable<U> annotateParameter(Collection<? extends AnnotationDescription> annotations) {
+ return new ParameterAnnotationAdapter(new ParameterDescription.Token(token.getType(),
+ CompoundList.of(token.getAnnotations(), new ArrayList<AnnotationDescription>(annotations)),
+ token.getName(),
+ token.getModifiers()));
+ }
+
+ @Override
+ protected MethodDefinition.ParameterDefinition<U> materialize() {
+ return new MethodDefinitionAdapter(new MethodDescription.Token(MethodDefinitionAdapter.this.token.getName(),
+ MethodDefinitionAdapter.this.token.getModifiers(),
+ MethodDefinitionAdapter.this.token.getTypeVariableTokens(),
+ MethodDefinitionAdapter.this.token.getReturnType(),
+ CompoundList.of(MethodDefinitionAdapter.this.token.getParameterTokens(), token),
+ MethodDefinitionAdapter.this.token.getExceptionTypes(),
+ MethodDefinitionAdapter.this.token.getAnnotations(),
+ MethodDefinitionAdapter.this.token.getDefaultValue(),
+ MethodDefinitionAdapter.this.token.getReceiverType()));
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private MethodDefinitionAdapter getOuter() {
+ return MethodDefinitionAdapter.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ @SuppressWarnings("unchecked")
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && token.equals(((ParameterAnnotationAdapter) other).token)
+ && getOuter().equals(((ParameterAnnotationAdapter) other).getOuter());
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return 31 * getOuter().hashCode() + token.hashCode();
+ }
+ }
+
+ /**
+ * An annotation adapter for a simple parameter definition.
+ */
+ protected class SimpleParameterAnnotationAdapter extends MethodDefinition.ParameterDefinition.Simple.Annotatable.AbstractBase.Adapter<U> {
+
+ /**
+ * The token of the currently defined parameter.
+ */
+ private final ParameterDescription.Token token;
+
+ /**
+ * Creates a new simple parameter annotation adapter.
+ *
+ * @param token The token of the currently defined parameter.
+ */
+ protected SimpleParameterAnnotationAdapter(ParameterDescription.Token token) {
+ this.token = token;
+ }
+
+ @Override
+ public MethodDefinition.ParameterDefinition.Simple.Annotatable<U> annotateParameter(Collection<? extends AnnotationDescription> annotations) {
+ return new SimpleParameterAnnotationAdapter(new ParameterDescription.Token(token.getType(),
+ CompoundList.of(token.getAnnotations(), new ArrayList<AnnotationDescription>(annotations)),
+ token.getName(),
+ token.getModifiers()));
+ }
+
+ @Override
+ protected MethodDefinition.ParameterDefinition.Simple<U> materialize() {
+ return new MethodDefinitionAdapter(new MethodDescription.Token(MethodDefinitionAdapter.this.token.getName(),
+ MethodDefinitionAdapter.this.token.getModifiers(),
+ MethodDefinitionAdapter.this.token.getTypeVariableTokens(),
+ MethodDefinitionAdapter.this.token.getReturnType(),
+ CompoundList.of(MethodDefinitionAdapter.this.token.getParameterTokens(), token),
+ MethodDefinitionAdapter.this.token.getExceptionTypes(),
+ MethodDefinitionAdapter.this.token.getAnnotations(),
+ MethodDefinitionAdapter.this.token.getDefaultValue(),
+ MethodDefinitionAdapter.this.token.getReceiverType()));
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private MethodDefinitionAdapter getOuter() {
+ return MethodDefinitionAdapter.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ @SuppressWarnings("unchecked")
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && token.equals(((SimpleParameterAnnotationAdapter) other).token)
+ && getOuter().equals(((SimpleParameterAnnotationAdapter) other).getOuter());
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return 31 * getOuter().hashCode() + token.hashCode();
+ }
+ }
+
+ /**
+ * An annotation adapter for a method definition.
+ */
+ protected class AnnotationAdapter extends MethodDefinition.AbstractBase.Adapter<U> {
+
+ /**
+ * Creates a new annotation adapter.
+ *
+ * @param handler The handler that determines how a method is implemented.
+ */
+ protected AnnotationAdapter(MethodRegistry.Handler handler) {
+ this(handler,
+ MethodAttributeAppender.ForInstrumentedMethod.INCLUDING_RECEIVER,
+ Transformer.NoOp.<MethodDescription>make());
+ }
+
+ /**
+ * Creates a new annotation adapter.
+ *
+ * @param handler The handler that determines how a method is implemented.
+ * @param methodAttributeAppenderFactory The method attribute appender factory to apply onto the method that is currently being implemented.
+ * @param transformer The method transformer to apply onto the method that is currently being implemented.
+ */
+ protected AnnotationAdapter(MethodRegistry.Handler handler,
+ MethodAttributeAppender.Factory methodAttributeAppenderFactory,
+ Transformer<MethodDescription> transformer) {
+ super(handler, methodAttributeAppenderFactory, transformer);
+ }
+
+ @Override
+ public MethodDefinition<U> receiverType(TypeDescription.Generic receiverType) {
+ return new MethodDefinitionAdapter(new MethodDescription.Token(token.getName(),
+ token.getModifiers(),
+ token.getTypeVariableTokens(),
+ token.getReturnType(),
+ token.getParameterTokens(),
+ token.getExceptionTypes(),
+ token.getAnnotations(),
+ token.getDefaultValue(),
+ receiverType)).new AnnotationAdapter(handler, methodAttributeAppenderFactory, transformer);
+ }
+
+ @Override
+ public MethodDefinition<U> annotateMethod(Collection<? extends AnnotationDescription> annotations) {
+ return new MethodDefinitionAdapter(new MethodDescription.Token(token.getName(),
+ token.getModifiers(),
+ token.getTypeVariableTokens(),
+ token.getReturnType(),
+ token.getParameterTokens(),
+ token.getExceptionTypes(),
+ CompoundList.of(token.getAnnotations(), new ArrayList<AnnotationDescription>(annotations)),
+ token.getDefaultValue(),
+ token.getReceiverType())).new AnnotationAdapter(handler, methodAttributeAppenderFactory, transformer);
+ }
+
+ @Override
+ public MethodDefinition<U> annotateParameter(int index, Collection<? extends AnnotationDescription> annotations) {
+ List<ParameterDescription.Token> parameterTokens = new ArrayList<ParameterDescription.Token>(token.getParameterTokens());
+ parameterTokens.set(index, new ParameterDescription.Token(token.getParameterTokens().get(index).getType(),
+ CompoundList.of(token.getParameterTokens().get(index).getAnnotations(), new ArrayList<AnnotationDescription>(annotations)),
+ token.getParameterTokens().get(index).getName(),
+ token.getParameterTokens().get(index).getModifiers()));
+ return new MethodDefinitionAdapter(new MethodDescription.Token(token.getName(),
+ token.getModifiers(),
+ token.getTypeVariableTokens(),
+ token.getReturnType(),
+ parameterTokens,
+ token.getExceptionTypes(),
+ token.getAnnotations(),
+ token.getDefaultValue(),
+ token.getReceiverType())).new AnnotationAdapter(handler, methodAttributeAppenderFactory, transformer);
+ }
+
+ @Override
+ protected MethodDefinition<U> materialize(MethodRegistry.Handler handler,
+ MethodAttributeAppender.Factory methodAttributeAppenderFactory,
+ Transformer<MethodDescription> transformer) {
+ return new AnnotationAdapter(handler, methodAttributeAppenderFactory, transformer);
+ }
+
+ @Override
+ protected Builder<U> materialize() {
+ return Builder.AbstractBase.Adapter.this.materialize(instrumentedType.withMethod(token),
+ fieldRegistry,
+ methodRegistry.prepend(new LatentMatcher.ForMethodToken(token),
+ handler,
+ methodAttributeAppenderFactory,
+ transformer),
+ typeAttributeAppender,
+ asmVisitorWrapper,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ typeValidation,
+ ignoredMethods);
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private MethodDefinitionAdapter getOuter() {
+ return MethodDefinitionAdapter.this;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && super.equals(other)
+ && getOuter().equals(((AnnotationAdapter) other).getOuter());
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + getOuter().hashCode();
+ }
+ }
+ }
+
+ /**
+ * An adapter for matching an existing method.
+ */
+ protected class MethodMatchAdapter extends MethodDefinition.ImplementationDefinition.AbstractBase<U> {
+
+ /**
+ * The method matcher of this adapter.
+ */
+ private final LatentMatcher<? super MethodDescription> matcher;
+
+ /**
+ * Creates a new method match adapter.
+ *
+ * @param matcher The method matcher of this adapter.
+ */
+ protected MethodMatchAdapter(LatentMatcher<? super MethodDescription> matcher) {
+ this.matcher = matcher;
+ }
+
+ @Override
+ public MethodDefinition.ReceiverTypeDefinition<U> intercept(Implementation implementation) {
+ return materialize(new MethodRegistry.Handler.ForImplementation(implementation));
+ }
+
+ @Override
+ public MethodDefinition.ReceiverTypeDefinition<U> withoutCode() {
+ return materialize(MethodRegistry.Handler.ForAbstractMethod.INSTANCE);
+ }
+
+ @Override
+ public MethodDefinition.ReceiverTypeDefinition<U> defaultValue(AnnotationValue<?, ?> annotationValue) {
+ return materialize(new MethodRegistry.Handler.ForAnnotationValue(annotationValue));
+ }
+
+ /**
+ * Materializes the method definition with the supplied handler.
+ *
+ * @param handler The handler that implementes any method matched by this instances matcher.
+ * @return A method definition where any matched method is implemented by the supplied handler.
+ */
+ private MethodDefinition.ReceiverTypeDefinition<U> materialize(MethodRegistry.Handler handler) {
+ return new AnnotationAdapter(handler);
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private Adapter<?> getOuter() {
+ return Adapter.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ @SuppressWarnings("unchecked")
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && matcher.equals(((MethodMatchAdapter) other).matcher)
+ && getOuter().equals(((MethodMatchAdapter) other).getOuter());
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return 31 * getOuter().hashCode() + matcher.hashCode();
+ }
+
+ /**
+ * An annotation adapter for implementing annotations during a method definition.
+ */
+ protected class AnnotationAdapter extends MethodDefinition.AbstractBase.Adapter<U> {
+
+ /**
+ * Creates a new annotation adapter.
+ *
+ * @param handler The handler that determines how a method is implemented.
+ */
+ protected AnnotationAdapter(MethodRegistry.Handler handler) {
+ this(handler, MethodAttributeAppender.NoOp.INSTANCE, Transformer.NoOp.<MethodDescription>make());
+ }
+
+ /**
+ * Creates a new annotation adapter.
+ *
+ * @param handler The handler that determines how a method is implemented.
+ * @param methodAttributeAppenderFactory The method attribute appender factory to apply onto the method that is currently being implemented.
+ * @param transformer The method transformer to apply onto the method that is currently being implemnted.
+ */
+ protected AnnotationAdapter(MethodRegistry.Handler handler,
+ MethodAttributeAppender.Factory methodAttributeAppenderFactory,
+ Transformer<MethodDescription> transformer) {
+ super(handler, methodAttributeAppenderFactory, transformer);
+ }
+
+ @Override
+ public MethodDefinition<U> receiverType(TypeDescription.Generic receiverType) {
+ return new AnnotationAdapter(handler,
+ new MethodAttributeAppender.Factory.Compound(methodAttributeAppenderFactory, new MethodAttributeAppender.ForReceiverType(receiverType)),
+ transformer);
+ }
+
+ @Override
+ public MethodDefinition<U> annotateMethod(Collection<? extends AnnotationDescription> annotations) {
+ return new AnnotationAdapter(handler,
+ new MethodAttributeAppender.Factory.Compound(methodAttributeAppenderFactory, new MethodAttributeAppender.Explicit(new ArrayList<AnnotationDescription>(annotations))),
+ transformer);
+ }
+
+ @Override
+ public MethodDefinition<U> annotateParameter(int index, Collection<? extends AnnotationDescription> annotations) {
+ return new AnnotationAdapter(handler,
+ new MethodAttributeAppender.Factory.Compound(methodAttributeAppenderFactory, new MethodAttributeAppender.Explicit(index, new ArrayList<AnnotationDescription>(annotations))),
+ transformer);
+ }
+
+ @Override
+ protected MethodDefinition<U> materialize(MethodRegistry.Handler handler,
+ MethodAttributeAppender.Factory methodAttributeAppenderFactory,
+ Transformer<MethodDescription> transformer) {
+ return new AnnotationAdapter(handler, methodAttributeAppenderFactory, transformer);
+ }
+
+ @Override
+ protected Builder<U> materialize() {
+ return Builder.AbstractBase.Adapter.this.materialize(instrumentedType,
+ fieldRegistry,
+ methodRegistry.prepend(matcher, handler, methodAttributeAppenderFactory, transformer),
+ typeAttributeAppender,
+ asmVisitorWrapper,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ typeValidation,
+ ignoredMethods);
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private MethodMatchAdapter getOuter() {
+ return MethodMatchAdapter.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ @SuppressWarnings("unchecked")
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && super.equals(other)
+ && getOuter().equals(((AnnotationAdapter) other).getOuter());
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return super.hashCode() + getOuter().hashCode();
+ }
+ }
+ }
+
+ /**
+ * An adapter for optionally matching methods defined by declared interfaces.
+ */
+ protected class OptionalMethodMatchAdapter extends Builder.AbstractBase.Delegator<U> implements MethodDefinition.ImplementationDefinition.Optional<U> {
+
+ /**
+ * The interfaces whose methods are optionally matched.
+ */
+ private final TypeList.Generic interfaces;
+
+ /**
+ * Creates a new optional method match adapter.
+ *
+ * @param interfaces The interfaces whose methods are optionally matched.
+ */
+ protected OptionalMethodMatchAdapter(TypeList.Generic interfaces) {
+ this.interfaces = interfaces;
+ }
+
+ @Override
+ protected Builder<U> materialize() {
+ return Adapter.this.materialize(instrumentedType.withInterfaces(interfaces),
+ fieldRegistry,
+ methodRegistry,
+ typeAttributeAppender,
+ asmVisitorWrapper,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ typeValidation,
+ ignoredMethods);
+ }
+
+ @Override
+ public MethodDefinition.ReceiverTypeDefinition<U> intercept(Implementation implementation) {
+ return interfaceType().intercept(implementation);
+ }
+
+ @Override
+ public MethodDefinition.ReceiverTypeDefinition<U> withoutCode() {
+ return interfaceType().withoutCode();
+ }
+
+ @Override
+ public MethodDefinition.ReceiverTypeDefinition<U> defaultValue(AnnotationValue<?, ?> annotationValue) {
+ return interfaceType().defaultValue(annotationValue);
+ }
+
+ @Override
+ public <V> MethodDefinition.ReceiverTypeDefinition<U> defaultValue(V value, Class<? extends V> type) {
+ return interfaceType().defaultValue(value, type);
+ }
+
+ /**
+ * Returns a matcher for the interfaces' methods.
+ *
+ * @return A matcher for the interfaces' methods.
+ */
+ private MethodDefinition.ImplementationDefinition<U> interfaceType() {
+ ElementMatcher.Junction<TypeDescription> elementMatcher = none();
+ for (TypeDescription typeDescription : interfaces.asErasures()) {
+ elementMatcher = elementMatcher.or(isSuperTypeOf(typeDescription));
+ }
+ return materialize().invokable(isDeclaredBy(isInterface().and(elementMatcher)));
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private Builder.AbstractBase.Adapter<U> getOuter() {
+ return Builder.AbstractBase.Adapter.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ @SuppressWarnings("unchecked")
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (other == null || getClass() != other.getClass()) return false;
+ OptionalMethodMatchAdapter that = (OptionalMethodMatchAdapter) other;
+ return interfaces.equals(that.interfaces)
+ && getOuter().equals(that.getOuter());
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return 31 * getOuter().hashCode() + interfaces.hashCode();
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * A dynamic type that has not yet been loaded by a given {@link java.lang.ClassLoader}.
+ *
+ * @param <T> The most specific known loaded type that is implemented by this dynamic type, usually the
+ * type itself, an interface or the direct super class.
+ */
+ interface Unloaded<T> extends DynamicType {
+
+ /**
+ * Attempts to load this dynamic type including all of its auxiliary types, if any. If the class loader
+ * is the bootstrap class loader, a new class loader is created for loading those types. If the class loader
+ * is an instance of {@link InjectionClassLoader}, the class is injected. And otherwise, the types are injected
+ * into the provided class loader.
+ *
+ * @param classLoader The class loader to use for this class loading.
+ * @return This dynamic type in its loaded state.
+ */
+ Loaded<T> load(ClassLoader classLoader);
+
+ /**
+ * Attempts to load this dynamic type including all of its auxiliary types, if any.
+ *
+ * @param classLoader The class loader to use for this class loading.
+ * @param classLoadingStrategy The class loader strategy which should be used for this class loading.
+ * @param <S> The least specific type of class loader this strategy can apply to.
+ * @return This dynamic type in its loaded state.
+ * @see net.bytebuddy.dynamic.loading.ClassLoadingStrategy.Default
+ */
+ <S extends ClassLoader> Loaded<T> load(S classLoader, ClassLoadingStrategy<? super S> classLoadingStrategy);
+
+ /**
+ * Includes the provided dynamic types as auxiliary types of this instance.
+ *
+ * @param dynamicType The dynamic types to include.
+ * @return A copy of this unloaded dynamic type which includes the provided dynamic types.
+ */
+ Unloaded<T> include(DynamicType... dynamicType);
+
+ /**
+ * Includes the provided dynamic types as auxiliary types of this instance.
+ *
+ * @param dynamicTypes The dynamic types to include.
+ * @return A copy of this unloaded dynamic type which includes the provided dynamic types.
+ */
+ Unloaded<T> include(List<? extends DynamicType> dynamicTypes);
+ }
+
+ /**
+ * A dynamic type that has been loaded into the running instance of the Java virtual machine.
+ *
+ * @param <T> The most specific known loaded type that is implemented by this dynamic type, usually the
+ * type itself, an interface or the direct super class.
+ */
+ interface Loaded<T> extends DynamicType {
+
+ /**
+ * Returns the loaded main class.
+ *
+ * @return A loaded class representation of this dynamic type.
+ */
+ Class<? extends T> getLoaded();
+
+ /**
+ * <p>
+ * Returns a map of all loaded auxiliary types to this dynamic type.
+ * </p>
+ * <p>
+ * <b>Note</b>: The type descriptions will most likely differ from the binary representation of this type.
+ * Normally, annotations and intercepted methods are not added to the type descriptions of auxiliary types.
+ * </p>
+ *
+ * @return A mapping from the fully qualified names of all auxiliary types to their loaded class representations.
+ */
+ Map<TypeDescription, Class<?>> getLoadedAuxiliaryTypes();
+ }
+
+ /**
+ * A default implementation of a dynamic type.
+ */
+ class Default implements DynamicType {
+
+ /**
+ * The file name extension for Java class files.
+ */
+ private static final String CLASS_FILE_EXTENSION = ".class";
+
+ /**
+ * The default version of a jar file manifest.
+ */
+ private static final String MANIFEST_VERSION = "1.0";
+
+ /**
+ * The size of a writing buffer.
+ */
+ private static final int BUFFER_SIZE = 1024;
+
+ /**
+ * A convenience index for the beginning of an array to improve the readability of the code.
+ */
+ private static final int FROM_BEGINNING = 0;
+
+ /**
+ * A convenience representative of an {@link java.io.InputStream}'s end to improve the readability of the code.
+ */
+ private static final int END_OF_FILE = -1;
+
+ /**
+ * A suffix for temporary files.
+ */
+ private static final String TEMP_SUFFIX = "tmp";
+
+ /**
+ * A type description of this dynamic type.
+ */
+ protected final TypeDescription typeDescription;
+
+ /**
+ * The byte array representing this dynamic type.
+ */
+ protected final byte[] binaryRepresentation;
+
+ /**
+ * The loaded type initializer for this dynamic type.
+ */
+ protected final LoadedTypeInitializer loadedTypeInitializer;
+
+ /**
+ * A list of auxiliary types for this dynamic type.
+ */
+ protected final List<? extends DynamicType> auxiliaryTypes;
+
+ /**
+ * Creates a new dynamic type.
+ *
+ * @param typeDescription A description of this dynamic type.
+ * @param binaryRepresentation A byte array containing the binary representation of this dynamic type. The array must not be modified.
+ * @param loadedTypeInitializer The loaded type initializer of this dynamic type.
+ * @param auxiliaryTypes The auxiliary type required for this dynamic type.
+ */
+ @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "The array is not to be modified by contract")
+ public Default(TypeDescription typeDescription,
+ byte[] binaryRepresentation,
+ LoadedTypeInitializer loadedTypeInitializer,
+ List<? extends DynamicType> auxiliaryTypes) {
+ this.typeDescription = typeDescription;
+ this.binaryRepresentation = binaryRepresentation;
+ this.loadedTypeInitializer = loadedTypeInitializer;
+ this.auxiliaryTypes = auxiliaryTypes;
+ }
+
+ @Override
+ public TypeDescription getTypeDescription() {
+ return typeDescription;
+ }
+
+ @Override
+ public Map<TypeDescription, byte[]> getAllTypes() {
+ Map<TypeDescription, byte[]> allTypes = new LinkedHashMap<TypeDescription, byte[]>();
+ allTypes.put(typeDescription, binaryRepresentation);
+ for (DynamicType auxiliaryType : auxiliaryTypes) {
+ allTypes.putAll(auxiliaryType.getAllTypes());
+ }
+ return allTypes;
+ }
+
+ @Override
+ public Map<TypeDescription, LoadedTypeInitializer> getLoadedTypeInitializers() {
+ Map<TypeDescription, LoadedTypeInitializer> classLoadingCallbacks = new HashMap<TypeDescription, LoadedTypeInitializer>();
+ for (DynamicType auxiliaryType : auxiliaryTypes) {
+ classLoadingCallbacks.putAll(auxiliaryType.getLoadedTypeInitializers());
+ }
+ classLoadingCallbacks.put(typeDescription, loadedTypeInitializer);
+ return classLoadingCallbacks;
+ }
+
+ @Override
+ public boolean hasAliveLoadedTypeInitializers() {
+ for (LoadedTypeInitializer loadedTypeInitializer : getLoadedTypeInitializers().values()) {
+ if (loadedTypeInitializer.isAlive()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "The array is not to be modified by contract")
+ public byte[] getBytes() {
+ return binaryRepresentation;
+ }
+
+ @Override
+ public Map<TypeDescription, byte[]> getAuxiliaryTypes() {
+ Map<TypeDescription, byte[]> auxiliaryTypes = new HashMap<TypeDescription, byte[]>();
+ for (DynamicType auxiliaryType : this.auxiliaryTypes) {
+ auxiliaryTypes.put(auxiliaryType.getTypeDescription(), auxiliaryType.getBytes());
+ auxiliaryTypes.putAll(auxiliaryType.getAuxiliaryTypes());
+ }
+ return auxiliaryTypes;
+ }
+
+ @Override
+ public Map<TypeDescription, File> saveIn(File folder) throws IOException {
+ Map<TypeDescription, File> savedFiles = new HashMap<TypeDescription, File>();
+ File target = new File(folder, typeDescription.getName().replace('.', File.separatorChar) + CLASS_FILE_EXTENSION);
+ if (target.getParentFile() != null && !target.getParentFile().isDirectory() && !target.getParentFile().mkdirs()) {
+ throw new IllegalArgumentException("Could not create directory: " + target.getParentFile());
+ }
+ OutputStream outputStream = new FileOutputStream(target);
+ try {
+ outputStream.write(binaryRepresentation);
+ } finally {
+ outputStream.close();
+ }
+ savedFiles.put(typeDescription, target);
+ for (DynamicType auxiliaryType : auxiliaryTypes) {
+ savedFiles.putAll(auxiliaryType.saveIn(folder));
+ }
+ return savedFiles;
+ }
+
+ @Override
+ public File inject(File sourceJar, File targetJar) throws IOException {
+ JarInputStream jarInputStream = new JarInputStream(new BufferedInputStream(new FileInputStream(sourceJar)));
+ try {
+ if (!targetJar.isFile() && !targetJar.createNewFile()) {
+ throw new IllegalArgumentException("Could not create file: " + targetJar);
+ }
+ JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(targetJar), jarInputStream.getManifest());
+ try {
+ Map<TypeDescription, byte[]> rawAuxiliaryTypes = getAuxiliaryTypes();
+ Map<String, byte[]> files = new HashMap<String, byte[]>();
+ for (Map.Entry<TypeDescription, byte[]> entry : rawAuxiliaryTypes.entrySet()) {
+ files.put(entry.getKey().getInternalName() + CLASS_FILE_EXTENSION, entry.getValue());
+ }
+ files.put(typeDescription.getInternalName() + CLASS_FILE_EXTENSION, binaryRepresentation);
+ JarEntry jarEntry;
+ while ((jarEntry = jarInputStream.getNextJarEntry()) != null) {
+ jarOutputStream.putNextEntry(jarEntry);
+ byte[] replacement = files.remove(jarEntry.getName());
+ if (replacement == null) {
+ byte[] buffer = new byte[BUFFER_SIZE];
+ int index;
+ while ((index = jarInputStream.read(buffer)) != END_OF_FILE) {
+ jarOutputStream.write(buffer, FROM_BEGINNING, index);
+ }
+ } else {
+ jarOutputStream.write(replacement);
+ }
+ jarInputStream.closeEntry();
+ jarOutputStream.closeEntry();
+ }
+ for (Map.Entry<String, byte[]> entry : files.entrySet()) {
+ jarOutputStream.putNextEntry(new JarEntry(entry.getKey()));
+ jarOutputStream.write(entry.getValue());
+ jarOutputStream.closeEntry();
+ }
+ } finally {
+ jarOutputStream.close();
+ }
+ } finally {
+ jarInputStream.close();
+ }
+ return targetJar;
+ }
+
+ @Override
+ public File inject(File jar) throws IOException {
+ File temporary = inject(jar, File.createTempFile(jar.getName(), TEMP_SUFFIX));
+ try {
+ InputStream jarInputStream = new BufferedInputStream(new FileInputStream(temporary));
+ try {
+ OutputStream jarOutputStream = new BufferedOutputStream(new FileOutputStream(jar));
+ try {
+ byte[] buffer = new byte[BUFFER_SIZE];
+ int index;
+ while ((index = jarInputStream.read(buffer)) != END_OF_FILE) {
+ jarOutputStream.write(buffer, FROM_BEGINNING, index);
+ }
+ } finally {
+ jarOutputStream.close();
+ }
+ } finally {
+ jarInputStream.close();
+ }
+ } finally {
+ if (!temporary.delete()) {
+ temporary.deleteOnExit();
+ }
+ }
+ return jar;
+ }
+
+ @Override
+ public File toJar(File file) throws IOException {
+ Manifest manifest = new Manifest();
+ manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, MANIFEST_VERSION);
+ return toJar(file, manifest);
+ }
+
+ @Override
+ public File toJar(File file, Manifest manifest) throws IOException {
+ if (!file.isFile() && !file.createNewFile()) {
+ throw new IllegalArgumentException("Could not create file: " + file);
+ }
+ JarOutputStream outputStream = new JarOutputStream(new FileOutputStream(file), manifest);
+ try {
+ for (Map.Entry<TypeDescription, byte[]> entry : getAuxiliaryTypes().entrySet()) {
+ outputStream.putNextEntry(new JarEntry(entry.getKey().getInternalName() + CLASS_FILE_EXTENSION));
+ outputStream.write(entry.getValue());
+ outputStream.closeEntry();
+ }
+ outputStream.putNextEntry(new JarEntry(typeDescription.getInternalName() + CLASS_FILE_EXTENSION));
+ outputStream.write(binaryRepresentation);
+ outputStream.closeEntry();
+ } finally {
+ outputStream.close();
+ }
+ return file;
+ }
+
+ @Override // HE: Remove when Lombok support for shadowed types is added.
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (other == null || getClass() != other.getClass()) return false;
+ Default aDefault = (Default) other;
+ return auxiliaryTypes.equals(aDefault.auxiliaryTypes)
+ && Arrays.equals(binaryRepresentation, aDefault.binaryRepresentation)
+ && typeDescription.equals(aDefault.typeDescription)
+ && loadedTypeInitializer.equals(aDefault.loadedTypeInitializer);
+
+ }
+
+ @Override // HE: Remove when Lombok support for shadowed types is added.
+ public int hashCode() {
+ int result = typeDescription.hashCode();
+ result = 31 * result + Arrays.hashCode(binaryRepresentation);
+ result = 31 * result + loadedTypeInitializer.hashCode();
+ result = 31 * result + auxiliaryTypes.hashCode();
+ return result;
+ }
+
+ /**
+ * A default implementation of an unloaded dynamic type.
+ *
+ * @param <T> The most specific known loaded type that is implemented by this dynamic type, usually the
+ * type itself, an interface or the direct super class.
+ */
+ public static class Unloaded<T> extends Default implements DynamicType.Unloaded<T> {
+
+ /**
+ * The type resolution strategy to use for initializing the dynamic type.
+ */
+ private final TypeResolutionStrategy.Resolved typeResolutionStrategy;
+
+ /**
+ * Creates a new unloaded representation of a dynamic type.
+ *
+ * @param typeDescription A description of this dynamic type.
+ * @param binaryRepresentation An array of byte of the binary representation of this dynamic type.
+ * @param loadedTypeInitializer The type initializer of this dynamic type.
+ * @param auxiliaryTypes The auxiliary types that are required for this dynamic type.
+ * @param typeResolutionStrategy The type resolution strategy to use for initializing the dynamic type.
+ */
+ public Unloaded(TypeDescription typeDescription,
+ byte[] binaryRepresentation,
+ LoadedTypeInitializer loadedTypeInitializer,
+ List<? extends DynamicType> auxiliaryTypes,
+ TypeResolutionStrategy.Resolved typeResolutionStrategy) {
+ super(typeDescription, binaryRepresentation, loadedTypeInitializer, auxiliaryTypes);
+ this.typeResolutionStrategy = typeResolutionStrategy;
+ }
+
+ @Override
+ public DynamicType.Loaded<T> load(ClassLoader classLoader) {
+ if (classLoader == null) {
+ return load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER);
+ } else if (classLoader instanceof InjectionClassLoader) {
+ return load((InjectionClassLoader) classLoader, InjectionClassLoader.Strategy.INSTANCE);
+ } else {
+ return load(classLoader, ClassLoadingStrategy.Default.INJECTION);
+ }
+ }
+
+ @Override
+ public <S extends ClassLoader> DynamicType.Loaded<T> load(S classLoader, ClassLoadingStrategy<? super S> classLoadingStrategy) {
+ return new Default.Loaded<T>(typeDescription,
+ binaryRepresentation,
+ loadedTypeInitializer,
+ auxiliaryTypes,
+ typeResolutionStrategy.initialize(this, classLoader, classLoadingStrategy));
+ }
+
+ @Override
+ public DynamicType.Unloaded<T> include(DynamicType... dynamicType) {
+ return include(Arrays.asList(dynamicType));
+ }
+
+ @Override
+ public DynamicType.Unloaded<T> include(List<? extends DynamicType> dynamicType) {
+ return new Default.Unloaded<T>(typeDescription,
+ binaryRepresentation,
+ loadedTypeInitializer,
+ CompoundList.of(auxiliaryTypes, dynamicType),
+ typeResolutionStrategy);
+ }
+
+ @Override // HE: Remove when Lombok support for shadowed types is added.
+ public boolean equals(Object object) {
+ if (this == object) return true;
+ if (object == null || getClass() != object.getClass()) return false;
+ if (!super.equals(object)) return false;
+ Default.Unloaded<?> unloaded = (Default.Unloaded<?>) object;
+ return typeResolutionStrategy.equals(unloaded.typeResolutionStrategy);
+ }
+
+ @Override // HE: Remove when Lombok support for shadowed types is added.
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + typeResolutionStrategy.hashCode();
+ return result;
+ }
+ }
+
+ /**
+ * A default implementation of a loaded dynamic type.
+ *
+ * @param <T> The most specific known loaded type that is implemented by this dynamic type, usually the
+ * type itself, an interface or the direct super class.
+ */
+ protected static class Loaded<T> extends Default implements DynamicType.Loaded<T> {
+
+ /**
+ * The loaded types for the given loaded dynamic type.
+ */
+ private final Map<TypeDescription, Class<?>> loadedTypes;
+
+ /**
+ * Creates a new representation of a loaded dynamic type.
+ *
+ * @param typeDescription A description of this dynamic type.
+ * @param typeByte An array of byte of the binary representation of this dynamic type.
+ * @param loadedTypeInitializer The type initializer of this dynamic type.
+ * @param auxiliaryTypes The auxiliary types that are required for this dynamic type.
+ * @param loadedTypes A map of loaded types for this dynamic type and all its auxiliary types.
+ */
+ protected Loaded(TypeDescription typeDescription,
+ byte[] typeByte,
+ LoadedTypeInitializer loadedTypeInitializer,
+ List<? extends DynamicType> auxiliaryTypes,
+ Map<TypeDescription, Class<?>> loadedTypes) {
+ super(typeDescription, typeByte, loadedTypeInitializer, auxiliaryTypes);
+ this.loadedTypes = loadedTypes;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Class<? extends T> getLoaded() {
+ return (Class<? extends T>) loadedTypes.get(typeDescription);
+ }
+
+ @Override
+ public Map<TypeDescription, Class<?>> getLoadedAuxiliaryTypes() {
+ Map<TypeDescription, Class<?>> loadedAuxiliaryTypes = new HashMap<TypeDescription, Class<?>>(
+ loadedTypes);
+ loadedAuxiliaryTypes.remove(typeDescription);
+ return loadedAuxiliaryTypes;
+ }
+
+ @Override // HE: Remove when Lombok support for shadowed types is added.
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && super.equals(other) && loadedTypes.equals(((Default.Loaded) other).loadedTypes);
+ }
+
+ @Override // HE: Remove when Lombok support for shadowed types is added.
+ public int hashCode() {
+ return 31 * super.hashCode() + loadedTypes.hashCode();
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/Nexus.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/Nexus.java
new file mode 100644
index 0000000..f026427
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/Nexus.java
@@ -0,0 +1,195 @@
+package net.bytebuddy.dynamic;
+
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * <p>
+ * This nexus is a global dispatcher for initializing classes with
+ * {@link net.bytebuddy.implementation.LoadedTypeInitializer}s. To do so, this class is to be loaded
+ * by the system class loader in an explicit manner. Any instrumented class is then injected a code
+ * block into its static type initializer that makes a call to this very same nexus which had the
+ * loaded type initializer registered before hand.
+ * </p>
+ * <p>
+ * <b>Note</b>: Availability of the {@link Nexus} class and its injection into the system class loader
+ * can be disabled entirely by setting the {@link Nexus#PROPERTY} system property to {@code false}.
+ * </p>
+ * <p>
+ * <b>Important</b>: The nexus must never be accessed directly but only by the {@link NexusAccessor}
+ * which makes sure that the nexus is loaded by the system class loader. Otherwise, a class might not
+ * be able to initialize itself if it is loaded by different class loader that does not have the
+ * system class loader in its hierarchy.
+ * </p>
+ */
+public class Nexus extends WeakReference<ClassLoader> {
+
+ /**
+ * A system property that allows to disable the use of the {@link Nexus} class which is normally injected into the system class loader.
+ */
+ public static final String PROPERTY = "net.bytebuddy.nexus.disabled";
+
+ /**
+ * An type-safe constant for a non-operational reference queue.
+ */
+ protected static final ReferenceQueue<ClassLoader> NO_QUEUE = null;
+
+ /**
+ * A map of keys identifying a loaded type by its name and class loader mapping their
+ * potential {@link net.bytebuddy.implementation.LoadedTypeInitializer} where the class
+ * loader of these initializers is however irrelevant.
+ */
+ private static final ConcurrentMap<Nexus, Object> TYPE_INITIALIZERS = new ConcurrentHashMap<Nexus, Object>();
+
+ /**
+ * The name of a type for which a loaded type initializer is registered.
+ */
+ private final String name;
+
+ /**
+ * The class loader's hash code upon registration.
+ */
+ private final int classLoaderHashCode;
+
+ /**
+ * A random value that uniquely identifies a Nexus entry in order to avoid conflicts when
+ * applying the self-initialization strategy in multiple transformations.
+ */
+ private final int identification;
+
+ /**
+ * Creates a key for identifying a loaded type initializer.
+ *
+ * @param type The loaded type for which a key is to be created.
+ * @param identification An identification for the initializer to run.
+ */
+ private Nexus(Class<?> type, int identification) {
+ this(nonAnonymous(type.getName()), type.getClassLoader(), NO_QUEUE, identification);
+ }
+
+ /**
+ * Creates a key for identifying a loaded type initializer.
+ *
+ * @param name The name of a type for which a loaded type initializer is registered.
+ * @param classLoader The class loader for which a loaded type initializer is registered.
+ * @param referenceQueue The reference queue to notify upon the class loader's collection or {@code null} if no queue should be notified.
+ * @param identification An identification for the initializer to run.
+ */
+ private Nexus(String name, ClassLoader classLoader, ReferenceQueue<? super ClassLoader> referenceQueue, int identification) {
+ super(classLoader, classLoader == null
+ ? null
+ : referenceQueue);
+ this.name = name;
+ classLoaderHashCode = System.identityHashCode(classLoader);
+ this.identification = identification;
+ }
+
+ /**
+ * Normalizes a type name if it is loaded by an anonymous class loader.
+ *
+ * @param typeName The name as returned by {@link Class#getName()}.
+ * @return The non-anonymous name of the given class.
+ */
+ private static String nonAnonymous(String typeName) {
+ int anonymousLoaderIndex = typeName.indexOf('/');
+ return anonymousLoaderIndex == -1
+ ? typeName
+ : typeName.substring(0, anonymousLoaderIndex);
+ }
+
+ /**
+ * <p>
+ * Initializes a loaded type. This method must only be invoked via the system class loader.
+ * </p>
+ * <p>
+ * <b>Important</b>: This method must never be called directly but only by using a {@link NexusAccessor.InitializationAppender} which enforces to
+ * access this class for the system class loader to assure a VM global singleton. This avoids a duplication of the class if this nexus is loaded
+ * by different class loaders. For this reason, the last parameter must not use a Byte Buddy specific type as those types can be loaded by
+ * different class loaders, too. Any access of the instance is done using Java reflection instead.
+ * </p>
+ *
+ * @param type The loaded type to initialize.
+ * @param identification An identification for the initializer to run.
+ * @throws Exception If an exception occurs.
+ */
+ @SuppressWarnings("unused")
+ public static void initialize(Class<?> type, int identification) throws Exception {
+ Object typeInitializer = TYPE_INITIALIZERS.remove(new Nexus(type, identification));
+ if (typeInitializer != null) {
+ typeInitializer.getClass().getMethod("onLoad", Class.class).invoke(typeInitializer, type);
+ }
+ }
+
+ /**
+ * <p>
+ * Registers a new loaded type initializer.
+ * </p>
+ * <p>
+ * <b>Important</b>: This method must never be called directly but only by using a {@link NexusAccessor} which enforces to access this class
+ * for the system class loader to assure a VM global singleton. This avoids a duplication of the class if this nexus is loaded by different class
+ * loaders. For this reason, the last parameter must not use a Byte Buddy specific type as those types can be loaded by different class loaders,
+ * too. Any access of the instance is done using Java reflection instead.
+ * </p>
+ *
+ * @param name The name of the type for the loaded type initializer.
+ * @param classLoader The class loader of the type for the loaded type initializer.
+ * @param referenceQueue The reference queue to notify upon the class loader's collection which will be enqueued a reference which can be
+ * handed to {@link Nexus#clean(Reference)} or {@code null} if no reference queue should be notified.
+ * @param identification An identification for the initializer to run.
+ * @param typeInitializer The type initializer to register. The initializer must be an instance
+ * of {@link net.bytebuddy.implementation.LoadedTypeInitializer} where
+ * it does however not matter which class loader loaded this latter type.
+ */
+ public static void register(String name, ClassLoader classLoader, ReferenceQueue<? super ClassLoader> referenceQueue, int identification, Object typeInitializer) {
+ TYPE_INITIALIZERS.put(new Nexus(name, classLoader, referenceQueue, identification), typeInitializer);
+ }
+
+ /**
+ * <p>
+ * Cleans any stale entries from this nexus. Entries are considered stale if their class loader was collected before a class was initialized.
+ * </p>
+ * <p>
+ * <b>Important</b>: This method must never be called directly but only by using a {@link NexusAccessor} which enforces to access this class
+ * for the system class loader to assure a VM global singleton. This avoids a duplication of the class if this nexus is loaded by different class
+ * loaders. For this reason, the last parameter must not use a Byte Buddy specific type as those types can be loaded by different class loaders,
+ * too. Any access of the instance is done using Java reflection instead.
+ * </p>
+ *
+ * @param reference The stale reference to clean.
+ */
+ public static void clean(Reference<? super ClassLoader> reference) {
+ TYPE_INITIALIZERS.remove(reference);
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (this == object) return true;
+ if (object == null || getClass() != object.getClass()) return false;
+ Nexus nexus = (Nexus) object;
+ return classLoaderHashCode == nexus.classLoaderHashCode
+ && identification == nexus.identification
+ && name.equals(nexus.name)
+ && get() == nexus.get();
+ }
+
+ @Override
+ public int hashCode() {
+ int result = name.hashCode();
+ result = 31 * result + classLoaderHashCode;
+ result = 31 * result + identification;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "Nexus{" +
+ "name='" + name + '\'' +
+ ", classLoaderHashCode=" + classLoaderHashCode +
+ ", identification=" + identification +
+ ", classLoader=" + get() +
+ '}';
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/NexusAccessor.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/NexusAccessor.java
new file mode 100644
index 0000000..21da085
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/NexusAccessor.java
@@ -0,0 +1,323 @@
+package net.bytebuddy.dynamic;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.loading.ClassInjector;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.LoadedTypeInitializer;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.implementation.bytecode.Removal;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.collection.ArrayFactory;
+import net.bytebuddy.implementation.bytecode.constant.ClassConstant;
+import net.bytebuddy.implementation.bytecode.constant.IntegerConstant;
+import net.bytebuddy.implementation.bytecode.constant.NullConstant;
+import net.bytebuddy.implementation.bytecode.constant.TextConstant;
+import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
+import org.objectweb.asm.MethodVisitor;
+
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Arrays;
+import java.util.Collections;
+
+/**
+ * The Nexus accessor is creating a VM-global singleton {@link Nexus} such that it can be seen by all class loaders of
+ * a virtual machine. Furthermore, it provides an API to access this global instance.
+ */
+ at EqualsAndHashCode
+public class NexusAccessor {
+
+ /**
+ * The dispatcher to use.
+ */
+ private static final Dispatcher DISPATCHER = AccessController.doPrivileged(Dispatcher.CreationAction.INSTANCE);
+
+ /**
+ * The reference queue that is notified upon a GC eligible {@link Nexus} entry or {@code null} if no such queue should be notified.
+ */
+ private final ReferenceQueue<? super ClassLoader> referenceQueue;
+
+ /**
+ * Creates a new accessor for the {@link Nexus} without any active management of stale references within a nexus.
+ */
+ public NexusAccessor() {
+ this(Nexus.NO_QUEUE);
+ }
+
+ /**
+ * Creates a new accessor for a {@link Nexus} where any GC eligible are enqueued to the supplid reference queue. Any such enqueued
+ * reference can be explicitly removed from the nexus via the {@link NexusAccessor#clean(Reference)} method. Nexus entries can
+ * become stale if a class loader is garbage collected after a class was loaded but before a class was initialized.
+ *
+ * @param referenceQueue The reference queue onto which stale references should be enqueued or {@code null} if no reference queue
+ * should be notified.
+ */
+ public NexusAccessor(ReferenceQueue<? super ClassLoader> referenceQueue) {
+ this.referenceQueue = referenceQueue;
+ }
+
+ /**
+ * Checks if this {@link NexusAccessor} is capable of registering loaded type initializers.
+ *
+ * @return {@code true} if this accessor is alive.
+ */
+ public static boolean isAlive() {
+ return DISPATCHER.isAlive();
+ }
+
+ /**
+ * Removes a stale entries that are registered in the {@link Nexus}. Entries can become stale if a class is loaded but never initialized
+ * prior to its garbage collection. As all class loaders within a nexus are only referenced weakly, such class loaders are always garbage
+ * collected. However, the initialization data stored by Byte Buddy does not become eligible which is why it needs to be cleaned explicitly.
+ *
+ * @param reference The reference to remove. References are collected via a reference queue that is supplied to the {@link NexusAccessor}.
+ */
+ public static void clean(Reference<? extends ClassLoader> reference) {
+ DISPATCHER.clean(reference);
+ }
+
+ /**
+ * Registers a loaded type initializer in Byte Buddy's {@link Nexus} which is injected into the system class loader.
+ *
+ * @param name The binary name of the class.
+ * @param classLoader The class's class loader.
+ * @param identification The id used for identifying the loaded type initializer that was added to the {@link Nexus}.
+ * @param loadedTypeInitializer The loaded type initializer to make available via the {@link Nexus}.
+ */
+ public void register(String name, ClassLoader classLoader, int identification, LoadedTypeInitializer loadedTypeInitializer) {
+ if (loadedTypeInitializer.isAlive()) {
+ DISPATCHER.register(name, classLoader, referenceQueue, identification, loadedTypeInitializer);
+ }
+ }
+
+ /**
+ * An initialization appender that looks up a loaded type initializer from Byte Buddy's {@link Nexus}.
+ */
+ @EqualsAndHashCode
+ public static class InitializationAppender implements ByteCodeAppender {
+
+ /**
+ * The id used for identifying the loaded type initializer that was added to the {@link Nexus}.
+ */
+ private final int identification;
+
+ /**
+ * Creates a new initialization appender.
+ *
+ * @param identification The id used for identifying the loaded type initializer that was added to the {@link Nexus}.
+ */
+ public InitializationAppender(int identification) {
+ this.identification = identification;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodDescription instrumentedMethod) {
+ try {
+ return new ByteCodeAppender.Simple(new StackManipulation.Compound(
+ MethodInvocation.invoke(new MethodDescription.ForLoadedMethod(ClassLoader.class.getMethod("getSystemClassLoader"))),
+ new TextConstant(Nexus.class.getName()),
+ MethodInvocation.invoke(new MethodDescription.ForLoadedMethod(ClassLoader.class.getMethod("loadClass", String.class))),
+ new TextConstant("initialize"),
+ ArrayFactory.forType(new TypeDescription.Generic.OfNonGenericType.ForLoadedType(Class.class))
+ .withValues(Arrays.asList(
+ ClassConstant.of(TypeDescription.CLASS),
+ ClassConstant.of(new TypeDescription.ForLoadedType(int.class)))),
+ MethodInvocation.invoke(new MethodDescription.ForLoadedMethod(Class.class.getMethod("getMethod", String.class, Class[].class))),
+ NullConstant.INSTANCE,
+ ArrayFactory.forType(TypeDescription.Generic.OBJECT)
+ .withValues(Arrays.asList(
+ ClassConstant.of(instrumentedMethod.getDeclaringType().asErasure()),
+ new StackManipulation.Compound(
+ IntegerConstant.forValue(identification),
+ MethodInvocation.invoke(new MethodDescription.ForLoadedMethod(Integer.class.getMethod("valueOf", int.class)))))),
+ MethodInvocation.invoke(new MethodDescription.ForLoadedMethod(Method.class.getMethod("invoke", Object.class, Object[].class))),
+ Removal.SINGLE
+ )).apply(methodVisitor, implementationContext, instrumentedMethod);
+ } catch (NoSuchMethodException exception) {
+ throw new IllegalStateException("Cannot locate method", exception);
+ }
+ }
+ }
+
+ /**
+ * A dispatcher for registering type initializers in the {@link Nexus}.
+ */
+ protected interface Dispatcher {
+
+ /**
+ * Returns {@code true} if this dispatcher is alive.
+ *
+ * @return {@code true} if this dispatcher is alive.
+ */
+ boolean isAlive();
+
+ /**
+ * Cleans any dead entries of the system class loader's {@link Nexus}.
+ *
+ * @param reference The reference to remove.
+ */
+ void clean(Reference<? extends ClassLoader> reference);
+
+ /**
+ * Registers a type initializer with the system class loader's nexus.
+ *
+ * @param name The name of a type for which a loaded type initializer is registered.
+ * @param classLoader The class loader for which a loaded type initializer is registered.
+ * @param referenceQueue A reference queue to notify about stale nexus entries or {@code null} if no queue should be referenced.
+ * @param identification An identification for the initializer to run.
+ * @param loadedTypeInitializer The loaded type initializer to be registered.
+ */
+ void register(String name,
+ ClassLoader classLoader,
+ ReferenceQueue<? super ClassLoader> referenceQueue,
+ int identification,
+ LoadedTypeInitializer loadedTypeInitializer);
+
+ /**
+ * Creates a new dispatcher for accessing a {@link Nexus}.
+ */
+ enum CreationAction implements PrivilegedAction<Dispatcher> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback")
+ public Dispatcher run() {
+ if (Boolean.getBoolean(Nexus.PROPERTY)) {
+ return new Unavailable(new IllegalStateException("Nexus injection was explicitly disabled"));
+ } else {
+ try {
+ Class<?> nexusType = new ClassInjector.UsingReflection(ClassLoader.getSystemClassLoader(), ClassLoadingStrategy.NO_PROTECTION_DOMAIN)
+ .inject(Collections.singletonMap(new TypeDescription.ForLoadedType(Nexus.class), ClassFileLocator.ForClassLoader.read(Nexus.class).resolve()))
+ .get(new TypeDescription.ForLoadedType(Nexus.class));
+ return new Dispatcher.Available(nexusType.getMethod("register", String.class, ClassLoader.class, ReferenceQueue.class, int.class, Object.class),
+ nexusType.getMethod("clean", Reference.class));
+ } catch (Exception exception) {
+ try {
+ Class<?> nexusType = ClassLoader.getSystemClassLoader().loadClass(Nexus.class.getName());
+ return new Dispatcher.Available(nexusType.getMethod("register", String.class, ClassLoader.class, ReferenceQueue.class, int.class, Object.class),
+ nexusType.getMethod("clean", Reference.class));
+ } catch (Exception ignored) {
+ return new Dispatcher.Unavailable(exception);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * An enabled dispatcher for registering a type initializer in a {@link Nexus}.
+ */
+ @EqualsAndHashCode
+ class Available implements Dispatcher {
+
+ /**
+ * Indicates that a static method is invoked by reflection.
+ */
+ private static final Object STATIC_METHOD = null;
+
+ /**
+ * The {@link Nexus#register(String, ClassLoader, ReferenceQueue, int, Object)} method.
+ */
+ private final Method register;
+
+ /**
+ * The {@link Nexus#clean(Reference)} method.
+ */
+ private final Method clean;
+
+ /**
+ * Creates a new dispatcher.
+ *
+ * @param register The {@link Nexus#register(String, ClassLoader, ReferenceQueue, int, Object)} method.
+ * @param clean The {@link Nexus#clean(Reference)} method.
+ */
+ protected Available(Method register, Method clean) {
+ this.register = register;
+ this.clean = clean;
+ }
+
+ @Override
+ public boolean isAlive() {
+ return true;
+ }
+
+ @Override
+ public void clean(Reference<? extends ClassLoader> reference) {
+ try {
+ clean.invoke(STATIC_METHOD, reference);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access: " + clean, exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Cannot invoke: " + clean, exception.getCause());
+ }
+ }
+
+ @Override
+ public void register(String name,
+ ClassLoader classLoader,
+ ReferenceQueue<? super ClassLoader> referenceQueue,
+ int identification,
+ LoadedTypeInitializer loadedTypeInitializer) {
+ try {
+ register.invoke(STATIC_METHOD, name, classLoader, referenceQueue, identification, loadedTypeInitializer);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access: " + register, exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Cannot invoke: " + register, exception.getCause());
+ }
+ }
+ }
+
+ /**
+ * A disabled dispatcher where a {@link Nexus} is not available.
+ */
+ @EqualsAndHashCode
+ class Unavailable implements Dispatcher {
+
+ /**
+ * The exception that was raised during the dispatcher initialization.
+ */
+ private final Exception exception;
+
+ /**
+ * Creates a new disabled dispatcher.
+ *
+ * @param exception The exception that was raised during the dispatcher initialization.
+ */
+ protected Unavailable(Exception exception) {
+ this.exception = exception;
+ }
+
+ @Override
+ public boolean isAlive() {
+ return false;
+ }
+
+ @Override
+ public void clean(Reference<? extends ClassLoader> reference) {
+ throw new IllegalStateException("Could not initialize Nexus accessor", exception);
+ }
+
+ @Override
+ public void register(String name,
+ ClassLoader classLoader,
+ ReferenceQueue<? super ClassLoader> referenceQueue,
+ int identification,
+ LoadedTypeInitializer loadedTypeInitializer) {
+ throw new IllegalStateException("Could not initialize Nexus accessor", exception);
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/TargetType.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/TargetType.java
new file mode 100644
index 0000000..f635f13
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/TargetType.java
@@ -0,0 +1,42 @@
+package net.bytebuddy.dynamic;
+
+import net.bytebuddy.description.type.TypeDescription;
+
+/**
+ * This type is used as a place holder for creating methods or fields that refer to the type that currently subject
+ * of creation within a {@link net.bytebuddy.dynamic.DynamicType.Builder}.
+ */
+public final class TargetType {
+
+ /**
+ * A description of the {@link net.bytebuddy.dynamic.TargetType}.
+ */
+ public static final TypeDescription DESCRIPTION = new TypeDescription.ForLoadedType(TargetType.class);
+
+ /**
+ * Resolves the given type description to the supplied target type if it represents the {@link TargetType} placeholder.
+ * Array types are resolved to their component type and rebuilt as an array of the actual target type, if necessary.
+ *
+ * @param typeDescription The type description that might represent the {@link TargetType} placeholder.
+ * @param targetType The actual target type.
+ * @return A description of the resolved type.
+ */
+ public static TypeDescription resolve(TypeDescription typeDescription, TypeDescription targetType) {
+ int arity = 0;
+ TypeDescription componentType = typeDescription;
+ while (componentType.isArray()) {
+ componentType = componentType.getComponentType();
+ arity++;
+ }
+ return componentType.represents(TargetType.class)
+ ? TypeDescription.ArrayProjection.of(targetType, arity)
+ : typeDescription;
+ }
+
+ /**
+ * An unusable constructor to avoid instance creation.
+ */
+ private TargetType() {
+ throw new UnsupportedOperationException("This class only serves as a marker type");
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/Transformer.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/Transformer.java
new file mode 100644
index 0000000..30b9c56
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/Transformer.java
@@ -0,0 +1,583 @@
+package net.bytebuddy.dynamic;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.annotation.AnnotationValue;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.modifier.ModifierContributor;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.none;
+
+/**
+ * A transformer is responsible for transforming an object into a compatible instance of the same type.
+ *
+ * @param <T> The type of the instance being transformed.
+ */
+public interface Transformer<T> {
+
+ /**
+ * Transforms the supplied target.
+ *
+ * @param instrumentedType The instrumented type that declares the target being transformed.
+ * @param target The target entity that is being transformed.
+ * @return The transformed instance.
+ */
+ T transform(TypeDescription instrumentedType, T target);
+
+ /**
+ * A non-operational transformer that returns the received instance.
+ */
+ enum NoOp implements Transformer<Object> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ /**
+ * Creates a transformer in a type-safe manner.
+ *
+ * @param <T> The type of the transformed object.
+ * @return A non-operational transformer.
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> Transformer<T> make() {
+ return (Transformer<T>) INSTANCE;
+ }
+
+ @Override
+ public Object transform(TypeDescription instrumentedType, Object target) {
+ return target;
+ }
+ }
+
+ /**
+ * A transformer for a field that delegates to another transformer that transforms a {@link net.bytebuddy.description.field.FieldDescription.Token}.
+ */
+ @EqualsAndHashCode
+ class ForField implements Transformer<FieldDescription> {
+
+ /**
+ * The token transformer to apply to a transformed field.
+ */
+ private final Transformer<FieldDescription.Token> transformer;
+
+ /**
+ * Creates a new simple field transformer.
+ *
+ * @param transformer The token transformer to apply to a transformed field.
+ */
+ public ForField(Transformer<FieldDescription.Token> transformer) {
+ this.transformer = transformer;
+ }
+
+ /**
+ * Creates a field transformer that patches the transformed field by the givien modifier contributors.
+ *
+ * @param modifierContributor The modifier contributors to apply.
+ * @return A suitable field transformer.
+ */
+ public static Transformer<FieldDescription> withModifiers(ModifierContributor.ForField... modifierContributor) {
+ return withModifiers(Arrays.asList(modifierContributor));
+ }
+
+ /**
+ * Creates a field transformer that patches the transformed field by the givien modifier contributors.
+ *
+ * @param modifierContributors The modifier contributors to apply.
+ * @return A suitable field transformer.
+ */
+ public static Transformer<FieldDescription> withModifiers(List<? extends ModifierContributor.ForField> modifierContributors) {
+ return new ForField(new FieldModifierTransformer(ModifierContributor.Resolver.of(modifierContributors)));
+ }
+
+ @Override
+ public FieldDescription transform(TypeDescription instrumentedType, FieldDescription fieldDescription) {
+ return new TransformedField(instrumentedType,
+ fieldDescription.getDeclaringType(),
+ transformer.transform(instrumentedType, fieldDescription.asToken(none())),
+ fieldDescription.asDefined());
+ }
+
+ /**
+ * A transformer for a field's modifiers.
+ */
+ @EqualsAndHashCode
+ protected static class FieldModifierTransformer implements Transformer<FieldDescription.Token> {
+
+ /**
+ * The resolver to apply for transforming the modifiers of a field.
+ */
+ private final ModifierContributor.Resolver<ModifierContributor.ForField> resolver;
+
+ /**
+ * Creates a new field token modifier for transforming a field's modifiers.
+ *
+ * @param resolver The resolver to apply for transforming the modifiers of a field.
+ */
+ protected FieldModifierTransformer(ModifierContributor.Resolver<ModifierContributor.ForField> resolver) {
+ this.resolver = resolver;
+ }
+
+ @Override
+ public FieldDescription.Token transform(TypeDescription instrumentedType, FieldDescription.Token target) {
+ return new FieldDescription.Token(target.getName(),
+ resolver.resolve(target.getModifiers()),
+ target.getType(),
+ target.getAnnotations());
+ }
+ }
+
+ /**
+ * An implementation of a transformed field.
+ */
+ protected static class TransformedField extends FieldDescription.AbstractBase {
+
+ /**
+ * The instrumented type for which this field is transformed.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * The field's declaring type.
+ */
+ private final TypeDefinition declaringType;
+
+ /**
+ * A field token representing the transformed field.
+ */
+ private final FieldDescription.Token token;
+
+ /**
+ * The field's defined shape.
+ */
+ private final FieldDescription.InDefinedShape fieldDescription;
+
+ /**
+ * Creates a new transformed field.
+ *
+ * @param instrumentedType The instrumented type for which this field is transformed.
+ * @param declaringType The field's declaring type.
+ * @param token A field token representing the transformed field.
+ * @param fieldDescription The field's defined shape.
+ */
+ protected TransformedField(TypeDescription instrumentedType,
+ TypeDefinition declaringType,
+ Token token,
+ InDefinedShape fieldDescription) {
+ this.instrumentedType = instrumentedType;
+ this.declaringType = declaringType;
+ this.token = token;
+ this.fieldDescription = fieldDescription;
+ }
+
+ @Override
+ public TypeDescription.Generic getType() {
+ return token.getType().accept(TypeDescription.Generic.Visitor.Substitutor.ForAttachment.of(instrumentedType));
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return token.getAnnotations();
+ }
+
+ @Override
+ public TypeDefinition getDeclaringType() {
+ return declaringType;
+ }
+
+ @Override
+ public int getModifiers() {
+ return token.getModifiers();
+ }
+
+ @Override
+ public InDefinedShape asDefined() {
+ return fieldDescription;
+ }
+
+ @Override
+ public String getName() {
+ return token.getName();
+ }
+ }
+ }
+
+ /**
+ * A transformer for a field that delegates to another transformer that transforms a {@link net.bytebuddy.description.method.MethodDescription.Token}.
+ */
+ @EqualsAndHashCode
+ class ForMethod implements Transformer<MethodDescription> {
+
+ /**
+ * The transformer to be applied.
+ */
+ private final Transformer<MethodDescription.Token> transformer;
+
+ /**
+ * Creates a new transforming method transformer.
+ *
+ * @param transformer The transformer to be applied.
+ */
+ public ForMethod(Transformer<MethodDescription.Token> transformer) {
+ this.transformer = transformer;
+ }
+
+ /**
+ * Creates a transformer that enforces the supplied modifier contributors. All ranges of each contributor is first cleared and then overridden
+ * by the specified modifiers in the order they are supplied.
+ *
+ * @param modifierContributor The modifier transformers in their application order.
+ * @return A method transformer where each method's modifiers are adapted to the given modifiers.
+ */
+ public static Transformer<MethodDescription> withModifiers(ModifierContributor.ForMethod... modifierContributor) {
+ return withModifiers(Arrays.asList(modifierContributor));
+ }
+
+ /**
+ * Creates a transformer that enforces the supplied modifier contributors. All ranges of each contributor is first cleared and then overridden
+ * by the specified modifiers in the order they are supplied.
+ *
+ * @param modifierContributors The modifier contributors in their application order.
+ * @return A method transformer where each method's modifiers are adapted to the given modifiers.
+ */
+ public static Transformer<MethodDescription> withModifiers(List<? extends ModifierContributor.ForMethod> modifierContributors) {
+ return new ForMethod(new MethodModifierTransformer(ModifierContributor.Resolver.of(modifierContributors)));
+ }
+
+ @Override
+ public MethodDescription transform(TypeDescription instrumentedType, MethodDescription methodDescription) {
+ return new TransformedMethod(instrumentedType,
+ methodDescription.getDeclaringType(),
+ transformer.transform(instrumentedType, methodDescription.asToken(none())),
+ methodDescription.asDefined());
+ }
+
+ /**
+ * A transformer for a method's modifiers.
+ */
+ @EqualsAndHashCode
+ protected static class MethodModifierTransformer implements Transformer<MethodDescription.Token> {
+
+ /**
+ * The resolver to apply onto the method's modifiers.
+ */
+ private final ModifierContributor.Resolver<ModifierContributor.ForMethod> resolver;
+
+ /**
+ * Creates a new modifier transformation.
+ *
+ * @param resolver The resolver to apply onto the method's modifiers.
+ */
+ protected MethodModifierTransformer(ModifierContributor.Resolver<ModifierContributor.ForMethod> resolver) {
+ this.resolver = resolver;
+ }
+
+ @Override
+ public MethodDescription.Token transform(TypeDescription instrumentedType, MethodDescription.Token target) {
+ return new MethodDescription.Token(target.getName(),
+ resolver.resolve(target.getModifiers()),
+ target.getTypeVariableTokens(),
+ target.getReturnType(),
+ target.getParameterTokens(),
+ target.getExceptionTypes(),
+ target.getAnnotations(),
+ target.getDefaultValue(),
+ target.getReceiverType());
+ }
+ }
+
+ /**
+ * The transformed method.
+ */
+ protected static class TransformedMethod extends MethodDescription.AbstractBase {
+
+ /**
+ * The instrumented type for which this method is transformed.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * The method's declaring type.
+ */
+ private final TypeDefinition declaringType;
+
+ /**
+ * The method representing the transformed method.
+ */
+ private final MethodDescription.Token token;
+
+ /**
+ * The defined shape of the transformed method.
+ */
+ private final MethodDescription.InDefinedShape methodDescription;
+
+ /**
+ * Creates a new transformed method.
+ *
+ * @param instrumentedType The instrumented type for which this method is transformed.
+ * @param declaringType The method's declaring type.
+ * @param token The method representing the transformed method.
+ * @param methodDescription The defined shape of the transformed method.
+ */
+ protected TransformedMethod(TypeDescription instrumentedType,
+ TypeDefinition declaringType,
+ Token token,
+ InDefinedShape methodDescription) {
+ this.instrumentedType = instrumentedType;
+ this.declaringType = declaringType;
+ this.token = token;
+ this.methodDescription = methodDescription;
+ }
+
+ @Override
+ public TypeList.Generic getTypeVariables() {
+ return new TypeList.Generic.ForDetachedTypes.OfTypeVariables(this, token.getTypeVariableTokens(), new AttachmentVisitor());
+ }
+
+ @Override
+ public TypeDescription.Generic getReturnType() {
+ return token.getReturnType().accept(new AttachmentVisitor());
+ }
+
+ @Override
+ public ParameterList<?> getParameters() {
+ return new TransformedParameterList();
+ }
+
+ @Override
+ public TypeList.Generic getExceptionTypes() {
+ return new TypeList.Generic.ForDetachedTypes(token.getExceptionTypes(), new AttachmentVisitor());
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return token.getAnnotations();
+ }
+
+ @Override
+ public String getInternalName() {
+ return token.getName();
+ }
+
+ @Override
+ public TypeDefinition getDeclaringType() {
+ return declaringType;
+ }
+
+ @Override
+ public int getModifiers() {
+ return token.getModifiers();
+ }
+
+ @Override
+ public AnnotationValue<?, ?> getDefaultValue() {
+ return token.getDefaultValue();
+ }
+
+ @Override
+ public InDefinedShape asDefined() {
+ return methodDescription;
+ }
+
+ @Override
+ public TypeDescription.Generic getReceiverType() {
+ TypeDescription.Generic receiverType = token.getReceiverType();
+ return receiverType == null
+ ? TypeDescription.Generic.UNDEFINED
+ : receiverType.accept(new AttachmentVisitor());
+ }
+
+ /**
+ * A parameter list representing the transformed method's parameters.
+ */
+ protected class TransformedParameterList extends ParameterList.AbstractBase<ParameterDescription> {
+
+ @Override
+ public ParameterDescription get(int index) {
+ return new TransformedParameter(index, token.getParameterTokens().get(index));
+ }
+
+ @Override
+ public int size() {
+ return token.getParameterTokens().size();
+ }
+ }
+
+ /**
+ * A transformed method's parameter.
+ */
+ protected class TransformedParameter extends ParameterDescription.AbstractBase {
+
+ /**
+ * The index of the transformed method.
+ */
+ private final int index;
+
+ /**
+ * The token representing the transformed method parameter's properties.
+ */
+ private final ParameterDescription.Token parameterToken;
+
+ /**
+ * Creates a transformed parameter.
+ *
+ * @param index The index of the transformed method.
+ * @param parameterToken The token representing the transformed method parameter's properties.
+ */
+ protected TransformedParameter(int index, ParameterDescription.Token parameterToken) {
+ this.index = index;
+ this.parameterToken = parameterToken;
+ }
+
+ @Override
+ public TypeDescription.Generic getType() {
+ return parameterToken.getType().accept(new AttachmentVisitor());
+ }
+
+ @Override
+ public MethodDescription getDeclaringMethod() {
+ return TransformedMethod.this;
+ }
+
+ @Override
+ public int getIndex() {
+ return index;
+ }
+
+ @Override
+ public boolean isNamed() {
+ return parameterToken.getName() != null;
+ }
+
+ @Override
+ public boolean hasModifiers() {
+ return parameterToken.getModifiers() != null;
+ }
+
+ @Override
+ public String getName() {
+ return isNamed()
+ ? parameterToken.getName()
+ : super.getName();
+ }
+
+ @Override
+ public int getModifiers() {
+ return hasModifiers()
+ ? parameterToken.getModifiers()
+ : super.getModifiers();
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return parameterToken.getAnnotations();
+ }
+
+ @Override
+ public InDefinedShape asDefined() {
+ return methodDescription.getParameters().get(index);
+ }
+ }
+
+ /**
+ * A visitor that attaches type variables based on the transformed method's type variables and the instrumented type. Binding type
+ * variables directly for this method is not possible as type variables are already resolved for the instrumented type such
+ * that it is required to bind variables for the instrumented type directly.
+ */
+ protected class AttachmentVisitor extends TypeDescription.Generic.Visitor.Substitutor.WithoutTypeSubstitution {
+
+ @Override
+ public TypeDescription.Generic onTypeVariable(TypeDescription.Generic typeVariable) {
+ TypeList.Generic candidates = getTypeVariables().filter(named(typeVariable.getSymbol()));
+ TypeDescription.Generic attached = candidates.isEmpty()
+ ? instrumentedType.findVariable(typeVariable.getSymbol())
+ : candidates.getOnly();
+ if (attached == null) {
+ throw new IllegalArgumentException("Cannot attach undefined variable: " + typeVariable);
+ } else {
+ return new TypeDescription.Generic.OfTypeVariable.WithAnnotationOverlay(attached, typeVariable);
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return TransformedMethod.this.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return this == other || (other instanceof AttachmentVisitor && ((AttachmentVisitor) other).getOuter().equals(TransformedMethod.this));
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private TransformedMethod getOuter() {
+ return TransformedMethod.this;
+ }
+ }
+ }
+ }
+
+ /**
+ * A compound transformer.
+ *
+ * @param <S> The type of the transformed instance.
+ */
+ @EqualsAndHashCode
+ class Compound<S> implements Transformer<S> {
+
+ /**
+ * The list of transformers to apply in their application order.
+ */
+ private final List<Transformer<S>> transformers;
+
+ /**
+ * Creates a new compound transformer.
+ *
+ * @param transformer The list of transformers to apply in their application order.
+ */
+ @SuppressWarnings("unchecked") // In absence of @SafeVarargs for Java 6
+ public Compound(Transformer<S>... transformer) {
+ this(Arrays.asList(transformer));
+ }
+
+ /**
+ * Creates a new compound transformer.
+ *
+ * @param transformers The list of transformers to apply in their application order.
+ */
+ public Compound(List<? extends Transformer<S>> transformers) {
+ this.transformers = new ArrayList<Transformer<S>>();
+ for (Transformer<S> transformer : transformers) {
+ if (transformer instanceof Compound) {
+ this.transformers.addAll(((Compound<S>) transformer).transformers);
+ } else if (!(transformer instanceof NoOp)) {
+ this.transformers.add(transformer);
+ }
+ }
+ }
+
+ @Override
+ public S transform(TypeDescription instrumentedType, S target) {
+ for (Transformer<S> transformer : transformers) {
+ target = transformer.transform(instrumentedType, target);
+ }
+ return target;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/TypeResolutionStrategy.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/TypeResolutionStrategy.java
new file mode 100644
index 0000000..fc3c10b
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/TypeResolutionStrategy.java
@@ -0,0 +1,227 @@
+package net.bytebuddy.dynamic;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.dynamic.scaffold.TypeInitializer;
+import net.bytebuddy.implementation.LoadedTypeInitializer;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+
+/**
+ * A type resolution strategy is responsible for loading a class and for initializing its {@link LoadedTypeInitializer}s.
+ */
+public interface TypeResolutionStrategy {
+
+ /**
+ * Resolves a type resolution strategy for actual application.
+ *
+ * @return A resolved version of this type resolution strategy.
+ */
+ Resolved resolve();
+
+ /**
+ * A resolved {@link TypeResolutionStrategy}.
+ */
+ interface Resolved {
+
+ /**
+ * Injects a type initializer into the supplied type initializer, if applicable. This way, a type resolution
+ * strategy is capable of injecting code into the generated class's initializer to inline the initialization.
+ *
+ * @param typeInitializer The type initializer to potentially expend.
+ * @return A type initializer to apply for performing the represented type resolution.
+ */
+ TypeInitializer injectedInto(TypeInitializer typeInitializer);
+
+ /**
+ * Loads and initializes a dynamic type.
+ *
+ * @param dynamicType The dynamic type to initialize.
+ * @param classLoader The class loader to use.
+ * @param classLoadingStrategy The class loading strategy to use.
+ * @param <S> The least specific type of class loader this strategy can apply to.
+ * @return A map of all type descriptions mapped to their representation as a loaded class.
+ */
+ <S extends ClassLoader> Map<TypeDescription, Class<?>> initialize(DynamicType dynamicType,
+ S classLoader,
+ ClassLoadingStrategy<? super S> classLoadingStrategy);
+ }
+
+ /**
+ * A type resolution strategy that applies all {@link LoadedTypeInitializer} after class loading using reflection. This implies that the initializers
+ * are executed <b>after</b> a type initializer is executed.
+ */
+ enum Passive implements TypeResolutionStrategy, Resolved {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Resolved resolve() {
+ return this;
+ }
+
+ @Override
+ public TypeInitializer injectedInto(TypeInitializer typeInitializer) {
+ return typeInitializer;
+ }
+
+ @Override
+ public <S extends ClassLoader> Map<TypeDescription, Class<?>> initialize(DynamicType dynamicType,
+ S classLoader,
+ ClassLoadingStrategy<? super S> classLoadingStrategy) {
+ Map<TypeDescription, Class<?>> types = classLoadingStrategy.load(classLoader, dynamicType.getAllTypes());
+ for (Map.Entry<TypeDescription, LoadedTypeInitializer> entry : dynamicType.getLoadedTypeInitializers().entrySet()) {
+ entry.getValue().onLoad(types.get(entry.getKey()));
+ }
+ return new HashMap<TypeDescription, Class<?>>(types);
+ }
+ }
+
+ /**
+ * A type resolution strategy that applies all {@link LoadedTypeInitializer} as a part of class loading using reflection. This implies that the initializers
+ * are executed <b>before</b> (as a first action of) a type initializer is executed.
+ */
+ @EqualsAndHashCode
+ class Active implements TypeResolutionStrategy {
+
+ /**
+ * The nexus accessor to use.
+ */
+ private final NexusAccessor nexusAccessor;
+
+ /**
+ * Creates a new active type resolution strategy that uses a default nexus accessor.
+ */
+ public Active() {
+ this(new NexusAccessor());
+ }
+
+ /**
+ * Creates a new active type resolution strategy that uses the supplied nexus accessor.
+ *
+ * @param nexusAccessor The nexus accessor to use.
+ */
+ public Active(NexusAccessor nexusAccessor) {
+ this.nexusAccessor = nexusAccessor;
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "DMI_RANDOM_USED_ONLY_ONCE", justification = "Avoid thread-contention")
+ public TypeResolutionStrategy.Resolved resolve() {
+ return new Resolved(nexusAccessor, new Random().nextInt());
+ }
+
+ /**
+ * A resolved version of an active type resolution strategy.
+ */
+ @EqualsAndHashCode
+ protected static class Resolved implements TypeResolutionStrategy.Resolved {
+
+ /**
+ * The nexus accessor to use.
+ */
+ private final NexusAccessor nexusAccessor;
+
+ /**
+ * The id used for identifying the loaded type initializer that was added to the {@link Nexus}.
+ */
+ private final int identification;
+
+ /**
+ * Creates a new resolved active type resolution strategy.
+ *
+ * @param nexusAccessor The nexus accessor to use.
+ * @param identification The id used for identifying the loaded type initializer that was added to the {@link Nexus}.
+ */
+ protected Resolved(NexusAccessor nexusAccessor, int identification) {
+ this.nexusAccessor = nexusAccessor;
+ this.identification = identification;
+ }
+
+ @Override
+ public TypeInitializer injectedInto(TypeInitializer typeInitializer) {
+ return typeInitializer.expandWith(new NexusAccessor.InitializationAppender(identification));
+ }
+
+ @Override
+ public <S extends ClassLoader> Map<TypeDescription, Class<?>> initialize(DynamicType dynamicType,
+ S classLoader,
+ ClassLoadingStrategy<? super S> classLoadingStrategy) {
+ Map<TypeDescription, LoadedTypeInitializer> loadedTypeInitializers = new HashMap<TypeDescription, LoadedTypeInitializer>(dynamicType.getLoadedTypeInitializers());
+ TypeDescription instrumentedType = dynamicType.getTypeDescription();
+ Map<TypeDescription, Class<?>> types = classLoadingStrategy.load(classLoader, dynamicType.getAllTypes());
+ nexusAccessor.register(instrumentedType.getName(),
+ types.get(instrumentedType).getClassLoader(),
+ identification,
+ loadedTypeInitializers.remove(instrumentedType));
+ for (Map.Entry<TypeDescription, LoadedTypeInitializer> entry : loadedTypeInitializers.entrySet()) {
+ entry.getValue().onLoad(types.get(entry.getKey()));
+ }
+ return types;
+ }
+ }
+ }
+
+ /**
+ * A type resolution strategy that does not apply any {@link LoadedTypeInitializer}s but only loads all types.
+ */
+ enum Lazy implements TypeResolutionStrategy, Resolved {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Resolved resolve() {
+ return this;
+ }
+
+ @Override
+ public TypeInitializer injectedInto(TypeInitializer typeInitializer) {
+ return typeInitializer;
+ }
+
+ @Override
+ public <S extends ClassLoader> Map<TypeDescription, Class<?>> initialize(DynamicType dynamicType,
+ S classLoader,
+ ClassLoadingStrategy<? super S> classLoadingStrategy) {
+ return classLoadingStrategy.load(classLoader, dynamicType.getAllTypes());
+ }
+ }
+
+ /**
+ * A type resolution strategy that does not allow for explicit loading of a class and that does not inject any code into the type initializer.
+ */
+ enum Disabled implements TypeResolutionStrategy, Resolved {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Resolved resolve() {
+ return this;
+ }
+
+ @Override
+ public TypeInitializer injectedInto(TypeInitializer typeInitializer) {
+ return typeInitializer;
+ }
+
+ @Override
+ public <S extends ClassLoader> Map<TypeDescription, Class<?>> initialize(DynamicType dynamicType,
+ S classLoader,
+ ClassLoadingStrategy<? super S> classLoadingStrategy) {
+ throw new IllegalStateException("Cannot initialize a dynamic type for a disabled type resolution strategy");
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/loading/ByteArrayClassLoader.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/loading/ByteArrayClassLoader.java
new file mode 100644
index 0000000..c8d31f4
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/loading/ByteArrayClassLoader.java
@@ -0,0 +1,1020 @@
+package net.bytebuddy.dynamic.loading;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.type.TypeDescription;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.IllegalClassFormatException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.*;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.ProtectionDomain;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * <p>
+ * A {@link java.lang.ClassLoader} that is capable of loading explicitly defined classes. The class loader will free
+ * any binary resources once a class that is defined by its binary data is loaded. This class loader is thread safe since
+ * the class loading mechanics are only called from synchronized context.
+ * </p>
+ * <p>
+ * <b>Note</b>: Instances of this class loader return URLs for their represented class loaders with the <i>bytebuddy</i> schema.
+ * These URLs do not represent URIs as two classes with the same name yield identical URLs but might represents different byte
+ * arrays.
+ * </p>
+ * <p>
+ * <b>Note</b>: Any class and package definition is performed using the creator's {@link AccessControlContext}.
+ * </p>
+ */
+public class ByteArrayClassLoader extends InjectionClassLoader {
+
+ /**
+ * The schema for URLs that represent a class file of byte array class loaders.
+ */
+ public static final String URL_SCHEMA = "bytebuddy";
+
+ /**
+ * Indicates that an array should be included from its first index. Improves the source code readability.
+ */
+ private static final int FROM_BEGINNING = 0;
+
+ /**
+ * Indicates a type that is not loaded.
+ */
+ private static final Class<?> UNLOADED_TYPE = null;
+
+ /**
+ * Indicates that a URL does not exist to improve code readability.
+ */
+ private static final URL NO_URL = null;
+
+ /**
+ * A strategy for locating a package by name.
+ */
+ private static final PackageLookupStrategy PACKAGE_LOOKUP_STRATEGY = AccessController.doPrivileged(PackageLookupStrategy.CreationAction.INSTANCE);
+
+ /**
+ * The synchronization engine for the executing JVM.
+ */
+ protected static final SynchronizationStrategy.Initializable SYNCHRONIZATION_STRATEGY = AccessController.doPrivileged(SynchronizationStrategy.CreationAction.INSTANCE);
+
+ /**
+ * A mutable map of type names mapped to their binary representation.
+ */
+ protected final ConcurrentMap<String, byte[]> typeDefinitions;
+
+ /**
+ * The persistence handler of this class loader.
+ */
+ protected final PersistenceHandler persistenceHandler;
+
+ /**
+ * The protection domain to apply. Might be {@code null} when referencing the default protection domain.
+ */
+ protected final ProtectionDomain protectionDomain;
+
+ /**
+ * The package definer to be queried for package definitions.
+ */
+ protected final PackageDefinitionStrategy packageDefinitionStrategy;
+
+ /**
+ * The class file transformer to apply on loaded classes.
+ */
+ protected final ClassFileTransformer classFileTransformer;
+
+ /**
+ * The access control context to use for loading classes.
+ */
+ protected final AccessControlContext accessControlContext;
+
+ /**
+ * Creates a new class loader for a given definition of classes.
+ *
+ * @param parent The {@link java.lang.ClassLoader} that is the parent of this class loader.
+ * @param typeDefinitions A map of fully qualified class names pointing to their binary representations.
+ */
+ public ByteArrayClassLoader(ClassLoader parent, Map<String, byte[]> typeDefinitions) {
+ this(parent, typeDefinitions, PersistenceHandler.LATENT);
+ }
+
+ /**
+ * Creates a new class loader for a given definition of classes.
+ *
+ * @param parent The {@link java.lang.ClassLoader} that is the parent of this class loader.
+ * @param typeDefinitions A map of fully qualified class names pointing to their binary representations.
+ * @param persistenceHandler The persistence handler of this class loader.
+ */
+ public ByteArrayClassLoader(ClassLoader parent, Map<String, byte[]> typeDefinitions, PersistenceHandler persistenceHandler) {
+ this(parent, typeDefinitions, ClassLoadingStrategy.NO_PROTECTION_DOMAIN, persistenceHandler, PackageDefinitionStrategy.Trivial.INSTANCE);
+ }
+
+ /**
+ * Creates a new class loader for a given definition of classes.
+ *
+ * @param parent The {@link java.lang.ClassLoader} that is the parent of this class loader.
+ * @param typeDefinitions A map of fully qualified class names pointing to their binary representations.
+ * @param protectionDomain The protection domain to apply where {@code null} references an implicit protection domain.
+ * @param packageDefinitionStrategy The package definer to be queried for package definitions.
+ * @param persistenceHandler The persistence handler of this class loader.
+ */
+ public ByteArrayClassLoader(ClassLoader parent,
+ Map<String, byte[]> typeDefinitions,
+ ProtectionDomain protectionDomain,
+ PersistenceHandler persistenceHandler,
+ PackageDefinitionStrategy packageDefinitionStrategy) {
+ this(parent, typeDefinitions, protectionDomain, persistenceHandler, packageDefinitionStrategy, NoOpClassFileTransformer.INSTANCE);
+ }
+
+ /**
+ * Creates a new class loader for a given definition of classes.
+ *
+ * @param parent The {@link java.lang.ClassLoader} that is the parent of this class loader.
+ * @param typeDefinitions A map of fully qualified class names pointing to their binary representations.
+ * @param protectionDomain The protection domain to apply where {@code null} references an implicit protection domain.
+ * @param packageDefinitionStrategy The package definer to be queried for package definitions.
+ * @param persistenceHandler The persistence handler of this class loader.
+ * @param classFileTransformer The class file transformer to apply on loaded classes.
+ */
+ public ByteArrayClassLoader(ClassLoader parent,
+ Map<String, byte[]> typeDefinitions,
+ ProtectionDomain protectionDomain,
+ PersistenceHandler persistenceHandler,
+ PackageDefinitionStrategy packageDefinitionStrategy,
+ ClassFileTransformer classFileTransformer) {
+ super(parent);
+ this.typeDefinitions = new ConcurrentHashMap<String, byte[]>(typeDefinitions);
+ this.protectionDomain = protectionDomain;
+ this.persistenceHandler = persistenceHandler;
+ this.packageDefinitionStrategy = packageDefinitionStrategy;
+ this.classFileTransformer = classFileTransformer;
+ accessControlContext = AccessController.getContext();
+ }
+
+ /**
+ * Creates a new class loader for a given definition of classes.
+ *
+ * @param parent The {@link java.lang.ClassLoader} that is the parent of this class loader.
+ * @param typeDefinitions A map of type descriptions pointing to their binary representations.
+ * @param protectionDomain The protection domain to apply where {@code null} references an implicit protection domain.
+ * @param persistenceHandler The persistence handler to be used by the created class loader.
+ * @param packageDefinitionStrategy The package definer to be queried for package definitions.
+ * @param childFirst {@code true} if the class loader should apply child first semantics when loading
+ * the {@code typeDefinitions}.
+ * @return A corresponding class loader.
+ */
+ @SuppressFBWarnings(value = "DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED", justification = "Privilege is explicit user responsibility")
+ public static ClassLoader of(ClassLoader parent,
+ Map<TypeDescription, byte[]> typeDefinitions,
+ ProtectionDomain protectionDomain,
+ PersistenceHandler persistenceHandler,
+ PackageDefinitionStrategy packageDefinitionStrategy,
+ boolean childFirst) {
+ Map<String, byte[]> namedTypeDefinitions = new HashMap<String, byte[]>();
+ for (Map.Entry<TypeDescription, byte[]> entry : typeDefinitions.entrySet()) {
+ namedTypeDefinitions.put(entry.getKey().getName(), entry.getValue());
+ }
+ return childFirst
+ ? new ChildFirst(parent, namedTypeDefinitions, protectionDomain, persistenceHandler, packageDefinitionStrategy)
+ : new ByteArrayClassLoader(parent, namedTypeDefinitions, protectionDomain, persistenceHandler, packageDefinitionStrategy);
+ }
+
+ /**
+ * Loads a given set of class descriptions and their binary representations.
+ *
+ * @param classLoader The parent class loader.
+ * @param types The unloaded types to be loaded.
+ * @param protectionDomain The protection domain to apply where {@code null} references an implicit protection domain.
+ * @param persistenceHandler The persistence handler of the created class loader.
+ * @param packageDefinitionStrategy The package definer to be queried for package definitions.
+ * @param childFirst {@code true} if the created class loader should apply child-first semantics when loading the {@code types}.
+ * @param forbidExisting {@code true} if the class loading should throw an exception if a class was already loaded by a parent class loader.
+ * @return A map of the given type descriptions pointing to their loaded representations.
+ */
+ public static Map<TypeDescription, Class<?>> load(ClassLoader classLoader,
+ Map<TypeDescription, byte[]> types,
+ ProtectionDomain protectionDomain,
+ PersistenceHandler persistenceHandler,
+ PackageDefinitionStrategy packageDefinitionStrategy,
+ boolean childFirst,
+ boolean forbidExisting) {
+ Map<TypeDescription, Class<?>> loadedTypes = new LinkedHashMap<TypeDescription, Class<?>>();
+ classLoader = ByteArrayClassLoader.of(classLoader,
+ types,
+ protectionDomain,
+ persistenceHandler,
+ packageDefinitionStrategy,
+ childFirst);
+ for (TypeDescription typeDescription : types.keySet()) {
+ try {
+ Class<?> type = Class.forName(typeDescription.getName(), false, classLoader);
+ if (forbidExisting && type.getClassLoader() != classLoader) {
+ throw new IllegalStateException("Class already loaded: " + type);
+ }
+ loadedTypes.put(typeDescription, type);
+ } catch (ClassNotFoundException exception) {
+ throw new IllegalStateException("Cannot load class " + typeDescription, exception);
+ }
+ }
+ return loadedTypes;
+ }
+
+ @Override
+ public Class<?> defineClass(String name, byte[] binaryRepresentation) throws ClassNotFoundException {
+ synchronized (SYNCHRONIZATION_STRATEGY.initialize().getClassLoadingLock(this, name)) {
+ byte[] previous = typeDefinitions.putIfAbsent(name, binaryRepresentation);
+ Class<?> type = null;
+ try {
+ return type = loadClass(name);
+ } finally {
+ if (type == null || type.getClassLoader() != this) {
+ if (previous == null) {
+ typeDefinitions.remove(name);
+ } else {
+ typeDefinitions.put(name, previous);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ byte[] binaryRepresentation = persistenceHandler.lookup(name, typeDefinitions);
+ if (binaryRepresentation == null) {
+ throw new ClassNotFoundException(name);
+ } else {
+ try {
+ byte[] transformed = classFileTransformer.transform(this, name, UNLOADED_TYPE, protectionDomain, binaryRepresentation);
+ if (transformed != null) {
+ binaryRepresentation = transformed;
+ }
+ return AccessController.doPrivileged(new ClassDefinitionAction(name, binaryRepresentation), accessControlContext);
+ } catch (IllegalClassFormatException exception) {
+ throw new IllegalStateException("The class file for " + name + " is not legal", exception);
+ }
+ }
+ }
+
+ @Override
+ protected URL findResource(String name) {
+ return persistenceHandler.url(name, typeDefinitions);
+ }
+
+ @Override
+ protected Enumeration<URL> findResources(String name) {
+ URL url = persistenceHandler.url(name, typeDefinitions);
+ return url == null
+ ? EmptyEnumeration.INSTANCE
+ : new SingletonEnumeration(url);
+ }
+
+ /**
+ * Returns the package for a given name.
+ *
+ * @param name The name of the package.
+ * @return A suitable package or {@code null} if no such package exists.
+ */
+ @SuppressWarnings("deprecation")
+ private Package doGetPackage(String name) {
+ return getPackage(name);
+ }
+
+ /**
+ * An engine for receiving a <i>class loading lock</i> when loading a class.
+ */
+ protected interface SynchronizationStrategy {
+
+ /**
+ * Receives the class loading lock.
+ *
+ * @param name The name of the class being loaded.
+ * @param classLoader The class loader loading the class.
+ * @return The corresponding class loading lock.
+ */
+ Object getClassLoadingLock(ClassLoader classLoader, String name);
+
+ /**
+ * An uninitialized synchronization strategy.
+ */
+ interface Initializable {
+
+ /**
+ * Initializes this synchronization strategy.
+ *
+ * @return The synchronization strategy to use.
+ */
+ SynchronizationStrategy initialize();
+ }
+
+ /**
+ * A creation action for a synchronization strategy.
+ */
+ enum CreationAction implements PrivilegedAction<Initializable> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback")
+ public Initializable run() {
+ try {
+ return new ForJava7CapableVm(ClassLoader.class.getDeclaredMethod("getClassLoadingLock", String.class));
+ } catch (Exception ignored) {
+ return SynchronizationStrategy.ForLegacyVm.INSTANCE;
+ }
+ }
+ }
+
+ /**
+ * A synchronization engine for a VM that is not aware of parallel-capable class loaders.
+ */
+ enum ForLegacyVm implements SynchronizationStrategy, Initializable {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Object getClassLoadingLock(ClassLoader classLoader, String name) {
+ return classLoader;
+ }
+
+ @Override
+ public SynchronizationStrategy initialize() {
+ return this;
+ }
+ }
+
+ /**
+ * A synchronization engine for a VM that is aware of parallel-capable class loaders.
+ */
+ @EqualsAndHashCode
+ class ForJava7CapableVm implements SynchronizationStrategy, Initializable {
+
+ /**
+ * The {@code ClassLoader#getClassLoadingLock(String)} method.
+ */
+ private final Method method;
+
+ /**
+ * Creates a new synchronization engine.
+ *
+ * @param method The {@code ClassLoader#getClassLoadingLock(String)} method.
+ */
+ protected ForJava7CapableVm(Method method) {
+ this.method = method;
+ }
+
+ @Override
+ public Object getClassLoadingLock(ClassLoader classLoader, String name) {
+ try {
+ return method.invoke(classLoader, name);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access class loading lock for " + name + " on " + classLoader, exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error when getting " + name + " on " + classLoader, exception);
+ }
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "DP_DO_INSIDE_DO_PRIVILEGED", justification = "Privilege is explicitly user responsibility")
+ public SynchronizationStrategy initialize() {
+ try {
+ method.setAccessible(true);
+ return this;
+ } catch (Exception ignored) {
+ return ForLegacyVm.INSTANCE;
+ }
+ }
+ }
+ }
+
+ /**
+ * An action for defining a located class that is not yet loaded.
+ */
+ protected class ClassDefinitionAction implements PrivilegedAction<Class<?>> {
+
+ /**
+ * The binary name of the class to define.
+ */
+ private final String name;
+
+ /**
+ * The binary representation of the class to be loaded.
+ */
+ private final byte[] binaryRepresentation;
+
+ /**
+ * Creates a new class definition action.
+ *
+ * @param name The binary name of the class to define.
+ * @param binaryRepresentation The binary representation of the class to be loaded.
+ */
+ protected ClassDefinitionAction(String name, byte[] binaryRepresentation) {
+ this.name = name;
+ this.binaryRepresentation = binaryRepresentation;
+ }
+
+ @Override
+ public Class<?> run() {
+ int packageIndex = name.lastIndexOf('.');
+ if (packageIndex != -1) {
+ String packageName = name.substring(0, packageIndex);
+ PackageDefinitionStrategy.Definition definition = packageDefinitionStrategy.define(ByteArrayClassLoader.this, packageName, name);
+ if (definition.isDefined()) {
+ Package definedPackage = PACKAGE_LOOKUP_STRATEGY.apply(ByteArrayClassLoader.this, packageName);
+ if (definedPackage == null) {
+ definePackage(packageName,
+ definition.getSpecificationTitle(),
+ definition.getSpecificationVersion(),
+ definition.getSpecificationVendor(),
+ definition.getImplementationTitle(),
+ definition.getImplementationVersion(),
+ definition.getImplementationVendor(),
+ definition.getSealBase());
+ } else if (!definition.isCompatibleTo(definedPackage)) {
+ throw new SecurityException("Sealing violation for package " + packageName);
+ }
+ }
+ }
+ return defineClass(name, binaryRepresentation, FROM_BEGINNING, binaryRepresentation.length, protectionDomain);
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private ByteArrayClassLoader getOuter() {
+ return ByteArrayClassLoader.this;
+ }
+
+ @Override //
+ public boolean equals(Object object) {
+ if (this == object) return true;
+ if (object == null || getClass() != object.getClass()) return false;
+ ClassDefinitionAction that = (ClassDefinitionAction) object;
+ return name.equals(that.name)
+ && ByteArrayClassLoader.this.equals(that.getOuter())
+ && Arrays.equals(binaryRepresentation, that.binaryRepresentation);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = name.hashCode();
+ result = 31 * result + ByteArrayClassLoader.this.hashCode();
+ result = 31 * result + Arrays.hashCode(binaryRepresentation);
+ return result;
+ }
+ }
+
+ /**
+ * A package lookup strategy for locating a package by name.
+ */
+ protected interface PackageLookupStrategy {
+
+ /**
+ * Returns a package for a given byte array class loader and a name.
+ *
+ * @param classLoader The class loader to locate a package for.
+ * @param name The name of the package.
+ * @return A suitable package or {@code null} if no such package exists.
+ */
+ Package apply(ByteArrayClassLoader classLoader, String name);
+
+ /**
+ * A creation action for a package lookup strategy.
+ */
+ enum CreationAction implements PrivilegedAction<PackageLookupStrategy> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback")
+ public PackageLookupStrategy run() {
+ try {
+ return new PackageLookupStrategy.ForJava9CapableVm(ClassLoader.class.getDeclaredMethod("getDefinedPackage", String.class));
+ } catch (Exception ignored) {
+ return PackageLookupStrategy.ForLegacyVm.INSTANCE;
+ }
+ }
+ }
+
+ /**
+ * A package lookup strategy for a VM prior to Java 9.
+ */
+ enum ForLegacyVm implements PackageLookupStrategy {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Package apply(ByteArrayClassLoader classLoader, String name) {
+ return classLoader.doGetPackage(name);
+ }
+ }
+
+ /**
+ * A package lookup strategy for Java 9 or newer.
+ */
+ @EqualsAndHashCode
+ class ForJava9CapableVm implements PackageLookupStrategy {
+
+ /**
+ * The {@code java.lang.ClassLoader#getDefinedPackage(String)} method.
+ */
+ private final Method getDefinedPackage;
+
+ /**
+ * Creates a new package lookup strategy for a modern VM.
+ *
+ * @param getDefinedPackage The {@code java.lang.ClassLoader#getDefinedPackage(String)} method.
+ */
+ protected ForJava9CapableVm(Method getDefinedPackage) {
+ this.getDefinedPackage = getDefinedPackage;
+ }
+
+ @Override
+ public Package apply(ByteArrayClassLoader classLoader, String name) {
+ try {
+ return (Package) getDefinedPackage.invoke(classLoader, name);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access " + getDefinedPackage, exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Cannot invoke " + getDefinedPackage, exception.getCause());
+ }
+ }
+ }
+ }
+
+ /**
+ * A persistence handler decides on weather the byte array that represents a loaded class is exposed by
+ * the {@link java.lang.ClassLoader#getResourceAsStream(String)} method.
+ */
+ public enum PersistenceHandler {
+
+ /**
+ * The manifest persistence handler retains all class file representations and makes them accessible.
+ */
+ MANIFEST(true) {
+ @Override
+ protected byte[] lookup(String name, ConcurrentMap<String, byte[]> typeDefinitions) {
+ return typeDefinitions.get(name);
+ }
+
+ @Override
+ protected URL url(String resourceName, ConcurrentMap<String, byte[]> typeDefinitions) {
+ if (!resourceName.endsWith(CLASS_FILE_SUFFIX)) {
+ return NO_URL;
+ } else if (resourceName.startsWith("/")) {
+ resourceName = resourceName.substring(1);
+ }
+ String typeName = resourceName.replace('/', '.').substring(FROM_BEGINNING, resourceName.length() - CLASS_FILE_SUFFIX.length());
+ byte[] binaryRepresentation = typeDefinitions.get(typeName);
+ return binaryRepresentation == null
+ ? NO_URL
+ : AccessController.doPrivileged(new UrlDefinitionAction(resourceName, binaryRepresentation));
+
+ }
+ },
+
+ /**
+ * The latent persistence handler hides all class file representations and does not make them accessible
+ * even before they are loaded.
+ */
+ LATENT(false) {
+ @Override
+ protected byte[] lookup(String name, ConcurrentMap<String, byte[]> typeDefinitions) {
+ return typeDefinitions.remove(name);
+ }
+
+ @Override
+ protected URL url(String resourceName, ConcurrentMap<String, byte[]> typeDefinitions) {
+ return NO_URL;
+ }
+ };
+
+ /**
+ * The suffix of files in the Java class file format.
+ */
+ private static final String CLASS_FILE_SUFFIX = ".class";
+
+ /**
+ * {@code true} if this persistence handler represents manifest class file storage.
+ */
+ private final boolean manifest;
+
+ /**
+ * Creates a new persistence handler.
+ *
+ * @param manifest {@code true} if this persistence handler represents manifest class file storage.
+ */
+ PersistenceHandler(boolean manifest) {
+ this.manifest = manifest;
+ }
+
+ /**
+ * Checks if this persistence handler represents manifest class file storage.
+ *
+ * @return {@code true} if this persistence handler represents manifest class file storage.
+ */
+ public boolean isManifest() {
+ return manifest;
+ }
+
+ /**
+ * Performs a lookup of a class file by its name.
+ *
+ * @param name The name of the class to be loaded.
+ * @param typeDefinitions A map of fully qualified class names pointing to their binary representations.
+ * @return The byte array representing the requested class or {@code null} if no such class is known.
+ */
+ protected abstract byte[] lookup(String name, ConcurrentMap<String, byte[]> typeDefinitions);
+
+ /**
+ * Returns a URL representing a class file.
+ *
+ * @param resourceName The name of the requested resource.
+ * @param typeDefinitions A mapping of byte arrays by their type names.
+ * @return A URL representing the type definition or {@code null} if the requested resource does not represent a class file.
+ */
+ protected abstract URL url(String resourceName, ConcurrentMap<String, byte[]> typeDefinitions);
+
+ /**
+ * An action to define a URL that represents a class file.
+ */
+ @EqualsAndHashCode
+ protected static class UrlDefinitionAction implements PrivilegedAction<URL> {
+
+ /**
+ * The URL's encoding character set.
+ */
+ private static final String ENCODING = "UTF-8";
+
+ /**
+ * A value to define a standard port as Byte Buddy's URLs do not represent a port.
+ */
+ private static final int NO_PORT = -1;
+
+ /**
+ * Indicates that Byte Buddy's URLs do not have a file segment.
+ */
+ private static final String NO_FILE = "";
+
+ /**
+ * The name of the type that this URL represents.
+ */
+ private final String typeName;
+
+ /**
+ * The binary representation of the type's class file.
+ */
+ private final byte[] binaryRepresentation;
+
+ /**
+ * Creates a new URL definition action.
+ *
+ * @param typeName The name of the type that this URL represents.
+ * @param binaryRepresentation The binary representation of the type's class file.
+ */
+ protected UrlDefinitionAction(String typeName, byte[] binaryRepresentation) {
+ this.typeName = typeName;
+ this.binaryRepresentation = binaryRepresentation;
+ }
+
+ @Override
+ public URL run() {
+ try {
+ return new URL(URL_SCHEMA,
+ URLEncoder.encode(typeName.replace('.', '/'), ENCODING),
+ NO_PORT,
+ NO_FILE,
+ new ByteArrayUrlStreamHandler(binaryRepresentation));
+ } catch (MalformedURLException exception) {
+ throw new IllegalStateException("Cannot create URL for " + typeName, exception);
+ } catch (UnsupportedEncodingException exception) {
+ throw new IllegalStateException("Could not find encoding: " + ENCODING, exception);
+ }
+ }
+
+ /**
+ * A stream handler that returns the given binary representation.
+ */
+ @EqualsAndHashCode(callSuper = false)
+ protected static class ByteArrayUrlStreamHandler extends URLStreamHandler {
+
+ /**
+ * The binary representation of a type's class file.
+ */
+ private final byte[] binaryRepresentation;
+
+ /**
+ * Creates a new byte array URL stream handler.
+ *
+ * @param binaryRepresentation The binary representation of a type's class file.
+ */
+ protected ByteArrayUrlStreamHandler(byte[] binaryRepresentation) {
+ this.binaryRepresentation = binaryRepresentation;
+ }
+
+ @Override
+ protected URLConnection openConnection(URL url) throws IOException {
+ return new ByteArrayUrlConnection(url, new ByteArrayInputStream(binaryRepresentation));
+ }
+
+ /**
+ * A URL connection for a given byte array.
+ */
+ protected static class ByteArrayUrlConnection extends URLConnection {
+
+ /**
+ * The input stream to return for this connection.
+ */
+ private final InputStream inputStream;
+
+ /**
+ * Creates a new byte array URL connection.
+ *
+ * @param url The URL that this connection represents.
+ * @param inputStream The input stream to return from this connection.
+ */
+ protected ByteArrayUrlConnection(URL url, InputStream inputStream) {
+ super(url);
+ this.inputStream = inputStream;
+ }
+
+ @Override
+ public void connect() {
+ connected = true;
+ }
+
+ @Override
+ public InputStream getInputStream() {
+ connect(); // Mimics the semantics of an actual URL connection.
+ return inputStream;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * <p>
+ * A {@link net.bytebuddy.dynamic.loading.ByteArrayClassLoader} which applies child-first semantics for the
+ * given type definitions.
+ * </p>
+ * <p>
+ * <b>Important</b>: Package definitions remain their parent-first semantics as loaded package definitions do not expose their class loaders.
+ * Also, it is not possible to make this class or its subclass parallel-capable as the loading strategy is overridden.
+ * </p>
+ */
+ public static class ChildFirst extends ByteArrayClassLoader {
+
+ /**
+ * The suffix of files in the Java class file format.
+ */
+ private static final String CLASS_FILE_SUFFIX = ".class";
+
+ /**
+ * Creates a new child-first byte array class loader.
+ *
+ * @param parent The {@link java.lang.ClassLoader} that is the parent of this class loader.
+ * @param typeDefinitions A map of fully qualified class names pointing to their binary representations.
+ */
+ public ChildFirst(ClassLoader parent, Map<String, byte[]> typeDefinitions) {
+ super(parent, typeDefinitions);
+ }
+
+ /**
+ * Creates a new child-first byte array class loader.
+ *
+ * @param parent The {@link java.lang.ClassLoader} that is the parent of this class loader.
+ * @param typeDefinitions A map of fully qualified class names pointing to their binary representations.
+ * @param persistenceHandler The persistence handler of this class loader.
+ */
+ public ChildFirst(ClassLoader parent, Map<String, byte[]> typeDefinitions, PersistenceHandler persistenceHandler) {
+ super(parent, typeDefinitions, persistenceHandler);
+ }
+
+ /**
+ * Creates a new child-first byte array class loader.
+ *
+ * @param parent The {@link java.lang.ClassLoader} that is the parent of this class loader.
+ * @param typeDefinitions A map of fully qualified class names pointing to their binary representations.
+ * @param protectionDomain The protection domain to apply where {@code null} references an implicit protection domain.
+ * @param persistenceHandler The persistence handler of this class loader.
+ * @param packageDefinitionStrategy The package definer to be queried for package definitions.
+ */
+ public ChildFirst(ClassLoader parent,
+ Map<String, byte[]> typeDefinitions,
+ ProtectionDomain protectionDomain,
+ PersistenceHandler persistenceHandler,
+ PackageDefinitionStrategy packageDefinitionStrategy) {
+ super(parent, typeDefinitions, protectionDomain, persistenceHandler, packageDefinitionStrategy);
+ }
+
+ /**
+ * Creates a new child-first byte array class loader.
+ *
+ * @param parent The {@link java.lang.ClassLoader} that is the parent of this class loader.
+ * @param typeDefinitions A map of fully qualified class names pointing to their binary representations.
+ * @param protectionDomain The protection domain to apply where {@code null} references an implicit protection domain.
+ * @param persistenceHandler The persistence handler of this class loader.
+ * @param packageDefinitionStrategy The package definer to be queried for package definitions.
+ * @param classFileTransformer The class file transformer to apply on loaded classes.
+ */
+ public ChildFirst(ClassLoader parent,
+ Map<String, byte[]> typeDefinitions,
+ ProtectionDomain protectionDomain,
+ PersistenceHandler persistenceHandler,
+ PackageDefinitionStrategy packageDefinitionStrategy,
+ ClassFileTransformer classFileTransformer) {
+ super(parent, typeDefinitions, protectionDomain, persistenceHandler, packageDefinitionStrategy, classFileTransformer);
+ }
+
+ @Override
+ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+ synchronized (SYNCHRONIZATION_STRATEGY.initialize().getClassLoadingLock(this, name)) {
+ Class<?> type = findLoadedClass(name);
+ if (type != null) {
+ return type;
+ }
+ try {
+ type = findClass(name);
+ if (resolve) {
+ resolveClass(type);
+ }
+ return type;
+ } catch (ClassNotFoundException exception) {
+ // If an unknown class is loaded, this implementation causes the findClass method of this instance
+ // to be triggered twice. This is however of minor importance because this would result in a
+ // ClassNotFoundException what does not alter the outcome.
+ return super.loadClass(name, resolve);
+ }
+ }
+ }
+
+ @Override
+ public URL getResource(String name) {
+ URL url = persistenceHandler.url(name, typeDefinitions);
+ // If a class resource is defined by this class loader but it is not defined in a manifest manner,
+ // the resource of the parent class loader should be shadowed by 'null'. Note that the delegation
+ // model causes a redundant query to the persistent handler but renders a correct result.
+ return url != null || isShadowed(name)
+ ? url
+ : super.getResource(name);
+ }
+
+ @Override
+ public Enumeration<URL> getResources(String name) throws IOException {
+ URL url = persistenceHandler.url(name, typeDefinitions);
+ return url == null
+ ? super.getResources(name)
+ : new PrependingEnumeration(url, super.getResources(name));
+ }
+
+ /**
+ * Checks if a resource name represents a class file of a class that was loaded by this class loader.
+ *
+ * @param resourceName The resource name of the class to be exposed as its class file.
+ * @return {@code true} if this class represents a class that is being loaded by this class loader.
+ */
+ private boolean isShadowed(String resourceName) {
+ if (persistenceHandler.isManifest() || !resourceName.endsWith(CLASS_FILE_SUFFIX)) {
+ return false;
+ }
+ // This synchronization is required to avoid a racing condition to the actual class loading.
+ synchronized (this) {
+ String typeName = resourceName.replace('/', '.').substring(0, resourceName.length() - CLASS_FILE_SUFFIX.length());
+ if (typeDefinitions.containsKey(typeName)) {
+ return true;
+ }
+ Class<?> loadedClass = findLoadedClass(typeName);
+ return loadedClass != null && loadedClass.getClassLoader() == this;
+ }
+ }
+
+ /**
+ * An enumeration that prepends an element to another enumeration and skips the last element of the provided enumeration.
+ */
+ protected static class PrependingEnumeration implements Enumeration<URL> {
+
+ /**
+ * The next element to return from this enumeration or {@code null} if such an element does not exist.
+ */
+ private URL nextElement;
+
+ /**
+ * The enumeration from which the next elements should be pulled.
+ */
+ private final Enumeration<URL> enumeration;
+
+ /**
+ * Creates a new prepending enumeration.
+ *
+ * @param url The first element of the enumeration.
+ * @param enumeration An enumeration that is used for pulling subsequent urls.
+ */
+ protected PrependingEnumeration(URL url, Enumeration<URL> enumeration) {
+ nextElement = url;
+ this.enumeration = enumeration;
+ }
+
+ @Override
+ public boolean hasMoreElements() {
+ return nextElement != null && enumeration.hasMoreElements();
+ }
+
+ @Override
+ public URL nextElement() {
+ if (nextElement != null && enumeration.hasMoreElements()) {
+ try {
+ return nextElement;
+ } finally {
+ nextElement = enumeration.nextElement();
+ }
+ } else {
+ throw new NoSuchElementException();
+ }
+ }
+ }
+ }
+
+ /**
+ * An enumeration without any elements.
+ */
+ protected enum EmptyEnumeration implements Enumeration<URL> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public boolean hasMoreElements() {
+ return false;
+ }
+
+ @Override
+ public URL nextElement() {
+ throw new NoSuchElementException();
+ }
+ }
+
+ /**
+ * An enumeration that contains a single element.
+ */
+ protected static class SingletonEnumeration implements Enumeration<URL> {
+
+ /**
+ * The current element or {@code null} if this enumeration does not contain further elements.
+ */
+ private URL element;
+
+ /**
+ * Creates a new singleton enumeration.
+ *
+ * @param element The only element.
+ */
+ protected SingletonEnumeration(URL element) {
+ this.element = element;
+ }
+
+ @Override
+ public boolean hasMoreElements() {
+ return element != null;
+ }
+
+ @Override
+ public URL nextElement() {
+ if (element == null) {
+ throw new NoSuchElementException();
+ } else {
+ try {
+ return element;
+ } finally {
+ element = null;
+ }
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/loading/ClassInjector.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/loading/ClassInjector.java
new file mode 100644
index 0000000..7b7032b
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/loading/ClassInjector.java
@@ -0,0 +1,1493 @@
+package net.bytebuddy.dynamic.loading;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
+import net.bytebuddy.implementation.FixedValue;
+import net.bytebuddy.implementation.MethodCall;
+import net.bytebuddy.utility.JavaType;
+import net.bytebuddy.utility.RandomString;
+import org.objectweb.asm.Opcodes;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.lang.instrument.Instrumentation;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.ReflectPermission;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.Permission;
+import java.security.PrivilegedAction;
+import java.security.ProtectionDomain;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+
+/**
+ * <p>
+ * A class injector is capable of injecting classes into a {@link java.lang.ClassLoader} without
+ * requiring the class loader to being able to explicitly look up these classes.
+ * </p>
+ * <p>
+ * <b>Important</b>: Byte Buddy does not supply privileges when injecting code. When using a {@link SecurityManager},
+ * the user of this injector is responsible for providing access to non-public properties.
+ * </p>
+ */
+public interface ClassInjector {
+
+ /**
+ * Determines the default behavior for type injections when a type is already loaded.
+ */
+ boolean ALLOW_EXISTING_TYPES = false;
+
+ /**
+ * Injects the given types into the represented class loader.
+ *
+ * @param types The types to load via injection.
+ * @return The loaded types that were passed as arguments.
+ */
+ Map<TypeDescription, Class<?>> inject(Map<? extends TypeDescription, byte[]> types);
+
+ /**
+ * A class injector that uses reflective method calls.
+ */
+ @EqualsAndHashCode
+ class UsingReflection implements ClassInjector {
+
+ /**
+ * The dispatcher to use for accessing a class loader via reflection.
+ */
+ private static final Dispatcher.Initializable DISPATCHER = AccessController.doPrivileged(Dispatcher.CreationAction.INSTANCE);
+
+ /**
+ * The class loader into which the classes are to be injected.
+ */
+ private final ClassLoader classLoader;
+
+ /**
+ * The protection domain that is used when loading classes.
+ */
+ private final ProtectionDomain protectionDomain;
+
+ /**
+ * The package definer to be queried for package definitions.
+ */
+ private final PackageDefinitionStrategy packageDefinitionStrategy;
+
+ /**
+ * Determines if an exception should be thrown when attempting to load a type that already exists.
+ */
+ private final boolean forbidExisting;
+
+ /**
+ * Creates a new injector for the given {@link java.lang.ClassLoader} and a default {@link java.security.ProtectionDomain} and a
+ * trivial {@link PackageDefinitionStrategy} which does not trigger an error when discovering existent classes.
+ *
+ * @param classLoader The {@link java.lang.ClassLoader} into which new class definitions are to be injected. Must not be the bootstrap loader.
+ */
+ public UsingReflection(ClassLoader classLoader) {
+ this(classLoader, ClassLoadingStrategy.NO_PROTECTION_DOMAIN);
+ }
+
+ /**
+ * Creates a new injector for the given {@link java.lang.ClassLoader} and a default {@link PackageDefinitionStrategy} where the
+ * injection of existent classes does not trigger an error.
+ *
+ * @param classLoader The {@link java.lang.ClassLoader} into which new class definitions are to be injected. Must not be the bootstrap loader.
+ * @param protectionDomain The protection domain to apply during class definition.
+ */
+ public UsingReflection(ClassLoader classLoader, ProtectionDomain protectionDomain) {
+ this(classLoader,
+ protectionDomain,
+ PackageDefinitionStrategy.Trivial.INSTANCE,
+ ALLOW_EXISTING_TYPES);
+ }
+
+ /**
+ * Creates a new injector for the given {@link java.lang.ClassLoader} and {@link java.security.ProtectionDomain}.
+ *
+ * @param classLoader The {@link java.lang.ClassLoader} into which new class definitions are to be injected.Must not be the bootstrap loader.
+ * @param protectionDomain The protection domain to apply during class definition.
+ * @param packageDefinitionStrategy The package definer to be queried for package definitions.
+ * @param forbidExisting Determines if an exception should be thrown when attempting to load a type that already exists.
+ */
+ public UsingReflection(ClassLoader classLoader,
+ ProtectionDomain protectionDomain,
+ PackageDefinitionStrategy packageDefinitionStrategy,
+ boolean forbidExisting) {
+ if (classLoader == null) {
+ throw new IllegalArgumentException("Cannot inject classes into the bootstrap class loader");
+ }
+ this.classLoader = classLoader;
+ this.protectionDomain = protectionDomain;
+ this.packageDefinitionStrategy = packageDefinitionStrategy;
+ this.forbidExisting = forbidExisting;
+ }
+
+ /**
+ * Indicates if this class injection is available on the current VM.
+ *
+ * @return {@code true} if this class injection is available.
+ */
+ public static boolean isAvailable() {
+ return DISPATCHER.isAvailable();
+ }
+
+ /**
+ * Creates a class injector for the system class loader.
+ *
+ * @return A class injector for the system class loader.
+ */
+ public static ClassInjector ofSystemClassLoader() {
+ return new UsingReflection(ClassLoader.getSystemClassLoader());
+ }
+
+ @Override
+ public Map<TypeDescription, Class<?>> inject(Map<? extends TypeDescription, byte[]> types) {
+ Dispatcher dispatcher = DISPATCHER.initialize();
+ Map<TypeDescription, Class<?>> loadedTypes = new HashMap<TypeDescription, Class<?>>();
+ for (Map.Entry<? extends TypeDescription, byte[]> entry : types.entrySet()) {
+ String typeName = entry.getKey().getName();
+ synchronized (dispatcher.getClassLoadingLock(classLoader, typeName)) {
+ Class<?> type = dispatcher.findClass(classLoader, typeName);
+ if (type == null) {
+ int packageIndex = typeName.lastIndexOf('.');
+ if (packageIndex != -1) {
+ String packageName = typeName.substring(0, packageIndex);
+ PackageDefinitionStrategy.Definition definition = packageDefinitionStrategy.define(classLoader, packageName, typeName);
+ if (definition.isDefined()) {
+ Package definedPackage = dispatcher.getPackage(classLoader, packageName);
+ if (definedPackage == null) {
+ dispatcher.definePackage(classLoader,
+ packageName,
+ definition.getSpecificationTitle(),
+ definition.getSpecificationVersion(),
+ definition.getSpecificationVendor(),
+ definition.getImplementationTitle(),
+ definition.getImplementationVersion(),
+ definition.getImplementationVendor(),
+ definition.getSealBase());
+ } else if (!definition.isCompatibleTo(definedPackage)) {
+ throw new SecurityException("Sealing violation for package " + packageName);
+ }
+ }
+ }
+ type = dispatcher.defineClass(classLoader, typeName, entry.getValue(), protectionDomain);
+ } else if (forbidExisting) {
+ throw new IllegalStateException("Cannot inject already loaded type: " + type);
+ }
+ loadedTypes.put(entry.getKey(), type);
+ }
+ }
+ return loadedTypes;
+ }
+
+ /**
+ * A dispatcher for accessing a {@link ClassLoader} reflectively.
+ */
+ protected interface Dispatcher {
+
+ /**
+ * Indicates a class that is currently not defined.
+ */
+ Class<?> UNDEFINED = null;
+
+ /**
+ * Returns the lock for loading the specified class.
+ *
+ * @param classLoader the class loader to inject the class into.
+ * @param name The name of the class.
+ * @return The lock for loading this class.
+ */
+ Object getClassLoadingLock(ClassLoader classLoader, String name);
+
+ /**
+ * Looks up a class from the given class loader.
+ *
+ * @param classLoader The class loader for which a class should be located.
+ * @param name The binary name of the class that should be located.
+ * @return The class for the binary name or {@code null} if no such class is defined for the provided class loader.
+ */
+ Class<?> findClass(ClassLoader classLoader, String name);
+
+ /**
+ * Defines a class for the given class loader.
+ *
+ * @param classLoader The class loader for which a new class should be defined.
+ * @param name The binary name of the class that should be defined.
+ * @param binaryRepresentation The binary representation of the class.
+ * @param protectionDomain The protection domain for the defined class.
+ * @return The defined, loaded class.
+ */
+ Class<?> defineClass(ClassLoader classLoader, String name, byte[] binaryRepresentation, ProtectionDomain protectionDomain);
+
+ /**
+ * Looks up a package from a class loader.
+ *
+ * @param classLoader The class loader to query.
+ * @param name The binary name of the package.
+ * @return The package for the given name as defined by the provided class loader or {@code null} if no such package exists.
+ */
+ Package getPackage(ClassLoader classLoader, String name);
+
+ /**
+ * Defines a package for the given class loader.
+ *
+ * @param classLoader The class loader for which a package is to be defined.
+ * @param name The binary name of the package.
+ * @param specificationTitle The specification title of the package or {@code null} if no specification title exists.
+ * @param specificationVersion The specification version of the package or {@code null} if no specification version exists.
+ * @param specificationVendor The specification vendor of the package or {@code null} if no specification vendor exists.
+ * @param implementationTitle The implementation title of the package or {@code null} if no implementation title exists.
+ * @param implementationVersion The implementation version of the package or {@code null} if no implementation version exists.
+ * @param implementationVendor The implementation vendor of the package or {@code null} if no implementation vendor exists.
+ * @param sealBase The seal base URL or {@code null} if the package should not be sealed.
+ * @return The defined package.
+ */
+ Package definePackage(ClassLoader classLoader,
+ String name,
+ String specificationTitle,
+ String specificationVersion,
+ String specificationVendor,
+ String implementationTitle,
+ String implementationVersion,
+ String implementationVendor,
+ URL sealBase);
+
+ /**
+ * Initializes a dispatcher to make non-accessible APIs accessible.
+ */
+ interface Initializable {
+
+ /**
+ * Indicates if this dispatcher is available.
+ *
+ * @return {@code true} if this dispatcher is available.
+ */
+ boolean isAvailable();
+
+ /**
+ * Initializes this dispatcher.
+ *
+ * @return The initiailized dispatcher.
+ */
+ Dispatcher initialize();
+ }
+
+ /**
+ * A creation action for a dispatcher.
+ */
+ enum CreationAction implements PrivilegedAction<Initializable> {
+
+ /**
+ * The singelton instance.
+ */
+ INSTANCE;
+
+ @Override
+ @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback")
+ public Initializable run() {
+ try {
+ return ClassFileVersion.ofThisVm().isAtLeast(ClassFileVersion.JAVA_V9)
+ ? Dispatcher.Indirect.make()
+ : Dispatcher.Direct.make();
+ } catch (Exception exception) {
+ return new Unavailable(exception);
+ }
+ }
+ }
+
+ /**
+ * A class injection dispatcher that is using reflection on the {@link ClassLoader} methods.
+ */
+ @EqualsAndHashCode
+ abstract class Direct implements Dispatcher, Initializable {
+
+ /**
+ * An instance of {@link ClassLoader#findLoadedClass(String)}.
+ */
+ protected final Method findLoadedClass;
+
+ /**
+ * An instance of {@link ClassLoader#defineClass(String, byte[], int, int, ProtectionDomain)}.
+ */
+ protected final Method defineClass;
+
+ /**
+ * An instance of {@link ClassLoader#getPackage(String)} or {@code ClassLoader#getDefinedPackage(String)}.
+ */
+ protected final Method getPackage;
+
+ /**
+ * An instance of {@link ClassLoader#definePackage(String, String, String, String, String, String, String, URL)}.
+ */
+ protected final Method definePackage;
+
+ /**
+ * Creates a new direct injection dispatcher.
+ *
+ * @param findLoadedClass An instance of {@link ClassLoader#findLoadedClass(String)}.
+ * @param defineClass An instance of {@link ClassLoader#defineClass(String, byte[], int, int, ProtectionDomain)}.
+ * @param getPackage An instance of {@link ClassLoader#getPackage(String)} or {@code ClassLoader#getDefinedPackage(String)}.
+ * @param definePackage An instance of {@link ClassLoader#definePackage(String, String, String, String, String, String, String, URL)}.
+ */
+ protected Direct(Method findLoadedClass,
+ Method defineClass,
+ Method getPackage,
+ Method definePackage) {
+ this.findLoadedClass = findLoadedClass;
+ this.defineClass = defineClass;
+ this.getPackage = getPackage;
+ this.definePackage = definePackage;
+ }
+
+ /**
+ * Creates a direct dispatcher.
+ *
+ * @return A direct dispatcher for class injection.
+ * @throws Exception If the creation is impossible.
+ */
+ protected static Initializable make() throws Exception {
+ Method getPackage;
+ try {
+ getPackage = ClassLoader.class.getDeclaredMethod("getDefinedPackage", String.class);
+ } catch (NoSuchMethodException ignored) {
+ getPackage = ClassLoader.class.getDeclaredMethod("getPackage", String.class);
+ }
+ Method findLoadedClass = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class);
+ Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass",
+ String.class,
+ byte[].class,
+ int.class,
+ int.class,
+ ProtectionDomain.class);
+ Method definePackage = ClassLoader.class.getDeclaredMethod("definePackage",
+ String.class,
+ String.class,
+ String.class,
+ String.class,
+ String.class,
+ String.class,
+ String.class,
+ URL.class);
+ try {
+ return new ForJava7CapableVm(findLoadedClass,
+ defineClass,
+ getPackage,
+ definePackage,
+ ClassLoader.class.getDeclaredMethod("getClassLoadingLock", String.class));
+ } catch (NoSuchMethodException ignored) {
+ return new ForLegacyVm(findLoadedClass, defineClass, getPackage, definePackage);
+ }
+ }
+
+ @Override
+ public Class<?> findClass(ClassLoader classLoader, String name) {
+ try {
+ return (Class<?>) findLoadedClass.invoke(classLoader, name);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Could not access java.lang.ClassLoader#findClass", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.ClassLoader#findClass", exception.getCause());
+ }
+ }
+
+ @Override
+ public Class<?> defineClass(ClassLoader classLoader, String name, byte[] binaryRepresentation, ProtectionDomain protectionDomain) {
+ try {
+ return (Class<?>) defineClass.invoke(classLoader, name, binaryRepresentation, 0, binaryRepresentation.length, protectionDomain);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Could not access java.lang.ClassLoader#defineClass", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.ClassLoader#defineClass", exception.getCause());
+ }
+ }
+
+ @Override
+ public Package getPackage(ClassLoader classLoader, String name) {
+ try {
+ return (Package) getPackage.invoke(classLoader, name);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Could not access java.lang.ClassLoader#getPackage", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.ClassLoader#getPackage", exception.getCause());
+ }
+ }
+
+ @Override
+ public Package definePackage(ClassLoader classLoader,
+ String name,
+ String specificationTitle,
+ String specificationVersion,
+ String specificationVendor,
+ String implementationTitle,
+ String implementationVersion,
+ String implementationVendor,
+ URL sealBase) {
+ try {
+ return (Package) definePackage.invoke(classLoader,
+ name,
+ specificationTitle,
+ specificationVersion,
+ specificationVendor,
+ implementationTitle,
+ implementationVersion,
+ implementationVendor,
+ sealBase);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Could not access java.lang.ClassLoader#definePackage", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.ClassLoader#definePackage", exception.getCause());
+ }
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return true;
+ }
+
+ @Override
+ @SuppressFBWarnings(value = {"DP_DO_INSIDE_DO_PRIVILEGED", "REC_CATCH_EXCEPTION"}, justification = "Privilege is explicit user responsibility")
+ public Dispatcher initialize() {
+ try {
+ // This is safe even in a multi-threaded environment as all threads set the instances accessible before invoking any methods.
+ // By always setting accessibility, the security manager is always triggered if this operation was illegal.
+ findLoadedClass.setAccessible(true);
+ defineClass.setAccessible(true);
+ getPackage.setAccessible(true);
+ definePackage.setAccessible(true);
+ onInitialization();
+ return this;
+ } catch (Exception exception) {
+ return new Unavailable(exception);
+ }
+ }
+
+ /**
+ * Invoked upon initializing methods.
+ */
+ protected abstract void onInitialization();
+
+ /**
+ * A resolved class dispatcher for a class injector on a VM running at least Java 7.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ protected static class ForJava7CapableVm extends Direct {
+
+ /**
+ * An instance of {@code ClassLoader#getClassLoadingLock(String)}.
+ */
+ private final Method getClassLoadingLock;
+
+ /**
+ * Creates a new resolved reflection store for a VM running at least Java 7.
+ *
+ * @param getClassLoadingLock An instance of {@code ClassLoader#getClassLoadingLock(String)}.
+ * @param findLoadedClass An instance of {@link ClassLoader#findLoadedClass(String)}.
+ * @param defineClass An instance of {@link ClassLoader#defineClass(String, byte[], int, int, ProtectionDomain)}.
+ * @param getPackage An instance of {@link ClassLoader#getPackage(String)} or {@code ClassLoader#getDefinedPackage(String)}.
+ * @param definePackage An instance of {@link ClassLoader#definePackage(String, String, String, String, String, String, String, URL)}.
+ */
+ protected ForJava7CapableVm(Method findLoadedClass,
+ Method defineClass,
+ Method getPackage,
+ Method definePackage,
+ Method getClassLoadingLock) {
+ super(findLoadedClass, defineClass, getPackage, definePackage);
+ this.getClassLoadingLock = getClassLoadingLock;
+ }
+
+ @Override
+ public Object getClassLoadingLock(ClassLoader classLoader, String name) {
+ try {
+ return getClassLoadingLock.invoke(classLoader, name);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Could not access java.lang.ClassLoader#getClassLoadingLock", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.ClassLoader#getClassLoadingLock", exception.getCause());
+ }
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "DP_DO_INSIDE_DO_PRIVILEGED", justification = "Privilege is explicit user responsibility")
+ protected void onInitialization() {
+ getClassLoadingLock.setAccessible(true);
+ }
+ }
+
+ /**
+ * A resolved class dispatcher for a class injector prior to Java 7.
+ */
+ protected static class ForLegacyVm extends Direct {
+
+ /**
+ * Creates a new resolved reflection store for a VM prior to Java 8.
+ *
+ * @param findLoadedClass An instance of {@link ClassLoader#findLoadedClass(String)}.
+ * @param defineClass An instance of {@link ClassLoader#defineClass(String, byte[], int, int, ProtectionDomain)}.
+ * @param getPackage An instance of {@link ClassLoader#getPackage(String)} or {@code ClassLoader#getDefinedPackage(String)}.
+ * @param definePackage An instance of {@link ClassLoader#definePackage(String, String, String, String, String, String, String, URL)}.
+ */
+ protected ForLegacyVm(Method findLoadedClass,
+ Method defineClass,
+ Method getPackage,
+ Method definePackage) {
+ super(findLoadedClass, defineClass, getPackage, definePackage);
+ }
+
+ @Override
+ public Object getClassLoadingLock(ClassLoader classLoader, String name) {
+ return classLoader;
+ }
+
+ @Override
+ protected void onInitialization() {
+ /* do nothing */
+ }
+ }
+ }
+
+ /**
+ * An indirect dispatcher that uses a redirection accessor class that was injected into the bootstrap class loader.
+ */
+ @EqualsAndHashCode
+ class Indirect implements Dispatcher, Initializable {
+
+ /**
+ * The access permission to check upon injection if a security manager is active.
+ */
+ private static final Permission ACCESS_PERMISSION = new ReflectPermission("suppressAccessChecks");
+
+ /**
+ * An instance of the accessor class that is required for using it's intentionally non-static methods.
+ */
+ private final Object accessor;
+
+ /**
+ * The accessor method for using {@link ClassLoader#findLoadedClass(String)}.
+ */
+ private final Method findLoadedClass;
+
+ /**
+ * The accessor method for using {@link ClassLoader#defineClass(String, byte[], int, int, ProtectionDomain)}.
+ */
+ private final Method defineClass;
+
+ /**
+ * The accessor method for using {@link ClassLoader#getPackage(String)} or {@code ClassLoader#getDefinedPackage(String)}.
+ */
+ private final Method getPackage;
+
+ /**
+ * The accessor method for using {@link ClassLoader#definePackage(String, String, String, String, String, String, String, URL)}.
+ */
+ private final Method definePackage;
+
+ /**
+ * The accessor method for using {@code ClassLoader#getClassLoadingLock(String)} or returning the supplied {@link ClassLoader}
+ * if this method does not exist on the current VM.
+ */
+ private final Method getClassLoadingLock;
+
+ /**
+ * Creates a new indirect class loading injection dispatcher.
+ *
+ * @param accessor An instance of the accessor class that is required for using it's intentionally non-static methods.
+ * @param findLoadedClass An instance of {@link ClassLoader#findLoadedClass(String)}.
+ * @param defineClass An instance of {@link ClassLoader#defineClass(String, byte[], int, int, ProtectionDomain)}.
+ * @param getPackage An instance of {@link ClassLoader#getPackage(String)} or {@code ClassLoader#getDefinedPackage(String)}.
+ * @param definePackage An instance of {@link ClassLoader#definePackage(String, String, String, String, String, String, String, URL)}.
+ * @param getClassLoadingLock The accessor method for using {@code ClassLoader#getClassLoadingLock(String)} or returning the
+ * supplied {@link ClassLoader} if this method does not exist on the current VM.
+ */
+ protected Indirect(Object accessor,
+ Method findLoadedClass,
+ Method defineClass,
+ Method getPackage,
+ Method definePackage,
+ Method getClassLoadingLock) {
+ this.accessor = accessor;
+ this.findLoadedClass = findLoadedClass;
+ this.defineClass = defineClass;
+ this.getPackage = getPackage;
+ this.definePackage = definePackage;
+ this.getClassLoadingLock = getClassLoadingLock;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return true;
+ }
+
+ /**
+ * Creates an indirect dispatcher.
+ *
+ * @return An indirect dispatcher for class creation.
+ * @throws Exception If the dispatcher cannot be created.
+ */
+ @SuppressFBWarnings(value = "DP_DO_INSIDE_DO_PRIVILEGED", justification = "Privilege is explicit caller responsibility")
+ public static Initializable make() throws Exception {
+ Class<?> unsafe = Class.forName("sun.misc.Unsafe");
+ Field theUnsafe = unsafe.getDeclaredField("theUnsafe");
+ theUnsafe.setAccessible(true);
+ Object unsafeInstance = theUnsafe.get(null);
+ Method getPackage;
+ try {
+ getPackage = ClassLoader.class.getDeclaredMethod("getDeclaredPackage", String.class);
+ } catch (NoSuchMethodException ignored) {
+ getPackage = ClassLoader.class.getDeclaredMethod("getPackage", String.class);
+ }
+ DynamicType.Builder<?> builder = new ByteBuddy()
+ .subclass(Object.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
+ .name(ClassLoader.class.getName() + "$ByteBuddyAccessor$" + RandomString.make())
+ .defineMethod("findLoadedClass", Class.class, Visibility.PUBLIC)
+ .withParameters(ClassLoader.class, String.class)
+ .intercept(MethodCall.invoke(ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class))
+ .onArgument(0)
+ .withArgument(1))
+ .defineMethod("defineClass", Class.class, Visibility.PUBLIC)
+ .withParameters(ClassLoader.class, String.class, byte[].class, int.class, int.class, ProtectionDomain.class)
+ .intercept(MethodCall.invoke(ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class, ProtectionDomain.class))
+ .onArgument(0)
+ .withArgument(1, 2, 3, 4, 5))
+ .defineMethod("getPackage", Package.class, Visibility.PUBLIC)
+ .withParameters(ClassLoader.class, String.class)
+ .intercept(MethodCall.invoke(getPackage)
+ .onArgument(0)
+ .withArgument(1))
+ .defineMethod("definePackage", Package.class, Visibility.PUBLIC)
+ .withParameters(ClassLoader.class, String.class, String.class, String.class, String.class, String.class, String.class, String.class, URL.class)
+ .intercept(MethodCall.invoke(ClassLoader.class.getDeclaredMethod("definePackage", String.class, String.class, String.class, String.class, String.class, String.class, String.class, URL.class))
+ .onArgument(0)
+ .withArgument(1, 2, 3, 4, 5, 6, 7, 8));
+ try {
+ builder = builder.defineMethod("getClassLoadingLock", Object.class, Visibility.PUBLIC)
+ .withParameters(ClassLoader.class, String.class)
+ .intercept(MethodCall.invoke(ClassLoader.class.getDeclaredMethod("getClassLoadingLock", String.class))
+ .onArgument(0)
+ .withArgument(1));
+ } catch (NoSuchMethodException ignored) {
+ builder = builder.defineMethod("getClassLoadingLock", Object.class, Visibility.PUBLIC)
+ .withParameters(ClassLoader.class, String.class)
+ .intercept(FixedValue.argument(0));
+ }
+ Class<?> type = builder.make().load(ClassLoadingStrategy.BOOTSTRAP_LOADER, new ClassLoadingStrategy.ForUnsafeInjection()).getLoaded();
+ return new Indirect(unsafe.getDeclaredMethod("allocateInstance", Class.class).invoke(unsafeInstance, type),
+ type.getMethod("findLoadedClass", ClassLoader.class, String.class),
+ type.getMethod("defineClass", ClassLoader.class, String.class, byte[].class, int.class, int.class, ProtectionDomain.class),
+ type.getMethod("getPackage", ClassLoader.class, String.class),
+ type.getMethod("definePackage", ClassLoader.class, String.class, String.class, String.class, String.class, String.class, String.class, String.class, URL.class),
+ type.getMethod("getClassLoadingLock", ClassLoader.class, String.class));
+ }
+
+ @Override
+ public Dispatcher initialize() {
+ SecurityManager securityManager = System.getSecurityManager();
+ if (securityManager != null) {
+ securityManager.checkPermission(ACCESS_PERMISSION);
+ }
+ return this;
+ }
+
+ @Override
+ public Object getClassLoadingLock(ClassLoader classLoader, String name) {
+ try {
+ return getClassLoadingLock.invoke(accessor, classLoader, name);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Could not access (accessor)::getClassLoadingLock", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking (accessor)::getClassLoadingLock", exception.getCause());
+ }
+ }
+
+ @Override
+ public Class<?> findClass(ClassLoader classLoader, String name) {
+ try {
+ return (Class<?>) findLoadedClass.invoke(accessor, classLoader, name);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Could not access (accessor)::findLoadedClass", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking (accessor)::findLoadedClass", exception.getCause());
+ }
+ }
+
+ @Override
+ public Class<?> defineClass(ClassLoader classLoader, String name, byte[] binaryRepresentation, ProtectionDomain protectionDomain) {
+ try {
+ return (Class<?>) defineClass.invoke(accessor, classLoader, name, binaryRepresentation, 0, binaryRepresentation.length, protectionDomain);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Could not access (accessor)::defineClass", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking (accessor)::defineClass", exception.getCause());
+ }
+ }
+
+ @Override
+ public Package getPackage(ClassLoader classLoader, String name) {
+ try {
+ return (Package) getPackage.invoke(accessor, classLoader, name);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Could not access (accessor)::getPackage", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking (accessor)::getPackage", exception.getCause());
+ }
+ }
+
+ @Override
+ public Package definePackage(ClassLoader classLoader,
+ String name,
+ String specificationTitle,
+ String specificationVersion,
+ String specificationVendor,
+ String implementationTitle,
+ String implementationVersion,
+ String implementationVendor,
+ URL sealBase) {
+ try {
+ return (Package) definePackage.invoke(accessor,
+ classLoader,
+ name,
+ specificationTitle,
+ specificationVersion,
+ specificationVendor,
+ implementationTitle,
+ implementationVersion,
+ implementationVendor,
+ sealBase);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Could not access (accessor)::definePackage", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking (accessor)::definePackage", exception.getCause());
+ }
+ }
+ }
+
+ /**
+ * Represents an unsuccessfully loaded method lookup.
+ */
+ @EqualsAndHashCode
+ class Unavailable implements Dispatcher, Initializable {
+
+ /**
+ * The exception that occurred when looking up the reflection methods.
+ */
+ private final Exception exception;
+
+ /**
+ * Creates a new faulty reflection store.
+ *
+ * @param exception The exception that was thrown when attempting to lookup the method.
+ */
+ protected Unavailable(Exception exception) {
+ this.exception = exception;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return false;
+ }
+
+ @Override
+ public Dispatcher initialize() {
+ return this;
+ }
+
+ @Override
+ public Object getClassLoadingLock(ClassLoader classLoader, String name) {
+ return classLoader;
+ }
+
+ @Override
+ public Class<?> findClass(ClassLoader classLoader, String name) {
+ try {
+ return classLoader.loadClass(name);
+ } catch (ClassNotFoundException ignored) {
+ return UNDEFINED;
+ }
+ }
+
+ @Override
+ public Class<?> defineClass(ClassLoader classLoader, String name, byte[] binaryRepresentation, ProtectionDomain protectionDomain) {
+ throw new UnsupportedOperationException("Cannot define class using reflection", exception);
+ }
+
+ @Override
+ public Package getPackage(ClassLoader classLoader, String name) {
+ throw new UnsupportedOperationException("Cannot get package using reflection", exception);
+ }
+
+ @Override
+ public Package definePackage(ClassLoader classLoader,
+ String name,
+ String specificationTitle,
+ String specificationVersion,
+ String specificationVendor,
+ String implementationTitle,
+ String implementationVersion,
+ String implementationVendor,
+ URL sealBase) {
+ throw new UnsupportedOperationException("Cannot define package using injection", exception);
+ }
+ }
+ }
+ }
+
+ /**
+ * <p>
+ * A class injector that uses a {@code java.lang.invoke.MethodHandles$Lookup} object for defining a class.
+ * </p>
+ * <p>
+ * <b>Important</b>: This functionality is only available starting from Java 9.
+ * </p>
+ */
+ @EqualsAndHashCode
+ class UsingLookup implements ClassInjector {
+
+ /**
+ * The dispatcher to interacting with method handles.
+ */
+ private static final Dispatcher DISPATCHER = AccessController.doPrivileged(Dispatcher.Creator.INSTANCE);
+
+ /**
+ * Indicates a lookup instance's package lookup mode.
+ */
+ private static final int PACKAGE_LOOKUP = 0x8;
+
+ /**
+ * The {@code java.lang.invoke.MethodHandles$Lookup} to use.
+ */
+ private final Object lookup;
+
+ /**
+ * Creates a new class injector using a lookup instance.
+ *
+ * @param lookup The {@code java.lang.invoke.MethodHandles$Lookup} instance to use.
+ */
+ protected UsingLookup(Object lookup) {
+ this.lookup = lookup;
+ }
+
+ /**
+ * Creates class injector that defines a class using a method handle lookup.
+ *
+ * @param lookup The {@code java.lang.invoke.MethodHandles$Lookup} instance to use.
+ * @return An appropriate class injector.
+ */
+ public static UsingLookup of(Object lookup) {
+ if (!DISPATCHER.isAlive()) {
+ throw new IllegalStateException("The current VM does not support class definition via method handle lookups");
+ } else if (!JavaType.METHOD_HANDLES_LOOKUP.getTypeStub().isInstance(lookup)) {
+ throw new IllegalArgumentException("Not a method handle lookup: " + lookup);
+ } else if ((DISPATCHER.lookupModes(lookup) & PACKAGE_LOOKUP) == 0) {
+ throw new IllegalArgumentException("Lookup does not imply package-access: " + lookup);
+ }
+ return new UsingLookup(DISPATCHER.dropLookupMode(lookup, Opcodes.ACC_PRIVATE));
+ }
+
+ /**
+ * Returns the lookup type this injector is based upon.
+ *
+ * @return The lookup type.
+ */
+ public Class<?> lookupType() {
+ return DISPATCHER.lookupType(lookup);
+ }
+
+ @Override
+ public Map<TypeDescription, Class<?>> inject(Map<? extends TypeDescription, byte[]> types) {
+ Map<TypeDescription, Class<?>> loaded = new HashMap<TypeDescription, Class<?>>();
+ for (Map.Entry<? extends TypeDescription, byte[]> entry : types.entrySet()) {
+ if (!entry.getKey().isSamePackage(new TypeDescription.ForLoadedType(lookupType()))) {
+ throw new IllegalArgumentException(entry.getKey() + " cannot be defined in its package using " + lookup);
+ }
+ loaded.put(entry.getKey(), DISPATCHER.defineClass(lookup, entry.getValue()));
+ }
+ return loaded;
+ }
+
+ /**
+ * A dispatcher for interacting with a method handle lookup.
+ */
+ protected interface Dispatcher {
+
+ /**
+ * Indicates if this dispatcher is available on the current VM.
+ *
+ * @return {@code true} if this dispatcher is alive.
+ */
+ boolean isAlive();
+
+ /**
+ * Returns the lookup type for a given method handle lookup.
+ *
+ * @param lookup The lookup instance.
+ * @return The lookup type.
+ */
+ Class<?> lookupType(Object lookup);
+
+ /**
+ * Returns a lookup objects lookup types.
+ *
+ * @param lookup The lookup instance.
+ * @return The modifiers indicating the instance's lookup modes.
+ */
+ int lookupModes(Object lookup);
+
+ /**
+ * Drops a given lookup mode from a lookup instance.
+ *
+ * @param lookup The lookup instance.
+ * @param mode The modes to drop.
+ * @return A new lookup instance where the modes were dropped.
+ */
+ Object dropLookupMode(Object lookup, int mode);
+
+ /**
+ * Defines a class.
+ *
+ * @param lookup The {@code java.lang.invoke.MethodHandles$Lookup} instance to use.
+ * @param binaryRepresentation The defined class's binary representation.
+ * @return The defined class.
+ */
+ Class<?> defineClass(Object lookup, byte[] binaryRepresentation);
+
+ /**
+ * An action for defining a dispatcher.
+ */
+ enum Creator implements PrivilegedAction<Dispatcher> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback")
+ public Dispatcher run() {
+ try {
+ Class<?> lookup = JavaType.METHOD_HANDLES_LOOKUP.load();
+ return new Dispatcher.ForJava9CapableVm(lookup.getMethod("lookupClass"),
+ lookup.getMethod("lookupModes"),
+ lookup.getMethod("dropLookupMode", int.class),
+ lookup.getMethod("defineClass", byte[].class));
+ } catch (Exception ignored) {
+ return Dispatcher.ForLegacyVm.INSTANCE;
+ }
+ }
+ }
+
+ /**
+ * A dispatcher for a legacy VM that does not support class definition via method handles.
+ */
+ enum ForLegacyVm implements Dispatcher {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public boolean isAlive() {
+ return false;
+ }
+
+ @Override
+ public Class<?> lookupType(Object lookup) {
+ throw new IllegalStateException("Cannot dispatch method for java.lang.invoke.MethodHandles$Lookup");
+ }
+
+ @Override
+ public int lookupModes(Object lookup) {
+ throw new IllegalStateException("Cannot dispatch method for java.lang.invoke.MethodHandles$Lookup");
+ }
+
+ @Override
+ public Object dropLookupMode(Object lookup, int mode) {
+ throw new IllegalStateException("Cannot dispatch method for java.lang.invoke.MethodHandles$Lookup");
+ }
+
+ @Override
+ public Class<?> defineClass(Object lookup, byte[] binaryRepresentation) {
+ throw new IllegalStateException("Cannot dispatch method for java.lang.invoke.MethodHandles$Lookup");
+ }
+ }
+
+ /**
+ * A dispatcher for a Java 9 capable VM that supports class definition via method handles.
+ */
+ @EqualsAndHashCode
+ class ForJava9CapableVm implements Dispatcher {
+
+ /**
+ * The {@code java.lang.invoke.MethodHandles$Lookup#lookupClass} method.
+ */
+ private final Method lookupClass;
+
+ /**
+ * The {@code java.lang.invoke.MethodHandles$Lookup#lookupModes} method.
+ */
+ private final Method lookupModes;
+
+ /**
+ * The {@code java.lang.invoke.MethodHandles$Lookup#dropLookupMode} method.
+ */
+ private final Method dropLookupMode;
+
+ /**
+ * The {@code java.lang.invoke.MethodHandles$Lookup#defineClass} method.
+ */
+ private final Method defineClass;
+
+ /**
+ * Creates a new dispatcher for a Java 9 capable VM.
+ *
+ * @param lookupClass The {@code java.lang.invoke.MethodHandles$Lookup#lookupClass} method.
+ * @param lookupModes The {@code java.lang.invoke.MethodHandles$Lookup#lookupModes} method.
+ * @param dropLookupMode The {@code java.lang.invoke.MethodHandles$Lookup#dropLookupMode} method.
+ * @param defineClass The {@code java.lang.invoke.MethodHandles$Lookup#defineClass} method.
+ */
+ protected ForJava9CapableVm(Method lookupClass, Method lookupModes, Method dropLookupMode, Method defineClass) {
+ this.lookupClass = lookupClass;
+ this.lookupModes = lookupModes;
+ this.defineClass = defineClass;
+ this.dropLookupMode = dropLookupMode;
+ }
+
+ @Override
+ public boolean isAlive() {
+ return true;
+ }
+
+ @Override
+ public Class<?> lookupType(Object lookup) {
+ try {
+ return (Class<?>) lookupClass.invoke(lookup);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.invoke.MethodHandles$Lookup#lookupClass", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.invoke.MethodHandles$Lookup#lookupClass", exception.getCause());
+ }
+ }
+
+ @Override
+ public int lookupModes(Object lookup) {
+ try {
+ return (Integer) lookupModes.invoke(lookup);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.invoke.MethodHandles$Lookup#lookupModes", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.invoke.MethodHandles$Lookup#lookupModes", exception.getCause());
+ }
+ }
+
+ @Override
+ public Object dropLookupMode(Object lookup, int mode) {
+ try {
+ return dropLookupMode.invoke(lookup, mode);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.invoke.MethodHandles$Lookup#lookupModes", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.invoke.MethodHandles$Lookup#lookupModes", exception.getCause());
+ }
+ }
+
+ @Override
+ public Class<?> defineClass(Object lookup, byte[] binaryRepresentation) {
+ try {
+ return (Class<?>) defineClass.invoke(lookup, (Object) binaryRepresentation);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.invoke.MethodHandles$Lookup#defineClass", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.invoke.MethodHandles$Lookup#defineClass", exception.getCause());
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * A class injector that uses {@code sun.misc.Unsafe} to inject classes.
+ */
+ @EqualsAndHashCode
+ class UsingUnsafe implements ClassInjector {
+
+ /**
+ * The dispatcher to use.
+ */
+ private static final Dispatcher.Initializable DISPATCHER = AccessController.doPrivileged(Dispatcher.CreationAction.INSTANCE);
+
+ /**
+ * A lock for the bootstrap loader when injecting code.
+ */
+ private static final Object BOOTSTRAP_LOADER_LOCK = new Object();
+
+ /**
+ * The class loader to inject classes into or {@code null} for the bootstrap loader.
+ */
+ private final ClassLoader classLoader;
+
+ /**
+ * The protection domain to use or {@code null} for no protection domain.
+ */
+ private final ProtectionDomain protectionDomain;
+
+ /**
+ * Creates a new unsafe injector for the given class loader with a default protection domain.
+ *
+ * @param classLoader The class loader to inject classes into or {@code null} for the bootstrap loader.
+ */
+ public UsingUnsafe(ClassLoader classLoader) {
+ this(classLoader, ClassLoadingStrategy.NO_PROTECTION_DOMAIN);
+ }
+
+ /**
+ * Creates a new unsafe injector for the given class loader with a default protection domain.
+ *
+ * @param classLoader The class loader to inject classes into or {@code null} for the bootstrap loader.
+ * @param protectionDomain The protection domain to use or {@code null} for no protection domain.
+ */
+ public UsingUnsafe(ClassLoader classLoader, ProtectionDomain protectionDomain) {
+ this.classLoader = classLoader;
+ this.protectionDomain = protectionDomain;
+ }
+
+ /**
+ * Checks if unsafe class injection is available on the current VM.
+ *
+ * @return {@code true} if unsafe class injection is available on the current VM.
+ */
+ public static boolean isAvailable() {
+ return DISPATCHER.isAvailable();
+ }
+
+ /**
+ * Returns an unsafe class injector for the bootstrap class loader.
+ *
+ * @return A class injector for the bootstrap loader.
+ */
+ public static ClassInjector ofBootstrapLoader() {
+ return new UsingUnsafe(ClassLoadingStrategy.BOOTSTRAP_LOADER);
+ }
+
+ /**
+ * Returns an unsafe class injector for the class path.
+ *
+ * @return A class injector for the system class loader.
+ */
+ public static ClassInjector ofClassPath() {
+ return new UsingUnsafe(ClassLoader.getSystemClassLoader());
+ }
+
+ @Override
+ public Map<TypeDescription, Class<?>> inject(Map<? extends TypeDescription, byte[]> types) {
+ Dispatcher dispatcher = DISPATCHER.initialize();
+ Map<TypeDescription, Class<?>> loaded = new HashMap<TypeDescription, Class<?>>();
+ synchronized (classLoader == null
+ ? BOOTSTRAP_LOADER_LOCK
+ : classLoader) {
+ for (Map.Entry<? extends TypeDescription, byte[]> entry : types.entrySet()) {
+ try {
+ loaded.put(entry.getKey(), Class.forName(entry.getKey().getName(), false, classLoader));
+ } catch (ClassNotFoundException ignored) {
+ loaded.put(entry.getKey(), dispatcher.defineClass(classLoader, entry.getKey().getName(), entry.getValue(), protectionDomain));
+ }
+ }
+ }
+ return loaded;
+ }
+
+ /**
+ * A dispatcher for using {@code sun.misc.Unsafe}.
+ */
+ interface Dispatcher {
+
+ /**
+ * Defines a class.
+ *
+ * @param classLoader The class loader to inject the class into.
+ * @param name The type's name.
+ * @param binaryRepresentation The type's binary representation.
+ * @param protectionDomain The type's protection domain.
+ * @return The defined class.
+ */
+ Class<?> defineClass(ClassLoader classLoader, String name, byte[] binaryRepresentation, ProtectionDomain protectionDomain);
+
+ /**
+ * A class injection dispatcher that is not yet initialized.
+ */
+ interface Initializable {
+
+ /**
+ * Checks if unsafe class injection is available on the current VM.
+ *
+ * @return {@code true} if unsafe class injection is available.
+ */
+ boolean isAvailable();
+
+ /**
+ * Initializes the dispatcher.
+ *
+ * @return The initialized dispatcher.
+ */
+ Dispatcher initialize();
+ }
+
+ /**
+ * A privileged action for creating a dispatcher.
+ */
+ enum CreationAction implements PrivilegedAction<Initializable> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback")
+ public Initializable run() {
+ try {
+ Class<?> unsafe = Class.forName("sun.misc.Unsafe");
+ return new Enabled(unsafe.getDeclaredField("theUnsafe"), unsafe.getMethod("defineClass",
+ String.class,
+ byte[].class,
+ int.class,
+ int.class,
+ ClassLoader.class,
+ ProtectionDomain.class));
+ } catch (Exception exception) {
+ return new Disabled(exception);
+ }
+ }
+ }
+
+ /**
+ * An enabled dispatcher.
+ */
+ @EqualsAndHashCode
+ class Enabled implements Dispatcher, Initializable {
+
+ /**
+ * A field containing {@code sun.misc.Unsafe}.
+ */
+ private final Field theUnsafe;
+
+ /**
+ * The {@code sun.misc.Unsafe#defineClass} method.
+ */
+ private final Method defineClass;
+
+ /**
+ * Creates an enabled dispatcher.
+ *
+ * @param theUnsafe A field containing {@code sun.misc.Unsafe}.
+ * @param defineClass The {@code sun.misc.Unsafe#defineClass} method.
+ */
+ protected Enabled(Field theUnsafe, Method defineClass) {
+ this.theUnsafe = theUnsafe;
+ this.defineClass = defineClass;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return true;
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "DP_DO_INSIDE_DO_PRIVILEGED", justification = "Privilege is explicit caller responsibility")
+ public Dispatcher initialize() {
+ theUnsafe.setAccessible(true);
+ return this;
+ }
+
+ @Override
+ public Class<?> defineClass(ClassLoader classLoader, String name, byte[] binaryRepresentation, ProtectionDomain protectionDomain) {
+ try {
+ return (Class<?>) defineClass.invoke(theUnsafe.get(null),
+ name,
+ binaryRepresentation,
+ 0,
+ binaryRepresentation.length,
+ classLoader,
+ protectionDomain);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Could not access Unsafe::defineClass", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking Unsafe::defineClass", exception.getCause());
+ }
+ }
+ }
+
+ /**
+ * A disabled dispatcher.
+ */
+ @EqualsAndHashCode
+ class Disabled implements Initializable {
+
+ /**
+ * The exception causing this dispatcher's creation.
+ */
+ private final Exception exception;
+
+ /**
+ * Creates a disabled dispatcher.
+ *
+ * @param exception The exception causing this dispatcher's creation.
+ */
+ protected Disabled(Exception exception) {
+ this.exception = exception;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return false;
+ }
+
+ @Override
+ public Dispatcher initialize() {
+ throw new IllegalStateException("Could not find sun.misc.Unsafe", exception);
+ }
+ }
+ }
+ }
+
+ /**
+ * A class injector using a {@link java.lang.instrument.Instrumentation} to append to either the boot classpath
+ * or the system class path.
+ */
+ @EqualsAndHashCode
+ class UsingInstrumentation implements ClassInjector {
+
+ /**
+ * A prefix to use of generated files.
+ */
+ private static final String PREFIX = "jar";
+
+ /**
+ * The class file extension.
+ */
+ private static final String CLASS_FILE_EXTENSION = ".class";
+
+ /**
+ * The instrumentation to use for appending to the class path or the boot path.
+ */
+ private final Instrumentation instrumentation;
+
+ /**
+ * A representation of the target path to which classes are to be appended.
+ */
+ private final Target target;
+
+ /**
+ * The folder to be used for storing jar files.
+ */
+ private final File folder;
+
+ /**
+ * A random string generator for creating file names.
+ */
+ private final RandomString randomString;
+
+ /**
+ * Creates an instrumentation-based class injector.
+ *
+ * @param folder The folder to be used for storing jar files.
+ * @param target A representation of the target path to which classes are to be appended.
+ * @param instrumentation The instrumentation to use for appending to the class path or the boot path.
+ * @return An appropriate class injector that applies instrumentation.
+ */
+ public static ClassInjector of(File folder, Target target, Instrumentation instrumentation) {
+ return new UsingInstrumentation(folder, target, instrumentation, new RandomString());
+ }
+
+ /**
+ * Creates an instrumentation-based class injector.
+ *
+ * @param folder The folder to be used for storing jar files.
+ * @param target A representation of the target path to which classes are to be appended.
+ * @param instrumentation The instrumentation to use for appending to the class path or the boot path.
+ * @param randomString The random string generator to use.
+ */
+ protected UsingInstrumentation(File folder,
+ Target target,
+ Instrumentation instrumentation,
+ RandomString randomString) {
+ this.folder = folder;
+ this.target = target;
+ this.instrumentation = instrumentation;
+ this.randomString = randomString;
+ }
+
+ @Override
+ public Map<TypeDescription, Class<?>> inject(Map<? extends TypeDescription, byte[]> types) {
+ File jarFile = new File(folder, String.format("%s%s.jar", PREFIX, randomString.nextString()));
+ try {
+ if (!jarFile.createNewFile()) {
+ throw new IllegalStateException("Cannot create file " + jarFile);
+ }
+ JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(jarFile));
+ try {
+ for (Map.Entry<? extends TypeDescription, byte[]> entry : types.entrySet()) {
+ jarOutputStream.putNextEntry(new JarEntry(entry.getKey().getInternalName() + CLASS_FILE_EXTENSION));
+ jarOutputStream.write(entry.getValue());
+ }
+ } finally {
+ jarOutputStream.close();
+ }
+ target.inject(instrumentation, new JarFile(jarFile));
+ Map<TypeDescription, Class<?>> loaded = new HashMap<TypeDescription, Class<?>>();
+ for (TypeDescription typeDescription : types.keySet()) {
+ loaded.put(typeDescription, Class.forName(typeDescription.getName(), false, ClassLoader.getSystemClassLoader()));
+ }
+ return loaded;
+ } catch (IOException exception) {
+ throw new IllegalStateException("Cannot write jar file to disk", exception);
+ } catch (ClassNotFoundException exception) {
+ throw new IllegalStateException("Cannot load injected class", exception);
+ }
+ }
+
+ /**
+ * A representation of the target to which Java classes should be appended to.
+ */
+ public enum Target {
+
+ /**
+ * Representation of the bootstrap class loader.
+ */
+ BOOTSTRAP {
+ @Override
+ protected void inject(Instrumentation instrumentation, JarFile jarFile) {
+ instrumentation.appendToBootstrapClassLoaderSearch(jarFile);
+ }
+ },
+
+ /**
+ * Representation of the system class loader.
+ */
+ SYSTEM {
+ @Override
+ protected void inject(Instrumentation instrumentation, JarFile jarFile) {
+ instrumentation.appendToSystemClassLoaderSearch(jarFile);
+ }
+ };
+
+ /**
+ * Adds the given classes to the represented class loader.
+ *
+ * @param instrumentation The instrumentation instance to use.
+ * @param jarFile The jar file to append.
+ */
+ protected abstract void inject(Instrumentation instrumentation, JarFile jarFile);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/loading/ClassLoadingStrategy.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/loading/ClassLoadingStrategy.java
new file mode 100644
index 0000000..b1ad1ad
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/loading/ClassLoadingStrategy.java
@@ -0,0 +1,461 @@
+package net.bytebuddy.dynamic.loading;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.type.TypeDescription;
+
+import java.io.File;
+import java.lang.instrument.Instrumentation;
+import java.security.ProtectionDomain;
+import java.util.Map;
+
+/**
+ * A strategy for loading a collection of types.
+ *
+ * @param <T> The least specific type of class loader this strategy can apply to.
+ */
+public interface ClassLoadingStrategy<T extends ClassLoader> {
+
+ /**
+ * A type-safe constant representing the bootstrap class loader which is represented by {@code null} within Java.
+ */
+ ClassLoader BOOTSTRAP_LOADER = null;
+
+ /**
+ * An undefined protection domain.
+ */
+ ProtectionDomain NO_PROTECTION_DOMAIN = null;
+
+ /**
+ * Loads a given collection of classes given their binary representation.
+ *
+ * @param classLoader The class loader to used for loading the classes.
+ * @param types Byte array representations of the types to be loaded mapped by their descriptions,
+ * where an iteration order defines an order in which they are supposed to be loaded,
+ * if relevant.
+ * @return A collection of the loaded classes which will be initialized in the iteration order of the
+ * returned collection.
+ */
+ Map<TypeDescription, Class<?>> load(T classLoader, Map<TypeDescription, byte[]> types);
+
+ /**
+ * This class contains implementations of default class loading strategies.
+ */
+ enum Default implements Configurable<ClassLoader> {
+
+ /**
+ * This strategy creates a new {@link net.bytebuddy.dynamic.loading.ByteArrayClassLoader} with the given
+ * class loader as its parent. The byte array class loader is aware of a any dynamically created type and can
+ * natively load the given classes. This allows to load classes with cyclic load-time dependencies since the
+ * byte array class loader is queried on each encountered unknown class. Due to the encapsulation of the
+ * classes that were loaded by a byte array class loader, this strategy will lead to the unloading of these
+ * classes once this class loader, its classes or any instances of these classes become unreachable.
+ */
+ WRAPPER(new WrappingDispatcher(ByteArrayClassLoader.PersistenceHandler.LATENT, WrappingDispatcher.PARENT_FIRST)),
+
+ /**
+ * The strategy is identical to {@link ClassLoadingStrategy.Default#WRAPPER} but exposes
+ * the byte arrays that represent a class by {@link java.lang.ClassLoader#getResourceAsStream(String)}. For
+ * this purpose, all class files are persisted as byte arrays withing the wrapping class loader.
+ */
+ WRAPPER_PERSISTENT(new WrappingDispatcher(ByteArrayClassLoader.PersistenceHandler.MANIFEST, WrappingDispatcher.PARENT_FIRST)),
+
+ /**
+ * <p>
+ * The child-first class loading strategy is a modified version of the
+ * {@link ClassLoadingStrategy.Default#WRAPPER} where the dynamic types are given
+ * priority over any types of a parent class loader with the same name.
+ * </p>
+ * <p>
+ * <b>Important</b>: This does <i>not</i> replace a type of the same name, but it makes the type invisible by
+ * the reach of this class loader.
+ * </p>
+ */
+ CHILD_FIRST(new WrappingDispatcher(ByteArrayClassLoader.PersistenceHandler.LATENT, WrappingDispatcher.CHILD_FIRST)),
+
+ /**
+ * The strategy is identical to {@link ClassLoadingStrategy.Default#CHILD_FIRST} but
+ * exposes the byte arrays that represent a class by {@link java.lang.ClassLoader#getResourceAsStream(String)}.
+ * For this purpose, all class files are persisted as byte arrays withing the wrapping class loader.
+ */
+ CHILD_FIRST_PERSISTENT(new WrappingDispatcher(ByteArrayClassLoader.PersistenceHandler.MANIFEST, WrappingDispatcher.CHILD_FIRST)),
+
+ /**
+ * <p>
+ * This strategy does not create a new class loader but injects all classes into the given {@link java.lang.ClassLoader}
+ * by reflective access. This prevents the loading of classes with cyclic load-time dependencies but avoids the
+ * creation of an additional class loader. The advantage of this strategy is that the loaded classes will have
+ * package-private access to other classes within their package of the class loader into which they are
+ * injected what is not permitted when the wrapper class loader is used. This strategy is implemented using a
+ * {@link net.bytebuddy.dynamic.loading.ClassInjector.UsingReflection}. Note that this strategy usually yields
+ * a better runtime performance.
+ * </p>
+ * <p>
+ * <b>Important</b>: This class loader does not define packages for injected classes by default. Therefore, calls to
+ * {@link Class#getPackage()} might return {@code null}. Packages are only defined
+ * </p>
+ */
+ INJECTION(new InjectionDispatcher());
+
+ /**
+ * The default behavior when attempting to load a type that was already loaded.
+ */
+ private static final boolean DEFAULT_FORBID_EXISTING = true;
+
+ /**
+ * The dispatcher to be used when loading a class.
+ */
+ private final Configurable<ClassLoader> dispatcher;
+
+ /**
+ * Creates a new default class loading strategy.
+ *
+ * @param dispatcher The dispatcher to be used when loading a class.
+ */
+ Default(Configurable<ClassLoader> dispatcher) {
+ this.dispatcher = dispatcher;
+ }
+
+ @Override
+ public Map<TypeDescription, Class<?>> load(ClassLoader classLoader, Map<TypeDescription, byte[]> types) {
+ return dispatcher.load(classLoader, types);
+ }
+
+ @Override
+ public Configurable<ClassLoader> with(ProtectionDomain protectionDomain) {
+ return dispatcher.with(protectionDomain);
+ }
+
+ @Override
+ public Configurable<ClassLoader> with(PackageDefinitionStrategy packageDefinitionStrategy) {
+ return dispatcher.with(packageDefinitionStrategy);
+ }
+
+ @Override
+ public Configurable<ClassLoader> allowExistingTypes() {
+ return dispatcher.allowExistingTypes();
+ }
+
+ /**
+ * A class loading strategy which applies a class loader injection while applying a given
+ * {@link java.security.ProtectionDomain} on class injection.
+ */
+ @EqualsAndHashCode
+ protected static class InjectionDispatcher implements ClassLoadingStrategy.Configurable<ClassLoader> {
+
+ /**
+ * The protection domain to apply.
+ */
+ private final ProtectionDomain protectionDomain;
+
+ /**
+ * The package definer to be used for querying information on package information.
+ */
+ private final PackageDefinitionStrategy packageDefinitionStrategy;
+
+ /**
+ * Determines if an exception should be thrown when attempting to load a type that already exists.
+ */
+ private final boolean forbidExisting;
+
+ /**
+ * Creates a new injection dispatcher.
+ */
+ protected InjectionDispatcher() {
+ this(NO_PROTECTION_DOMAIN, PackageDefinitionStrategy.NoOp.INSTANCE, DEFAULT_FORBID_EXISTING);
+ }
+
+ /**
+ * Creates a new injection dispatcher.
+ *
+ * @param protectionDomain The protection domain to apply.
+ * @param packageDefinitionStrategy The package definer to be used for querying information on package information.
+ * @param forbidExisting Determines if an exception should be thrown when attempting to load a type that already exists.
+ */
+ private InjectionDispatcher(ProtectionDomain protectionDomain,
+ PackageDefinitionStrategy packageDefinitionStrategy,
+ boolean forbidExisting) {
+ this.protectionDomain = protectionDomain;
+ this.packageDefinitionStrategy = packageDefinitionStrategy;
+ this.forbidExisting = forbidExisting;
+ }
+
+ @Override
+ public Map<TypeDescription, Class<?>> load(ClassLoader classLoader, Map<TypeDescription, byte[]> types) {
+ return new ClassInjector.UsingReflection(classLoader,
+ protectionDomain,
+ packageDefinitionStrategy,
+ forbidExisting).inject(types);
+ }
+
+ @Override
+ public Configurable<ClassLoader> with(ProtectionDomain protectionDomain) {
+ return new InjectionDispatcher(protectionDomain, packageDefinitionStrategy, forbidExisting);
+ }
+
+ @Override
+ public Configurable<ClassLoader> with(PackageDefinitionStrategy packageDefinitionStrategy) {
+ return new InjectionDispatcher(protectionDomain, packageDefinitionStrategy, forbidExisting);
+ }
+
+ @Override
+ public Configurable<ClassLoader> allowExistingTypes() {
+ return new InjectionDispatcher(protectionDomain, packageDefinitionStrategy, false);
+ }
+ }
+
+ /**
+ * A class loading strategy which creates a wrapping class loader while applying a given
+ * {@link java.security.ProtectionDomain} on class loading.
+ */
+ @EqualsAndHashCode
+ protected static class WrappingDispatcher implements ClassLoadingStrategy.Configurable<ClassLoader> {
+
+ /**
+ * Indicates that a child first loading strategy should be attempted.
+ */
+ private static final boolean CHILD_FIRST = true;
+
+ /**
+ * Indicates that a parent first loading strategy should be attempted.
+ */
+ private static final boolean PARENT_FIRST = false;
+
+ /**
+ * The protection domain to apply.
+ */
+ private final ProtectionDomain protectionDomain;
+
+ /**
+ * The persistence handler to apply.
+ */
+ private final ByteArrayClassLoader.PersistenceHandler persistenceHandler;
+
+ /**
+ * The package definer to be used for querying information on package information.
+ */
+ private final PackageDefinitionStrategy packageDefinitionStrategy;
+
+ /**
+ * {@code true} if the created class loader should apply child-first semantics.
+ */
+ private final boolean childFirst;
+
+ /**
+ * Determines if an exception should be thrown when attempting to load a type that already exists.
+ */
+ private final boolean forbidExisting;
+
+ /**
+ * Creates a new wrapping dispatcher with a default protection domain and a default access control context.
+ *
+ * @param persistenceHandler The persistence handler to apply.
+ * @param childFirst {@code true} if the created class loader should apply child-first semantics.
+ */
+ protected WrappingDispatcher(ByteArrayClassLoader.PersistenceHandler persistenceHandler, boolean childFirst) {
+ this(NO_PROTECTION_DOMAIN,
+ PackageDefinitionStrategy.Trivial.INSTANCE,
+ persistenceHandler,
+ childFirst,
+ DEFAULT_FORBID_EXISTING);
+ }
+
+ /**
+ * Creates a new protection domain specific class loading wrapper.
+ *
+ * @param protectionDomain The protection domain to apply.
+ * @param packageDefinitionStrategy The package definer to be used for querying information on package information.
+ * @param persistenceHandler The persistence handler to apply.
+ * @param childFirst {@code true} if the created class loader should apply child-first semantics.
+ * @param forbidExisting Determines if an exception should be thrown when attempting to load a type that already exists.
+ */
+ private WrappingDispatcher(ProtectionDomain protectionDomain,
+ PackageDefinitionStrategy packageDefinitionStrategy,
+ ByteArrayClassLoader.PersistenceHandler persistenceHandler,
+ boolean childFirst,
+ boolean forbidExisting) {
+ this.protectionDomain = protectionDomain;
+ this.packageDefinitionStrategy = packageDefinitionStrategy;
+ this.persistenceHandler = persistenceHandler;
+ this.childFirst = childFirst;
+ this.forbidExisting = forbidExisting;
+ }
+
+ @Override
+ public Map<TypeDescription, Class<?>> load(ClassLoader classLoader, Map<TypeDescription, byte[]> types) {
+ return ByteArrayClassLoader.load(classLoader,
+ types,
+ protectionDomain,
+ persistenceHandler,
+ packageDefinitionStrategy,
+ childFirst,
+ forbidExisting);
+ }
+
+ @Override
+ public Configurable<ClassLoader> with(ProtectionDomain protectionDomain) {
+ return new WrappingDispatcher(protectionDomain, packageDefinitionStrategy, persistenceHandler, childFirst, forbidExisting);
+ }
+
+ @Override
+ public Configurable<ClassLoader> with(PackageDefinitionStrategy packageDefinitionStrategy) {
+ return new WrappingDispatcher(protectionDomain, packageDefinitionStrategy, persistenceHandler, childFirst, forbidExisting);
+ }
+
+ @Override
+ public Configurable<ClassLoader> allowExistingTypes() {
+ return new InjectionDispatcher(protectionDomain, packageDefinitionStrategy, false);
+ }
+ }
+ }
+
+ /**
+ * A {@link ClassLoadingStrategy} that allows configuring the strategy's behavior.
+ *
+ * @param <S> The least specific type of class loader this strategy can apply to.
+ */
+ interface Configurable<S extends ClassLoader> extends ClassLoadingStrategy<S> {
+
+ /**
+ * Overrides the implicitly set default {@link java.security.ProtectionDomain} with an explicit one.
+ *
+ * @param protectionDomain The protection domain to apply.
+ * @return This class loading strategy with an explicitly set {@link java.security.ProtectionDomain}.
+ */
+ Configurable<S> with(ProtectionDomain protectionDomain);
+
+ /**
+ * Defines the supplied package definition strategy to be used for defining packages.
+ *
+ * @param packageDefinitionStrategy The package definer to be used.
+ * @return A version of this class loading strategy that applies the supplied package definition strategy.
+ */
+ Configurable<S> with(PackageDefinitionStrategy packageDefinitionStrategy);
+
+ /**
+ * Determines if this class loading strategy should not throw an exception when attempting to load a class that
+ * was already loaded. In this case, the already loaded class is used instead of the generated class.
+ *
+ * @return A version of this class loading strategy that does not throw an exception when a class is already loaded.
+ */
+ Configurable<S> allowExistingTypes();
+ }
+
+ /**
+ * A class loading strategy that uses a {@code java.lang.invoke.MethodHandles$Lookup} instance for defining types.
+ * A lookup instance can define types only in the same class loader and in the same package as the type within which
+ * it was created. The supplied lookup must have package privileges, i.e. it must not be a public lookup.
+ */
+ @EqualsAndHashCode
+ class UsingLookup implements ClassLoadingStrategy<ClassLoader> {
+
+ /**
+ * The class injector to use.
+ */
+ private final ClassInjector classInjector;
+
+ /**
+ * The class loader in the supplied class injector defines classes.
+ */
+ private final ClassLoader classLoader;
+
+ /**
+ * Creaes a new class loading strategy that uses a lookup type.
+ *
+ * @param classInjector The class injector to use.
+ * @param classLoader The class loader in the supplied class injector defines classes.
+ */
+ protected UsingLookup(ClassInjector classInjector, ClassLoader classLoader) {
+ this.classInjector = classInjector;
+ this.classLoader = classLoader;
+ }
+
+ /**
+ * Creates a new class loading strategy that uses a {@code java.lang.invoke.MethodHandles$Lookup} instance.
+ *
+ * @param lookup The lookup instance to use for defining new types.
+ * @return A suitable class loading strategy.
+ */
+ public static ClassLoadingStrategy<ClassLoader> of(Object lookup) {
+ ClassInjector.UsingLookup classInjector = ClassInjector.UsingLookup.of(lookup);
+ return new UsingLookup(classInjector, classInjector.lookupType().getClassLoader());
+ }
+
+ @Override
+ public Map<TypeDescription, Class<?>> load(ClassLoader classLoader, Map<TypeDescription, byte[]> types) {
+ if (classLoader != this.classLoader) {
+ throw new IllegalStateException("Cannot define a type in " + classLoader + " with lookup based on " + this.classLoader);
+ }
+ return classInjector.inject(types);
+ }
+ }
+
+ /**
+ * A class loading strategy which allows class injection into the bootstrap class loader if
+ * appropriate.
+ */
+ @EqualsAndHashCode
+ class ForBootstrapInjection implements ClassLoadingStrategy<ClassLoader> {
+
+ /**
+ * The instrumentation to use.
+ */
+ private final Instrumentation instrumentation;
+
+ /**
+ * The folder to save jar files in.
+ */
+ private final File folder;
+
+ /**
+ * Creates a new injector which is capable of injecting classes into the bootstrap class loader.
+ *
+ * @param instrumentation The instrumentation to use.
+ * @param folder The folder to save jar files in.
+ */
+ public ForBootstrapInjection(Instrumentation instrumentation, File folder) {
+ this.instrumentation = instrumentation;
+ this.folder = folder;
+ }
+
+ @Override
+ public Map<TypeDescription, Class<?>> load(ClassLoader classLoader, Map<TypeDescription, byte[]> types) {
+ ClassInjector classInjector = classLoader == null
+ ? ClassInjector.UsingInstrumentation.of(folder, ClassInjector.UsingInstrumentation.Target.BOOTSTRAP, instrumentation)
+ : new ClassInjector.UsingReflection(classLoader);
+ return classInjector.inject(types);
+ }
+ }
+
+ /**
+ * A class loading strategy that injects a class using {@code sun.misc.Unsafe}.
+ */
+ @EqualsAndHashCode
+ class ForUnsafeInjection implements ClassLoadingStrategy<ClassLoader> {
+
+ /**
+ * The protection domain to use.
+ */
+ private final ProtectionDomain protectionDomain;
+
+ /**
+ * Creates a new class loading strategy for unsafe injection with a default protection domain.
+ */
+ public ForUnsafeInjection() {
+ this(NO_PROTECTION_DOMAIN);
+ }
+
+ /**
+ * Creates a new class loading strategy for unsafe injection.
+ *
+ * @param protectionDomain The protection domain to use.
+ */
+ public ForUnsafeInjection(ProtectionDomain protectionDomain) {
+ this.protectionDomain = protectionDomain;
+ }
+
+ @Override
+ public Map<TypeDescription, Class<?>> load(ClassLoader classLoader, Map<TypeDescription, byte[]> types) {
+ return new ClassInjector.UsingUnsafe(classLoader, protectionDomain).inject(types);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/loading/ClassReloadingStrategy.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/loading/ClassReloadingStrategy.java
new file mode 100644
index 0000000..086aa3a
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/loading/ClassReloadingStrategy.java
@@ -0,0 +1,514 @@
+package net.bytebuddy.dynamic.loading;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.instrument.ClassDefinition;
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.Instrumentation;
+import java.lang.instrument.UnmodifiableClassException;
+import java.security.ProtectionDomain;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * <p>
+ * The class reloading strategy allows to redefine loaded {@link java.lang.Class}es. Note that this strategy
+ * will always attempt to load an existing class prior to its redefinition, even if this class is not yet loaded.
+ * </p>
+ * <p>
+ * <b>Note</b>: In order to redefine any type, neither its name or its modifiers must be changed. Furthermore, no
+ * fields or methods must be removed or added. This makes this strategy generally incompatible to applying it to a
+ * rebased class definition as by {@link net.bytebuddy.ByteBuddy#rebase(Class)} which copies the original method
+ * implementations to additional methods. Furthermore, even the {@link net.bytebuddy.ByteBuddy#redefine(Class)}
+ * adds a method if the original class contains an explicit <i>class initializer</i>. For these reasons, it is not
+ * recommended to use this {@link ClassLoadingStrategy} with arbitrary classes.
+ * </p>
+ */
+ at EqualsAndHashCode
+public class ClassReloadingStrategy implements ClassLoadingStrategy<ClassLoader> {
+
+ /**
+ * The name of the Byte Buddy {@code net.bytebuddy.agent.Installer} class.
+ */
+ private static final String INSTALLER_TYPE = "net.bytebuddy.agent.Installer";
+
+ /**
+ * The name of the {@code net.bytebuddy.agent.Installer} getter for reading an installed {@link Instrumentation}.
+ */
+ private static final String INSTRUMENTATION_GETTER = "getInstrumentation";
+
+ /**
+ * Indicator for access to a static member via reflection to make the code more readable.
+ */
+ private static final Object STATIC_MEMBER = null;
+
+ /**
+ * This instance's instrumentation.
+ */
+ private final Instrumentation instrumentation;
+
+ /**
+ * An strategy which performs the actual redefinition of a {@link java.lang.Class}.
+ */
+ private final Strategy strategy;
+
+ /**
+ * The strategy to apply for injecting classes into the bootstrap class loader.
+ */
+ private final BootstrapInjection bootstrapInjection;
+
+ /**
+ * The preregistered types of this instance.
+ */
+ private final Map<String, Class<?>> preregisteredTypes;
+
+ /**
+ * Creates a class reloading strategy for the given instrumentation using an explicit transformation strategy which
+ * is represented by an {@link Strategy}. The given instrumentation
+ * must support the strategy's transformation type.
+ *
+ * @param instrumentation The instrumentation to be used by this reloading strategy.
+ * @param strategy A strategy which performs the actual redefinition of a {@link java.lang.Class}.
+ */
+ public ClassReloadingStrategy(Instrumentation instrumentation, Strategy strategy) {
+ this(instrumentation,
+ strategy,
+ BootstrapInjection.Disabled.INSTANCE,
+ Collections.<String, Class<?>>emptyMap());
+ }
+
+ /**
+ * Creates a new class reloading strategy.
+ *
+ * @param instrumentation The instrumentation to be used by this reloading strategy.
+ * @param strategy An strategy which performs the actual redefinition of a {@link java.lang.Class}.
+ * @param bootstrapInjection The bootstrap class loader injection strategy to use.
+ * @param preregisteredTypes The preregistered types of this instance.
+ */
+ protected ClassReloadingStrategy(Instrumentation instrumentation,
+ Strategy strategy,
+ BootstrapInjection bootstrapInjection,
+ Map<String, Class<?>> preregisteredTypes) {
+ this.instrumentation = instrumentation;
+ this.strategy = strategy.validate(instrumentation);
+ this.bootstrapInjection = bootstrapInjection;
+ this.preregisteredTypes = preregisteredTypes;
+ }
+
+ /**
+ * Creates a class reloading strategy for the given instrumentation. The given instrumentation must either
+ * support {@link java.lang.instrument.Instrumentation#isRedefineClassesSupported()} or
+ * {@link java.lang.instrument.Instrumentation#isRetransformClassesSupported()}. If both modes are supported,
+ * classes will be transformed using a class retransformation.
+ *
+ * @param instrumentation The instrumentation to be used by this reloading strategy.
+ * @return A suitable class reloading strategy.
+ */
+ public static ClassReloadingStrategy of(Instrumentation instrumentation) {
+ Strategy strategy;
+ if (instrumentation.isRedefineClassesSupported()) {
+ strategy = Strategy.REDEFINITION;
+ } else if (instrumentation.isRetransformClassesSupported()) {
+ strategy = Strategy.RETRANSFORMATION;
+ } else {
+ throw new IllegalArgumentException("Instrumentation does not support manipulation of loaded classes: " + instrumentation);
+ }
+ return new ClassReloadingStrategy(instrumentation, strategy);
+ }
+
+ /**
+ * <p>
+ * Obtains a {@link net.bytebuddy.dynamic.loading.ClassReloadingStrategy} from an installed Byte Buddy agent. This
+ * agent must be installed either by adding the {@code byte-buddy-agent.jar} when starting up the JVM by
+ * </p>
+ * <p>
+ * <code>
+ * java -javaagent:byte-buddy-agent.jar -jar app.jar
+ * </code>
+ * </p>
+ * or after the start up using the Attach API. A convenience installer for the OpenJDK is provided by the
+ * {@code ByteBuddyAgent} within the {@code byte-buddy-agent} module.
+ *
+ * @return A class reloading strategy which uses the Byte Buddy agent's {@link java.lang.instrument.Instrumentation}.
+ */
+ public static ClassReloadingStrategy fromInstalledAgent() {
+ try {
+ return ClassReloadingStrategy.of((Instrumentation) ClassLoader.getSystemClassLoader()
+ .loadClass(INSTALLER_TYPE)
+ .getMethod(INSTRUMENTATION_GETTER)
+ .invoke(STATIC_MEMBER));
+ } catch (RuntimeException exception) {
+ throw exception;
+ } catch (Exception exception) {
+ throw new IllegalStateException("The Byte Buddy agent is not installed or not accessible", exception);
+ }
+ }
+
+ @Override
+ public Map<TypeDescription, Class<?>> load(ClassLoader classLoader, Map<TypeDescription, byte[]> types) {
+ Map<String, Class<?>> availableTypes = new HashMap<String, Class<?>>(preregisteredTypes);
+ for (Class<?> type : instrumentation.getInitiatedClasses(classLoader)) {
+ availableTypes.put(TypeDescription.ForLoadedType.getName(type), type);
+ }
+ Map<Class<?>, ClassDefinition> classDefinitions = new ConcurrentHashMap<Class<?>, ClassDefinition>();
+ Map<TypeDescription, Class<?>> loadedClasses = new HashMap<TypeDescription, Class<?>>();
+ Map<TypeDescription, byte[]> unloadedClasses = new LinkedHashMap<TypeDescription, byte[]>();
+ for (Map.Entry<TypeDescription, byte[]> entry : types.entrySet()) {
+ Class<?> type = availableTypes.get(entry.getKey().getName());
+ if (type != null) {
+ classDefinitions.put(type, new ClassDefinition(type, entry.getValue()));
+ loadedClasses.put(entry.getKey(), type);
+ } else {
+ unloadedClasses.put(entry.getKey(), entry.getValue());
+ }
+ }
+ try {
+ strategy.apply(instrumentation, classDefinitions);
+ if (!unloadedClasses.isEmpty()) {
+ loadedClasses.putAll((classLoader == null
+ ? bootstrapInjection.make(instrumentation)
+ : new ClassInjector.UsingReflection(classLoader)).inject(unloadedClasses));
+ }
+ } catch (ClassNotFoundException exception) {
+ throw new IllegalArgumentException("Could not locate classes for redefinition", exception);
+ } catch (UnmodifiableClassException exception) {
+ throw new IllegalStateException("Cannot redefine specified class", exception);
+ }
+ return loadedClasses;
+ }
+
+ /**
+ * Resets all classes to their original definition while using the first type's class loader as a class file locator.
+ *
+ * @param type The types to reset.
+ * @return This class reloading strategy.
+ * @throws IOException If a class file locator causes an IO exception.
+ */
+ public ClassReloadingStrategy reset(Class<?>... type) throws IOException {
+ return type.length == 0
+ ? this
+ : reset(ClassFileLocator.ForClassLoader.of(type[0].getClassLoader()), type);
+ }
+
+ /**
+ * Resets all classes to their original definition.
+ *
+ * @param classFileLocator The class file locator to use.
+ * @param type The types to reset.
+ * @return This class reloading strategy.
+ * @throws IOException If a class file locator causes an IO exception.
+ */
+ public ClassReloadingStrategy reset(ClassFileLocator classFileLocator, Class<?>... type) throws IOException {
+ if (type.length > 0) {
+ try {
+ strategy.reset(instrumentation, classFileLocator, Arrays.asList(type));
+ } catch (ClassNotFoundException exception) {
+ throw new IllegalArgumentException("Cannot locate types " + Arrays.toString(type), exception);
+ } catch (UnmodifiableClassException exception) {
+ throw new IllegalStateException("Cannot reset types " + Arrays.toString(type), exception);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Enables bootstrap injection for this class reloading strategy.
+ *
+ * @param folder The folder to save jar files in that are appended to the bootstrap class path.
+ * @return A class reloading strategy with bootstrap injection enabled.
+ */
+ public ClassReloadingStrategy enableBootstrapInjection(File folder) {
+ return new ClassReloadingStrategy(instrumentation, strategy, new BootstrapInjection.Enabled(folder), preregisteredTypes);
+ }
+
+ /**
+ * Registers a type to be explicitly available without explicit lookup.
+ *
+ * @param type The loaded types that are explicitly available.
+ * @return This class reloading strategy with the given types being explicitly available.
+ */
+ public ClassReloadingStrategy preregistered(Class<?>... type) {
+ Map<String, Class<?>> preregisteredTypes = new HashMap<String, Class<?>>(this.preregisteredTypes);
+ for (Class<?> aType : type) {
+ preregisteredTypes.put(TypeDescription.ForLoadedType.getName(aType), aType);
+ }
+ return new ClassReloadingStrategy(instrumentation, strategy, bootstrapInjection, preregisteredTypes);
+ }
+
+ /**
+ * A strategy which performs the actual redefinition of a {@link java.lang.Class}.
+ */
+ public enum Strategy {
+
+ /**
+ * <p>
+ * Redefines a class using {@link java.lang.instrument.Instrumentation#redefineClasses(java.lang.instrument.ClassDefinition...)}.
+ * </p>
+ * <p>
+ * This strategy can be more efficient. However, the redefinition strategy does not allow to reset VM anonymous classes (e.g.
+ * classes that represent lambda expressions).
+ * </p>
+ */
+ REDEFINITION(true) {
+ @Override
+ protected void apply(Instrumentation instrumentation, Map<Class<?>, ClassDefinition> classDefinitions)
+ throws UnmodifiableClassException, ClassNotFoundException {
+ instrumentation.redefineClasses(classDefinitions.values().toArray(new ClassDefinition[classDefinitions.size()]));
+ }
+
+ @Override
+ protected Strategy validate(Instrumentation instrumentation) {
+ if (!instrumentation.isRedefineClassesSupported()) {
+ throw new IllegalArgumentException("Does not support redefinition: " + instrumentation);
+ }
+ return this;
+ }
+
+ @Override
+ public void reset(Instrumentation instrumentation, ClassFileLocator classFileLocator, List<Class<?>> types)
+ throws IOException, UnmodifiableClassException, ClassNotFoundException {
+ Map<Class<?>, ClassDefinition> classDefinitions = new HashMap<Class<?>, ClassDefinition>(types.size());
+ for (Class<?> type : types) {
+ classDefinitions.put(type, new ClassDefinition(type, classFileLocator.locate(TypeDescription.ForLoadedType.getName(type)).resolve()));
+ }
+ apply(instrumentation, classDefinitions);
+ }
+ },
+
+ /**
+ * <p>
+ * Redefines a class using
+ * {@link java.lang.instrument.Instrumentation#retransformClasses(Class[])}. This requires synchronization on
+ * the {@link net.bytebuddy.dynamic.loading.ClassReloadingStrategy#instrumentation} object.
+ * </p>
+ * <p>
+ * This strategy can require more time to be applied but does not struggle to reset VM anonymous classes (e.g. classes
+ * that represent lambda expressions).
+ * </p>
+ */
+ RETRANSFORMATION(false) {
+ @Override
+ protected void apply(Instrumentation instrumentation, Map<Class<?>, ClassDefinition> classDefinitions) throws UnmodifiableClassException {
+ ClassRedefinitionTransformer classRedefinitionTransformer = new ClassRedefinitionTransformer(classDefinitions);
+ synchronized (this) {
+ instrumentation.addTransformer(classRedefinitionTransformer, REDEFINE_CLASSES);
+ try {
+ instrumentation.retransformClasses(classDefinitions.keySet().toArray(new Class<?>[classDefinitions.size()]));
+ } finally {
+ instrumentation.removeTransformer(classRedefinitionTransformer);
+ }
+ }
+ classRedefinitionTransformer.assertTransformation();
+ }
+
+ @Override
+ protected Strategy validate(Instrumentation instrumentation) {
+ if (!instrumentation.isRetransformClassesSupported()) {
+ throw new IllegalArgumentException("Does not support retransformation: " + instrumentation);
+ }
+ return this;
+ }
+
+ @Override
+ public void reset(Instrumentation instrumentation, ClassFileLocator classFileLocator, List<Class<?>> types) throws IOException, UnmodifiableClassException, ClassNotFoundException {
+ instrumentation.addTransformer(ClassResettingTransformer.INSTANCE, true);
+ try {
+ instrumentation.retransformClasses(types.toArray(new Class<?>[types.size()]));
+ } finally {
+ instrumentation.removeTransformer(ClassResettingTransformer.INSTANCE);
+ }
+ }
+ };
+
+ /**
+ * Indicates that a class is not redefined.
+ */
+ private static final byte[] NO_REDEFINITION = null;
+
+ /**
+ * Instructs a {@link java.lang.instrument.ClassFileTransformer} to redefine classes.
+ */
+ private static final boolean REDEFINE_CLASSES = true;
+
+ /**
+ * {@code true} if the {@link Strategy#REDEFINITION} strategy
+ * is used.
+ */
+ private final boolean redefinition;
+
+ /**
+ * Creates a new strategy.
+ *
+ * @param redefinition {@code true} if the {@link Strategy#REDEFINITION} strategy is used.
+ */
+ Strategy(boolean redefinition) {
+ this.redefinition = redefinition;
+ }
+
+ /**
+ * Applies this strategy for the given arguments.
+ *
+ * @param instrumentation The instrumentation to be used for applying the redefinition.
+ * @param classDefinitions A mapping of the classes to be redefined to their redefinition.
+ * @throws UnmodifiableClassException If a class is not modifiable.
+ * @throws ClassNotFoundException If a class was not found.
+ */
+ protected abstract void apply(Instrumentation instrumentation, Map<Class<?>, ClassDefinition> classDefinitions)
+ throws UnmodifiableClassException, ClassNotFoundException;
+
+ /**
+ * Validates that this strategy supports a given transformation type.
+ *
+ * @param instrumentation The instrumentation instance being used.
+ * @return This strategy.
+ */
+ protected abstract Strategy validate(Instrumentation instrumentation);
+
+ /**
+ * Returns {@code true} if this strategy represents {@link Strategy#REDEFINITION}.
+ *
+ * @return {@code true} if this strategy represents {@link Strategy#REDEFINITION}.
+ */
+ public boolean isRedefinition() {
+ return redefinition;
+ }
+
+ /**
+ * Resets the provided types to their original format.
+ *
+ * @param instrumentation The instrumentation instance to use for class redefinition or retransformation.
+ * @param classFileLocator The class file locator to use.
+ * @param types The types to reset.
+ * @throws IOException If an I/O exception occurs.
+ * @throws UnmodifiableClassException If a class is not modifiable.
+ * @throws ClassNotFoundException If a class could not be found.
+ */
+ public abstract void reset(Instrumentation instrumentation, ClassFileLocator classFileLocator, List<Class<?>> types) throws IOException, UnmodifiableClassException, ClassNotFoundException;
+
+ /**
+ * A class file transformer that applies a given {@link java.lang.instrument.ClassDefinition}.
+ */
+ protected static class ClassRedefinitionTransformer implements ClassFileTransformer {
+
+ /**
+ * A mapping of classes to be redefined to their redefined class definitions.
+ */
+ private final Map<Class<?>, ClassDefinition> redefinedClasses;
+
+ /**
+ * Creates a new class redefinition transformer.
+ *
+ * @param redefinedClasses A mapping of classes to be redefined to their redefined class definitions.
+ */
+ protected ClassRedefinitionTransformer(Map<Class<?>, ClassDefinition> redefinedClasses) {
+ this.redefinedClasses = redefinedClasses;
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "Value is always null")
+ public byte[] transform(ClassLoader classLoader,
+ String internalTypeName,
+ Class<?> classBeingRedefined,
+ ProtectionDomain protectionDomain,
+ byte[] classfileBuffer) {
+ if (internalTypeName == null) {
+ return NO_REDEFINITION;
+ }
+ ClassDefinition redefinedClass = redefinedClasses.remove(classBeingRedefined);
+ return redefinedClass == null
+ ? NO_REDEFINITION
+ : redefinedClass.getDefinitionClassFile();
+ }
+
+ /**
+ * Validates that all given classes were redefined.
+ */
+ public void assertTransformation() {
+ if (!redefinedClasses.isEmpty()) {
+ throw new IllegalStateException("Could not transform: " + redefinedClasses.keySet());
+ }
+ }
+ }
+
+ /**
+ * A transformer that indicates that a class file should not be transformed.
+ */
+ protected enum ClassResettingTransformer implements ClassFileTransformer {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public byte[] transform(ClassLoader classLoader,
+ String internalTypeName,
+ Class<?> classBeingRedefined,
+ ProtectionDomain protectionDomain,
+ byte[] classfileBuffer) {
+ return NO_REDEFINITION;
+ }
+ }
+ }
+
+ /**
+ * A strategy to apply for injecting classes into the bootstrap class loader.
+ */
+ protected interface BootstrapInjection {
+
+ /**
+ * Creates a class injector to use.
+ *
+ * @param instrumentation The instrumentation of this instance.
+ * @return A class injector for the bootstrap class loader.
+ */
+ ClassInjector make(Instrumentation instrumentation);
+
+ /**
+ * A disabled bootstrap injection strategy.
+ */
+ enum Disabled implements BootstrapInjection {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public ClassInjector make(Instrumentation instrumentation) {
+ throw new IllegalStateException("Bootstrap injection is not enabled");
+ }
+ }
+
+ /**
+ * An enabled bootstrap class loader injection strategy.
+ */
+ @EqualsAndHashCode
+ class Enabled implements BootstrapInjection {
+
+ /**
+ * The folder to save jar files in.
+ */
+ private final File folder;
+
+ /**
+ * Creates an enabled bootstrap class injection strategy.
+ *
+ * @param folder The folder to save jar files in.
+ */
+ protected Enabled(File folder) {
+ this.folder = folder;
+ }
+
+ @Override
+ public ClassInjector make(Instrumentation instrumentation) {
+ return ClassInjector.UsingInstrumentation.of(folder, ClassInjector.UsingInstrumentation.Target.BOOTSTRAP, instrumentation);
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/loading/InjectionClassLoader.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/loading/InjectionClassLoader.java
new file mode 100644
index 0000000..6420a51
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/loading/InjectionClassLoader.java
@@ -0,0 +1,59 @@
+package net.bytebuddy.dynamic.loading;
+
+import net.bytebuddy.description.type.TypeDescription;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * An injection class loader allows for the injection of a class after the class loader was created.
+ */
+public abstract class InjectionClassLoader extends ClassLoader {
+
+ /**
+ * Creates a new injection class loader.
+ *
+ * @param parent The class loader's parent.
+ */
+ protected InjectionClassLoader(ClassLoader parent) {
+ super(parent);
+ }
+
+ /**
+ * Defines a new type to be loaded by this class loader. If a type with the same name was already defined, an
+ * {@link IllegalArgumentException} is thrown.
+ *
+ * @param name The name of the type.
+ * @param binaryRepresentation The type's binary representation.
+ * @return The defined class or a previously defined class.
+ * @throws ClassNotFoundException If the class could not be loaded.
+ */
+ public abstract Class<?> defineClass(String name, byte[] binaryRepresentation) throws ClassNotFoundException;
+
+ /**
+ * A class loading strategy for adding a type to an injection class loader.
+ */
+ public enum Strategy implements ClassLoadingStrategy<InjectionClassLoader> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Map<TypeDescription, Class<?>> load(InjectionClassLoader classLoader, Map<TypeDescription, byte[]> types) {
+ if (classLoader == null) {
+ throw new IllegalArgumentException("Cannot add types to bootstrap class loader: " + types);
+ }
+ Map<TypeDescription, Class<?>> loadedTypes = new HashMap<TypeDescription, Class<?>>();
+ try {
+ for (Map.Entry<TypeDescription, byte[]> entry : types.entrySet()) {
+ loadedTypes.put(entry.getKey(), classLoader.defineClass(entry.getKey().getName(), entry.getValue()));
+ }
+ } catch (ClassNotFoundException exception) {
+ throw new IllegalStateException("Cannot load classes: " + types, exception);
+ }
+ return loadedTypes;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/loading/MultipleParentClassLoader.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/loading/MultipleParentClassLoader.java
new file mode 100644
index 0000000..97b863a
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/loading/MultipleParentClassLoader.java
@@ -0,0 +1,316 @@
+package net.bytebuddy.dynamic.loading;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.matcher.ElementMatcher;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.*;
+
+import static net.bytebuddy.matcher.ElementMatchers.is;
+import static net.bytebuddy.matcher.ElementMatchers.not;
+
+/**
+ * <p>
+ * This {@link java.lang.ClassLoader} is capable of loading classes from multiple parents. This class loader
+ * implicitly defines the bootstrap class loader to be its direct parent as it is required for all class loaders.
+ * This can be useful when creating a type that inherits a super type and interfaces that are defined by different,
+ * non-compatible class loaders.
+ * </p>
+ * <p>
+ * <b>Note</b>: Instances of this class loader can have the same class loader as its parent multiple times,
+ * either directly or indirectly by multiple parents sharing a common parent class loader. By definition,
+ * this implies that the bootstrap class loader is {@code #(direct parents) + 1} times a parent of this class loader.
+ * For the {@link java.lang.ClassLoader#getResources(java.lang.String)} method, this means that this class loader
+ * might return the same url multiple times by representing the same class loader multiple times.
+ * </p>
+ * <p>
+ * <b>Important</b>: This class loader does not support the location of packages from its multiple parents. This breaks
+ * package equality when loading classes by either loading them directly via this class loader (e.g. by subclassing) or
+ * by loading classes with child class loaders of this class loader.
+ * </p>
+ */
+public class MultipleParentClassLoader extends ClassLoader {
+
+ /**
+ * The parents of this class loader in their application order.
+ */
+ private final List<? extends ClassLoader> parents;
+
+ /**
+ * Creates a new class loader with multiple parents.
+ *
+ * @param parents The parents of this class loader in their application order. This list must not contain {@code null},
+ * i.e. the bootstrap class loader which is an implicit parent of any class loader.
+ */
+ public MultipleParentClassLoader(List<? extends ClassLoader> parents) {
+ this(ClassLoadingStrategy.BOOTSTRAP_LOADER, parents);
+ }
+
+ /**
+ * Creates a new class loader with multiple parents.
+ *
+ * @param parent An explicit parent in compliance with the class loader API. This explicit parent should only be set if
+ * the current platform does not allow creating a class loader that extends the bootstrap loader.
+ * @param parents The parents of this class loader in their application order. This list must not contain {@code null},
+ * i.e. the bootstrap class loader which is an implicit parent of any class loader.
+ */
+ public MultipleParentClassLoader(ClassLoader parent, List<? extends ClassLoader> parents) {
+ super(parent);
+ this.parents = parents;
+ }
+
+ @Override
+ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+ for (ClassLoader parent : parents) {
+ try {
+ Class<?> type = parent.loadClass(name);
+ if (resolve) {
+ resolveClass(type);
+ }
+ return type;
+ } catch (ClassNotFoundException ignored) {
+ /* try next class loader */
+ }
+ }
+ return super.loadClass(name, resolve);
+ }
+
+ @Override
+ public URL getResource(String name) {
+ for (ClassLoader parent : parents) {
+ URL url = parent.getResource(name);
+ if (url != null) {
+ return url;
+ }
+ }
+ return super.getResource(name);
+ }
+
+ @Override
+ public Enumeration<URL> getResources(String name) throws IOException {
+ List<Enumeration<URL>> enumerations = new ArrayList<Enumeration<URL>>(parents.size() + 1);
+ for (ClassLoader parent : parents) {
+ enumerations.add(parent.getResources(name));
+ }
+ enumerations.add(super.getResources(name));
+ return new CompoundEnumeration(enumerations);
+ }
+
+ /**
+ * A compound URL enumeration.
+ */
+ protected static class CompoundEnumeration implements Enumeration<URL> {
+
+ /**
+ * Indicates the first index of a list.
+ */
+ private static final int FIRST = 0;
+
+ /**
+ * The remaining lists of enumerations.
+ */
+ private final List<Enumeration<URL>> enumerations;
+
+ /**
+ * The currently represented enumeration or {@code null} if no such enumeration is currently selected.
+ */
+ private Enumeration<URL> currentEnumeration;
+
+ /**
+ * Creates a compound enumeration.
+ *
+ * @param enumerations The enumerations to represent.
+ */
+ protected CompoundEnumeration(List<Enumeration<URL>> enumerations) {
+ this.enumerations = enumerations;
+ }
+
+ @Override
+ public boolean hasMoreElements() {
+ if (currentEnumeration != null && currentEnumeration.hasMoreElements()) {
+ return true;
+ } else if (!enumerations.isEmpty()) {
+ currentEnumeration = enumerations.remove(FIRST);
+ return hasMoreElements();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", justification = "Null reference is impossible due to element check")
+ public URL nextElement() {
+ if (hasMoreElements()) {
+ return currentEnumeration.nextElement();
+ } else {
+ throw new NoSuchElementException();
+ }
+ }
+ }
+
+ /**
+ * A builder to collect class loader and that creates a
+ * {@link net.bytebuddy.dynamic.loading.MultipleParentClassLoader} only if multiple or no
+ * {@link java.lang.ClassLoader}s are found in the process. If exactly a single class loader is found,
+ * this class loader is returned. All class loaders are applied in their collection order with the exception
+ * of the bootstrap class loader which is represented by {@code null} and which is an implicit parent of any
+ * class loader.
+ */
+ @EqualsAndHashCode
+ public static class Builder {
+
+ /**
+ * Indicates the first index of a list.
+ */
+ private static final int ONLY = 0;
+
+ /**
+ * The class loaders that were collected.
+ */
+ private final List<? extends ClassLoader> classLoaders;
+
+ /**
+ * Creates a new builder without any class loaders.
+ */
+ public Builder() {
+ this(Collections.<ClassLoader>emptyList());
+ }
+
+ /**
+ * Creates a new builder.
+ *
+ * @param classLoaders The class loaders that were collected until now.
+ */
+ private Builder(List<? extends ClassLoader> classLoaders) {
+ this.classLoaders = classLoaders;
+ }
+
+ /**
+ * Appends the class loaders of the given types. The bootstrap class loader is implicitly skipped as
+ * it is an implicit parent of any class loader.
+ *
+ * @param type The types of which to collect the class loaders.
+ * @return A new builder instance with the additional class loaders of the provided types if they were not
+ * yet collected.
+ */
+ public Builder append(Class<?>... type) {
+ return append(Arrays.asList(type));
+ }
+
+ /**
+ * Appends the class loaders of the given types if those class loaders were not yet collected. The bootstrap class
+ * loader is implicitly skipped as it is an implicit parent of any class loader.
+ *
+ * @param types The types of which to collect the class loaders.
+ * @return A new builder instance with the additional class loaders.
+ */
+ public Builder append(Collection<? extends Class<?>> types) {
+ List<ClassLoader> classLoaders = new ArrayList<ClassLoader>(types.size());
+ for (Class<?> type : types) {
+ classLoaders.add(type.getClassLoader());
+ }
+ return append(classLoaders);
+ }
+
+ /**
+ * Appends the given class loaders if they were not yet collected. The bootstrap class loader is implicitly
+ * skipped as it is an implicit parent of any class loader.
+ *
+ * @param classLoader The class loaders to be collected.
+ * @return A new builder instance with the additional class loaders.
+ */
+ public Builder append(ClassLoader... classLoader) {
+ return append(Arrays.asList(classLoader));
+ }
+
+ /**
+ * Appends the given class loaders if they were not yet appended. The bootstrap class loader is never appended as
+ * it is an implicit parent of any class loader.
+ *
+ * @param classLoaders The class loaders to collected.
+ * @return A new builder instance with the additional class loaders.
+ */
+ public Builder append(List<? extends ClassLoader> classLoaders) {
+ List<ClassLoader> filtered = new ArrayList<ClassLoader>(this.classLoaders.size() + classLoaders.size());
+ Set<ClassLoader> registered = new HashSet<ClassLoader>(this.classLoaders);
+ filtered.addAll(this.classLoaders);
+ for (ClassLoader classLoader : classLoaders) {
+ if (classLoader != null && registered.add(classLoader)) {
+ filtered.add(classLoader);
+ }
+ }
+ return new Builder(filtered);
+ }
+
+ /**
+ * Only retains all class loaders that match the given matcher.
+ *
+ * @param matcher The matcher to be used for filtering.
+ * @return A builder that does not longer consider any appended class loaders that matched the provided matcher.
+ */
+ public Builder filter(ElementMatcher<? super ClassLoader> matcher) {
+ List<ClassLoader> classLoaders = new ArrayList<ClassLoader>(this.classLoaders.size());
+ for (ClassLoader classLoader : this.classLoaders) {
+ if (matcher.matches(classLoader)) {
+ classLoaders.add(classLoader);
+ }
+ }
+ return new Builder(classLoaders);
+ }
+
+ /**
+ * <p>
+ * Returns the only class loader that was appended if exactly one class loader was appended or a multiple parent class loader as
+ * a parent of all supplied class loader and with the bootstrap class loader as an implicit parent. If no class loader was appended,
+ * a new class loader is created that declares no parents. If a class loader is created, its explicit parent is set to be the
+ * bootstrap class loader.
+ * </p>
+ * <p>
+ * <b>Important</b>: Byte Buddy does not provide any access control for the creation of the class loader. It is the responsibility
+ * of the user of this builder to provide such privileges.
+ * </p>
+ *
+ * @return A suitable class loader.
+ */
+ @SuppressFBWarnings(value = "DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED", justification = "Privilege is explicit user responsibility")
+ public ClassLoader build() {
+ return classLoaders.size() == 1
+ ? classLoaders.get(ONLY)
+ : new MultipleParentClassLoader(classLoaders);
+ }
+
+ /**
+ * <p>
+ * Returns the only class loader that was appended if exactly one class loader was appended or a multiple parent class loader as
+ * a parent of all supplied class loader and with the bootstrap class loader as an implicit parent. If no class loader was appended,
+ * or if only the supplied parent to this method was included, this class loader is returned,
+ * </p>
+ * <p>
+ * <b>Important</b>: Byte Buddy does not provide any access control for the creation of the class loader. It is the responsibility
+ * of the user of this builder to provide such privileges.
+ * </p>
+ *
+ * @param parent The class loader's contractual parent which is accessible via {@link ClassLoader#getParent()}. If this class loader
+ * is also included in the appended class loaders, it is not
+ * @return A suitable class loader.
+ */
+ public ClassLoader build(ClassLoader parent) {
+ return classLoaders.isEmpty() || (classLoaders.size() == 1 && classLoaders.contains(parent))
+ ? parent
+ : filter(not(is(parent))).doBuild(parent);
+ }
+
+ /**
+ * Creates a multiple parent class loader with an explicit parent.
+ *
+ * @param parent The explicit parent class loader.
+ * @return A multiple parent class loader that includes all collected class loaders and the explicit parent.
+ */
+ @SuppressFBWarnings(value = "DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED", justification = "Privilege is explicit user responsibility")
+ private ClassLoader doBuild(ClassLoader parent) {
+ return new MultipleParentClassLoader(parent, classLoaders);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/loading/NoOpClassFileTransformer.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/loading/NoOpClassFileTransformer.java
new file mode 100644
index 0000000..28d0fdb
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/loading/NoOpClassFileTransformer.java
@@ -0,0 +1,32 @@
+package net.bytebuddy.dynamic.loading;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+import java.lang.instrument.ClassFileTransformer;
+import java.security.ProtectionDomain;
+
+/**
+ * A class file transformer that does not apply a transformation.
+ */
+public enum NoOpClassFileTransformer implements ClassFileTransformer {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ /**
+ * Indicates that no transformation is to applied.
+ */
+ private static final byte[] NO_TRANSFORMATION = null;
+
+ @Override
+ @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "Array is guaranteed to be null")
+ public byte[] transform(ClassLoader loader,
+ String className,
+ Class<?> classBeingRedefined,
+ ProtectionDomain protectionDomain,
+ byte[] classfileBuffer) {
+ return NO_TRANSFORMATION;
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/loading/PackageDefinitionStrategy.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/loading/PackageDefinitionStrategy.java
new file mode 100644
index 0000000..805cfb9
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/loading/PackageDefinitionStrategy.java
@@ -0,0 +1,648 @@
+package net.bytebuddy.dynamic.loading;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+
+/**
+ * A package definer is responsible for defining a package's properties when a class of a new package is loaded. Also,
+ * a package definer can choose not to define a package at all.
+ */
+public interface PackageDefinitionStrategy {
+
+ /**
+ * Returns a package definition for a given package.
+ *
+ * @param classLoader The class loader for which this package is being defined.
+ * @param packageName The name of the package.
+ * @param typeName The name of the type being loaded that triggered the package definition.
+ * @return A definition of the package.
+ */
+ Definition define(ClassLoader classLoader, String packageName, String typeName);
+
+ /**
+ * A package definer that does not define any package.
+ */
+ enum NoOp implements PackageDefinitionStrategy {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Definition define(ClassLoader classLoader, String packageName, String typeName) {
+ return Definition.Undefined.INSTANCE;
+ }
+ }
+
+ /**
+ * A package definer that only defines packages without any meta data.
+ */
+ enum Trivial implements PackageDefinitionStrategy {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Definition define(ClassLoader classLoader, String packageName, String typeName) {
+ return Definition.Trivial.INSTANCE;
+ }
+ }
+
+ /**
+ * A definition of a package.
+ */
+ interface Definition {
+
+ /**
+ * Indicates if a package should be defined at all.
+ *
+ * @return {@code true} if the package is to be defined.
+ */
+ boolean isDefined();
+
+ /**
+ * Returns the package specification's title or {@code null} if no such title exists. This method must only be called
+ * for defined package definitions.
+ *
+ * @return The package specification's title.
+ */
+ String getSpecificationTitle();
+
+ /**
+ * Returns the package specification's version or {@code null} if no such version exists. This method must only be called
+ * for defined package definitions.
+ *
+ * @return The package specification's version.
+ */
+ String getSpecificationVersion();
+
+ /**
+ * Returns the package specification's vendor or {@code null} if no such vendor exists. This method must only be called
+ * for defined package definitions.
+ *
+ * @return The package specification's vendor.
+ */
+ String getSpecificationVendor();
+
+ /**
+ * Returns the package implementation's title or {@code null} if no such title exists. This method must only be called
+ * for defined package definitions.
+ *
+ * @return The package implementation's title.
+ */
+ String getImplementationTitle();
+
+ /**
+ * Returns the package implementation's version or {@code null} if no such version exists. This method must only be called
+ * for defined package definitions.
+ *
+ * @return The package implementation's version.
+ */
+ String getImplementationVersion();
+
+ /**
+ * Returns the package implementation's vendor or {@code null} if no such vendor exists. This method must only be called
+ * for defined package definitions.
+ *
+ * @return The package implementation's vendor.
+ */
+ String getImplementationVendor();
+
+ /**
+ * The URL representing the seal base. This method must only be called for defined package definitions.
+ *
+ * @return The seal base of the package.
+ */
+ URL getSealBase();
+
+ /**
+ * Validates that this package definition is compatible to a previously defined package. This method must only be
+ * called for defined package definitions.
+ *
+ * @param definedPackage The previously defined package.
+ * @return {@code false} if this package and the defined package's sealing information are not compatible.
+ */
+ boolean isCompatibleTo(Package definedPackage);
+
+ /**
+ * A canonical implementation of an undefined package.
+ */
+ enum Undefined implements Definition {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public boolean isDefined() {
+ return false;
+ }
+
+ @Override
+ public String getSpecificationTitle() {
+ throw new IllegalStateException("Cannot read property of undefined package");
+ }
+
+ @Override
+ public String getSpecificationVersion() {
+ throw new IllegalStateException("Cannot read property of undefined package");
+ }
+
+ @Override
+ public String getSpecificationVendor() {
+ throw new IllegalStateException("Cannot read property of undefined package");
+ }
+
+ @Override
+ public String getImplementationTitle() {
+ throw new IllegalStateException("Cannot read property of undefined package");
+ }
+
+ @Override
+ public String getImplementationVersion() {
+ throw new IllegalStateException("Cannot read property of undefined package");
+ }
+
+ @Override
+ public String getImplementationVendor() {
+ throw new IllegalStateException("Cannot read property of undefined package");
+ }
+
+ @Override
+ public URL getSealBase() {
+ throw new IllegalStateException("Cannot read property of undefined package");
+ }
+
+ @Override
+ public boolean isCompatibleTo(Package definedPackage) {
+ throw new IllegalStateException("Cannot check compatibility to undefined package");
+ }
+ }
+
+ /**
+ * A package definer that defines packages without any meta data.
+ */
+ enum Trivial implements Definition {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ /**
+ * An empty value of a package's property.
+ */
+ private static final String NO_VALUE = null;
+
+ /**
+ * Represents an unsealed package.
+ */
+ private static final URL NOT_SEALED = null;
+
+ @Override
+ public boolean isDefined() {
+ return true;
+ }
+
+ @Override
+ public String getSpecificationTitle() {
+ return NO_VALUE;
+ }
+
+ @Override
+ public String getSpecificationVersion() {
+ return NO_VALUE;
+ }
+
+ @Override
+ public String getSpecificationVendor() {
+ return NO_VALUE;
+ }
+
+ @Override
+ public String getImplementationTitle() {
+ return NO_VALUE;
+ }
+
+ @Override
+ public String getImplementationVersion() {
+ return NO_VALUE;
+ }
+
+ @Override
+ public String getImplementationVendor() {
+ return NO_VALUE;
+ }
+
+ @Override
+ public URL getSealBase() {
+ return NOT_SEALED;
+ }
+
+ @Override
+ public boolean isCompatibleTo(Package definedPackage) {
+ return true;
+ }
+ }
+
+ /**
+ * A simple package definition where any property is represented by a value.
+ */
+ class Simple implements Definition {
+
+ /**
+ * The seal base or {@code null} if the package is not sealed.
+ */
+ protected final URL sealBase;
+
+ /**
+ * The package specification's title or {@code null} if no such title exists.
+ */
+ private final String specificationTitle;
+
+ /**
+ * The package specification's version or {@code null} if no such version exists.
+ */
+ private final String specificationVersion;
+
+ /**
+ * The package specification's vendor or {@code null} if no such vendor exists.
+ */
+ private final String specificationVendor;
+
+ /**
+ * The package implementation's title or {@code null} if no such title exists.
+ */
+ private final String implementationTitle;
+
+ /**
+ * The package implementation's version or {@code null} if no such version exists.
+ */
+ private final String implementationVersion;
+
+ /**
+ * The package implementation's vendor or {@code null} if no such vendor exists.
+ */
+ private final String implementationVendor;
+
+ /**
+ * Creates a new simple package definition.
+ *
+ * @param specificationTitle The package specification's title or {@code null} if no such title exists.
+ * @param specificationVersion The package specification's version or {@code null} if no such version exists.
+ * @param specificationVendor The package specification's vendor or {@code null} if no such vendor exists.
+ * @param implementationTitle The package implementation's title or {@code null} if no such title exists.
+ * @param implementationVersion The package implementation's version or {@code null} if no such version exists.
+ * @param implementationVendor The package implementation's vendor or {@code null} if no such vendor exists.
+ * @param sealBase The seal base or {@code null} if the package is not sealed.
+ */
+ public Simple(String specificationTitle,
+ String specificationVersion,
+ String specificationVendor,
+ String implementationTitle,
+ String implementationVersion,
+ String implementationVendor,
+ URL sealBase) {
+ this.specificationTitle = specificationTitle;
+ this.specificationVersion = specificationVersion;
+ this.specificationVendor = specificationVendor;
+ this.implementationTitle = implementationTitle;
+ this.implementationVersion = implementationVersion;
+ this.implementationVendor = implementationVendor;
+ this.sealBase = sealBase;
+ }
+
+ @Override
+ public boolean isDefined() {
+ return true;
+ }
+
+ @Override
+ public String getSpecificationTitle() {
+ return specificationTitle;
+ }
+
+ @Override
+ public String getSpecificationVersion() {
+ return specificationVersion;
+ }
+
+ @Override
+ public String getSpecificationVendor() {
+ return specificationVendor;
+ }
+
+ @Override
+ public String getImplementationTitle() {
+ return implementationTitle;
+ }
+
+ @Override
+ public String getImplementationVersion() {
+ return implementationVersion;
+ }
+
+ @Override
+ public String getImplementationVendor() {
+ return implementationVendor;
+ }
+
+ @Override
+ public URL getSealBase() {
+ return sealBase;
+ }
+
+ @Override
+ public boolean isCompatibleTo(Package definedPackage) {
+ if (sealBase == null) {
+ return !definedPackage.isSealed();
+ } else {
+ return definedPackage.isSealed(sealBase);
+ }
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "DMI_BLOCKING_METHODS_ON_URL", justification = "Package sealing relies on URL equality")
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (other == null || getClass() != other.getClass()) return false;
+ Simple simple = (Simple) other;
+ return !(specificationTitle != null ? !specificationTitle.equals(simple.specificationTitle) : simple.specificationTitle != null)
+ && !(specificationVersion != null ? !specificationVersion.equals(simple.specificationVersion) : simple.specificationVersion != null)
+ && !(specificationVendor != null ? !specificationVendor.equals(simple.specificationVendor) : simple.specificationVendor != null)
+ && !(implementationTitle != null ? !implementationTitle.equals(simple.implementationTitle) : simple.implementationTitle != null)
+ && !(implementationVersion != null ? !implementationVersion.equals(simple.implementationVersion) : simple.implementationVersion != null)
+ && !(implementationVendor != null ? !implementationVendor.equals(simple.implementationVendor) : simple.implementationVendor != null)
+ && !(sealBase != null ? !sealBase.equals(simple.sealBase) : simple.sealBase != null);
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "DMI_BLOCKING_METHODS_ON_URL", justification = "Package sealing relies on URL equality")
+ public int hashCode() {
+ int result = specificationTitle != null ? specificationTitle.hashCode() : 0;
+ result = 31 * result + (specificationVersion != null ? specificationVersion.hashCode() : 0);
+ result = 31 * result + (specificationVendor != null ? specificationVendor.hashCode() : 0);
+ result = 31 * result + (implementationTitle != null ? implementationTitle.hashCode() : 0);
+ result = 31 * result + (implementationVersion != null ? implementationVersion.hashCode() : 0);
+ result = 31 * result + (implementationVendor != null ? implementationVendor.hashCode() : 0);
+ result = 31 * result + (sealBase != null ? sealBase.hashCode() : 0);
+ return result;
+ }
+ }
+ }
+
+ /**
+ * A package definer that reads a class loader's manifest file.
+ */
+ @EqualsAndHashCode
+ class ManifestReading implements PackageDefinitionStrategy {
+
+ /**
+ * The path to the manifest file.
+ */
+ private static final String MANIFEST_FILE = "/META-INF/MANIFEST.MF";
+
+ /**
+ * A URL defined a non-sealed package.
+ */
+ private static final URL NOT_SEALED = null;
+
+ /**
+ * Contains all attributes that are relevant for defining a package.
+ */
+ private static final Attributes.Name[] ATTRIBUTE_NAMES = new Attributes.Name[]{
+ Attributes.Name.SPECIFICATION_TITLE,
+ Attributes.Name.SPECIFICATION_VERSION,
+ Attributes.Name.SPECIFICATION_VENDOR,
+ Attributes.Name.IMPLEMENTATION_TITLE,
+ Attributes.Name.IMPLEMENTATION_VERSION,
+ Attributes.Name.IMPLEMENTATION_VENDOR,
+ Attributes.Name.SEALED
+ };
+
+ /**
+ * A locator for a sealed package's URL.
+ */
+ private final SealBaseLocator sealBaseLocator;
+
+ /**
+ * Creates a manifest reading package definition strategy that attempts to extract sealing information from a defined class's URL.
+ */
+ public ManifestReading() {
+ this(new SealBaseLocator.ForTypeResourceUrl());
+ }
+
+ /**
+ * Creates a new package definer that reads a class loader's manifest file.
+ *
+ * @param sealBaseLocator A locator for a sealed package's URL.
+ */
+ public ManifestReading(SealBaseLocator sealBaseLocator) {
+ this.sealBaseLocator = sealBaseLocator;
+ }
+
+ @Override
+ public Definition define(ClassLoader classLoader, String packageName, String typeName) {
+ InputStream inputStream = classLoader.getResourceAsStream(MANIFEST_FILE);
+ if (inputStream != null) {
+ try {
+ try {
+ Manifest manifest = new Manifest(inputStream);
+ Map<Attributes.Name, String> values = new HashMap<Attributes.Name, String>();
+ Attributes mainAttributes = manifest.getMainAttributes();
+ if (mainAttributes != null) {
+ for (Attributes.Name attributeName : ATTRIBUTE_NAMES) {
+ values.put(attributeName, mainAttributes.getValue(attributeName));
+ }
+ }
+ Attributes attributes = manifest.getAttributes(packageName.replace('.', '/').concat("/"));
+ if (attributes != null) {
+ for (Attributes.Name attributeName : ATTRIBUTE_NAMES) {
+ String value = attributes.getValue(attributeName);
+ if (value != null) {
+ values.put(attributeName, value);
+ }
+ }
+ }
+ return new Definition.Simple(values.get(Attributes.Name.SPECIFICATION_TITLE),
+ values.get(Attributes.Name.SPECIFICATION_VERSION),
+ values.get(Attributes.Name.SPECIFICATION_VENDOR),
+ values.get(Attributes.Name.IMPLEMENTATION_TITLE),
+ values.get(Attributes.Name.IMPLEMENTATION_VERSION),
+ values.get(Attributes.Name.IMPLEMENTATION_VENDOR),
+ Boolean.parseBoolean(values.get(Attributes.Name.SEALED))
+ ? sealBaseLocator.findSealBase(classLoader, typeName)
+ : NOT_SEALED);
+ } finally {
+ inputStream.close();
+ }
+ } catch (IOException exception) {
+ throw new IllegalStateException("Error while reading manifest file", exception);
+ }
+ } else {
+ return Definition.Trivial.INSTANCE;
+ }
+ }
+
+ /**
+ * A locator for a seal base URL.
+ */
+ public interface SealBaseLocator {
+
+ /**
+ * Locates the URL that should be used for sealing a package.
+ *
+ * @param classLoader The class loader loading the package.
+ * @param typeName The name of the type being loaded that triggered the package definition.
+ * @return The URL that is used for sealing a package or {@code null} if the package should not be sealed.
+ */
+ URL findSealBase(ClassLoader classLoader, String typeName);
+
+ /**
+ * A seal base locator that never seals a package.
+ */
+ enum NonSealing implements SealBaseLocator {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public URL findSealBase(ClassLoader classLoader, String typeName) {
+ return NOT_SEALED;
+ }
+ }
+
+ /**
+ * A seal base locator that seals all packages with a fixed URL.
+ */
+ class ForFixedValue implements SealBaseLocator {
+
+ /**
+ * The seal base URL.
+ */
+ private final URL sealBase;
+
+ /**
+ * Creates a new seal base locator for a fixed URL.
+ *
+ * @param sealBase The seal base URL.
+ */
+ public ForFixedValue(URL sealBase) {
+ this.sealBase = sealBase;
+ }
+
+ @Override
+ public URL findSealBase(ClassLoader classLoader, String typeName) {
+ return sealBase;
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "DMI_BLOCKING_METHODS_ON_URL", justification = "Package sealing relies on URL equality")
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && sealBase.equals(((ForFixedValue) other).sealBase);
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "DMI_BLOCKING_METHODS_ON_URL", justification = "Package sealing relies on URL equality")
+ public int hashCode() {
+ return sealBase.hashCode();
+ }
+ }
+
+ /**
+ * A seal base locator that imitates the behavior of a {@link java.net.URLClassLoader}, i.e. tries
+ * to deduct the base from a class's resource URL.
+ */
+ @EqualsAndHashCode
+ class ForTypeResourceUrl implements SealBaseLocator {
+
+ /**
+ * An index to indicate to a {@link String} manipulation that the initial slash should be excluded.
+ */
+ private static final int EXCLUDE_INITIAL_SLASH = 1;
+
+ /**
+ * The file extension for a class file.
+ */
+ private static final String CLASS_FILE_EXTENSION = ".class";
+
+ /**
+ * The protocol name of a jar file.
+ */
+ private static final String JAR_FILE = "jar";
+
+ /**
+ * The protocol name of a file system link.
+ */
+ private static final String FILE_SYSTEM = "file";
+
+ /**
+ * The protocol name of a Java 9 runtime image.
+ */
+ private static final String RUNTIME_IMAGE = "jrt";
+
+ /**
+ * The seal base locator to fallback to when a resource is not found or an unexpected URL protocol is discovered.
+ */
+ private final SealBaseLocator fallback;
+
+ /**
+ * Creates a new seal base locator that attempts deduction from a type's URL while using a
+ * {@link net.bytebuddy.dynamic.loading.PackageDefinitionStrategy.ManifestReading.SealBaseLocator.NonSealing} seal base locator
+ * as a fallback.
+ */
+ public ForTypeResourceUrl() {
+ this(NonSealing.INSTANCE);
+ }
+
+ /**
+ * Creates a new seal base locator that attempts deduction from a type's URL.
+ *
+ * @param fallback The seal base locator to fallback to when a resource is not found or an unexpected URL protocol is discovered.
+ */
+ public ForTypeResourceUrl(SealBaseLocator fallback) {
+ this.fallback = fallback;
+ }
+
+ @Override
+ public URL findSealBase(ClassLoader classLoader, String typeName) {
+ URL url = classLoader.getResource(typeName.replace('.', '/') + CLASS_FILE_EXTENSION);
+ if (url != null) {
+ try {
+ if (url.getProtocol().equals(JAR_FILE)) {
+ return new URL(url.getPath().substring(0, url.getPath().indexOf('!')));
+ } else if (url.getProtocol().equals(FILE_SYSTEM)) {
+ return url;
+ } else if (url.getProtocol().equals(RUNTIME_IMAGE)) {
+ String path = url.getPath();
+ int modulePathIndex = path.indexOf('/', EXCLUDE_INITIAL_SLASH);
+ return modulePathIndex == -1
+ ? url
+ : new URL(RUNTIME_IMAGE + ":" + path.substring(0, modulePathIndex));
+ }
+ } catch (MalformedURLException exception) {
+ throw new IllegalStateException("Unexpected URL: " + url, exception);
+ }
+ }
+ return fallback.findSealBase(classLoader, typeName);
+ }
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/loading/package-info.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/loading/package-info.java
new file mode 100644
index 0000000..26a636b
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/loading/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * This package contains classes that are responsible for class loading of classes that are represented by
+ * {@code byte} arrays.
+ */
+package net.bytebuddy.dynamic.loading;
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/package-info.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/package-info.java
new file mode 100644
index 0000000..57888eb
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * This package contains classes and interfaces that are connected to writing the byte stream that represents a Java
+ * type that is dynamically created and for loading this type into a running JVM process.
+ */
+package net.bytebuddy.dynamic;
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/FieldLocator.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/FieldLocator.java
new file mode 100644
index 0000000..8951b5b
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/FieldLocator.java
@@ -0,0 +1,349 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+import static net.bytebuddy.matcher.ElementMatchers.*;
+
+/**
+ * A field locator offers an interface for locating a field that is declared by a specified type.
+ */
+public interface FieldLocator {
+
+ /**
+ * Locates a field with the given name and throws an exception if no such type exists.
+ *
+ * @param name The name of the field to locate.
+ * @return A resolution for a field lookup.
+ */
+ Resolution locate(String name);
+
+ /**
+ * Locates a field with the given name and type and throws an exception if no such type exists.
+ *
+ * @param name The name of the field to locate.
+ * @param type The type fo the field to locate.
+ * @return A resolution for a field lookup.
+ */
+ Resolution locate(String name, TypeDescription type);
+
+ /**
+ * A resolution for a field lookup.
+ */
+ interface Resolution {
+
+ /**
+ * Returns {@code true} if a field was located.
+ *
+ * @return {@code true} if a field was located.
+ */
+ boolean isResolved();
+
+ /**
+ * Returns the field description if a field was located. This method must only be called if
+ * this resolution was actually resolved.
+ *
+ * @return The located field.
+ */
+ FieldDescription getField();
+
+ /**
+ * An illegal resolution.
+ */
+ enum Illegal implements Resolution {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public boolean isResolved() {
+ return false;
+ }
+
+ @Override
+ public FieldDescription getField() {
+ throw new IllegalStateException("Could not locate field");
+ }
+ }
+
+ /**
+ * A simple implementation for a field resolution.
+ */
+ @EqualsAndHashCode
+ class Simple implements Resolution {
+
+ /**
+ * A description of the located field.
+ */
+ private final FieldDescription fieldDescription;
+
+ /**
+ * Creates a new simple resolution for a field.
+ *
+ * @param fieldDescription A description of the located field.
+ */
+ protected Simple(FieldDescription fieldDescription) {
+ this.fieldDescription = fieldDescription;
+ }
+
+ @Override
+ public boolean isResolved() {
+ return true;
+ }
+
+ @Override
+ public FieldDescription getField() {
+ return fieldDescription;
+ }
+ }
+ }
+
+ /**
+ * A factory for creating a {@link FieldLocator}.
+ */
+ interface Factory {
+
+ /**
+ * Creates a field locator for a given type.
+ *
+ * @param typeDescription The type for which to create a field locator.
+ * @return A suitable field locator.
+ */
+ FieldLocator make(TypeDescription typeDescription);
+ }
+
+ /**
+ * A field locator that never discovers a field.
+ */
+ enum NoOp implements FieldLocator, Factory {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public FieldLocator make(TypeDescription typeDescription) {
+ return this;
+ }
+
+ @Override
+ public Resolution locate(String name) {
+ return Resolution.Illegal.INSTANCE;
+ }
+
+ @Override
+ public Resolution locate(String name, TypeDescription type) {
+ return Resolution.Illegal.INSTANCE;
+ }
+ }
+
+ /**
+ * An abstract base implementation of a field locator.
+ */
+ @EqualsAndHashCode
+ abstract class AbstractBase implements FieldLocator {
+
+ /**
+ * The type accessing the field.
+ */
+ protected final TypeDescription accessingType;
+
+ /**
+ * Creates a new field locator.
+ *
+ * @param accessingType The type accessing the field.
+ */
+ protected AbstractBase(TypeDescription accessingType) {
+ this.accessingType = accessingType;
+ }
+
+ @Override
+ public Resolution locate(String name) {
+ FieldList<?> candidates = locate(named(name).and(isVisibleTo(accessingType)));
+ return candidates.size() == 1
+ ? new Resolution.Simple(candidates.getOnly())
+ : Resolution.Illegal.INSTANCE;
+ }
+
+ @Override
+ public Resolution locate(String name, TypeDescription type) {
+ FieldList<?> candidates = locate(named(name).and(fieldType(type)).and(isVisibleTo(accessingType)));
+ return candidates.size() == 1
+ ? new Resolution.Simple(candidates.getOnly())
+ : Resolution.Illegal.INSTANCE;
+ }
+
+ /**
+ * Locates fields that match the given matcher.
+ *
+ * @param matcher The matcher that identifies fields of interest.
+ * @return A list of fields that match the specified matcher.
+ */
+ protected abstract FieldList<?> locate(ElementMatcher<? super FieldDescription> matcher);
+ }
+
+ /**
+ * A field locator that only looks up fields that are declared by a specific type.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ class ForExactType extends AbstractBase {
+
+ /**
+ * The type for which to look up fields.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new field locator for locating fields from a declared type.
+ *
+ * @param typeDescription The type for which to look up fields that is also providing the accessing type.
+ */
+ public ForExactType(TypeDescription typeDescription) {
+ this(typeDescription, typeDescription);
+ }
+
+ /**
+ * Creates a new field locator for locating fields from a declared type.
+ *
+ * @param typeDescription The type for which to look up fields.
+ * @param accessingType The accessing type.
+ */
+ public ForExactType(TypeDescription typeDescription, TypeDescription accessingType) {
+ super(accessingType);
+ this.typeDescription = typeDescription;
+ }
+
+ @Override
+ protected FieldList<?> locate(ElementMatcher<? super FieldDescription> matcher) {
+ return typeDescription.getDeclaredFields().filter(matcher);
+ }
+
+ /**
+ * A factory for creating a {@link ForExactType}.
+ */
+ @EqualsAndHashCode
+ public static class Factory implements FieldLocator.Factory {
+
+ /**
+ * The type for which to locate a field.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new factory for a field locator that locates a field for an exact type.
+ *
+ * @param typeDescription The type for which to locate a field.
+ */
+ public Factory(TypeDescription typeDescription) {
+ this.typeDescription = typeDescription;
+ }
+
+ @Override
+ public FieldLocator make(TypeDescription typeDescription) {
+ return new ForExactType(this.typeDescription, typeDescription);
+ }
+ }
+ }
+
+ /**
+ * A field locator that looks up fields that are declared within a class's class hierarchy.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ class ForClassHierarchy extends AbstractBase {
+
+ /**
+ * The type for which to look up a field within its class hierarchy.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a field locator that looks up fields that are declared within a class's class hierarchy.
+ *
+ * @param typeDescription The type for which to look up a field within its class hierarchy which is also the accessing type.
+ */
+ public ForClassHierarchy(TypeDescription typeDescription) {
+ this(typeDescription, typeDescription);
+ }
+
+ /**
+ * Creates a field locator that looks up fields that are declared within a class's class hierarchy.
+ *
+ * @param typeDescription The type for which to look up a field within its class hierarchy.
+ * @param accessingType The accessing type.
+ */
+ public ForClassHierarchy(TypeDescription typeDescription, TypeDescription accessingType) {
+ super(accessingType);
+ this.typeDescription = typeDescription;
+ }
+
+ @Override
+ protected FieldList<?> locate(ElementMatcher<? super FieldDescription> matcher) {
+ for (TypeDefinition typeDefinition : typeDescription) {
+ FieldList<?> candidates = typeDefinition.getDeclaredFields().filter(matcher);
+ if (!candidates.isEmpty()) {
+ return candidates;
+ }
+ }
+ return new FieldList.Empty<FieldDescription>();
+ }
+
+ /**
+ * A factory for creating a {@link ForClassHierarchy}.
+ */
+ public enum Factory implements FieldLocator.Factory {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public FieldLocator make(TypeDescription typeDescription) {
+ return new ForClassHierarchy(typeDescription);
+ }
+ }
+ }
+
+ /**
+ * A field locator that only locates fields in the top-level type.
+ */
+ class ForTopLevelType extends AbstractBase {
+
+ /**
+ * Creates a new type locator for a top-level type.
+ *
+ * @param typeDescription The type to access.
+ */
+ protected ForTopLevelType(TypeDescription typeDescription) {
+ super(typeDescription);
+ }
+
+ @Override
+ protected FieldList<?> locate(ElementMatcher<? super FieldDescription> matcher) {
+ return accessingType.getDeclaredFields().filter(matcher);
+ }
+
+ /**
+ * A factory for locating a field in a top-level type.
+ */
+ public enum Factory implements FieldLocator.Factory {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public FieldLocator make(TypeDescription typeDescription) {
+ return new ForTopLevelType(typeDescription);
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/FieldRegistry.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/FieldRegistry.java
new file mode 100644
index 0000000..e1ab261
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/FieldRegistry.java
@@ -0,0 +1,298 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.Transformer;
+import net.bytebuddy.implementation.attribute.FieldAttributeAppender;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.matcher.LatentMatcher;
+
+import java.util.*;
+
+/**
+ * A field registry represents an extendable collection of fields which are identified by their names that are mapped
+ * to a given {@link net.bytebuddy.implementation.attribute.FieldAttributeAppender}. Fields
+ * can be uniquely identified by their name for a given type since fields are never inherited.
+ * <p> </p>
+ * This registry is the counterpart of a {@link net.bytebuddy.dynamic.scaffold.MethodRegistry}.
+ * However, a field registry is implemented simpler since it does not have to deal with complex signatures or
+ * inheritance. For the sake of consistency, the field registry follows however a similar pattern without introducing
+ * unnecessary complexity.
+ */
+public interface FieldRegistry {
+
+ /**
+ * Prepends the given field definition to this field registry, i.e. this configuration is applied first.
+ *
+ * @param matcher The matcher to identify any field that this definition concerns.
+ * @param fieldAttributeAppenderFactory The field attribute appender factory to apply on any matched field.
+ * @param defaultValue The default value to write to the field or {@code null} if no default value is to be set for the field.
+ * @param transformer The field transformer to apply to any matched field.
+ * @return An adapted version of this method registry.
+ */
+ FieldRegistry prepend(LatentMatcher<? super FieldDescription> matcher,
+ FieldAttributeAppender.Factory fieldAttributeAppenderFactory,
+ Object defaultValue,
+ Transformer<FieldDescription> transformer);
+
+ /**
+ * Prepares the field registry for a given instrumented type.
+ *
+ * @param instrumentedType The instrumented type.
+ * @return A prepared field registry.
+ */
+ Compiled compile(TypeDescription instrumentedType);
+
+ /**
+ * Represents a compiled field registry.
+ */
+ interface Compiled extends TypeWriter.FieldPool {
+
+ /**
+ * A no-op field registry that does not register annotations for any field.
+ */
+ enum NoOp implements Compiled {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Record target(FieldDescription fieldDescription) {
+ return new Record.ForImplicitField(fieldDescription);
+ }
+ }
+ }
+
+ /**
+ * An immutable default implementation of a field registry.
+ */
+ @EqualsAndHashCode
+ class Default implements FieldRegistry {
+
+ /**
+ * This registries entries.
+ */
+ private final List<Entry> entries;
+
+ /**
+ * Creates a new empty default field registry.
+ */
+ public Default() {
+ this(Collections.<Entry>emptyList());
+ }
+
+ /**
+ * Creates a new default field registry.
+ *
+ * @param entries The entries of the field registry.
+ */
+ private Default(List<Entry> entries) {
+ this.entries = entries;
+ }
+
+ @Override
+ public FieldRegistry prepend(LatentMatcher<? super FieldDescription> matcher,
+ FieldAttributeAppender.Factory fieldAttributeAppenderFactory,
+ Object defaultValue,
+ Transformer<FieldDescription> transformer) {
+ List<Entry> entries = new ArrayList<Entry>(this.entries.size() + 1);
+ entries.add(new Entry(matcher, fieldAttributeAppenderFactory, defaultValue, transformer));
+ entries.addAll(this.entries);
+ return new Default(entries);
+ }
+
+ @Override
+ public FieldRegistry.Compiled compile(TypeDescription instrumentedType) {
+ List<Compiled.Entry> entries = new ArrayList<Compiled.Entry>(this.entries.size());
+ Map<FieldAttributeAppender.Factory, FieldAttributeAppender> fieldAttributeAppenders = new HashMap<FieldAttributeAppender.Factory, FieldAttributeAppender>();
+ for (Entry entry : this.entries) {
+ FieldAttributeAppender fieldAttributeAppender = fieldAttributeAppenders.get(entry.getFieldAttributeAppenderFactory());
+ if (fieldAttributeAppender == null) {
+ fieldAttributeAppender = entry.getFieldAttributeAppenderFactory().make(instrumentedType);
+ fieldAttributeAppenders.put(entry.getFieldAttributeAppenderFactory(), fieldAttributeAppender);
+ }
+ entries.add(new Compiled.Entry(entry.resolve(instrumentedType), fieldAttributeAppender, entry.getDefaultValue(), entry.getTransformer()));
+ }
+ return new Compiled(instrumentedType, entries);
+ }
+
+ /**
+ * An entry of the default field registry.
+ */
+ @EqualsAndHashCode
+ protected static class Entry implements LatentMatcher<FieldDescription> {
+
+ /**
+ * The matcher to identify any field that this definition concerns.
+ */
+ private final LatentMatcher<? super FieldDescription> matcher;
+
+ /**
+ * The field attribute appender factory to apply on any matched field.
+ */
+ private final FieldAttributeAppender.Factory fieldAttributeAppenderFactory;
+
+ /**
+ * The default value to write to the field or {@code null} if no default value is to be set for the field.
+ */
+ private final Object defaultValue;
+
+ /**
+ * The field transformer to apply to any matched field.
+ */
+ private final Transformer<FieldDescription> transformer;
+
+ /**
+ * Creates a new entry.
+ *
+ * @param matcher The matcher to identify any field that this definition concerns.
+ * @param fieldAttributeAppenderFactory The field attribute appender factory to apply on any matched field.
+ * @param defaultValue The default value to write to the field or {@code null} if no default value is to be set for the field.
+ * @param transformer The field transformer to apply to any matched field.
+ */
+ protected Entry(LatentMatcher<? super FieldDescription> matcher,
+ FieldAttributeAppender.Factory fieldAttributeAppenderFactory,
+ Object defaultValue,
+ Transformer<FieldDescription> transformer) {
+ this.matcher = matcher;
+ this.fieldAttributeAppenderFactory = fieldAttributeAppenderFactory;
+ this.defaultValue = defaultValue;
+ this.transformer = transformer;
+ }
+
+ /**
+ * Returns the field attribute appender factory to apply on any matched field.
+ *
+ * @return The field attribute appender factory to apply on any matched field.
+ */
+ protected FieldAttributeAppender.Factory getFieldAttributeAppenderFactory() {
+ return fieldAttributeAppenderFactory;
+ }
+
+ /**
+ * Returns the default value to write to the field or {@code null} if no default value is to be set for the field.
+ *
+ * @return The default value to write to the field or {@code null} if no default value is to be set for the field.
+ */
+ protected Object getDefaultValue() {
+ return defaultValue;
+ }
+
+ /**
+ * Returns the field transformer to apply to any matched field.
+ *
+ * @return The field transformer to apply to any matched field.
+ */
+ protected Transformer<FieldDescription> getTransformer() {
+ return transformer;
+ }
+
+ @Override
+ public ElementMatcher<? super FieldDescription> resolve(TypeDescription typeDescription) {
+ return matcher.resolve(typeDescription);
+ }
+ }
+
+ /**
+ * A compiled default field registry.
+ */
+ @EqualsAndHashCode
+ protected static class Compiled implements FieldRegistry.Compiled {
+
+ /**
+ * The instrumented type for which this registry was compiled for.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * The entries of this compiled field registry.
+ */
+ private final List<Entry> entries;
+
+ /**
+ * Creates a new compiled field registry.
+ *
+ * @param instrumentedType The instrumented type for which this registry was compiled for.
+ * @param entries The entries of this compiled field registry.
+ */
+ protected Compiled(TypeDescription instrumentedType, List<Entry> entries) {
+ this.instrumentedType = instrumentedType;
+ this.entries = entries;
+ }
+
+ @Override
+ public Record target(FieldDescription fieldDescription) {
+ for (Entry entry : entries) {
+ if (entry.matches(fieldDescription)) {
+ return entry.bind(instrumentedType, fieldDescription);
+ }
+ }
+ return new Record.ForImplicitField(fieldDescription);
+ }
+
+ /**
+ * An entry of a compiled field registry.
+ */
+ @EqualsAndHashCode
+ protected static class Entry implements ElementMatcher<FieldDescription> {
+
+ /**
+ * The matcher to identify any field that this definition concerns.
+ */
+ private final ElementMatcher<? super FieldDescription> matcher;
+
+ /**
+ * The field attribute appender to apply on any matched field.
+ */
+ private final FieldAttributeAppender fieldAttributeAppender;
+
+ /**
+ * The default value to write to the field or {@code null} if no default value is to be set for the field.
+ */
+ private final Object defaultValue;
+
+ /**
+ * The field transformer to apply to any matched field.
+ */
+ private final Transformer<FieldDescription> transformer;
+
+ /**
+ * Creates a new entry.
+ *
+ * @param matcher The matcher to identify any field that this definition concerns.
+ * @param fieldAttributeAppender The field attribute appender to apply on any matched field.
+ * @param defaultValue The default value to write to the field or {@code null} if no default value is to be set for the field.
+ * @param transformer The field transformer to apply to any matched field.
+ */
+ protected Entry(ElementMatcher<? super FieldDescription> matcher,
+ FieldAttributeAppender fieldAttributeAppender,
+ Object defaultValue,
+ Transformer<FieldDescription> transformer) {
+ this.matcher = matcher;
+ this.fieldAttributeAppender = fieldAttributeAppender;
+ this.defaultValue = defaultValue;
+ this.transformer = transformer;
+ }
+
+ /**
+ * Binds this entry to the provided field description.
+ *
+ * @param instrumentedType The instrumented type for which this entry applies.
+ * @param fieldDescription The field description to be bound to this entry.
+ * @return A record representing the binding of this entry to the provided field.
+ */
+ protected Record bind(TypeDescription instrumentedType, FieldDescription fieldDescription) {
+ return new Record.ForExplicitField(fieldAttributeAppender, defaultValue, transformer.transform(instrumentedType, fieldDescription));
+ }
+
+ @Override
+ public boolean matches(FieldDescription target) {
+ return matcher.matches(target);
+ }
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/InstrumentedType.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/InstrumentedType.java
new file mode 100644
index 0000000..31bc025
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/InstrumentedType.java
@@ -0,0 +1,1188 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.annotation.AnnotationValue;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.modifier.ModifierContributor;
+import net.bytebuddy.description.type.PackageDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.description.type.TypeVariableToken;
+import net.bytebuddy.dynamic.Transformer;
+import net.bytebuddy.implementation.LoadedTypeInitializer;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.utility.CompoundList;
+
+import java.lang.annotation.ElementType;
+import java.util.*;
+
+import static net.bytebuddy.matcher.ElementMatchers.is;
+
+/**
+ * Implementations of this interface represent an instrumented type that is subject to change. Implementations
+ * should however be immutable and return new instance when its builder methods are invoked.
+ */
+public interface InstrumentedType extends TypeDescription {
+
+ /**
+ * Creates a new instrumented type that includes a new field.
+ *
+ * @param token A token that represents the field's shape. This token must represent types in their detached state.
+ * @return A new instrumented type that is equal to this instrumented type but with the additional field.
+ */
+ InstrumentedType withField(FieldDescription.Token token);
+
+ /**
+ * Creates a new instrumented type that includes a new method or constructor.
+ *
+ * @param token A token that represents the method's shape. This token must represent types in their detached state.
+ * @return A new instrumented type that is equal to this instrumented type but with the additional method.
+ */
+ InstrumentedType withMethod(MethodDescription.Token token);
+
+ /**
+ * Creates a new instrumented type with changed modifiers.
+ *
+ * @param modifiers The instrumented type's modifiers.
+ * @return A new instrumented type that is equal to this instrumented type but with the given modifiers.
+ */
+ InstrumentedType withModifiers(int modifiers);
+
+ /**
+ * Creates a new instrumented type with the given interfaces implemented.
+ *
+ * @param interfaceTypes The interface types to implement.
+ * @return A new instrumented type that is equal to this instrumented type but with the given interfaces implemented.
+ */
+ InstrumentedType withInterfaces(TypeList.Generic interfaceTypes);
+
+ /**
+ * Creates a new instrumented type with the given type variable defined.
+ *
+ * @param typeVariable The type variable to declare.
+ * @return A new instrumented type that is equal to this instrumented type but with the given type variable declared.
+ */
+ InstrumentedType withTypeVariable(TypeVariableToken typeVariable);
+
+ /**
+ * Creates a new instrumented type with the given annotations.
+ *
+ * @param annotationDescriptions The annotations to add to the instrumented type.
+ * @return A new instrumented type that is equal to this instrumented type but annotated with the given annotations
+ */
+ InstrumentedType withAnnotations(List<? extends AnnotationDescription> annotationDescriptions);
+
+ /**
+ * Creates a new instrumented type that includes the given {@link net.bytebuddy.implementation.LoadedTypeInitializer}.
+ *
+ * @param loadedTypeInitializer The type initializer to include.
+ * @return A new instrumented type that is equal to this instrumented type but with the additional type initializer.
+ */
+ InstrumentedType withInitializer(LoadedTypeInitializer loadedTypeInitializer);
+
+ /**
+ * Creates a new instrumented type that executes the given initializer in the instrumented type's
+ * type initializer.
+ *
+ * @param byteCodeAppender The byte code to add to the type initializer.
+ * @return A new instrumented type that is equal to this instrumented type but with the given stack manipulation
+ * attached to its type initializer.
+ */
+ InstrumentedType withInitializer(ByteCodeAppender byteCodeAppender);
+
+ /**
+ * Returns the {@link net.bytebuddy.implementation.LoadedTypeInitializer}s that were registered
+ * for this instrumented type.
+ *
+ * @return The registered loaded type initializers for this instrumented type.
+ */
+ LoadedTypeInitializer getLoadedTypeInitializer();
+
+ /**
+ * Returns this instrumented type's type initializer.
+ *
+ * @return This instrumented type's type initializer.
+ */
+ TypeInitializer getTypeInitializer();
+
+ /**
+ * Validates the instrumented type to define a legal Java type.
+ *
+ * @return This instrumented type as a non-modifiable type description.
+ */
+ TypeDescription validated();
+
+ /**
+ * Implementations represent an {@link InstrumentedType} with a flexible name.
+ */
+ interface WithFlexibleName extends InstrumentedType {
+
+ @Override
+ WithFlexibleName withField(FieldDescription.Token token);
+
+ @Override
+ WithFlexibleName withMethod(MethodDescription.Token token);
+
+ @Override
+ WithFlexibleName withModifiers(int modifiers);
+
+ @Override
+ WithFlexibleName withInterfaces(TypeList.Generic interfaceTypes);
+
+ @Override
+ WithFlexibleName withTypeVariable(TypeVariableToken typeVariable);
+
+ @Override
+ WithFlexibleName withAnnotations(List<? extends AnnotationDescription> annotationDescriptions);
+
+ @Override
+ WithFlexibleName withInitializer(LoadedTypeInitializer loadedTypeInitializer);
+
+ @Override
+ WithFlexibleName withInitializer(ByteCodeAppender byteCodeAppender);
+
+ /**
+ * Creates a new instrumented type with a changed name.
+ *
+ * @param name The name of the instrumented type.
+ * @return A new instrumented type that has the given name.
+ */
+ WithFlexibleName withName(String name);
+
+ /**
+ * Applies a transformation onto all existing type variables of this instrumented type. A transformation is potentially unsafe
+ * and it is the responsibility of the supplier to return a valid type variable token from the transformer.
+ *
+ * @param matcher The matcher to decide what type variables to transform.
+ * @param transformer The transformer to apply on all matched type variables.
+ * @return A new instrumented type with all matched type variables transformed.
+ */
+ WithFlexibleName withTypeVariables(ElementMatcher<? super Generic> matcher, Transformer<TypeVariableToken> transformer);
+ }
+
+ /**
+ * Implementations are able to prepare an {@link InstrumentedType}.
+ */
+ interface Prepareable {
+
+ /**
+ * Prepares a given instrumented type.
+ *
+ * @param instrumentedType The instrumented type in its current form.
+ * @return The prepared instrumented type.
+ */
+ InstrumentedType prepare(InstrumentedType instrumentedType);
+ }
+
+ /**
+ * A factory for creating an {@link InstrumentedType}.
+ */
+ interface Factory {
+
+ /**
+ * Creates an instrumented type that represents the provided type.
+ *
+ * @param typeDescription The type to represent.
+ * @return An appropriate instrumented type.
+ */
+ InstrumentedType.WithFlexibleName represent(TypeDescription typeDescription);
+
+ /**
+ * Creates a new instrumented type as a subclass.
+ *
+ * @param name The type's name.
+ * @param modifiers The type's modifiers.
+ * @param superClass The type's super class.
+ * @return A new instrumented type representing a subclass of the given parameters.
+ */
+ InstrumentedType.WithFlexibleName subclass(String name, int modifiers, TypeDescription.Generic superClass);
+
+ /**
+ * Default implementations of instrumented type factories.
+ */
+ enum Default implements Factory {
+
+ /**
+ * A factory for an instrumented type that allows to modify represented types.
+ */
+ MODIFIABLE {
+ @Override
+ public InstrumentedType.WithFlexibleName represent(TypeDescription typeDescription) {
+ return new InstrumentedType.Default(typeDescription.getName(),
+ typeDescription.getModifiers(),
+ typeDescription.getSuperClass(),
+ typeDescription.getTypeVariables().asTokenList(is(typeDescription)),
+ typeDescription.getInterfaces().accept(Generic.Visitor.Substitutor.ForDetachment.of(typeDescription)),
+ typeDescription.getDeclaredFields().asTokenList(is(typeDescription)),
+ typeDescription.getDeclaredMethods().asTokenList(is(typeDescription)),
+ typeDescription.getDeclaredAnnotations(),
+ TypeInitializer.None.INSTANCE,
+ LoadedTypeInitializer.NoOp.INSTANCE,
+ typeDescription.getDeclaringType(),
+ typeDescription.getEnclosingMethod(),
+ typeDescription.getEnclosingType(),
+ typeDescription.getDeclaredTypes(),
+ typeDescription.isMemberClass(),
+ typeDescription.isAnonymousClass(),
+ typeDescription.isLocalClass());
+ }
+ },
+
+ /**
+ * A factory for an instrumented type that does not allow to modify represented types.
+ */
+ FROZEN {
+ @Override
+ public InstrumentedType.WithFlexibleName represent(TypeDescription typeDescription) {
+ return new Frozen(typeDescription, LoadedTypeInitializer.NoOp.INSTANCE);
+ }
+ };
+
+ @Override
+ public InstrumentedType.WithFlexibleName subclass(String name, int modifiers, TypeDescription.Generic superClass) {
+ return new InstrumentedType.Default(name,
+ modifiers,
+ superClass,
+ Collections.<TypeVariableToken>emptyList(),
+ Collections.<Generic>emptyList(),
+ Collections.<FieldDescription.Token>emptyList(),
+ Collections.<MethodDescription.Token>emptyList(),
+ Collections.<AnnotationDescription>emptyList(),
+ TypeInitializer.None.INSTANCE,
+ LoadedTypeInitializer.NoOp.INSTANCE,
+ TypeDescription.UNDEFINED,
+ MethodDescription.UNDEFINED,
+ TypeDescription.UNDEFINED,
+ Collections.<TypeDescription>emptyList(),
+ false,
+ false,
+ false);
+ }
+ }
+ }
+
+ /**
+ * A default implementation of an instrumented type.
+ */
+ class Default extends AbstractBase.OfSimpleType implements InstrumentedType.WithFlexibleName {
+
+ /**
+ * A set containing all keywords of the Java programming language.
+ */
+ private static final Set<String> KEYWORDS = new HashSet<String>(Arrays.asList(
+ "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized", "boolean",
+ "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte", "else", "import",
+ "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch", "extends", "int", "short",
+ "try", "char", "final", "interface", "static", "void", "class", "finally", "long", "strictfp", "volatile",
+ "const", "float", "native", "super", "while"
+ ));
+
+ /**
+ * The binary name of the instrumented type.
+ */
+ private final String name;
+
+ /**
+ * The modifiers of the instrumented type.
+ */
+ private final int modifiers;
+
+ /**
+ * The generic super type of the instrumented type.
+ */
+ private final Generic superClass;
+
+ /**
+ * The instrumented type's type variables in their tokenized form.
+ */
+ private final List<? extends TypeVariableToken> typeVariables;
+
+ /**
+ * A list of interfaces of the instrumented type.
+ */
+ private final List<? extends Generic> interfaceTypes;
+
+ /**
+ * A list of field tokens describing the fields of the instrumented type.
+ */
+ private final List<? extends FieldDescription.Token> fieldTokens;
+
+ /**
+ * A list of method tokens describing the methods of the instrumented type.
+ */
+ private final List<? extends MethodDescription.Token> methodTokens;
+
+ /**
+ * A list of annotations of the annotated type.
+ */
+ private final List<? extends AnnotationDescription> annotationDescriptions;
+
+ /**
+ * The type initializer of the instrumented type.
+ */
+ private final TypeInitializer typeInitializer;
+
+ /**
+ * The loaded type initializer of the instrumented type.
+ */
+ private final LoadedTypeInitializer loadedTypeInitializer;
+
+ /**
+ * The declaring type of the instrumented type or {@code null} if no such type exists.
+ */
+ private final TypeDescription declaringType;
+
+ /**
+ * The enclosing method of the instrumented type or {@code null} if no such type exists.
+ */
+ private final MethodDescription enclosingMethod;
+
+ /**
+ * The enclosing type of the instrumented type or {@code null} if no such type exists.
+ */
+ private final TypeDescription enclosingType;
+
+ /**
+ * A list of types that are declared by this type.
+ */
+ private final List<? extends TypeDescription> declaredTypes;
+
+ /**
+ * {@code true} if this type is a member class.
+ */
+ private final boolean memberClass;
+
+ /**
+ * {@code true} if this type is a anonymous class.
+ */
+ private final boolean anonymousClass;
+
+ /**
+ * {@code true} if this type is a local class.
+ */
+ private final boolean localClass;
+
+ /**
+ * Creates a new instrumented type.
+ *
+ * @param name The binary name of the instrumented type.
+ * @param modifiers The modifiers of the instrumented type.
+ * @param typeVariables The instrumented type's type variables in their tokenized form.
+ * @param superClass The generic super type of the instrumented type.
+ * @param interfaceTypes A list of interfaces of the instrumented type.
+ * @param fieldTokens A list of field tokens describing the fields of the instrumented type.
+ * @param methodTokens A list of method tokens describing the methods of the instrumented type.
+ * @param annotationDescriptions A list of annotations of the annotated type.
+ * @param typeInitializer The type initializer of the instrumented type.
+ * @param loadedTypeInitializer The loaded type initializer of the instrumented type.
+ * @param declaringType The declaring type of the instrumented type or {@code null} if no such type exists.
+ * @param enclosingMethod The enclosing method of the instrumented type or {@code null} if no such type exists.
+ * @param enclosingType The enclosing type of the instrumented type or {@code null} if no such type exists.
+ * @param declaredTypes A list of types that are declared by this type.
+ * @param memberClass {@code true} if this type is a member class.
+ * @param anonymousClass {@code true} if this type is a anonymous class.
+ * @param localClass {@code true} if this type is a local class.
+ */
+ protected Default(String name,
+ int modifiers,
+ Generic superClass,
+ List<? extends TypeVariableToken> typeVariables,
+ List<? extends Generic> interfaceTypes,
+ List<? extends FieldDescription.Token> fieldTokens,
+ List<? extends MethodDescription.Token> methodTokens,
+ List<? extends AnnotationDescription> annotationDescriptions,
+ TypeInitializer typeInitializer,
+ LoadedTypeInitializer loadedTypeInitializer,
+ TypeDescription declaringType,
+ MethodDescription enclosingMethod,
+ TypeDescription enclosingType,
+ List<? extends TypeDescription> declaredTypes,
+ boolean memberClass,
+ boolean anonymousClass,
+ boolean localClass) {
+ this.name = name;
+ this.modifiers = modifiers;
+ this.typeVariables = typeVariables;
+ this.superClass = superClass;
+ this.interfaceTypes = interfaceTypes;
+ this.fieldTokens = fieldTokens;
+ this.methodTokens = methodTokens;
+ this.annotationDescriptions = annotationDescriptions;
+ this.typeInitializer = typeInitializer;
+ this.loadedTypeInitializer = loadedTypeInitializer;
+ this.declaringType = declaringType;
+ this.enclosingMethod = enclosingMethod;
+ this.enclosingType = enclosingType;
+ this.declaredTypes = declaredTypes;
+ this.memberClass = memberClass;
+ this.anonymousClass = anonymousClass;
+ this.localClass = localClass;
+ }
+
+ @Override
+ public WithFlexibleName withModifiers(int modifiers) {
+ return new Default(name,
+ modifiers,
+ superClass,
+ typeVariables,
+ interfaceTypes,
+ fieldTokens,
+ methodTokens,
+ annotationDescriptions,
+ typeInitializer,
+ loadedTypeInitializer,
+ declaringType,
+ enclosingMethod,
+ enclosingType,
+ declaredTypes,
+ memberClass,
+ anonymousClass,
+ localClass);
+ }
+
+ @Override
+ public WithFlexibleName withField(FieldDescription.Token token) {
+ return new Default(this.name,
+ modifiers,
+ superClass,
+ typeVariables,
+ interfaceTypes,
+ CompoundList.of(fieldTokens, token.accept(Generic.Visitor.Substitutor.ForDetachment.of(this))),
+ methodTokens,
+ annotationDescriptions,
+ typeInitializer,
+ loadedTypeInitializer,
+ declaringType,
+ enclosingMethod,
+ enclosingType,
+ declaredTypes,
+ memberClass,
+ anonymousClass,
+ localClass);
+ }
+
+ @Override
+ public WithFlexibleName withMethod(MethodDescription.Token token) {
+ return new Default(name,
+ modifiers,
+ superClass,
+ typeVariables,
+ interfaceTypes,
+ fieldTokens,
+ CompoundList.of(methodTokens, token.accept(Generic.Visitor.Substitutor.ForDetachment.of(this))),
+ annotationDescriptions,
+ typeInitializer,
+ loadedTypeInitializer,
+ declaringType,
+ enclosingMethod,
+ enclosingType,
+ declaredTypes,
+ memberClass,
+ anonymousClass,
+ localClass);
+ }
+
+ @Override
+ public WithFlexibleName withInterfaces(TypeList.Generic interfaceTypes) {
+ return new Default(name,
+ modifiers,
+ superClass,
+ typeVariables,
+ CompoundList.of(this.interfaceTypes, interfaceTypes.accept(Generic.Visitor.Substitutor.ForDetachment.of(this))),
+ fieldTokens,
+ methodTokens,
+ annotationDescriptions,
+ typeInitializer,
+ loadedTypeInitializer,
+ declaringType,
+ enclosingMethod,
+ enclosingType,
+ declaredTypes,
+ memberClass,
+ anonymousClass,
+ localClass);
+ }
+
+ @Override
+ public WithFlexibleName withAnnotations(List<? extends AnnotationDescription> annotationDescriptions) {
+ return new Default(name,
+ modifiers,
+ superClass,
+ typeVariables,
+ interfaceTypes,
+ fieldTokens,
+ methodTokens,
+ CompoundList.of(this.annotationDescriptions, annotationDescriptions),
+ typeInitializer,
+ loadedTypeInitializer,
+ declaringType,
+ enclosingMethod,
+ enclosingType,
+ declaredTypes,
+ memberClass,
+ anonymousClass,
+ localClass);
+ }
+
+ @Override
+ public WithFlexibleName withTypeVariable(TypeVariableToken typeVariable) {
+ return new Default(name,
+ modifiers,
+ superClass,
+ CompoundList.of(typeVariables, typeVariable.accept(Generic.Visitor.Substitutor.ForDetachment.of(this))),
+ interfaceTypes,
+ fieldTokens,
+ methodTokens,
+ annotationDescriptions,
+ typeInitializer,
+ loadedTypeInitializer,
+ declaringType,
+ enclosingMethod,
+ enclosingType,
+ declaredTypes,
+ memberClass,
+ anonymousClass,
+ localClass);
+ }
+
+ @Override
+ public WithFlexibleName withName(String name) {
+ return new Default(name,
+ modifiers,
+ superClass,
+ typeVariables,
+ interfaceTypes,
+ fieldTokens,
+ methodTokens,
+ annotationDescriptions,
+ typeInitializer,
+ loadedTypeInitializer,
+ declaringType,
+ enclosingMethod,
+ enclosingType,
+ declaredTypes,
+ memberClass,
+ anonymousClass,
+ localClass);
+ }
+
+ @Override
+ public WithFlexibleName withTypeVariables(ElementMatcher<? super Generic> matcher, Transformer<TypeVariableToken> transformer) {
+ List<TypeVariableToken> typeVariables = new ArrayList<TypeVariableToken>(this.typeVariables.size());
+ int index = 0;
+ for (TypeVariableToken typeVariableToken : this.typeVariables) {
+ typeVariables.add(matcher.matches(getTypeVariables().get(index++))
+ ? transformer.transform(this, typeVariableToken)
+ : typeVariableToken);
+ }
+ return new Default(name,
+ modifiers,
+ superClass,
+ typeVariables,
+ interfaceTypes,
+ fieldTokens,
+ methodTokens,
+ annotationDescriptions,
+ typeInitializer,
+ loadedTypeInitializer,
+ declaringType,
+ enclosingMethod,
+ enclosingType,
+ declaredTypes,
+ memberClass,
+ anonymousClass,
+ localClass);
+ }
+
+ @Override
+ public WithFlexibleName withInitializer(LoadedTypeInitializer loadedTypeInitializer) {
+ return new Default(name,
+ modifiers,
+ superClass,
+ typeVariables,
+ interfaceTypes,
+ fieldTokens,
+ methodTokens,
+ annotationDescriptions,
+ typeInitializer,
+ new LoadedTypeInitializer.Compound(this.loadedTypeInitializer, loadedTypeInitializer),
+ declaringType,
+ enclosingMethod,
+ enclosingType,
+ declaredTypes,
+ memberClass,
+ anonymousClass,
+ localClass);
+ }
+
+ @Override
+ public WithFlexibleName withInitializer(ByteCodeAppender byteCodeAppender) {
+ return new Default(name,
+ modifiers,
+ superClass,
+ typeVariables,
+ interfaceTypes,
+ fieldTokens,
+ methodTokens,
+ annotationDescriptions,
+ typeInitializer.expandWith(byteCodeAppender),
+ loadedTypeInitializer,
+ declaringType,
+ enclosingMethod,
+ enclosingType,
+ declaredTypes,
+ memberClass,
+ anonymousClass,
+ localClass);
+ }
+
+ @Override
+ public LoadedTypeInitializer getLoadedTypeInitializer() {
+ return loadedTypeInitializer;
+ }
+
+ @Override
+ public TypeInitializer getTypeInitializer() {
+ return typeInitializer;
+ }
+
+ @Override
+ public MethodDescription getEnclosingMethod() {
+ return enclosingMethod;
+ }
+
+ @Override
+ public TypeDescription getEnclosingType() {
+ return enclosingType;
+ }
+
+ @Override
+ public TypeList getDeclaredTypes() {
+ return new TypeList.Explicit(declaredTypes);
+ }
+
+ @Override
+ public boolean isAnonymousClass() {
+ return anonymousClass;
+ }
+
+ @Override
+ public boolean isLocalClass() {
+ return localClass;
+ }
+
+ @Override
+ public boolean isMemberClass() {
+ return memberClass;
+ }
+
+ @Override
+ public PackageDescription getPackage() {
+ int packageIndex = name.lastIndexOf('.');
+ return packageIndex == -1
+ ? PackageDescription.UNDEFINED
+ : new PackageDescription.Simple(name.substring(0, packageIndex));
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return new AnnotationList.Explicit(annotationDescriptions);
+ }
+
+ @Override
+ public TypeDescription getDeclaringType() {
+ return declaringType;
+ }
+
+ @Override
+ public Generic getSuperClass() {
+ return superClass == null
+ ? Generic.UNDEFINED
+ : new Generic.LazyProjection.WithResolvedErasure(superClass, Generic.Visitor.Substitutor.ForAttachment.of(this));
+ }
+
+ @Override
+ public TypeList.Generic getInterfaces() {
+ return new TypeList.Generic.ForDetachedTypes.WithResolvedErasure(interfaceTypes, TypeDescription.Generic.Visitor.Substitutor.ForAttachment.of(this));
+ }
+
+ @Override
+ public FieldList<FieldDescription.InDefinedShape> getDeclaredFields() {
+ return new FieldList.ForTokens(this, fieldTokens);
+ }
+
+ @Override
+ public MethodList<MethodDescription.InDefinedShape> getDeclaredMethods() {
+ return new MethodList.ForTokens(this, methodTokens);
+ }
+
+ @Override
+ public TypeList.Generic getTypeVariables() {
+ return TypeList.Generic.ForDetachedTypes.attachVariables(this, typeVariables);
+ }
+
+ @Override
+ public int getModifiers() {
+ return modifiers;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public TypeDescription validated() {
+ if (!isValidIdentifier(getName().split("\\."))) {
+ throw new IllegalStateException("Illegal type name: " + getName() + " for " + this);
+ } else if ((getModifiers() & ~ModifierContributor.ForType.MASK) != EMPTY_MASK) {
+ throw new IllegalStateException("Illegal modifiers " + getModifiers() + " for " + this);
+ } else if (isPackageType() && getModifiers() != PackageDescription.PACKAGE_MODIFIERS) {
+ throw new IllegalStateException("Illegal modifiers " + getModifiers() + " for package " + this);
+ }
+ TypeDescription.Generic superClass = getSuperClass();
+ if (superClass != null) {
+ if (!superClass.accept(Generic.Visitor.Validator.SUPER_CLASS)) {
+ throw new IllegalStateException("Illegal super class " + superClass + " for " + this);
+ } else if (!superClass.accept(Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE)) {
+ throw new IllegalStateException("Illegal type annotations on super class " + superClass + " for " + this);
+ } else if (!superClass.asErasure().isVisibleTo(this)) {
+ throw new IllegalStateException("Invisible super type " + superClass + " for " + this);
+ }
+ }
+ Set<TypeDescription> interfaceErasures = new HashSet<TypeDescription>();
+ for (TypeDescription.Generic interfaceType : getInterfaces()) {
+ if (!interfaceType.accept(Generic.Visitor.Validator.INTERFACE)) {
+ throw new IllegalStateException("Illegal interface " + interfaceType + " for " + this);
+ } else if (!interfaceType.accept(Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE)) {
+ throw new IllegalStateException("Illegal type annotations on interface " + interfaceType + " for " + this);
+ } else if (!interfaceErasures.add(interfaceType.asErasure())) {
+ throw new IllegalStateException("Already implemented interface " + interfaceType + " for " + this);
+ } else if (!interfaceType.asErasure().isVisibleTo(this)) {
+ throw new IllegalStateException("Invisible interface type " + interfaceType + " for " + this);
+ }
+ }
+ TypeList.Generic typeVariables = getTypeVariables();
+ if (!typeVariables.isEmpty() && isAssignableTo(Throwable.class)) {
+ throw new IllegalStateException("Cannot define throwable " + this + " to be generic");
+ }
+ Set<String> typeVariableNames = new HashSet<String>();
+ for (TypeDescription.Generic typeVariable : typeVariables) {
+ String variableSymbol = typeVariable.getSymbol();
+ if (!typeVariableNames.add(variableSymbol)) {
+ throw new IllegalStateException("Duplicate type variable symbol '" + typeVariable + "' for " + this);
+ } else if (!isValidIdentifier(variableSymbol)) {
+ throw new IllegalStateException("Illegal type variable name '" + typeVariable + "' for " + this);
+ } else if (!Generic.Visitor.Validator.ForTypeAnnotations.ofFormalTypeVariable(typeVariable)) {
+ throw new IllegalStateException("Illegal type annotation on '" + typeVariable + "' for " + this);
+ }
+ boolean interfaceBound = false;
+ Set<TypeDescription.Generic> bounds = new HashSet<Generic>();
+ for (TypeDescription.Generic bound : typeVariable.getUpperBounds()) {
+ if (!bound.accept(Generic.Visitor.Validator.TYPE_VARIABLE)) {
+ throw new IllegalStateException("Illegal type variable bound " + bound + " of " + typeVariable + " for " + this);
+ } else if (!bound.accept(Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE)) {
+ throw new IllegalStateException("Illegal type annotations on type variable " + bound + " for " + this);
+ } else if (!bounds.add(bound)) {
+ throw new IllegalStateException("Duplicate bound " + bound + " of " + typeVariable + " for " + this);
+ } else if (interfaceBound && (bound.getSort().isTypeVariable() || !bound.isInterface())) {
+ throw new IllegalStateException("Illegal interface bound " + bound + " of " + typeVariable + " for " + this);
+ }
+ interfaceBound = true;
+ }
+ if (!interfaceBound) {
+ throw new IllegalStateException("Type variable " + typeVariable + " for " + this + " does not define at least one bound");
+ }
+ }
+ Set<TypeDescription> typeAnnotationTypes = new HashSet<TypeDescription>();
+ for (AnnotationDescription annotationDescription : getDeclaredAnnotations()) {
+ if (!annotationDescription.getElementTypes().contains(ElementType.TYPE)
+ && !(isAnnotation() && annotationDescription.getElementTypes().contains(ElementType.ANNOTATION_TYPE))
+ && !(isPackageType() && annotationDescription.getElementTypes().contains(ElementType.PACKAGE))) {
+ throw new IllegalStateException("Cannot add " + annotationDescription + " on " + this);
+ } else if (!typeAnnotationTypes.add(annotationDescription.getAnnotationType())) {
+ throw new IllegalStateException("Duplicate annotation " + annotationDescription + " for " + this);
+ }
+ }
+ Set<FieldDescription.SignatureToken> fieldSignatureTokens = new HashSet<FieldDescription.SignatureToken>();
+ for (FieldDescription.InDefinedShape fieldDescription : getDeclaredFields()) {
+ String fieldName = fieldDescription.getName();
+ if (!fieldSignatureTokens.add(fieldDescription.asSignatureToken())) {
+ throw new IllegalStateException("Duplicate field definition for " + fieldDescription);
+ } else if (!isValidIdentifier(fieldName)) {
+ throw new IllegalStateException("Illegal field name for " + fieldDescription);
+ } else if ((fieldDescription.getModifiers() & ~ModifierContributor.ForField.MASK) != EMPTY_MASK) {
+ throw new IllegalStateException("Illegal field modifiers " + fieldDescription.getModifiers() + " for " + fieldDescription);
+ }
+ Generic fieldType = fieldDescription.getType();
+ if (!fieldType.accept(Generic.Visitor.Validator.FIELD)) {
+ throw new IllegalStateException("Illegal field type " + fieldType + " for " + fieldDescription);
+ } else if (!fieldType.accept(Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE)) {
+ throw new IllegalStateException("Illegal type annotations on " + fieldType + " for " + this);
+ } else if (!fieldDescription.isSynthetic() && !fieldType.asErasure().isVisibleTo(this)) {
+ throw new IllegalStateException("Invisible field type " + fieldDescription.getType() + " for " + fieldDescription);
+ }
+ Set<TypeDescription> fieldAnnotationTypes = new HashSet<TypeDescription>();
+ for (AnnotationDescription annotationDescription : fieldDescription.getDeclaredAnnotations()) {
+ if (!annotationDescription.getElementTypes().contains(ElementType.FIELD)) {
+ throw new IllegalStateException("Cannot add " + annotationDescription + " on " + fieldDescription);
+ } else if (!fieldAnnotationTypes.add(annotationDescription.getAnnotationType())) {
+ throw new IllegalStateException("Duplicate annotation " + annotationDescription + " for " + fieldDescription);
+ }
+ }
+ }
+ Set<MethodDescription.SignatureToken> methodSignatureTokens = new HashSet<MethodDescription.SignatureToken>();
+ for (MethodDescription.InDefinedShape methodDescription : getDeclaredMethods()) {
+ if (!methodSignatureTokens.add(methodDescription.asSignatureToken())) {
+ throw new IllegalStateException("Duplicate method signature for " + methodDescription);
+ } else if ((methodDescription.getModifiers() & ~ModifierContributor.ForMethod.MASK) != 0) {
+ throw new IllegalStateException("Illegal modifiers " + methodDescription.getModifiers() + " for " + methodDescription);
+ }
+ Set<String> methodTypeVariableNames = new HashSet<String>();
+ for (TypeDescription.Generic typeVariable : methodDescription.getTypeVariables()) {
+ String variableSymbol = typeVariable.getSymbol();
+ if (!methodTypeVariableNames.add(variableSymbol)) {
+ throw new IllegalStateException("Duplicate type variable symbol '" + typeVariable + "' for " + methodDescription);
+ } else if (!isValidIdentifier(variableSymbol)) {
+ throw new IllegalStateException("Illegal type variable name '" + typeVariable + "' for " + methodDescription);
+ } else if (!Generic.Visitor.Validator.ForTypeAnnotations.ofFormalTypeVariable(typeVariable)) {
+ throw new IllegalStateException("Illegal type annotation on '" + typeVariable + "' for " + methodDescription);
+ }
+ boolean interfaceBound = false;
+ Set<TypeDescription.Generic> bounds = new HashSet<Generic>();
+ for (TypeDescription.Generic bound : typeVariable.getUpperBounds()) {
+ if (!bound.accept(Generic.Visitor.Validator.TYPE_VARIABLE)) {
+ throw new IllegalStateException("Illegal type variable bound " + bound + " of " + typeVariable + " for " + methodDescription);
+ } else if (!bound.accept(Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE)) {
+ throw new IllegalStateException("Illegal type annotations on bound " + bound + " of " + typeVariable + " for " + this);
+ } else if (!bounds.add(bound)) {
+ throw new IllegalStateException("Duplicate bound " + bound + " of " + typeVariable + " for " + methodDescription);
+ } else if (interfaceBound && (bound.getSort().isTypeVariable() || !bound.isInterface())) {
+ throw new IllegalStateException("Illegal interface bound " + bound + " of " + typeVariable + " for " + methodDescription);
+ }
+ interfaceBound = true;
+ }
+ if (!interfaceBound) {
+ throw new IllegalStateException("Type variable " + typeVariable + " for " + methodDescription + " does not define at least one bound");
+ }
+ }
+ Generic returnType = methodDescription.getReturnType();
+ if (methodDescription.isTypeInitializer()) {
+ throw new IllegalStateException("Illegal explicit declaration of a type initializer by " + this);
+ } else if (methodDescription.isConstructor()) {
+ if (!returnType.represents(void.class)) {
+ throw new IllegalStateException("A constructor must return void " + methodDescription);
+ } else if (!returnType.getDeclaredAnnotations().isEmpty()) {
+ throw new IllegalStateException("The void non-type must not be annotated for " + methodDescription);
+ }
+ } else if (!isValidIdentifier(methodDescription.getInternalName())) {
+ throw new IllegalStateException("Illegal method name " + returnType + " for " + methodDescription);
+ } else if (!returnType.accept(Generic.Visitor.Validator.METHOD_RETURN)) {
+ throw new IllegalStateException("Illegal return type " + returnType + " for " + methodDescription);
+ } else if (!returnType.accept(Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE)) {
+ throw new IllegalStateException("Illegal type annotations return type " + returnType + " for " + methodDescription);
+ } else if (!methodDescription.isSynthetic() && !methodDescription.getReturnType().asErasure().isVisibleTo(this)) {
+ throw new IllegalStateException("Invisible return type " + methodDescription.getReturnType() + " for " + methodDescription);
+ }
+ Set<String> parameterNames = new HashSet<String>();
+ for (ParameterDescription.InDefinedShape parameterDescription : methodDescription.getParameters()) {
+ Generic parameterType = parameterDescription.getType();
+ if (!parameterType.accept(Generic.Visitor.Validator.METHOD_PARAMETER)) {
+ throw new IllegalStateException("Illegal parameter type of " + parameterDescription + " for " + methodDescription);
+ } else if (!parameterType.accept(Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE)) {
+ throw new IllegalStateException("Illegal type annotations return type " + parameterType + " for " + methodDescription);
+ } else if (!methodDescription.isSynthetic() && !parameterType.asErasure().isVisibleTo(this)) {
+ throw new IllegalStateException("Invisible parameter type of " + parameterDescription + " for " + methodDescription);
+ }
+ if (parameterDescription.isNamed()) {
+ String parameterName = parameterDescription.getName();
+ if (!parameterNames.add(parameterName)) {
+ throw new IllegalStateException("Duplicate parameter name of " + parameterDescription + " for " + methodDescription);
+ } else if (!isValidIdentifier(parameterName)) {
+ throw new IllegalStateException("Illegal parameter name of " + parameterDescription + " for " + methodDescription);
+ }
+ }
+ if (parameterDescription.hasModifiers() && (parameterDescription.getModifiers() & ~ModifierContributor.ForParameter.MASK) != EMPTY_MASK) {
+ throw new IllegalStateException("Illegal modifiers of " + parameterDescription + " for " + methodDescription);
+ }
+ Set<TypeDescription> parameterAnnotationTypes = new HashSet<TypeDescription>();
+ for (AnnotationDescription annotationDescription : parameterDescription.getDeclaredAnnotations()) {
+ if (!annotationDescription.getElementTypes().contains(ElementType.PARAMETER)) {
+ throw new IllegalStateException("Cannot add " + annotationDescription + " on " + parameterDescription);
+ } else if (!parameterAnnotationTypes.add(annotationDescription.getAnnotationType())) {
+ throw new IllegalStateException("Duplicate annotation " + annotationDescription + " of " + parameterDescription + " for " + methodDescription);
+ }
+ }
+ }
+ Set<TypeDescription.Generic> exceptionTypes = new HashSet<Generic>();
+ for (TypeDescription.Generic exceptionType : methodDescription.getExceptionTypes()) {
+ if (!exceptionTypes.add(exceptionType)) {
+ throw new IllegalStateException("Duplicate exception type " + exceptionType + " for " + methodDescription);
+ } else if (!exceptionType.accept(Generic.Visitor.Validator.EXCEPTION)) {
+ throw new IllegalStateException("Illegal exception type " + exceptionType + " for " + methodDescription);
+ } else if (!exceptionType.accept(Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE)) {
+ throw new IllegalStateException("Illegal type annotations on " + exceptionType + " for " + methodDescription);
+ } else if (!methodDescription.isSynthetic() && !exceptionType.asErasure().isVisibleTo(this)) {
+ throw new IllegalStateException("Invisible exception type " + exceptionType + " for " + methodDescription);
+ }
+ }
+ Set<TypeDescription> methodAnnotationTypes = new HashSet<TypeDescription>();
+ for (AnnotationDescription annotationDescription : methodDescription.getDeclaredAnnotations()) {
+ if (!annotationDescription.getElementTypes().contains(methodDescription.isMethod() ? ElementType.METHOD : ElementType.CONSTRUCTOR)) {
+ throw new IllegalStateException("Cannot add " + annotationDescription + " on " + methodDescription);
+ } else if (!methodAnnotationTypes.add(annotationDescription.getAnnotationType())) {
+ throw new IllegalStateException("Duplicate annotation " + annotationDescription + " for " + methodDescription);
+ }
+ }
+ AnnotationValue<?, ?> defaultValue = methodDescription.getDefaultValue();
+ if (defaultValue != null && !methodDescription.isDefaultValue(defaultValue)) {
+ throw new IllegalStateException("Illegal default value " + defaultValue + "for " + methodDescription);
+ }
+ Generic receiverType = methodDescription.getReceiverType();
+ if (receiverType != null && !receiverType.accept(Generic.Visitor.Validator.RECEIVER)) {
+ throw new IllegalStateException("Illegal receiver type " + receiverType + " for " + methodDescription);
+ } else if (methodDescription.isStatic()) {
+ if (receiverType != null) {
+ throw new IllegalStateException("Static method " + methodDescription + " defines a non-null receiver " + receiverType);
+ }
+ } else if (methodDescription.isConstructor()) {
+ TypeDescription enclosingType = getEnclosingType();
+ if (receiverType == null || !receiverType.asErasure().equals(enclosingType == null ? this : enclosingType)) {
+ throw new IllegalStateException("Constructor " + methodDescription + " defines an illegal receiver " + receiverType);
+ }
+ } else if (/* methodDescription.isMethod() */ receiverType == null || !equals(receiverType.asErasure())) {
+ throw new IllegalStateException("Method " + methodDescription + " defines an illegal receiver " + receiverType);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Checks if an array of identifiers is a valid compound Java identifier.
+ *
+ * @param identifier an array of potentially invalid Java identifiers.
+ * @return {@code true} if all identifiers are valid and the array is not empty.
+ */
+ private static boolean isValidIdentifier(String[] identifier) {
+ if (identifier.length == 0) {
+ return false;
+ }
+ for (String part : identifier) {
+ if (!isValidIdentifier(part)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks if a Java identifier is valid.
+ *
+ * @param identifier The identifier to check for validity.
+ * @return {@code true} if the given identifier is valid.
+ */
+ private static boolean isValidIdentifier(String identifier) {
+ if (KEYWORDS.contains(identifier) || identifier.isEmpty() || !Character.isJavaIdentifierStart(identifier.charAt(0))) {
+ return false;
+ } else if (identifier.equals(PackageDescription.PACKAGE_CLASS_NAME)) {
+ return true;
+ }
+ for (int index = 1; index < identifier.length(); index++) {
+ if (!Character.isJavaIdentifierPart(identifier.charAt(index))) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ /**
+ * A frozen representation of an instrumented type of which the structure must not be modified.
+ */
+ class Frozen extends AbstractBase.OfSimpleType implements InstrumentedType.WithFlexibleName {
+
+ /**
+ * The represented type description.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * The type's loaded type initializer.
+ */
+ private final LoadedTypeInitializer loadedTypeInitializer;
+
+ /**
+ * Creates a new frozen representation of an instrumented type.
+ *
+ * @param typeDescription The represented type description.
+ * @param loadedTypeInitializer The type's loaded type initializer.
+ */
+ protected Frozen(TypeDescription typeDescription, LoadedTypeInitializer loadedTypeInitializer) {
+ this.typeDescription = typeDescription;
+ this.loadedTypeInitializer = loadedTypeInitializer;
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return typeDescription.getDeclaredAnnotations();
+ }
+
+ @Override
+ public int getModifiers() {
+ return typeDescription.getModifiers();
+ }
+
+ @Override
+ public TypeList.Generic getTypeVariables() {
+ return typeDescription.getTypeVariables();
+ }
+
+ @Override
+ public String getName() {
+ return typeDescription.getName();
+ }
+
+ @Override
+ public Generic getSuperClass() {
+ return typeDescription.getSuperClass();
+ }
+
+ @Override
+ public TypeList.Generic getInterfaces() {
+ return typeDescription.getInterfaces();
+ }
+
+ @Override
+ public FieldList<FieldDescription.InDefinedShape> getDeclaredFields() {
+ return typeDescription.getDeclaredFields();
+ }
+
+ @Override
+ public MethodList<MethodDescription.InDefinedShape> getDeclaredMethods() {
+ return typeDescription.getDeclaredMethods();
+ }
+
+ @Override
+ public boolean isAnonymousClass() {
+ return typeDescription.isAnonymousClass();
+ }
+
+ @Override
+ public boolean isLocalClass() {
+ return typeDescription.isLocalClass();
+ }
+
+ @Override
+ public boolean isMemberClass() {
+ return typeDescription.isMemberClass();
+ }
+
+ @Override
+ public PackageDescription getPackage() {
+ return typeDescription.getPackage();
+ }
+
+ @Override
+ public TypeDescription getEnclosingType() {
+ return typeDescription.getEnclosingType();
+ }
+
+ @Override
+ public TypeDescription getDeclaringType() {
+ return typeDescription.getDeclaringType();
+ }
+
+ @Override
+ public TypeList getDeclaredTypes() {
+ return typeDescription.getDeclaredTypes();
+ }
+
+ @Override
+ public MethodDescription getEnclosingMethod() {
+ return typeDescription.getEnclosingMethod();
+ }
+
+ @Override
+ public String getGenericSignature() {
+ // Embrace use of native generic signature by direct delegation.
+ return typeDescription.getGenericSignature();
+ }
+
+ @Override
+ public int getActualModifiers(boolean superFlag) {
+ // Embrace use of native actual modifiers by direct delegation.
+ return typeDescription.getActualModifiers(superFlag);
+ }
+
+ @Override
+ public WithFlexibleName withField(FieldDescription.Token token) {
+ throw new IllegalStateException("Cannot define field for frozen type: " + typeDescription);
+ }
+
+ @Override
+ public WithFlexibleName withMethod(MethodDescription.Token token) {
+ throw new IllegalStateException("Cannot define method for frozen type: " + typeDescription);
+ }
+
+ @Override
+ public WithFlexibleName withModifiers(int modifiers) {
+ throw new IllegalStateException("Cannot change modifiers for frozen type: " + typeDescription);
+ }
+
+ @Override
+ public WithFlexibleName withInterfaces(TypeList.Generic interfaceTypes) {
+ throw new IllegalStateException("Cannot add interfaces for frozen type: " + typeDescription);
+ }
+
+ @Override
+ public WithFlexibleName withTypeVariable(TypeVariableToken typeVariable) {
+ throw new IllegalStateException("Cannot define type variable for frozen type: " + typeDescription);
+ }
+
+ @Override
+ public WithFlexibleName withAnnotations(List<? extends AnnotationDescription> annotationDescriptions) {
+ throw new IllegalStateException("Cannot add annotation to frozen type: " + typeDescription);
+ }
+
+ @Override
+ public WithFlexibleName withInitializer(LoadedTypeInitializer loadedTypeInitializer) {
+ return new Frozen(typeDescription, new LoadedTypeInitializer.Compound(this.loadedTypeInitializer, loadedTypeInitializer));
+ }
+
+ @Override
+ public WithFlexibleName withInitializer(ByteCodeAppender byteCodeAppender) {
+ throw new IllegalStateException("Cannot add initializer to frozen type: " + typeDescription);
+ }
+
+ @Override
+ public WithFlexibleName withName(String name) {
+ throw new IllegalStateException("Cannot change name of frozen type: " + typeDescription);
+ }
+
+ @Override
+ public WithFlexibleName withTypeVariables(ElementMatcher<? super Generic> matcher, Transformer<TypeVariableToken> transformer) {
+ throw new IllegalStateException("Cannot add type variables of frozen type: " + typeDescription);
+ }
+
+ @Override
+ public LoadedTypeInitializer getLoadedTypeInitializer() {
+ return loadedTypeInitializer;
+ }
+
+ @Override
+ public TypeInitializer getTypeInitializer() {
+ return TypeInitializer.None.INSTANCE;
+ }
+
+ @Override
+ public TypeDescription validated() {
+ return typeDescription;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/MethodGraph.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/MethodGraph.java
new file mode 100644
index 0000000..2235bc1
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/MethodGraph.java
@@ -0,0 +1,1681 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.matcher.FilterableList;
+import org.objectweb.asm.Opcodes;
+
+import java.util.*;
+
+import static net.bytebuddy.matcher.ElementMatchers.*;
+
+/**
+ * A method graph represents a view on a set of methods as they are seen from a given type. Any method is represented as a node that represents
+ * a method, its bridge methods, its resolution state and information on if it was made visible by a visibility bridge.
+ */
+public interface MethodGraph {
+
+ /**
+ * Locates a node in this graph which represents the provided method token.
+ *
+ * @param token A method token that represents the method to be located.
+ * @return The node representing the given token.
+ */
+ Node locate(MethodDescription.SignatureToken token);
+
+ /**
+ * Lists all nodes of this method graph.
+ *
+ * @return A list of all nodes of this method graph.
+ */
+ NodeList listNodes();
+
+ /**
+ * A canonical implementation of an empty method graph.
+ */
+ enum Empty implements MethodGraph.Linked, MethodGraph.Compiler {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Node locate(MethodDescription.SignatureToken token) {
+ return Node.Unresolved.INSTANCE;
+ }
+
+ @Override
+ public NodeList listNodes() {
+ return new NodeList(Collections.<Node>emptyList());
+ }
+
+ @Override
+ public MethodGraph getSuperClassGraph() {
+ return this;
+ }
+
+ @Override
+ public MethodGraph getInterfaceGraph(TypeDescription typeDescription) {
+ return this;
+ }
+
+ @Override
+ public Linked compile(TypeDescription typeDescription) {
+ return this;
+ }
+
+ @Override
+ public Linked compile(TypeDefinition typeDefinition, TypeDescription viewPoint) {
+ return this;
+ }
+ }
+
+ /**
+ * A linked method graph represents a view that additionally exposes information of a given type's super type view and a
+ * view on this graph's directly implemented interfaces.
+ */
+ interface Linked extends MethodGraph {
+
+ /**
+ * Returns a graph representing the view on this represented type's super type.
+ *
+ * @return A graph representing the view on this represented type's super type.
+ */
+ MethodGraph getSuperClassGraph();
+
+ /**
+ * Returns a graph representing the view on this represented type's directly implemented interface type.
+ *
+ * @param typeDescription The interface type for which a view is to be returned.
+ * @return A graph representing the view on this represented type's directly implemented interface type.
+ */
+ MethodGraph getInterfaceGraph(TypeDescription typeDescription);
+
+ /**
+ * A simple implementation of a linked method graph that exposes views by delegation to given method graphs.
+ */
+ @EqualsAndHashCode
+ class Delegation implements Linked {
+
+ /**
+ * The represented type's method graph.
+ */
+ private final MethodGraph methodGraph;
+
+ /**
+ * The super class's method graph.
+ */
+ private final MethodGraph superClassGraph;
+
+ /**
+ * A mapping of method graphs of the represented type's directly implemented interfaces to their graph representatives.
+ */
+ private final Map<TypeDescription, MethodGraph> interfaceGraphs;
+
+ /**
+ * Creates a new delegation method graph.
+ *
+ * @param methodGraph The represented type's method graph.
+ * @param superClassGraph The super class's method graph.
+ * @param interfaceGraphs A mapping of method graphs of the represented type's directly implemented interfaces to their graph representatives.
+ */
+ public Delegation(MethodGraph methodGraph, MethodGraph superClassGraph, Map<TypeDescription, MethodGraph> interfaceGraphs) {
+ this.methodGraph = methodGraph;
+ this.superClassGraph = superClassGraph;
+ this.interfaceGraphs = interfaceGraphs;
+ }
+
+ @Override
+ public MethodGraph getSuperClassGraph() {
+ return superClassGraph;
+ }
+
+ @Override
+ public MethodGraph getInterfaceGraph(TypeDescription typeDescription) {
+ MethodGraph interfaceGraph = interfaceGraphs.get(typeDescription);
+ return interfaceGraph == null
+ ? Empty.INSTANCE
+ : interfaceGraph;
+ }
+
+ @Override
+ public Node locate(MethodDescription.SignatureToken token) {
+ return methodGraph.locate(token);
+ }
+
+ @Override
+ public NodeList listNodes() {
+ return methodGraph.listNodes();
+ }
+ }
+ }
+
+ /**
+ * Represents a node within a method graph.
+ */
+ interface Node {
+
+ /**
+ * Returns the sort of this node.
+ *
+ * @return The sort of this node.
+ */
+ Sort getSort();
+
+ /**
+ * Returns the method that is represented by this node.
+ *
+ * @return The method that is represented by this node.
+ */
+ MethodDescription getRepresentative();
+
+ /**
+ * Returns a set of type tokens that this method represents. This set contains the actual method's type including the
+ * types of all bridge methods.
+ *
+ * @return A set of type tokens that this method represents.
+ */
+ Set<MethodDescription.TypeToken> getMethodTypes();
+
+ /**
+ * Returns the minimal method visibility of all methods that are represented by this node.
+ *
+ * @return The minimal method visibility of all methods that are represented by this node.
+ */
+ Visibility getVisibility();
+
+ /**
+ * Represents a {@link net.bytebuddy.dynamic.scaffold.MethodGraph.Node}'s state.
+ */
+ enum Sort {
+
+ /**
+ * Represents a resolved node that was made visible by a visibility bridge.
+ */
+ VISIBLE(true, true, true),
+
+ /**
+ * Represents a resolved node that was not made visible by a visibility bridge.
+ */
+ RESOLVED(true, true, false),
+
+ /**
+ * Represents an ambiguous node, i.e. a node that might refer to several methods.
+ */
+ AMBIGUOUS(true, false, false),
+
+ /**
+ * Represents an unresolved node.
+ */
+ UNRESOLVED(false, false, false);
+
+ /**
+ * {@code true} if this sort represents a resolved node.
+ */
+ private final boolean resolved;
+
+ /**
+ * {@code true} if this sort represents a non-ambiguous node.
+ */
+ private final boolean unique;
+
+ /**
+ * {@code true} if this sort represents a node that was made by a visibility bridge.
+ */
+ private final boolean madeVisible;
+
+ /**
+ * Creates a new sort.
+ *
+ * @param resolved {@code true} if this sort represents a resolved node.
+ * @param unique {@code true} if this sort represents a non-ambiguous node.
+ * @param madeVisible {@code true} if this sort represents a node that was made by a visibility bridge.
+ */
+ Sort(boolean resolved, boolean unique, boolean madeVisible) {
+ this.resolved = resolved;
+ this.unique = unique;
+ this.madeVisible = madeVisible;
+ }
+
+ /**
+ * Verifies if this sort represents a resolved node.
+ *
+ * @return {@code true} if this sort represents a resolved node.
+ */
+ public boolean isResolved() {
+ return resolved;
+ }
+
+ /**
+ * Verifies if this sort represents a non-ambiguous node.
+ *
+ * @return {@code true} if this sort represents a non-ambiguous node.
+ */
+ public boolean isUnique() {
+ return unique;
+ }
+
+ /**
+ * Verifies if this sort represents a node that was made visible by a visibility bridge.
+ *
+ * @return {@code true} if this sort represents a node that was made visible by a visibility bridge.
+ */
+ public boolean isMadeVisible() {
+ return madeVisible;
+ }
+ }
+
+ /**
+ * A canonical implementation of an unresolved node.
+ */
+ enum Unresolved implements Node {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Sort getSort() {
+ return Sort.UNRESOLVED;
+ }
+
+ @Override
+ public MethodDescription getRepresentative() {
+ throw new IllegalStateException("Cannot resolve the method of an illegal node");
+ }
+
+ @Override
+ public Set<MethodDescription.TypeToken> getMethodTypes() {
+ throw new IllegalStateException("Cannot resolve bridge method of an illegal node");
+ }
+
+ @Override
+ public Visibility getVisibility() {
+ throw new IllegalStateException("Cannot resolve visibility of an illegal node");
+ }
+ }
+
+ /**
+ * A simple implementation of a resolved node of a method without bridges.
+ */
+ @EqualsAndHashCode
+ class Simple implements Node {
+
+ /**
+ * The represented method.
+ */
+ private final MethodDescription methodDescription;
+
+ /**
+ * Creates a simple node.
+ *
+ * @param methodDescription The represented method.
+ */
+ public Simple(MethodDescription methodDescription) {
+ this.methodDescription = methodDescription;
+ }
+
+ @Override
+ public Sort getSort() {
+ return Sort.RESOLVED;
+ }
+
+ @Override
+ public MethodDescription getRepresentative() {
+ return methodDescription;
+ }
+
+ @Override
+ public Set<MethodDescription.TypeToken> getMethodTypes() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Visibility getVisibility() {
+ return methodDescription.getVisibility();
+ }
+ }
+ }
+
+ /**
+ * A compiler to produce a {@link MethodGraph} from a given type.
+ */
+ @SuppressFBWarnings(value = "IC_SUPERCLASS_USES_SUBCLASS_DURING_INITIALIZATION", justification = "Safe initialization is implied")
+ interface Compiler {
+
+ /**
+ * The default compiler for compiling Java methods.
+ */
+ Compiler DEFAULT = MethodGraph.Compiler.Default.forJavaHierarchy();
+
+ /**
+ * Compiles the given type into a method graph considering the type to be the viewpoint.
+ *
+ * @param typeDescription The type to be compiled.
+ * @return A linked method graph representing the given type.
+ */
+ MethodGraph.Linked compile(TypeDescription typeDescription);
+
+ /**
+ * Compiles the given type into a method graph.
+ *
+ * @param typeDefinition The type to be compiled.
+ * @param viewPoint The view point that determines the method's visibility.
+ * @return A linked method graph representing the given type.
+ */
+ MethodGraph.Linked compile(TypeDefinition typeDefinition, TypeDescription viewPoint);
+
+ /**
+ * A flat compiler that simply returns the methods that are declared by the instrumented type.
+ */
+ enum ForDeclaredMethods implements Compiler {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Linked compile(TypeDescription typeDescription) {
+ return compile(typeDescription, typeDescription);
+ }
+
+ @Override
+ public Linked compile(TypeDefinition typeDefinition, TypeDescription viewPoint) {
+ LinkedHashMap<MethodDescription.SignatureToken, Node> nodes = new LinkedHashMap<MethodDescription.SignatureToken, Node>();
+ for (MethodDescription methodDescription : typeDefinition.getDeclaredMethods().filter(isVirtual().and(not(isBridge())).and(isVisibleTo(viewPoint)))) {
+ nodes.put(methodDescription.asSignatureToken(), new Node.Simple(methodDescription));
+ }
+ return new Linked.Delegation(new MethodGraph.Simple(nodes), Empty.INSTANCE, Collections.<TypeDescription, MethodGraph>emptyMap());
+ }
+ }
+
+ /**
+ * An abstract base implementation of a method graph compiler.
+ */
+ abstract class AbstractBase implements Compiler {
+
+ @Override
+ public Linked compile(TypeDescription typeDescription) {
+ return compile(typeDescription, typeDescription);
+ }
+ }
+
+ /**
+ * A default implementation of a method graph.
+ *
+ * @param <T> The type of the harmonizer token to be used for linking methods of different types.
+ */
+ @EqualsAndHashCode(callSuper = false)
+ class Default<T> extends AbstractBase {
+
+ /**
+ * The harmonizer to be used.
+ */
+ private final Harmonizer<T> harmonizer;
+
+ /**
+ * The merger to be used.
+ */
+ private final Merger merger;
+
+ /**
+ * A visitor to apply to all type descriptions before analyzing their methods or resolving super types.
+ */
+ private final TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor;
+
+ /**
+ * Creates a new default method graph compiler.
+ *
+ * @param harmonizer The harmonizer to be used.
+ * @param merger The merger to be used.
+ * @param visitor A visitor to apply to all type descriptions before analyzing their methods or resolving super types.
+ */
+ protected Default(Harmonizer<T> harmonizer, Merger merger, TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor) {
+ this.harmonizer = harmonizer;
+ this.merger = merger;
+ this.visitor = visitor;
+ }
+
+ /**
+ * Creates a default compiler using the given harmonizer and merger. All raw types are reified before analyzing their properties.
+ *
+ * @param harmonizer The harmonizer to be used for creating tokens that uniquely identify a method hierarchy.
+ * @param merger The merger to be used for identifying a method to represent an ambiguous method resolution.
+ * @param <S> The type of the harmonizer token.
+ * @return A default compiler for the given harmonizer and merger.
+ */
+ public static <S> Compiler of(Harmonizer<S> harmonizer, Merger merger) {
+ return new Default<S>(harmonizer, merger, TypeDescription.Generic.Visitor.Reifying.INITIATING);
+ }
+
+ /**
+ * Creates a default compiler using the given harmonizer and merger.
+ *
+ * @param harmonizer The harmonizer to be used for creating tokens that uniquely identify a method hierarchy.
+ * @param merger The merger to be used for identifying a method to represent an ambiguous method resolution.
+ * @param visitor A visitor to apply to all type descriptions before analyzing their methods or resolving super types.
+ * @param <S> The type of the harmonizer token.
+ * @return A default compiler for the given harmonizer and merger.
+ */
+ public static <S> Compiler of(Harmonizer<S> harmonizer, Merger merger, TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor) {
+ return new Default<S>(harmonizer, merger, visitor);
+ }
+
+ /**
+ * <p>
+ * Creates a default compiler for a method hierarchy following the rules of the Java programming language. According
+ * to these rules, two methods of the same name are only different if their parameter types represent different raw
+ * types. The return type is not considered as a part of the signature.
+ * </p>
+ * <p>
+ * Ambiguous methods are merged by considering the method that was discovered first.
+ * </p>
+ *
+ * @return A compiler for resolving a method hierarchy following the rules of the Java programming language.
+ */
+ public static Compiler forJavaHierarchy() {
+ return of(Harmonizer.ForJavaMethod.INSTANCE, Merger.Directional.LEFT);
+ }
+
+ /**
+ * <p>
+ * Creates a default compiler for a method hierarchy following the rules of the Java virtual machine. According
+ * to these rules, two methods of the same name are different if their parameter types and return types represent
+ * different type erasures.
+ * </p>
+ * <p>
+ * Ambiguous methods are merged by considering the method that was discovered first.
+ * </p>
+ *
+ * @return A compiler for resolving a method hierarchy following the rules of the Java programming language.
+ */
+ public static Compiler forJVMHierarchy() {
+ return of(Harmonizer.ForJVMMethod.INSTANCE, Merger.Directional.LEFT);
+ }
+
+ @Override
+ public MethodGraph.Linked compile(TypeDefinition typeDefinition, TypeDescription viewPoint) {
+ Map<TypeDefinition, Key.Store<T>> snapshots = new HashMap<TypeDefinition, Key.Store<T>>();
+ Key.Store<?> rootStore = doAnalyze(typeDefinition, snapshots, isVirtual().and(isVisibleTo(viewPoint)));
+ TypeDescription.Generic superClass = typeDefinition.getSuperClass();
+ List<TypeDescription.Generic> interfaceTypes = typeDefinition.getInterfaces();
+ Map<TypeDescription, MethodGraph> interfaceGraphs = new HashMap<TypeDescription, MethodGraph>();
+ for (TypeDescription.Generic interfaceType : interfaceTypes) {
+ interfaceGraphs.put(interfaceType.asErasure(), snapshots.get(interfaceType).asGraph(merger));
+ }
+ return new Linked.Delegation(rootStore.asGraph(merger),
+ superClass == null
+ ? Empty.INSTANCE
+ : snapshots.get(superClass).asGraph(merger),
+ interfaceGraphs);
+ }
+
+ /**
+ * Analyzes the given type description without checking if the end of the type hierarchy was reached.
+ *
+ * @param typeDefinition The type to analyze.
+ * @param key The type in its original form before applying the visitor.
+ * @param snapshots A map containing snapshots of key stores for previously analyzed types.
+ * @param relevanceMatcher A matcher for filtering methods that should be included in the graph.
+ * @return A key store describing the provided type.
+ */
+ protected Key.Store<T> analyze(TypeDefinition typeDefinition,
+ TypeDefinition key,
+ Map<TypeDefinition, Key.Store<T>> snapshots,
+ ElementMatcher<? super MethodDescription> relevanceMatcher) {
+ Key.Store<T> store = snapshots.get(key);
+ if (store == null) {
+ store = doAnalyze(typeDefinition, snapshots, relevanceMatcher);
+ snapshots.put(key, store);
+ }
+ return store;
+ }
+
+ /**
+ * Analyzes the given type description.
+ *
+ * @param typeDescription The type to analyze.
+ * @param snapshots A map containing snapshots of key stores for previously analyzed types.
+ * @param relevanceMatcher A matcher for filtering methods that should be included in the graph.
+ * @return A key store describing the provided type.
+ */
+ protected Key.Store<T> analyzeNullable(TypeDescription.Generic typeDescription,
+ Map<TypeDefinition, Key.Store<T>> snapshots,
+ ElementMatcher<? super MethodDescription> relevanceMatcher) {
+ return typeDescription == null
+ ? new Key.Store<T>()
+ : analyze(typeDescription.accept(visitor), typeDescription, snapshots, relevanceMatcher);
+ }
+
+ /**
+ * Analyzes the given type description without checking if it is already presented in the key store.
+ *
+ * @param typeDefinition The type to analyze.
+ * @param snapshots A map containing snapshots of key stores for previously analyzed types.
+ * @param relevanceMatcher A matcher for filtering methods that should be included in the graph.
+ * @return A key store describing the provided type.
+ */
+ protected Key.Store<T> doAnalyze(TypeDefinition typeDefinition,
+ Map<TypeDefinition, Key.Store<T>> snapshots,
+ ElementMatcher<? super MethodDescription> relevanceMatcher) {
+ Key.Store<T> store = analyzeNullable(typeDefinition.getSuperClass(), snapshots, relevanceMatcher);
+ Key.Store<T> interfaceStore = new Key.Store<T>();
+ for (TypeDescription.Generic interfaceType : typeDefinition.getInterfaces()) {
+ interfaceStore = interfaceStore.combineWith(analyze(interfaceType.accept(visitor), interfaceType, snapshots, relevanceMatcher));
+ }
+ store = store.inject(interfaceStore);
+ for (MethodDescription methodDescription : typeDefinition.getDeclaredMethods().filter(relevanceMatcher)) {
+ store = store.registerTopLevel(methodDescription, harmonizer);
+ }
+ return store;
+ }
+
+ /**
+ * A harmonizer is responsible for creating a token that identifies a method's relevant attributes for considering
+ * two methods of being equal or not.
+ *
+ * @param <S> The type of the token that is created by the implementing harmonizer.
+ */
+ public interface Harmonizer<S> {
+
+ /**
+ * Harmonizes the given type token.
+ *
+ * @param typeToken The type token to harmonize.
+ * @return A token representing the given type token.
+ */
+ S harmonize(MethodDescription.TypeToken typeToken);
+
+ /**
+ * A harmonizer for the Java programming language that identifies a method by its parameter types only.
+ */
+ enum ForJavaMethod implements Harmonizer<ForJavaMethod.Token> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Token harmonize(MethodDescription.TypeToken typeToken) {
+ return new Token(typeToken);
+ }
+
+ /**
+ * A token that identifies a Java method's type by its parameter types only.
+ */
+ protected static class Token {
+
+ /**
+ * The represented type token.
+ */
+ private final MethodDescription.TypeToken typeToken;
+
+ /**
+ * Creates a new type token for a Java method.
+ *
+ * @param typeToken The represented type token.
+ */
+ protected Token(MethodDescription.TypeToken typeToken) {
+ this.typeToken = typeToken;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return this == other || (other instanceof Token && typeToken.getParameterTypes().equals(((Token) other).typeToken.getParameterTypes()));
+ }
+
+ @Override
+ public int hashCode() {
+ return typeToken.getParameterTypes().hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return typeToken.getParameterTypes().toString();
+ }
+ }
+ }
+
+ /**
+ * A harmonizer for the Java virtual machine's method dispatching rules that identifies a method by its parameter types and return type.
+ */
+ enum ForJVMMethod implements Harmonizer<ForJVMMethod.Token> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Token harmonize(MethodDescription.TypeToken typeToken) {
+ return new Token(typeToken);
+ }
+
+ /**
+ * A token that identifies a Java method's type by its parameter types and return type.
+ */
+ protected static class Token {
+
+ /**
+ * The represented type token.
+ */
+ private final MethodDescription.TypeToken typeToken;
+
+ /**
+ * Creates a new type token for a JVM method.
+ *
+ * @param typeToken The represented type token.
+ */
+ public Token(MethodDescription.TypeToken typeToken) {
+ this.typeToken = typeToken;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return this == other || other instanceof Token
+ && typeToken.getReturnType().equals(((Token) other).typeToken.getReturnType())
+ && typeToken.getParameterTypes().equals(((Token) other).typeToken.getParameterTypes());
+ }
+
+ @Override
+ public int hashCode() {
+ return typeToken.getReturnType().hashCode() + 31 * typeToken.getParameterTypes().hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return typeToken.toString();
+ }
+ }
+ }
+ }
+
+ /**
+ * Implementations are responsible for identifying a representative method for a {@link net.bytebuddy.dynamic.scaffold.MethodGraph.Node}
+ * between several ambiguously resolved methods.
+ */
+ public interface Merger {
+
+ /**
+ * Merges two ambiguously resolved methods to yield a single representative.
+ *
+ * @param left The left method description, i.e. the method that was discovered first or was previously merged.
+ * @param right The right method description, i.e. the method that was discovered last.
+ * @return A method description compatible to both method's types that is used as a representative.
+ */
+ MethodDescription merge(MethodDescription left, MethodDescription right);
+
+ /**
+ * A directional merger that always returns either the left or right method description.
+ */
+ enum Directional implements Merger {
+
+ /**
+ * A merger that always returns the left method, i.e. the method that was discovered first or was previously merged.
+ */
+ LEFT(true),
+
+ /**
+ * A merger that always returns the right method, i.e. the method that was discovered last.
+ */
+ RIGHT(false);
+
+ /**
+ * {@code true} if the left method should be returned when merging methods.
+ */
+ private final boolean left;
+
+ /**
+ * Creates a directional merger.
+ *
+ * @param left {@code true} if the left method should be returned when merging methods.
+ */
+ Directional(boolean left) {
+ this.left = left;
+ }
+
+ @Override
+ public MethodDescription merge(MethodDescription left, MethodDescription right) {
+ return this.left
+ ? left
+ : right;
+ }
+ }
+ }
+
+ /**
+ * A key represents a collection of methods within a method graph to later yield a node representing a collection of methods,
+ * i.e. a method representative including information on the required method bridges.
+ *
+ * @param <S> The type of the token used for deciding on method equality.
+ */
+ protected abstract static class Key<S> {
+
+ /**
+ * The internal name of the method this key identifies.
+ */
+ protected final String internalName;
+
+ /**
+ * Creates a new key.
+ *
+ * @param internalName The internal name of the method this key identifies.
+ */
+ protected Key(String internalName) {
+ this.internalName = internalName;
+ }
+
+ /**
+ * Returns a set of all identifiers of this key.
+ *
+ * @return A set of all identifiers of this key.
+ */
+ protected abstract Set<S> getIdentifiers();
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this || (other instanceof Key
+ && internalName.equals(((Key) other).internalName)
+ && !Collections.disjoint(getIdentifiers(), ((Key) other).getIdentifiers()));
+ }
+
+ @Override
+ public int hashCode() {
+ return internalName.hashCode();
+ }
+
+ /**
+ * A harmonized key represents a key where equality is decided based on tokens that are returned by a
+ * {@link net.bytebuddy.dynamic.scaffold.MethodGraph.Compiler.Default.Harmonizer}.
+ *
+ * @param <V> The type of the tokens yielded by a harmonizer.
+ */
+ protected static class Harmonized<V> extends Key<V> {
+
+ /**
+ * A mapping of identifiers to the type tokens they represent.
+ */
+ private final Map<V, Set<MethodDescription.TypeToken>> identifiers;
+
+ /**
+ * Creates a new harmonized key.
+ *
+ * @param internalName The internal name of the method this key identifies.
+ * @param identifiers A mapping of identifiers to the type tokens they represent.
+ */
+ protected Harmonized(String internalName, Map<V, Set<MethodDescription.TypeToken>> identifiers) {
+ super(internalName);
+ this.identifiers = identifiers;
+ }
+
+ /**
+ * Creates a new harmonized key for the given method description.
+ *
+ * @param methodDescription The method description to represent as a harmonized key.
+ * @param harmonizer The harmonizer to use.
+ * @param <Q> The type of the token yielded by a harmonizer.
+ * @return A harmonized key representing the provided method.
+ */
+ protected static <Q> Harmonized<Q> of(MethodDescription methodDescription, Harmonizer<Q> harmonizer) {
+ MethodDescription.TypeToken typeToken = methodDescription.asTypeToken();
+ return new Harmonized<Q>(methodDescription.getInternalName(),
+ Collections.singletonMap(harmonizer.harmonize(typeToken), Collections.<MethodDescription.TypeToken>emptySet()));
+ }
+
+ /**
+ * Creates a detached version of this key.
+ *
+ * @param typeToken The type token of the representative method.
+ * @return The detached version of this key.
+ */
+ protected Detached detach(MethodDescription.TypeToken typeToken) {
+ Set<MethodDescription.TypeToken> identifiers = new HashSet<MethodDescription.TypeToken>();
+ for (Set<MethodDescription.TypeToken> typeTokens : this.identifiers.values()) {
+ identifiers.addAll(typeTokens);
+ }
+ identifiers.add(typeToken);
+ return new Detached(internalName, identifiers);
+ }
+
+ /**
+ * Combines this key with the given key.
+ *
+ * @param key The key to be merged with this key.
+ * @return A harmonized key representing the merger of this key and the given key.
+ */
+ protected Harmonized<V> combineWith(Harmonized<V> key) {
+ Map<V, Set<MethodDescription.TypeToken>> identifiers = new HashMap<V, Set<MethodDescription.TypeToken>>(this.identifiers);
+ for (Map.Entry<V, Set<MethodDescription.TypeToken>> entry : key.identifiers.entrySet()) {
+ Set<MethodDescription.TypeToken> typeTokens = identifiers.get(entry.getKey());
+ if (typeTokens == null) {
+ identifiers.put(entry.getKey(), entry.getValue());
+ } else {
+ typeTokens = new HashSet<MethodDescription.TypeToken>(typeTokens);
+ typeTokens.addAll(entry.getValue());
+ identifiers.put(entry.getKey(), typeTokens);
+ }
+ }
+ return new Harmonized<V>(internalName, identifiers);
+ }
+
+ /**
+ * Extends this key by the given method description.
+ *
+ * @param methodDescription The method to extend this key with.
+ * @param harmonizer The harmonizer to use for determining method equality.
+ * @return The harmonized key representing the extension of this key with the provided method.
+ */
+ protected Harmonized<V> extend(MethodDescription.InDefinedShape methodDescription, Harmonizer<V> harmonizer) {
+ Map<V, Set<MethodDescription.TypeToken>> identifiers = new HashMap<V, Set<MethodDescription.TypeToken>>(this.identifiers);
+ MethodDescription.TypeToken typeToken = methodDescription.asTypeToken();
+ V identifier = harmonizer.harmonize(typeToken);
+ Set<MethodDescription.TypeToken> typeTokens = identifiers.get(identifier);
+ if (typeTokens == null) {
+ identifiers.put(identifier, Collections.singleton(typeToken));
+ } else {
+ typeTokens = new HashSet<MethodDescription.TypeToken>(typeTokens);
+ typeTokens.add(typeToken);
+ identifiers.put(identifier, typeTokens);
+ }
+ return new Harmonized<V>(internalName, identifiers);
+ }
+
+ @Override
+ protected Set<V> getIdentifiers() {
+ return identifiers.keySet();
+ }
+ }
+
+ /**
+ * A detached version of a key that identifies methods by their JVM signature, i.e. parameter types and return type.
+ */
+ protected static class Detached extends Key<MethodDescription.TypeToken> {
+
+ /**
+ * The type tokens represented by this key.
+ */
+ private final Set<MethodDescription.TypeToken> identifiers;
+
+ /**
+ * Creates a new detached key.
+ *
+ * @param internalName The internal name of the method this key identifies.
+ * @param identifiers The type tokens represented by this key.
+ */
+ protected Detached(String internalName, Set<MethodDescription.TypeToken> identifiers) {
+ super(internalName);
+ this.identifiers = identifiers;
+ }
+
+ /**
+ * Creates a new detached key of the given method token.
+ *
+ * @param token The method token to represent as a key.
+ * @return A detached key representing the given method token..
+ */
+ protected static Detached of(MethodDescription.SignatureToken token) {
+ return new Detached(token.getName(), Collections.singleton(token.asTypeToken()));
+ }
+
+ @Override
+ protected Set<MethodDescription.TypeToken> getIdentifiers() {
+ return identifiers;
+ }
+ }
+
+ /**
+ * A store for collected methods that are identified by keys.
+ *
+ * @param <V> The type of the token used for deciding on method equality.
+ */
+ @EqualsAndHashCode
+ protected static class Store<V> {
+
+ /**
+ * A mapping of harmonized keys to their represented entry.
+ */
+ private final LinkedHashMap<Harmonized<V>, Entry<V>> entries;
+
+ /**
+ * Creates an empty store.
+ */
+ protected Store() {
+ this(new LinkedHashMap<Harmonized<V>, Entry<V>>());
+ }
+
+ /**
+ * Creates a new store representing the given entries.
+ *
+ * @param entries A mapping of harmonized keys to their represented entry.
+ */
+ private Store(LinkedHashMap<Harmonized<V>, Entry<V>> entries) {
+ this.entries = entries;
+ }
+
+ /**
+ * Combines the two given stores.
+ *
+ * @param left The left store to be combined.
+ * @param right The right store to be combined.
+ * @param <W> The type of the harmonized key of both stores.
+ * @return An entry representing the combination of both stores.
+ */
+ private static <W> Entry<W> combine(Entry<W> left, Entry<W> right) {
+ Set<MethodDescription> leftMethods = left.getCandidates(), rightMethods = right.getCandidates();
+ LinkedHashSet<MethodDescription> combined = new LinkedHashSet<MethodDescription>(leftMethods.size() + rightMethods.size());
+ combined.addAll(leftMethods);
+ combined.addAll(rightMethods);
+ for (MethodDescription leftMethod : leftMethods) {
+ TypeDescription leftType = leftMethod.getDeclaringType().asErasure();
+ for (MethodDescription rightMethod : rightMethods) {
+ TypeDescription rightType = rightMethod.getDeclaringType().asErasure();
+ if (leftType.equals(rightType)) {
+ break;
+ } else if (leftType.isAssignableTo(rightType)) {
+ combined.remove(rightMethod);
+ break;
+ } else if (leftType.isAssignableFrom(rightType)) {
+ combined.remove(leftMethod);
+ break;
+ }
+ }
+ }
+ Key.Harmonized<W> key = left.getKey().combineWith(right.getKey());
+ Visibility visibility = left.getVisibility().expandTo(right.getVisibility());
+ return combined.size() == 1
+ ? new Entry.Resolved<W>(key, combined.iterator().next(), visibility, Entry.Resolved.NOT_MADE_VISIBLE)
+ : new Entry.Ambiguous<W>(key, combined, visibility);
+ }
+
+ /**
+ * Registers a new top level method within this store.
+ *
+ * @param methodDescription The method to register.
+ * @param harmonizer The harmonizer to use for determining method equality.
+ * @return A store with the given method registered as a top-level method.
+ */
+ protected Store<V> registerTopLevel(MethodDescription methodDescription, Harmonizer<V> harmonizer) {
+ Harmonized<V> key = Harmonized.of(methodDescription, harmonizer);
+ LinkedHashMap<Harmonized<V>, Entry<V>> entries = new LinkedHashMap<Harmonized<V>, Entry<V>>(this.entries);
+ Entry<V> currentEntry = entries.remove(key);
+ Entry<V> extendedEntry = (currentEntry == null
+ ? new Entry.Initial<V>(key)
+ : currentEntry).extendBy(methodDescription, harmonizer);
+ entries.put(extendedEntry.getKey(), extendedEntry);
+ return new Store<V>(entries);
+ }
+
+ /**
+ * Combines this store with the given store.
+ *
+ * @param store The store to combine with this store.
+ * @return A store representing a combination of this store and the given store.
+ */
+ protected Store<V> combineWith(Store<V> store) {
+ Store<V> combinedStore = this;
+ for (Entry<V> entry : store.entries.values()) {
+ combinedStore = combinedStore.combineWith(entry);
+ }
+ return combinedStore;
+ }
+
+ /**
+ * Combines this store with the given entry.
+ *
+ * @param entry The entry to combine with this store.
+ * @return A store representing a combination of this store and the given entry.
+ */
+ protected Store<V> combineWith(Entry<V> entry) {
+ LinkedHashMap<Harmonized<V>, Entry<V>> entries = new LinkedHashMap<Harmonized<V>, Entry<V>>(this.entries);
+ Entry<V> previousEntry = entries.remove(entry.getKey());
+ Entry<V> injectedEntry = previousEntry == null
+ ? entry
+ : combine(previousEntry, entry);
+ entries.put(injectedEntry.getKey(), injectedEntry);
+ return new Store<V>(entries);
+ }
+
+ /**
+ * Injects the given store into this store.
+ *
+ * @param store The key store to inject into this store.
+ * @return A store that represents this store with the given store injected.
+ */
+ protected Store<V> inject(Store<V> store) {
+ Store<V> injectedStore = this;
+ for (Entry<V> entry : store.entries.values()) {
+ injectedStore = injectedStore.inject(entry);
+ }
+ return injectedStore;
+ }
+
+ /**
+ * Injects the given entry into this store.
+ *
+ * @param entry The entry to be injected into this store.
+ * @return A store that represents this store with the given entry injected.
+ */
+ protected Store<V> inject(Entry<V> entry) {
+ LinkedHashMap<Harmonized<V>, Entry<V>> entries = new LinkedHashMap<Harmonized<V>, Entry<V>>(this.entries);
+ Entry<V> dominantEntry = entries.remove(entry.getKey());
+ Entry<V> injectedEntry = dominantEntry == null
+ ? entry
+ : dominantEntry.inject(entry.getKey(), entry.getVisibility());
+ entries.put(injectedEntry.getKey(), injectedEntry);
+ return new Store<V>(entries);
+ }
+
+ /**
+ * Transforms this store into a method graph by applying the given merger.
+ *
+ * @param merger The merger to apply for resolving the representative for ambiguous resolutions.
+ * @return The method graph that represents this key store.
+ */
+ protected MethodGraph asGraph(Merger merger) {
+ LinkedHashMap<Key<MethodDescription.TypeToken>, Node> entries = new LinkedHashMap<Key<MethodDescription.TypeToken>, Node>();
+ for (Entry<V> entry : this.entries.values()) {
+ Node node = entry.asNode(merger);
+ entries.put(entry.getKey().detach(node.getRepresentative().asTypeToken()), node);
+ }
+ return new Graph(entries);
+ }
+
+ /**
+ * An entry of a key store.
+ *
+ * @param <W> The type of the harmonized token used for determining method equality.
+ */
+ protected interface Entry<W> {
+
+ /**
+ * Returns the harmonized key of this entry.
+ *
+ * @return The harmonized key of this entry.
+ */
+ Harmonized<W> getKey();
+
+ /**
+ * Returns all candidate methods represented by this entry.
+ *
+ * @return All candidate methods represented by this entry.
+ */
+ Set<MethodDescription> getCandidates();
+
+ /**
+ * Returns the minimal visibility of this entry.
+ *
+ * @return The minimal visibility of this entry.
+ */
+ Visibility getVisibility();
+
+ /**
+ * Extends this entry by the given method.
+ *
+ * @param methodDescription The method description to extend this entry with.
+ * @param harmonizer The harmonizer to use for determining method equality.
+ * @return This key extended by the given method.
+ */
+ Entry<W> extendBy(MethodDescription methodDescription, Harmonizer<W> harmonizer);
+
+ /**
+ * Injects the given key into this entry.
+ *
+ * @param key The key to inject into this entry.
+ * @param visibility The entry's minimal visibility.
+ * @return This entry extended with the given key.
+ */
+ Entry<W> inject(Harmonized<W> key, Visibility visibility);
+
+ /**
+ * Transforms this entry into a node.
+ *
+ * @param merger The merger to use for determining the representative method of an ambiguous node.
+ * @return The resolved node.
+ */
+ Node asNode(Merger merger);
+
+ /**
+ * An entry in its initial state before registering any method as a representative.
+ *
+ * @param <U> The type of the harmonized key to determine method equality.
+ */
+ class Initial<U> implements Entry<U> {
+
+ /**
+ * The harmonized key this entry represents.
+ */
+ private final Harmonized<U> key;
+
+ /**
+ * Creates a new initial key.
+ *
+ * @param key The harmonized key this entry represents.
+ */
+ protected Initial(Harmonized<U> key) {
+ this.key = key;
+ }
+
+ @Override
+ public Harmonized<U> getKey() {
+ throw new IllegalStateException("Cannot extract key from initial entry:" + this);
+ }
+
+ @Override
+ public Set<MethodDescription> getCandidates() {
+ throw new IllegalStateException("Cannot extract method from initial entry:" + this);
+ }
+
+ @Override
+ public Visibility getVisibility() {
+ throw new IllegalStateException("Cannot extract visibility from initial entry:" + this);
+ }
+
+ @Override
+ public Entry<U> extendBy(MethodDescription methodDescription, Harmonizer<U> harmonizer) {
+ return new Resolved<U>(key.extend(methodDescription.asDefined(), harmonizer),
+ methodDescription,
+ methodDescription.getVisibility(),
+ Resolved.NOT_MADE_VISIBLE);
+ }
+
+ @Override
+ public Entry<U> inject(Harmonized<U> key, Visibility visibility) {
+ throw new IllegalStateException("Cannot inject into initial entry without a registered method: " + this);
+ }
+
+ @Override
+ public Node asNode(Merger merger) {
+ throw new IllegalStateException("Cannot transform initial entry without a registered method: " + this);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && key.equals(((Initial<?>) other).key);
+ }
+
+ @Override
+ public int hashCode() {
+ return key.hashCode();
+ }
+ }
+
+ /**
+ * An entry representing a non-ambiguous node resolution.
+ *
+ * @param <U> The type of the harmonized key to determine method equality.
+ */
+ @EqualsAndHashCode
+ class Resolved<U> implements Entry<U> {
+
+ /**
+ * Indicates that a type's methods are already globally visible, meaning that a bridge method is not added
+ * with the intend of creating a visibility bridge.
+ */
+ private static final int MADE_VISIBLE = Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED;
+
+ /**
+ * Indicates that the entry was not made visible.
+ */
+ private static final boolean NOT_MADE_VISIBLE = false;
+
+ /**
+ * The harmonized key this entry represents.
+ */
+ private final Harmonized<U> key;
+
+ /**
+ * The non-ambiguous, representative method of this entry.
+ */
+ private final MethodDescription methodDescription;
+
+ /**
+ * The minimal required visibility for this method.
+ */
+ private final Visibility visibility;
+
+ /**
+ * {@code true} if this entry's representative was made visible by a visibility bridge.
+ */
+ private final boolean madeVisible;
+
+ /**
+ * Creates a new resolved entry.
+ *
+ * @param key The harmonized key this entry represents.
+ * @param methodDescription The non-ambiguous, representative method of this entry.
+ * @param visibility The minimal required visibility for this method.
+ * @param madeVisible {@code true} if this entry's representative was made visible by a visibility bridge.
+ */
+ protected Resolved(Harmonized<U> key, MethodDescription methodDescription, Visibility visibility, boolean madeVisible) {
+ this.key = key;
+ this.methodDescription = methodDescription;
+ this.visibility = visibility;
+ this.madeVisible = madeVisible;
+ }
+
+ /**
+ * Creates an entry for an override where a method overrides another method within a super class.
+ *
+ * @param key The merged key for both methods.
+ * @param override The method declared by the extending type, potentially a bridge method.
+ * @param original The method that is overridden by the extending type.
+ * @param visibility The minimal required visibility for this entry.
+ * @param <V> The type of the harmonized key to determine method equality.
+ * @return An entry representing the merger of both methods.
+ */
+ private static <V> Entry<V> of(Harmonized<V> key, MethodDescription override, MethodDescription original, Visibility visibility) {
+ visibility = visibility.expandTo(original.getVisibility()).expandTo(override.getVisibility());
+ return override.isBridge()
+ ? new Resolved<V>(key, original, visibility, (original.getDeclaringType().getModifiers() & MADE_VISIBLE) == 0)
+ : new Resolved<V>(key, override, visibility, NOT_MADE_VISIBLE);
+ }
+
+ @Override
+ public Harmonized<U> getKey() {
+ return key;
+ }
+
+ @Override
+ public Set<MethodDescription> getCandidates() {
+ return Collections.singleton(methodDescription);
+ }
+
+ @Override
+ public Visibility getVisibility() {
+ return visibility;
+ }
+
+ @Override
+ public Entry<U> extendBy(MethodDescription methodDescription, Harmonizer<U> harmonizer) {
+ Harmonized<U> key = this.key.extend(methodDescription.asDefined(), harmonizer);
+ Visibility visibility = this.visibility.expandTo(methodDescription.getVisibility());
+ return methodDescription.getDeclaringType().equals(this.methodDescription.getDeclaringType())
+ ? Ambiguous.of(key, methodDescription, this.methodDescription, visibility)
+ : Resolved.of(key, methodDescription, this.methodDescription, visibility);
+ }
+
+ @Override
+ public Entry<U> inject(Harmonized<U> key, Visibility visibility) {
+ return new Resolved<U>(this.key.combineWith(key), methodDescription, this.visibility.expandTo(visibility), madeVisible);
+ }
+
+ @Override
+ public MethodGraph.Node asNode(Merger merger) {
+ return new Node(key.detach(methodDescription.asTypeToken()), methodDescription, visibility, madeVisible);
+ }
+
+ /**
+ * A node implementation representing a non-ambiguous method.
+ */
+ @EqualsAndHashCode
+ protected static class Node implements MethodGraph.Node {
+
+ /**
+ * The detached key representing this node.
+ */
+ private final Detached key;
+
+ /**
+ * The representative method of this node.
+ */
+ private final MethodDescription methodDescription;
+
+ /**
+ * The node's minimal visibility.
+ */
+ private final Visibility visibility;
+
+ /**
+ * {@code true} if the represented method was made explicitly visible by a visibility bridge.
+ */
+ private final boolean visible;
+
+ /**
+ * Creates a new node.
+ *
+ * @param key The detached key representing this node.
+ * @param methodDescription The representative method of this node.
+ * @param visibility The node's minimal visibility.
+ * @param visible {@code true} if the represented method was made explicitly visible by a visibility bridge.
+ */
+ protected Node(Detached key, MethodDescription methodDescription, Visibility visibility, boolean visible) {
+ this.key = key;
+ this.methodDescription = methodDescription;
+ this.visibility = visibility;
+ this.visible = visible;
+ }
+
+ @Override
+ public Sort getSort() {
+ return visible
+ ? Sort.VISIBLE
+ : Sort.RESOLVED;
+ }
+
+ @Override
+ public MethodDescription getRepresentative() {
+ return methodDescription;
+ }
+
+ @Override
+ public Set<MethodDescription.TypeToken> getMethodTypes() {
+ return key.getIdentifiers();
+ }
+
+ @Override
+ public Visibility getVisibility() {
+ return visibility;
+ }
+ }
+ }
+
+ /**
+ * An entry representing an ambiguous node resolution.
+ *
+ * @param <U> The type of the harmonized key to determine method equality.
+ */
+ @EqualsAndHashCode
+ class Ambiguous<U> implements Entry<U> {
+
+ /**
+ * The harmonized key this entry represents.
+ */
+ private final Harmonized<U> key;
+
+ /**
+ * A set of ambiguous methods that this entry represents.
+ */
+ private final LinkedHashSet<MethodDescription> methodDescriptions;
+
+ /**
+ * The minimal required visibility for this method.
+ */
+ private final Visibility visibility;
+
+ /**
+ * Creates a new ambiguous entry.
+ *
+ * @param key The harmonized key this entry represents.
+ * @param methodDescriptions A set of ambiguous methods that this entry represents.
+ * @param visibility The minimal required visibility for this method.
+ */
+ protected Ambiguous(Harmonized<U> key, LinkedHashSet<MethodDescription> methodDescriptions, Visibility visibility) {
+ this.key = key;
+ this.methodDescriptions = methodDescriptions;
+ this.visibility = visibility;
+ }
+
+ /**
+ * Creates a new ambiguous entry if both provided entries are not considered to be a bridge of one another.
+ *
+ * @param key The key of the entry to be created.
+ * @param left The left method to be considered.
+ * @param right The right method to be considered.
+ * @param visibility The entry's minimal visibility.
+ * @param <Q> The type of the token of the harmonized key to determine method equality.
+ * @return The entry representing both methods.
+ */
+ protected static <Q> Entry<Q> of(Harmonized<Q> key, MethodDescription left, MethodDescription right, Visibility visibility) {
+ visibility = visibility.expandTo(left.getVisibility()).expandTo(right.getVisibility());
+ return left.isBridge() ^ right.isBridge()
+ ? new Resolved<Q>(key, left.isBridge() ? right : left, visibility, Resolved.NOT_MADE_VISIBLE)
+ : new Ambiguous<Q>(key, new LinkedHashSet<MethodDescription>(Arrays.asList(left, right)), visibility);
+ }
+
+ @Override
+ public Harmonized<U> getKey() {
+ return key;
+ }
+
+ @Override
+ public Set<MethodDescription> getCandidates() {
+ return methodDescriptions;
+ }
+
+ @Override
+ public Visibility getVisibility() {
+ return visibility;
+ }
+
+ @Override
+ public Entry<U> extendBy(MethodDescription methodDescription, Harmonizer<U> harmonizer) {
+ Harmonized<U> key = this.key.extend(methodDescription.asDefined(), harmonizer);
+ LinkedHashSet<MethodDescription> methodDescriptions = new LinkedHashSet<MethodDescription>(this.methodDescriptions.size() + 1);
+ TypeDescription declaringType = methodDescription.getDeclaringType().asErasure();
+ boolean bridge = methodDescription.isBridge();
+ Visibility visibility = this.visibility;
+ for (MethodDescription extendedMethod : this.methodDescriptions) {
+ if (extendedMethod.getDeclaringType().asErasure().equals(declaringType)) {
+ if (extendedMethod.isBridge() ^ bridge) {
+ methodDescriptions.add(bridge ? extendedMethod : methodDescription);
+ } else {
+ methodDescriptions.add(methodDescription);
+ methodDescriptions.add(extendedMethod);
+ }
+ }
+ visibility = visibility.expandTo(extendedMethod.getVisibility());
+ }
+ if (methodDescriptions.isEmpty()) {
+ return new Resolved<U>(key, methodDescription, visibility, bridge);
+ } else if (methodDescriptions.size() == 1) {
+ return new Resolved<U>(key, methodDescriptions.iterator().next(), visibility, Resolved.NOT_MADE_VISIBLE);
+ } else {
+ return new Ambiguous<U>(key, methodDescriptions, visibility);
+ }
+ }
+
+ @Override
+ public Entry<U> inject(Harmonized<U> key, Visibility visibility) {
+ return new Ambiguous<U>(this.key.combineWith(key), methodDescriptions, this.visibility.expandTo(visibility));
+ }
+
+ @Override
+ public MethodGraph.Node asNode(Merger merger) {
+ Iterator<MethodDescription> iterator = methodDescriptions.iterator();
+ MethodDescription methodDescription = iterator.next();
+ while (iterator.hasNext()) {
+ methodDescription = merger.merge(methodDescription, iterator.next());
+ }
+ return new Node(key.detach(methodDescription.asTypeToken()), methodDescription, visibility);
+ }
+
+ /**
+ * A node implementation representing an ambiguous method resolution.
+ */
+ @EqualsAndHashCode
+ protected static class Node implements MethodGraph.Node {
+
+ /**
+ * The detached key representing this node.
+ */
+ private final Detached key;
+
+ /**
+ * The representative method of this node.
+ */
+ private final MethodDescription methodDescription;
+
+ /**
+ * The node's minimal visibility.
+ */
+ private final Visibility visibility;
+
+ /**
+ * @param key The detached key representing this node.
+ * @param methodDescription The representative method of this node.
+ * @param visibility The node's minimal visibility.
+ */
+ protected Node(Detached key, MethodDescription methodDescription, Visibility visibility) {
+ this.key = key;
+ this.methodDescription = methodDescription;
+ this.visibility = visibility;
+ }
+
+ @Override
+ public Sort getSort() {
+ return Sort.AMBIGUOUS;
+ }
+
+ @Override
+ public MethodDescription getRepresentative() {
+ return methodDescription;
+ }
+
+ @Override
+ public Set<MethodDescription.TypeToken> getMethodTypes() {
+ return key.getIdentifiers();
+ }
+
+ @Override
+ public Visibility getVisibility() {
+ return visibility;
+ }
+ }
+ }
+ }
+
+ /**
+ * A graph implementation based on a key store.
+ */
+ @EqualsAndHashCode
+ protected static class Graph implements MethodGraph {
+
+ /**
+ * A mapping of a node's type tokens to the represented node.
+ */
+ private final LinkedHashMap<Key<MethodDescription.TypeToken>, Node> entries;
+
+ /**
+ * Creates a new graph.
+ *
+ * @param entries A mapping of a node's type tokens to the represented node.
+ */
+ protected Graph(LinkedHashMap<Key<MethodDescription.TypeToken>, Node> entries) {
+ this.entries = entries;
+ }
+
+ @Override
+ public Node locate(MethodDescription.SignatureToken token) {
+ Node node = entries.get(Detached.of(token));
+ return node == null
+ ? Node.Unresolved.INSTANCE
+ : node;
+ }
+
+ @Override
+ public NodeList listNodes() {
+ return new NodeList(new ArrayList<Node>(entries.values()));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * A list of nodes.
+ */
+ class NodeList extends FilterableList.AbstractBase<Node, NodeList> {
+
+ /**
+ * The represented nodes.
+ */
+ private final List<? extends Node> nodes;
+
+ /**
+ * Creates a list of nodes.
+ *
+ * @param nodes The represented nodes.
+ */
+ public NodeList(List<? extends Node> nodes) {
+ this.nodes = nodes;
+ }
+
+ @Override
+ public Node get(int index) {
+ return nodes.get(index);
+ }
+
+ @Override
+ public int size() {
+ return nodes.size();
+ }
+
+ @Override
+ protected NodeList wrap(List<Node> values) {
+ return new NodeList(values);
+ }
+
+ /**
+ * Transforms this list of nodes into a list of the node's representatives.
+ *
+ * @return A list of these node's representatives.
+ */
+ public MethodList<?> asMethodList() {
+ List<MethodDescription> methodDescriptions = new ArrayList<MethodDescription>(size());
+ for (Node node : nodes) {
+ methodDescriptions.add(node.getRepresentative());
+ }
+ return new MethodList.Explicit<MethodDescription>(methodDescriptions);
+ }
+ }
+
+ /**
+ * A simple implementation of a method graph.
+ */
+ @EqualsAndHashCode
+ class Simple implements MethodGraph {
+
+ /**
+ * The nodes represented by this method graph.
+ */
+ private final LinkedHashMap<MethodDescription.SignatureToken, Node> nodes;
+
+ /**
+ * Creates a new simple method graph.
+ *
+ * @param nodes The nodes represented by this method graph.
+ */
+ public Simple(LinkedHashMap<MethodDescription.SignatureToken, Node> nodes) {
+ this.nodes = nodes;
+ }
+
+ /**
+ * Returns a method graph that contains all of the provided methods as simple nodes.
+ *
+ * @param methodDescriptions A list of method descriptions to be represented as simple nodes.
+ * @return A method graph that represents all of the provided methods as simple nodes.
+ */
+ public static MethodGraph of(List<? extends MethodDescription> methodDescriptions) {
+ LinkedHashMap<MethodDescription.SignatureToken, Node> nodes = new LinkedHashMap<MethodDescription.SignatureToken, Node>();
+ for (MethodDescription methodDescription : methodDescriptions) {
+ nodes.put(methodDescription.asSignatureToken(), new Node.Simple(methodDescription));
+ }
+ return new Simple(nodes);
+ }
+
+ @Override
+ public Node locate(MethodDescription.SignatureToken token) {
+ Node node = nodes.get(token);
+ return node == null
+ ? Node.Unresolved.INSTANCE
+ : node;
+ }
+
+ @Override
+ public NodeList listNodes() {
+ return new NodeList(new ArrayList<Node>(nodes.values()));
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/MethodRegistry.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/MethodRegistry.java
new file mode 100644
index 0000000..a8b7c81
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/MethodRegistry.java
@@ -0,0 +1,1001 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.description.annotation.AnnotationValue;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.Transformer;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.LoadedTypeInitializer;
+import net.bytebuddy.implementation.attribute.MethodAttributeAppender;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.matcher.LatentMatcher;
+import net.bytebuddy.utility.CompoundList;
+
+import java.util.*;
+
+import static net.bytebuddy.matcher.ElementMatchers.*;
+
+/**
+ * A method registry is responsible for storing information on how a method is intercepted.
+ */
+public interface MethodRegistry {
+
+ /**
+ * Prepends the given method definition to this method registry, i.e. this configuration is applied first.
+ *
+ * @param methodMatcher A matcher to identify any method that this definition concerns.
+ * @param handler The handler to instrument any matched method.
+ * @param attributeAppenderFactory A method attribute appender to apply to any matched method.
+ * @param transformer The method transformer to be applied to implemented methods.
+ * @return An adapted version of this method registry.
+ */
+ MethodRegistry prepend(LatentMatcher<? super MethodDescription> methodMatcher,
+ Handler handler,
+ MethodAttributeAppender.Factory attributeAppenderFactory,
+ Transformer<MethodDescription> transformer);
+
+ /**
+ * Appends the given method definition to this method registry, i.e. this configuration is applied last.
+ *
+ * @param methodMatcher A matcher to identify all entries that are to be matched.
+ * @param handler The handler to instrument any matched method.
+ * @param attributeAppenderFactory A method attribute appender to apply to any matched method.
+ * @param transformer The method transformer to be applied to implemented methods.
+ * @return An adapted version of this method registry.
+ */
+ MethodRegistry append(LatentMatcher<? super MethodDescription> methodMatcher,
+ Handler handler,
+ MethodAttributeAppender.Factory attributeAppenderFactory,
+ Transformer<MethodDescription> transformer);
+
+ /**
+ * Prepares this method registry.
+ *
+ * @param instrumentedType The instrumented type that should be created.
+ * @param methodGraphCompiler The method graph compiler to be used for analyzing the fully assembled instrumented type.
+ * @param typeValidation Determines if a type should be explicitly validated.
+ * @param ignoredMethods A filter that only matches methods that should be instrumented.
+ * @return A prepared version of this method registry.
+ */
+ Prepared prepare(InstrumentedType instrumentedType,
+ MethodGraph.Compiler methodGraphCompiler,
+ TypeValidation typeValidation,
+ LatentMatcher<? super MethodDescription> ignoredMethods);
+
+ /**
+ * A handler for implementing a method.
+ */
+ interface Handler extends InstrumentedType.Prepareable {
+
+ /**
+ * Compiles this handler.
+ *
+ * @param implementationTarget The implementation target to compile this handler for.
+ * @return A compiled handler.
+ */
+ Handler.Compiled compile(Implementation.Target implementationTarget);
+
+ /**
+ * A handler for defining an abstract or native method.
+ */
+ enum ForAbstractMethod implements Handler, Compiled {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public Compiled compile(Implementation.Target implementationTarget) {
+ return this;
+ }
+
+ @Override
+ public TypeWriter.MethodPool.Record assemble(MethodDescription methodDescription, MethodAttributeAppender attributeAppender, Visibility visibility) {
+ return new TypeWriter.MethodPool.Record.ForDefinedMethod.WithoutBody(methodDescription, attributeAppender, visibility);
+ }
+ }
+
+ /**
+ * A handler for implementing a visibility bridge.
+ */
+ enum ForVisibilityBridge implements Handler {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ throw new IllegalStateException("A visibility bridge handler must not apply any preparations");
+ }
+
+ @Override
+ public Compiled compile(Implementation.Target implementationTarget) {
+ return new Compiled(implementationTarget.getInstrumentedType());
+ }
+
+ /**
+ * A compiled handler for a visibility bridge handler.
+ */
+ @EqualsAndHashCode
+ protected static class Compiled implements Handler.Compiled {
+
+ /**
+ * The instrumented type.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * Creates a new compiled handler for a visibility bridge.
+ *
+ * @param instrumentedType The instrumented type.
+ */
+ protected Compiled(TypeDescription instrumentedType) {
+ this.instrumentedType = instrumentedType;
+ }
+
+ @Override
+ public TypeWriter.MethodPool.Record assemble(MethodDescription methodDescription, MethodAttributeAppender attributeAppender, Visibility visibility) {
+ return TypeWriter.MethodPool.Record.ForDefinedMethod.OfVisibilityBridge.of(instrumentedType, methodDescription, attributeAppender);
+ }
+ }
+ }
+
+ /**
+ * A compiled handler for implementing a method.
+ */
+ interface Compiled {
+
+ /**
+ * Assembles this compiled entry with a method attribute appender.
+ *
+ * @param methodDescription The method description to apply with this handler.
+ * @param attributeAppender The method attribute appender to apply together with this handler.
+ * @param visibility The represented method's minimum visibility.
+ * @return A method pool entry representing this handler and the given attribute appender.
+ */
+ TypeWriter.MethodPool.Record assemble(MethodDescription methodDescription, MethodAttributeAppender attributeAppender, Visibility visibility);
+ }
+
+ /**
+ * A handler for a method that is implemented as byte code.
+ */
+ @EqualsAndHashCode
+ class ForImplementation implements Handler {
+
+ /**
+ * The implementation to apply.
+ */
+ private final Implementation implementation;
+
+ /**
+ * Creates a new handler for implementing a method with byte code.
+ *
+ * @param implementation The implementation to apply.
+ */
+ public ForImplementation(Implementation implementation) {
+ this.implementation = implementation;
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return implementation.prepare(instrumentedType);
+ }
+
+ @Override
+ public Compiled compile(Implementation.Target implementationTarget) {
+ return new Compiled(implementation.appender(implementationTarget));
+ }
+
+ /**
+ * A compiled handler for implementing a method.
+ */
+ @EqualsAndHashCode
+ protected static class Compiled implements Handler.Compiled {
+
+ /**
+ * The byte code appender to apply.
+ */
+ private final ByteCodeAppender byteCodeAppender;
+
+ /**
+ * Creates a new compiled handler for a method implementation.
+ *
+ * @param byteCodeAppender The byte code appender to apply.
+ */
+ protected Compiled(ByteCodeAppender byteCodeAppender) {
+ this.byteCodeAppender = byteCodeAppender;
+ }
+
+ @Override
+ public TypeWriter.MethodPool.Record assemble(MethodDescription methodDescription, MethodAttributeAppender attributeAppender, Visibility visibility) {
+ return new TypeWriter.MethodPool.Record.ForDefinedMethod.WithBody(methodDescription, byteCodeAppender, attributeAppender, visibility);
+ }
+ }
+ }
+
+ /**
+ * A handler for defining a default annotation value for a method.
+ */
+ @EqualsAndHashCode
+ class ForAnnotationValue implements Handler, Compiled {
+
+ /**
+ * The annotation value to set as a default value.
+ */
+ private final AnnotationValue<?, ?> annotationValue;
+
+ /**
+ * Creates a handler for defining a default annotation value for a method.
+ *
+ * @param annotationValue The annotation value to set as a default value.
+ */
+ public ForAnnotationValue(AnnotationValue<?, ?> annotationValue) {
+ this.annotationValue = annotationValue;
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public Compiled compile(Implementation.Target implementationTarget) {
+ return this;
+ }
+
+ @Override
+ public TypeWriter.MethodPool.Record assemble(MethodDescription methodDescription, MethodAttributeAppender attributeAppender, Visibility visibility) {
+ return new TypeWriter.MethodPool.Record.ForDefinedMethod.WithAnnotationDefaultValue(methodDescription, annotationValue, attributeAppender);
+ }
+ }
+ }
+
+ /**
+ * A method registry that fully prepared the instrumented type.
+ */
+ interface Prepared {
+
+ /**
+ * Returns the fully prepared instrumented type.
+ *
+ * @return The fully prepared instrumented type.
+ */
+ TypeDescription getInstrumentedType();
+
+ /**
+ * Returns the declared or virtually inherited methods of this type.
+ *
+ * @return The declared or virtually inherited methods of this type.
+ */
+ MethodList<?> getMethods();
+
+ /**
+ * Returns a list of all methods that should be instrumented.
+ *
+ * @return A list of all methods that should be instrumented.
+ */
+ MethodList<?> getInstrumentedMethods();
+
+ /**
+ * Returns the loaded type initializer of the instrumented type.
+ *
+ * @return The loaded type initializer of the instrumented type.
+ */
+ LoadedTypeInitializer getLoadedTypeInitializer();
+
+ /**
+ * The type initializer of the instrumented type.
+ *
+ * @return The type initializer of the instrumented type.
+ */
+ TypeInitializer getTypeInitializer();
+
+ /**
+ * Compiles this prepared method registry.
+ *
+ * @param implementationTargetFactory A factory for creating an implementation target.
+ * @param classFileVersion The type's class file version.
+ * @return A factory for creating an implementation target.
+ */
+ Compiled compile(Implementation.Target.Factory implementationTargetFactory, ClassFileVersion classFileVersion);
+ }
+
+ /**
+ * A compiled version of a method registry.
+ */
+ interface Compiled extends TypeWriter.MethodPool {
+
+ /**
+ * Returns the instrumented type that is to be created.
+ *
+ * @return The instrumented type that is to be created.
+ */
+ TypeDescription getInstrumentedType();
+
+ /**
+ * Returns the declared or virtually inherited methods of this type.
+ *
+ * @return The declared or virtually inherited methods of this type.
+ */
+ MethodList<?> getMethods();
+
+ /**
+ * Returns a list of all methods that should be instrumented.
+ *
+ * @return A list of all methods that should be instrumented.
+ */
+ MethodList<?> getInstrumentedMethods();
+
+ /**
+ * Returns the loaded type initializer of the instrumented type.
+ *
+ * @return The loaded type initializer of the instrumented type.
+ */
+ LoadedTypeInitializer getLoadedTypeInitializer();
+
+ /**
+ * The type initializer of the instrumented type.
+ *
+ * @return The type initializer of the instrumented type.
+ */
+ TypeInitializer getTypeInitializer();
+ }
+
+ /**
+ * A default implementation of a method registry.
+ */
+ @EqualsAndHashCode
+ class Default implements MethodRegistry {
+
+ /**
+ * The list of currently registered entries in their application order.
+ */
+ private final List<Entry> entries;
+
+ /**
+ * Creates a new default method registry without entries.
+ */
+ public Default() {
+ entries = Collections.emptyList();
+ }
+
+ /**
+ * Creates a new default method registry.
+ *
+ * @param entries The currently registered entries.
+ */
+ private Default(List<Entry> entries) {
+ this.entries = entries;
+ }
+
+ @Override
+ public MethodRegistry prepend(LatentMatcher<? super MethodDescription> matcher,
+ Handler handler,
+ MethodAttributeAppender.Factory attributeAppenderFactory,
+ Transformer<MethodDescription> transformer) {
+ return new Default(CompoundList.of(new Entry(matcher, handler, attributeAppenderFactory, transformer), entries));
+ }
+
+ @Override
+ public MethodRegistry append(LatentMatcher<? super MethodDescription> matcher,
+ Handler handler,
+ MethodAttributeAppender.Factory attributeAppenderFactory,
+ Transformer<MethodDescription> transformer) {
+ return new Default(CompoundList.of(entries, new Entry(matcher, handler, attributeAppenderFactory, transformer)));
+ }
+
+ @Override
+ public MethodRegistry.Prepared prepare(InstrumentedType instrumentedType,
+ MethodGraph.Compiler methodGraphCompiler,
+ TypeValidation typeValidation,
+ LatentMatcher<? super MethodDescription> ignoredMethods) {
+ LinkedHashMap<MethodDescription, Prepared.Entry> implementations = new LinkedHashMap<MethodDescription, Prepared.Entry>();
+ Set<Handler> handlers = new HashSet<Handler>();
+ MethodList<?> helperMethods = instrumentedType.getDeclaredMethods();
+ for (Entry entry : entries) {
+ if (handlers.add(entry.getHandler())) {
+ instrumentedType = entry.getHandler().prepare(instrumentedType);
+ ElementMatcher<? super MethodDescription> handledMethods = noneOf(helperMethods);
+ helperMethods = instrumentedType.getDeclaredMethods();
+ for (MethodDescription helperMethod : helperMethods.filter(handledMethods)) {
+ implementations.put(helperMethod, entry.asSupplementaryEntry(helperMethod));
+ }
+ }
+ }
+ MethodGraph.Linked methodGraph = methodGraphCompiler.compile(instrumentedType);
+ // Casting required for Java 6 compiler.
+ ElementMatcher<? super MethodDescription> relevanceMatcher = (ElementMatcher<? super MethodDescription>) not(anyOf(implementations.keySet()))
+ .and(returns(isVisibleTo(instrumentedType)))
+ .and(hasParameters(whereNone(hasType(not(isVisibleTo(instrumentedType))))))
+ .and(ignoredMethods.resolve(instrumentedType));
+ List<MethodDescription> methods = new ArrayList<MethodDescription>();
+ for (MethodGraph.Node node : methodGraph.listNodes()) {
+ MethodDescription methodDescription = node.getRepresentative();
+ boolean visibilityBridge = instrumentedType.isPublic() && !instrumentedType.isInterface();
+ if (relevanceMatcher.matches(methodDescription)) {
+ for (Entry entry : entries) {
+ if (entry.resolve(instrumentedType).matches(methodDescription)) {
+ implementations.put(methodDescription, entry.asPreparedEntry(instrumentedType,
+ methodDescription,
+ node.getMethodTypes(),
+ node.getVisibility()));
+ visibilityBridge = false;
+ break;
+ }
+ }
+ }
+ if (visibilityBridge
+ && !node.getSort().isMadeVisible()
+ && methodDescription.isPublic()
+ && !(methodDescription.isAbstract() || methodDescription.isFinal())
+ && methodDescription.getDeclaringType().isPackagePrivate()) {
+ // Visibility bridges are required for public classes that inherit a public method from a package-private class.
+ implementations.put(methodDescription, Prepared.Entry.forVisibilityBridge(methodDescription, node.getVisibility()));
+ }
+ methods.add(methodDescription);
+ }
+ for (MethodDescription methodDescription : CompoundList.of(
+ instrumentedType.getDeclaredMethods().filter(not(isVirtual()).and(relevanceMatcher)),
+ new MethodDescription.Latent.TypeInitializer(instrumentedType))) {
+ for (Entry entry : entries) {
+ if (entry.resolve(instrumentedType).matches(methodDescription)) {
+ implementations.put(methodDescription, entry.asPreparedEntry(instrumentedType, methodDescription, methodDescription.getVisibility()));
+ break;
+ }
+ }
+ methods.add(methodDescription);
+ }
+ return new Prepared(implementations,
+ instrumentedType.getLoadedTypeInitializer(),
+ instrumentedType.getTypeInitializer(),
+ typeValidation.isEnabled()
+ ? instrumentedType.validated()
+ : instrumentedType,
+ methodGraph,
+ new MethodList.Explicit<MethodDescription>(methods));
+ }
+
+ /**
+ * An entry of a default method registry.
+ */
+ @EqualsAndHashCode
+ protected static class Entry implements LatentMatcher<MethodDescription> {
+
+ /**
+ * The latent method matcher that this entry represents.
+ */
+ private final LatentMatcher<? super MethodDescription> matcher;
+
+ /**
+ * The handler to apply to all matched entries.
+ */
+ private final Handler handler;
+
+ /**
+ * A method attribute appender factory to apply to all entries.
+ */
+ private final MethodAttributeAppender.Factory attributeAppenderFactory;
+
+ /**
+ * The method transformer to be applied to implemented methods.
+ */
+ private final Transformer<MethodDescription> transformer;
+
+ /**
+ * Creates a new entry.
+ *
+ * @param matcher The latent method matcher that this entry represents.
+ * @param handler The handler to apply to all matched entries.
+ * @param attributeAppenderFactory A method attribute appender factory to apply to all entries.
+ * @param transformer The method transformer to be applied to implemented methods.
+ */
+ protected Entry(LatentMatcher<? super MethodDescription> matcher,
+ Handler handler,
+ MethodAttributeAppender.Factory attributeAppenderFactory,
+ Transformer<MethodDescription> transformer) {
+ this.matcher = matcher;
+ this.handler = handler;
+ this.attributeAppenderFactory = attributeAppenderFactory;
+ this.transformer = transformer;
+ }
+
+ /**
+ * Transforms this entry into a prepared state.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param methodDescription The non-transformed method to be implemented.
+ * @param visibility The represented method's minimum visibility.
+ * @return A prepared version of this entry.
+ */
+ protected Prepared.Entry asPreparedEntry(TypeDescription instrumentedType, MethodDescription methodDescription, Visibility visibility) {
+ return asPreparedEntry(instrumentedType, methodDescription, Collections.<MethodDescription.TypeToken>emptySet(), visibility);
+ }
+
+ /**
+ * Transforms this entry into a prepared state.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param methodDescription The non-transformed method to be implemented.
+ * @param methodTypes The method types this method represents.
+ * @param visibility The represented method's minimum visibility.
+ * @return A prepared version of this entry.
+ */
+ protected Prepared.Entry asPreparedEntry(TypeDescription instrumentedType,
+ MethodDescription methodDescription,
+ Set<MethodDescription.TypeToken> methodTypes,
+ Visibility visibility) {
+ return new Prepared.Entry(handler,
+ attributeAppenderFactory,
+ transformer.transform(instrumentedType, methodDescription),
+ methodTypes,
+ visibility,
+ false);
+ }
+
+ /**
+ * Returns a prepared entry for a supplementary method.
+ *
+ * @param methodDescription The method to be implemented.
+ * @return An entry for a supplementary entry that is defined by a method implementation instance.
+ */
+ protected Prepared.Entry asSupplementaryEntry(MethodDescription methodDescription) {
+ return new Prepared.Entry(handler,
+ MethodAttributeAppender.Explicit.of(methodDescription),
+ methodDescription,
+ Collections.<MethodDescription.TypeToken>emptySet(),
+ methodDescription.getVisibility(),
+ false);
+ }
+
+ /**
+ * Returns this entry's handler.
+ *
+ * @return The handler of this entry.
+ */
+ protected Handler getHandler() {
+ return handler;
+ }
+
+ @Override
+ public ElementMatcher<? super MethodDescription> resolve(TypeDescription typeDescription) {
+ return matcher.resolve(typeDescription);
+ }
+ }
+
+ /**
+ * A prepared version of a default method registry.
+ */
+ @EqualsAndHashCode
+ protected static class Prepared implements MethodRegistry.Prepared {
+
+ /**
+ * A map of all method descriptions mapped to their handling entries.
+ */
+ private final LinkedHashMap<MethodDescription, Entry> implementations;
+
+ /**
+ * The loaded type initializer of the instrumented type.
+ */
+ private final LoadedTypeInitializer loadedTypeInitializer;
+
+ /**
+ * The type initializer of the instrumented type.
+ */
+ private final TypeInitializer typeInitializer;
+
+ /**
+ * The instrumented type.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * A method graph describing the instrumented type.
+ */
+ private final MethodGraph.Linked methodGraph;
+
+ /**
+ * The declared or virtually inherited methods of this type.
+ */
+ private final MethodList<?> methods;
+
+ /**
+ * Creates a prepared version of a default method registry.
+ *
+ * @param implementations A map of all method descriptions mapped to their handling entries.
+ * @param loadedTypeInitializer The loaded type initializer of the instrumented type.
+ * @param typeInitializer The type initializer of the instrumented type.
+ * @param instrumentedType The instrumented type.
+ * @param methodGraph A method graph describing the instrumented type.
+ * @param methods The declared or virtually inherited methods of this type.
+ */
+ protected Prepared(LinkedHashMap<MethodDescription, Entry> implementations,
+ LoadedTypeInitializer loadedTypeInitializer,
+ TypeInitializer typeInitializer,
+ TypeDescription instrumentedType,
+ MethodGraph.Linked methodGraph,
+ MethodList<?> methods) {
+ this.implementations = implementations;
+ this.loadedTypeInitializer = loadedTypeInitializer;
+ this.typeInitializer = typeInitializer;
+ this.instrumentedType = instrumentedType;
+ this.methodGraph = methodGraph;
+ this.methods = methods;
+ }
+
+ @Override
+ public TypeDescription getInstrumentedType() {
+ return instrumentedType;
+ }
+
+ @Override
+ public LoadedTypeInitializer getLoadedTypeInitializer() {
+ return loadedTypeInitializer;
+ }
+
+ @Override
+ public TypeInitializer getTypeInitializer() {
+ return typeInitializer;
+ }
+
+ @Override
+ public MethodList<?> getMethods() {
+ return methods;
+ }
+
+ @Override
+ public MethodList<?> getInstrumentedMethods() {
+ return new MethodList.Explicit<MethodDescription>(new ArrayList<MethodDescription>(implementations.keySet())).filter(not(isTypeInitializer()));
+ }
+
+ @Override
+ public MethodRegistry.Compiled compile(Implementation.Target.Factory implementationTargetFactory, ClassFileVersion classFileVersion) {
+ Map<Handler, Handler.Compiled> compilationCache = new HashMap<Handler, Handler.Compiled>();
+ Map<MethodAttributeAppender.Factory, MethodAttributeAppender> attributeAppenderCache = new HashMap<MethodAttributeAppender.Factory, MethodAttributeAppender>();
+ LinkedHashMap<MethodDescription, Compiled.Entry> entries = new LinkedHashMap<MethodDescription, Compiled.Entry>();
+ Implementation.Target implementationTarget = implementationTargetFactory.make(instrumentedType, methodGraph, classFileVersion);
+ for (Map.Entry<MethodDescription, Entry> entry : implementations.entrySet()) {
+ Handler.Compiled cachedHandler = compilationCache.get(entry.getValue().getHandler());
+ if (cachedHandler == null) {
+ cachedHandler = entry.getValue().getHandler().compile(implementationTarget);
+ compilationCache.put(entry.getValue().getHandler(), cachedHandler);
+ }
+ MethodAttributeAppender cachedAttributeAppender = attributeAppenderCache.get(entry.getValue().getAppenderFactory());
+ if (cachedAttributeAppender == null) {
+ cachedAttributeAppender = entry.getValue().getAppenderFactory().make(instrumentedType);
+ attributeAppenderCache.put(entry.getValue().getAppenderFactory(), cachedAttributeAppender);
+ }
+ entries.put(entry.getKey(), new Compiled.Entry(cachedHandler,
+ cachedAttributeAppender,
+ entry.getValue().getMethodDescription(),
+ entry.getValue().resolveBridgeTypes(),
+ entry.getValue().getVisibility(),
+ entry.getValue().isBridgeMethod()));
+ }
+ return new Compiled(instrumentedType,
+ loadedTypeInitializer,
+ typeInitializer,
+ methods,
+ entries,
+ classFileVersion.isAtLeast(ClassFileVersion.JAVA_V5));
+ }
+
+ /**
+ * An entry of a prepared method registry.
+ */
+ @EqualsAndHashCode
+ protected static class Entry {
+
+ /**
+ * The handler for implementing methods.
+ */
+ private final Handler handler;
+
+ /**
+ * A attribute appender factory for appending attributes for any implemented method.
+ */
+ private final MethodAttributeAppender.Factory attributeAppenderFactory;
+
+ /**
+ * The method this entry represents.
+ */
+ private final MethodDescription methodDescription;
+
+ /**
+ * The method's type tokens.
+ */
+ private final Set<MethodDescription.TypeToken> typeTokens;
+
+ /**
+ * The minimum required visibility of this method.
+ */
+ private Visibility visibility;
+
+ /**
+ * Is {@code true} if this entry represents a bridge method.
+ */
+ private final boolean bridgeMethod;
+
+ /**
+ * Creates a new prepared entry.
+ *
+ * @param handler The handler for implementing methods.
+ * @param attributeAppenderFactory A attribute appender factory for appending attributes for any implemented method.
+ * @param methodDescription The method this entry represents.
+ * @param typeTokens A set of bridges representing the bridge methods of this method.
+ * @param visibility The minimum required visibility of this method.
+ * @param bridgeMethod {@code true} if this entry represents a bridge method.
+ */
+ protected Entry(Handler handler,
+ MethodAttributeAppender.Factory attributeAppenderFactory,
+ MethodDescription methodDescription,
+ Set<MethodDescription.TypeToken> typeTokens,
+ Visibility visibility,
+ boolean bridgeMethod) {
+ this.handler = handler;
+ this.attributeAppenderFactory = attributeAppenderFactory;
+ this.methodDescription = methodDescription;
+ this.typeTokens = typeTokens;
+ this.visibility = visibility;
+ this.bridgeMethod = bridgeMethod;
+ }
+
+ /**
+ * Creates an entry for a visibility bridge.
+ *
+ * @param bridgeTarget The bridge method's target.
+ * @param visibility The represented method's minimum visibility.
+ * @return An entry representing a visibility bridge.
+ */
+ protected static Entry forVisibilityBridge(MethodDescription bridgeTarget, Visibility visibility) {
+ return new Entry(Handler.ForVisibilityBridge.INSTANCE,
+ MethodAttributeAppender.Explicit.of(bridgeTarget),
+ bridgeTarget,
+ Collections.<MethodDescription.TypeToken>emptySet(),
+ visibility,
+ true);
+ }
+
+ /**
+ * Returns this entry's handler.
+ *
+ * @return The entry's handler.
+ */
+ protected Handler getHandler() {
+ return handler;
+ }
+
+ /**
+ * Returns this entry's attribute appender factory.
+ *
+ * @return This entry's attribute appender factory.
+ */
+ protected MethodAttributeAppender.Factory getAppenderFactory() {
+ return attributeAppenderFactory;
+ }
+
+ /**
+ * Returns the method description this entry represents.
+ *
+ * @return The method description this entry represents.
+ */
+ protected MethodDescription getMethodDescription() {
+ return methodDescription;
+ }
+
+ /**
+ * Resolves the type tokens of all bridge methods that are required to be implemented for this entry.
+ *
+ * @return A set of type tokens representing the bridge methods required for implementing this type.
+ */
+ protected Set<MethodDescription.TypeToken> resolveBridgeTypes() {
+ HashSet<MethodDescription.TypeToken> typeTokens = new HashSet<MethodDescription.TypeToken>(this.typeTokens);
+ typeTokens.remove(methodDescription.asTypeToken());
+ return typeTokens;
+ }
+
+ /**
+ * Returns the represented method's minimum visibility.
+ *
+ * @return The represented method's minimum visibility.
+ */
+ protected Visibility getVisibility() {
+ return visibility;
+ }
+
+ /**
+ * Returns {@code true} if this entry represents a bridge method.
+ *
+ * @return {@code true} if this entry represents a bridge method.
+ */
+ protected boolean isBridgeMethod() {
+ return bridgeMethod;
+ }
+ }
+ }
+
+ /**
+ * A compiled version of a default method registry.
+ */
+ @EqualsAndHashCode
+ protected static class Compiled implements MethodRegistry.Compiled {
+
+ /**
+ * The instrumented type.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * The loaded type initializer of the instrumented type.
+ */
+ private final LoadedTypeInitializer loadedTypeInitializer;
+
+ /**
+ * The type initializer of the instrumented type.
+ */
+ private final TypeInitializer typeInitializer;
+
+ /**
+ * The declared or virtually inherited methods of this type.
+ */
+ private final MethodList<?> methods;
+
+ /**
+ * A map of all method descriptions mapped to their handling entries.
+ */
+ private final LinkedHashMap<MethodDescription, Entry> implementations;
+
+ /**
+ * {@code true} if the created type supports bridge methods.
+ */
+ private final boolean supportsBridges;
+
+ /**
+ * Creates a new compiled version of a default method registry.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param loadedTypeInitializer The loaded type initializer of the instrumented type.
+ * @param typeInitializer The type initializer of the instrumented type.
+ * @param methods The declared or virtually inherited methods of this type.
+ * @param implementations A map of all method descriptions mapped to their handling entries.
+ * @param supportsBridges {@code true} if the created type supports bridge methods.
+ */
+ protected Compiled(TypeDescription instrumentedType,
+ LoadedTypeInitializer loadedTypeInitializer,
+ TypeInitializer typeInitializer,
+ MethodList<?> methods,
+ LinkedHashMap<MethodDescription, Entry> implementations,
+ boolean supportsBridges) {
+ this.instrumentedType = instrumentedType;
+ this.loadedTypeInitializer = loadedTypeInitializer;
+ this.typeInitializer = typeInitializer;
+ this.methods = methods;
+ this.implementations = implementations;
+ this.supportsBridges = supportsBridges;
+ }
+
+ @Override
+ public TypeDescription getInstrumentedType() {
+ return instrumentedType;
+ }
+
+ @Override
+ public LoadedTypeInitializer getLoadedTypeInitializer() {
+ return loadedTypeInitializer;
+ }
+
+ @Override
+ public TypeInitializer getTypeInitializer() {
+ return typeInitializer;
+ }
+
+ @Override
+ public MethodList<?> getMethods() {
+ return methods;
+ }
+
+ @Override
+ public MethodList<?> getInstrumentedMethods() {
+ return new MethodList.Explicit<MethodDescription>(new ArrayList<MethodDescription>(implementations.keySet())).filter(not(isTypeInitializer()));
+ }
+
+ @Override
+ public Record target(MethodDescription methodDescription) {
+ Entry entry = implementations.get(methodDescription);
+ return entry == null
+ ? new Record.ForNonImplementedMethod(methodDescription)
+ : entry.bind(instrumentedType, supportsBridges);
+ }
+
+ /**
+ * An entry of a compiled method registry.
+ */
+ @EqualsAndHashCode
+ protected static class Entry {
+
+ /**
+ * The handler to be used for implementing a method.
+ */
+ private final Handler.Compiled handler;
+
+ /**
+ * The attribute appender of a compiled method.
+ */
+ private final MethodAttributeAppender attributeAppender;
+
+ /**
+ * The method to be implemented including potential transformations.
+ */
+ private final MethodDescription methodDescription;
+
+ /**
+ * The type tokens representing all bridge methods for the method.
+ */
+ private final Set<MethodDescription.TypeToken> bridgeTypes;
+
+ /**
+ * The represented method's minimum visibility.
+ */
+ private final Visibility visibility;
+
+ /**
+ * {@code true} if this entry represents a bridge method.
+ */
+ private final boolean bridgeMethod;
+
+ /**
+ * Creates a new entry for a compiled method registry.
+ *
+ * @param handler The handler to be used for implementing a method.
+ * @param attributeAppender The attribute appender of a compiled method.
+ * @param methodDescription The method to be implemented including potential transformations.
+ * @param bridgeTypes The type tokens representing all bridge methods for the method.
+ * @param visibility The represented method's minimum visibility.
+ * @param bridgeMethod {@code true} if this entry represents a bridge method.
+ */
+ protected Entry(Handler.Compiled handler,
+ MethodAttributeAppender attributeAppender,
+ MethodDescription methodDescription,
+ Set<MethodDescription.TypeToken> bridgeTypes,
+ Visibility visibility,
+ boolean bridgeMethod) {
+ this.handler = handler;
+ this.attributeAppender = attributeAppender;
+ this.methodDescription = methodDescription;
+ this.bridgeTypes = bridgeTypes;
+ this.visibility = visibility;
+ this.bridgeMethod = bridgeMethod;
+ }
+
+ /**
+ * Transforms this entry into a method record.
+ *
+ * @param instrumentedType The instrumented type to bind.
+ * @param supportsBridges {@code true} if the record should support bridge methods.
+ * @return A record representing this entry's properties.
+ */
+ protected Record bind(TypeDescription instrumentedType, boolean supportsBridges) {
+ if (bridgeMethod && !supportsBridges) {
+ return new Record.ForNonImplementedMethod(methodDescription);
+ }
+ Record record = handler.assemble(methodDescription, attributeAppender, visibility);
+ return supportsBridges
+ ? TypeWriter.MethodPool.Record.AccessBridgeWrapper.of(record, instrumentedType, methodDescription, bridgeTypes, attributeAppender)
+ : record;
+ }
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/TypeInitializer.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/TypeInitializer.java
new file mode 100644
index 0000000..fa9f304
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/TypeInitializer.java
@@ -0,0 +1,171 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.attribute.AnnotationValueFilter;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+
+/**
+ * A type initializer is responsible for defining a type's static initialization block.
+ */
+public interface TypeInitializer extends ByteCodeAppender {
+
+ /**
+ * Indicates if this type initializer is defined.
+ *
+ * @return {@code true} if this type initializer is defined.
+ */
+ boolean isDefined();
+
+ /**
+ * Expands this type initializer with another byte code appender. For this to be possible, this type initializer must
+ * be defined.
+ *
+ * @param byteCodeAppender The byte code appender to apply as the type initializer.
+ * @return A defined type initializer.
+ */
+ TypeInitializer expandWith(ByteCodeAppender byteCodeAppender);
+
+ /**
+ * Creates a method pool record that applies this type initializer while preserving the record that was supplied.
+ *
+ * @param record The record to wrap.
+ * @return A new record that represents the supplied record while also executing this type initializer.
+ */
+ TypeWriter.MethodPool.Record wrap(TypeWriter.MethodPool.Record record);
+
+ /**
+ * A drain for writing a type initializer.
+ */
+ interface Drain {
+
+ /**
+ * Applies the drain.
+ *
+ * @param classVisitor The class visitor to apply the initializer to.
+ * @param typeInitializer The type initializer to write.
+ * @param implementationContext The corresponding implementation context.
+ */
+ void apply(ClassVisitor classVisitor, TypeInitializer typeInitializer, Implementation.Context implementationContext);
+
+ /**
+ * A default implementation of a type initializer drain that creates a initializer method.
+ */
+ @EqualsAndHashCode
+ class Default implements Drain {
+
+ /**
+ * The instrumented type.
+ */
+ protected final TypeDescription instrumentedType;
+
+ /**
+ * The method pool to use.
+ */
+ protected final TypeWriter.MethodPool methodPool;
+
+ /**
+ * The annotation value filter factory to use.
+ */
+ protected final AnnotationValueFilter.Factory annotationValueFilterFactory;
+
+ /**
+ * Creates a new default type initializer drain.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param methodPool The method pool to use.
+ * @param annotationValueFilterFactory The annotation value filter factory to use.
+ */
+ public Default(TypeDescription instrumentedType,
+ TypeWriter.MethodPool methodPool,
+ AnnotationValueFilter.Factory annotationValueFilterFactory) {
+ this.instrumentedType = instrumentedType;
+ this.methodPool = methodPool;
+ this.annotationValueFilterFactory = annotationValueFilterFactory;
+ }
+
+ @Override
+ public void apply(ClassVisitor classVisitor, TypeInitializer typeInitializer, Implementation.Context implementationContext) {
+ typeInitializer.wrap(methodPool.target(new MethodDescription.Latent.TypeInitializer(instrumentedType))).apply(classVisitor,
+ implementationContext,
+ annotationValueFilterFactory);
+ }
+ }
+ }
+
+ /**
+ * Canonical implementation of a non-defined type initializer.
+ */
+ enum None implements TypeInitializer {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public boolean isDefined() {
+ return false;
+ }
+
+ @Override
+ public TypeInitializer expandWith(ByteCodeAppender byteCodeAppenderFactory) {
+ return new TypeInitializer.Simple(byteCodeAppenderFactory);
+ }
+
+ @Override
+ public TypeWriter.MethodPool.Record wrap(TypeWriter.MethodPool.Record record) {
+ return record;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodDescription instrumentedMethod) {
+ return new Size(0, 0);
+ }
+ }
+
+ /**
+ * A simple, defined type initializer that executes a given {@link ByteCodeAppender}.
+ */
+ @EqualsAndHashCode
+ class Simple implements TypeInitializer {
+
+ /**
+ * The byte code appender to apply as the type initializer.
+ */
+ private final ByteCodeAppender byteCodeAppender;
+
+ /**
+ * Creates a new simple type initializer.
+ *
+ * @param byteCodeAppender The byte code appender to apply as the type initializer.
+ */
+ public Simple(ByteCodeAppender byteCodeAppender) {
+ this.byteCodeAppender = byteCodeAppender;
+ }
+
+ @Override
+ public boolean isDefined() {
+ return true;
+ }
+
+ @Override
+ public TypeInitializer expandWith(ByteCodeAppender byteCodeAppender) {
+ return new TypeInitializer.Simple(new Compound(this.byteCodeAppender, byteCodeAppender));
+ }
+
+ @Override
+ public TypeWriter.MethodPool.Record wrap(TypeWriter.MethodPool.Record record) {
+ return record.prepend(byteCodeAppender);
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodDescription instrumentedMethod) {
+ return byteCodeAppender.apply(methodVisitor, implementationContext, instrumentedMethod);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/TypeValidation.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/TypeValidation.java
new file mode 100644
index 0000000..97b162c
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/TypeValidation.java
@@ -0,0 +1,62 @@
+package net.bytebuddy.dynamic.scaffold;
+
+/**
+ * <p>
+ * If type validation is enabled, Byte Buddy performs several checks to ensure that a generated
+ * class file is specified in a valid manner. This involves checks of the generated instrumented
+ * type and checks of the generated byte code. Byte Buddy's {@link net.bytebuddy.implementation.Implementation}
+ * instances perform their own checks, independently of any type validation.
+ * </p>
+ * <p>
+ * The JVM's verifier performs its own checks; an illegal class file is never loaded. However, Byte Buddy's
+ * checks might be more expressive in the context of using the library. Also, Byte Buddy emits exceptions
+ * at class creation time while the JVM emits errors at class loading time.
+ * </p>
+ */
+public enum TypeValidation {
+
+ /**
+ * Enables Byte Buddy's validation.
+ */
+ ENABLED(true),
+
+ /**
+ * Disables Byte Buddy's validation.
+ */
+ DISABLED(false);
+
+ /**
+ * {@code true} if type validation is enabled.
+ */
+ private final boolean enabled;
+
+ /**
+ * Creates a new type validation enumeration.
+ *
+ * @param enabled {@code true} if type validation is enabled.
+ */
+ TypeValidation(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ /**
+ * Returns {@link TypeValidation#ENABLED} if the supplied argument is {@code true}.
+ *
+ * @param enabled {@code true} if type validation should be enabled.
+ * @return A suitable type validation representation.
+ */
+ public static TypeValidation of(boolean enabled) {
+ return enabled
+ ? ENABLED
+ : DISABLED;
+ }
+
+ /**
+ * Returns {@code true} if type validation is enabled.
+ *
+ * @return {@code true} if type validation is enabled.
+ */
+ public boolean isEnabled() {
+ return enabled;
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/TypeWriter.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/TypeWriter.java
new file mode 100644
index 0000000..6a9e813
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/TypeWriter.java
@@ -0,0 +1,4220 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.asm.AsmVisitorWrapper;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.annotation.AnnotationValue;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.modifier.ModifierContributor;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.description.type.PackageDescription;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.TypeResolutionStrategy;
+import net.bytebuddy.dynamic.scaffold.inline.MethodRebaseResolver;
+import net.bytebuddy.dynamic.scaffold.inline.RebaseImplementationTarget;
+import net.bytebuddy.dynamic.scaffold.subclass.SubclassImplementationTarget;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.LoadedTypeInitializer;
+import net.bytebuddy.implementation.attribute.*;
+import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.TypeCasting;
+import net.bytebuddy.implementation.bytecode.constant.DefaultValue;
+import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
+import net.bytebuddy.implementation.bytecode.member.MethodReturn;
+import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+import net.bytebuddy.pool.TypePool;
+import net.bytebuddy.utility.CompoundList;
+import net.bytebuddy.utility.privilege.GetSystemPropertyAction;
+import org.objectweb.asm.*;
+import org.objectweb.asm.commons.ClassRemapper;
+import org.objectweb.asm.commons.SimpleRemapper;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.AccessController;
+import java.security.PrivilegedExceptionAction;
+import java.util.*;
+
+import static net.bytebuddy.matcher.ElementMatchers.is;
+import static net.bytebuddy.matcher.ElementMatchers.isSubTypeOf;
+
+/**
+ * A type writer is a utility for writing an actual class file using the ASM library.
+ *
+ * @param <T> The best known loaded type for the dynamically created type.
+ */
+public interface TypeWriter<T> {
+
+ /**
+ * A system property that indicates a folder for Byte Buddy to dump class files of all types that it creates.
+ * If this property is not set, Byte Buddy does not dump any class files. This property is only read a single
+ * time which is why it must be set on application start-up.
+ */
+ String DUMP_PROPERTY = "net.bytebuddy.dump";
+
+ /**
+ * Creates the dynamic type that is described by this type writer.
+ *
+ * @param typeResolver The type resolution strategy to use.
+ * @return An unloaded dynamic type that describes the created type.
+ */
+ DynamicType.Unloaded<T> make(TypeResolutionStrategy.Resolved typeResolver);
+
+ /**
+ * An field pool that allows a lookup for how to implement a field.
+ */
+ interface FieldPool {
+
+ /**
+ * Returns the field attribute appender that matches a given field description or a default field
+ * attribute appender if no appender was registered for the given field.
+ *
+ * @param fieldDescription The field description of interest.
+ * @return The registered field attribute appender for the given field or the default appender if no such
+ * appender was found.
+ */
+ Record target(FieldDescription fieldDescription);
+
+ /**
+ * An entry of a field pool that describes how a field is implemented.
+ *
+ * @see net.bytebuddy.dynamic.scaffold.TypeWriter.FieldPool
+ */
+ interface Record {
+
+ /**
+ * Determines if this record is implicit, i.e is not defined by a {@link FieldPool}.
+ *
+ * @return {@code true} if this record is implicit.
+ */
+ boolean isImplicit();
+
+ /**
+ * Returns the field that this record represents.
+ *
+ * @return The field that this record represents.
+ */
+ FieldDescription getField();
+
+ /**
+ * Returns the field attribute appender for a given field.
+ *
+ * @return The attribute appender to be applied on the given field.
+ */
+ FieldAttributeAppender getFieldAppender();
+
+ /**
+ * Resolves the default value that this record represents. This is not possible for implicit records.
+ *
+ * @param defaultValue The default value that was defined previously or {@code null} if no default value is defined.
+ * @return The default value for the represented field or {@code null} if no default value is to be defined.
+ */
+ Object resolveDefault(Object defaultValue);
+
+ /**
+ * Writes this entry to a given class visitor.
+ *
+ * @param classVisitor The class visitor to which this entry is to be written to.
+ * @param annotationValueFilterFactory The annotation value filter factory to apply when writing annotations.
+ */
+ void apply(ClassVisitor classVisitor, AnnotationValueFilter.Factory annotationValueFilterFactory);
+
+ /**
+ * Applies this record to a field visitor. This is not possible for implicit records.
+ *
+ * @param fieldVisitor The field visitor onto which this record is to be applied.
+ * @param annotationValueFilterFactory The annotation value filter factory to use for annotations.
+ */
+ void apply(FieldVisitor fieldVisitor, AnnotationValueFilter.Factory annotationValueFilterFactory);
+
+ /**
+ * A record for a simple field without a default value where all of the field's declared annotations are appended.
+ */
+ @EqualsAndHashCode
+ class ForImplicitField implements Record {
+
+ /**
+ * The implemented field.
+ */
+ private final FieldDescription fieldDescription;
+
+ /**
+ * Creates a new record for a simple field.
+ *
+ * @param fieldDescription The described field.
+ */
+ public ForImplicitField(FieldDescription fieldDescription) {
+ this.fieldDescription = fieldDescription;
+ }
+
+ @Override
+ public boolean isImplicit() {
+ return true;
+ }
+
+ @Override
+ public FieldDescription getField() {
+ return fieldDescription;
+ }
+
+ @Override
+ public FieldAttributeAppender getFieldAppender() {
+ throw new IllegalStateException("An implicit field record does not expose a field appender: " + this);
+ }
+
+ @Override
+ public Object resolveDefault(Object defaultValue) {
+ throw new IllegalStateException("An implicit field record does not expose a default value: " + this);
+ }
+
+ @Override
+ public void apply(ClassVisitor classVisitor, AnnotationValueFilter.Factory annotationValueFilterFactory) {
+ FieldVisitor fieldVisitor = classVisitor.visitField(fieldDescription.getActualModifiers(),
+ fieldDescription.getInternalName(),
+ fieldDescription.getDescriptor(),
+ fieldDescription.getGenericSignature(),
+ FieldDescription.NO_DEFAULT_VALUE);
+ if (fieldVisitor != null) {
+ FieldAttributeAppender.ForInstrumentedField.INSTANCE.apply(fieldVisitor,
+ fieldDescription,
+ annotationValueFilterFactory.on(fieldDescription));
+ fieldVisitor.visitEnd();
+ }
+ }
+
+ @Override
+ public void apply(FieldVisitor fieldVisitor, AnnotationValueFilter.Factory annotationValueFilterFactory) {
+ throw new IllegalStateException("An implicit field record is not intended for partial application: " + this);
+ }
+ }
+
+ /**
+ * A record for a rich field with attributes and a potential default value.
+ */
+ @EqualsAndHashCode
+ class ForExplicitField implements Record {
+
+ /**
+ * The attribute appender for the field.
+ */
+ private final FieldAttributeAppender attributeAppender;
+
+ /**
+ * The field's default value.
+ */
+ private final Object defaultValue;
+
+ /**
+ * The implemented field.
+ */
+ private final FieldDescription fieldDescription;
+
+ /**
+ * Creates a record for a rich field.
+ *
+ * @param attributeAppender The attribute appender for the field.
+ * @param defaultValue The field's default value.
+ * @param fieldDescription The implemented field.
+ */
+ public ForExplicitField(FieldAttributeAppender attributeAppender, Object defaultValue, FieldDescription fieldDescription) {
+ this.attributeAppender = attributeAppender;
+ this.defaultValue = defaultValue;
+ this.fieldDescription = fieldDescription;
+ }
+
+ @Override
+ public boolean isImplicit() {
+ return false;
+ }
+
+ @Override
+ public FieldDescription getField() {
+ return fieldDescription;
+ }
+
+ @Override
+ public FieldAttributeAppender getFieldAppender() {
+ return attributeAppender;
+ }
+
+ @Override
+ public Object resolveDefault(Object defaultValue) {
+ return this.defaultValue == null
+ ? defaultValue
+ : this.defaultValue;
+ }
+
+ @Override
+ public void apply(ClassVisitor classVisitor, AnnotationValueFilter.Factory annotationValueFilterFactory) {
+ FieldVisitor fieldVisitor = classVisitor.visitField(fieldDescription.getActualModifiers(),
+ fieldDescription.getInternalName(),
+ fieldDescription.getDescriptor(),
+ fieldDescription.getGenericSignature(),
+ resolveDefault(FieldDescription.NO_DEFAULT_VALUE));
+ if (fieldVisitor != null) {
+ attributeAppender.apply(fieldVisitor, fieldDescription, annotationValueFilterFactory.on(fieldDescription));
+ fieldVisitor.visitEnd();
+ }
+ }
+
+ @Override
+ public void apply(FieldVisitor fieldVisitor, AnnotationValueFilter.Factory annotationValueFilterFactory) {
+ attributeAppender.apply(fieldVisitor, fieldDescription, annotationValueFilterFactory.on(fieldDescription));
+ }
+ }
+ }
+ }
+
+ /**
+ * An method pool that allows a lookup for how to implement a method.
+ */
+ interface MethodPool {
+
+ /**
+ * Looks up a handler entry for a given method.
+ *
+ * @param methodDescription The method being processed.
+ * @return A handler entry for the given method.
+ */
+ Record target(MethodDescription methodDescription);
+
+ /**
+ * An entry of a method pool that describes how a method is implemented.
+ *
+ * @see net.bytebuddy.dynamic.scaffold.TypeWriter.MethodPool
+ */
+ interface Record {
+
+ /**
+ * Returns the sort of this method instrumentation.
+ *
+ * @return The sort of this method instrumentation.
+ */
+ Sort getSort();
+
+ /**
+ * Returns the method that is implemented where the returned method resembles a potential transformation. An implemented
+ * method is only defined if a method is not {@link Record.Sort#SKIPPED}.
+ *
+ * @return The implemented method.
+ */
+ MethodDescription getMethod();
+
+ /**
+ * The visibility to enforce for this method.
+ *
+ * @return The visibility to enforce for this method.
+ */
+ Visibility getVisibility();
+
+ /**
+ * Prepends the given method appender to this entry.
+ *
+ * @param byteCodeAppender The byte code appender to prepend.
+ * @return This entry with the given code prepended.
+ */
+ Record prepend(ByteCodeAppender byteCodeAppender);
+
+ /**
+ * Applies this method entry. This method can always be called and might be a no-op.
+ *
+ * @param classVisitor The class visitor to which this entry should be applied.
+ * @param implementationContext The implementation context to which this entry should be applied.
+ * @param annotationValueFilterFactory The annotation value filter factory to apply when writing annotations.
+ */
+ void apply(ClassVisitor classVisitor, Implementation.Context implementationContext, AnnotationValueFilter.Factory annotationValueFilterFactory);
+
+ /**
+ * Applies the head of this entry. Applying an entry is only possible if a method is defined, i.e. the sort of this entry is not
+ * {@link Record.Sort#SKIPPED}.
+ *
+ * @param methodVisitor The method visitor to which this entry should be applied.
+ */
+ void applyHead(MethodVisitor methodVisitor);
+
+ /**
+ * Applies the body of this entry. Applying the body of an entry is only possible if a method is implemented, i.e. the sort of this
+ * entry is {@link Record.Sort#IMPLEMENTED}.
+ *
+ * @param methodVisitor The method visitor to which this entry should be applied.
+ * @param implementationContext The implementation context to which this entry should be applied.
+ * @param annotationValueFilterFactory The annotation value filter factory to apply when writing annotations.
+ */
+ void applyBody(MethodVisitor methodVisitor, Implementation.Context implementationContext, AnnotationValueFilter.Factory annotationValueFilterFactory);
+
+ /**
+ * Applies the attributes of this entry. Applying the body of an entry is only possible if a method is implemented, i.e. the sort of this
+ * entry is {@link Record.Sort#DEFINED}.
+ *
+ * @param methodVisitor The method visitor to which this entry should be applied.
+ * @param annotationValueFilterFactory The annotation value filter factory to apply when writing annotations.
+ */
+ void applyAttributes(MethodVisitor methodVisitor, AnnotationValueFilter.Factory annotationValueFilterFactory);
+
+ /**
+ * Applies the code of this entry. Applying the body of an entry is only possible if a method is implemented, i.e. the sort of this
+ * entry is {@link Record.Sort#IMPLEMENTED}.
+ *
+ * @param methodVisitor The method visitor to which this entry should be applied.
+ * @param implementationContext The implementation context to which this entry should be applied.
+ * @return The size requirements of the implemented code.
+ */
+ ByteCodeAppender.Size applyCode(MethodVisitor methodVisitor, Implementation.Context implementationContext);
+
+ /**
+ * The sort of an entry.
+ */
+ enum Sort {
+
+ /**
+ * Describes a method that should not be implemented or retained in its original state.
+ */
+ SKIPPED(false, false),
+
+ /**
+ * Describes a method that should be defined but is abstract or native, i.e. does not define any byte code.
+ */
+ DEFINED(true, false),
+
+ /**
+ * Describes a method that is implemented in byte code.
+ */
+ IMPLEMENTED(true, true);
+
+ /**
+ * Indicates if this sort defines a method, with or without byte code.
+ */
+ private final boolean define;
+
+ /**
+ * Indicates if this sort defines byte code.
+ */
+ private final boolean implement;
+
+ /**
+ * Creates a new sort.
+ *
+ * @param define Indicates if this sort defines a method, with or without byte code.
+ * @param implement Indicates if this sort defines byte code.
+ */
+ Sort(boolean define, boolean implement) {
+ this.define = define;
+ this.implement = implement;
+ }
+
+ /**
+ * Indicates if this sort defines a method, with or without byte code.
+ *
+ * @return {@code true} if this sort defines a method, with or without byte code.
+ */
+ public boolean isDefined() {
+ return define;
+ }
+
+ /**
+ * Indicates if this sort defines byte code.
+ *
+ * @return {@code true} if this sort defines byte code.
+ */
+ public boolean isImplemented() {
+ return implement;
+ }
+ }
+
+ /**
+ * A canonical implementation of a method that is not declared but inherited by the instrumented type.
+ */
+ @EqualsAndHashCode
+ class ForNonImplementedMethod implements Record {
+
+ /**
+ * The undefined method.
+ */
+ private final MethodDescription methodDescription;
+
+ /**
+ * Creates a new undefined record.
+ *
+ * @param methodDescription The undefined method.
+ */
+ public ForNonImplementedMethod(MethodDescription methodDescription) {
+ this.methodDescription = methodDescription;
+ }
+
+ @Override
+ public void apply(ClassVisitor classVisitor, Implementation.Context implementationContext, AnnotationValueFilter.Factory annotationValueFilterFactory) {
+ /* do nothing */
+ }
+
+ @Override
+ public void applyBody(MethodVisitor methodVisitor, Implementation.Context implementationContext, AnnotationValueFilter.Factory annotationValueFilterFactory) {
+ throw new IllegalStateException("Cannot apply body for non-implemented method on " + methodDescription);
+ }
+
+ @Override
+ public void applyAttributes(MethodVisitor methodVisitor, AnnotationValueFilter.Factory annotationValueFilterFactory) {
+ /* do nothing */
+ }
+
+ @Override
+ public ByteCodeAppender.Size applyCode(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ throw new IllegalStateException("Cannot apply code for non-implemented method on " + methodDescription);
+ }
+
+ @Override
+ public void applyHead(MethodVisitor methodVisitor) {
+ throw new IllegalStateException("Cannot apply head for non-implemented method on " + methodDescription);
+ }
+
+ @Override
+ public MethodDescription getMethod() {
+ return methodDescription;
+ }
+
+ @Override
+ public Visibility getVisibility() {
+ return methodDescription.getVisibility();
+ }
+
+ @Override
+ public Sort getSort() {
+ return Sort.SKIPPED;
+ }
+
+ @Override
+ public Record prepend(ByteCodeAppender byteCodeAppender) {
+ return new ForDefinedMethod.WithBody(methodDescription, new ByteCodeAppender.Compound(byteCodeAppender,
+ new ByteCodeAppender.Simple(DefaultValue.of(methodDescription.getReturnType()), MethodReturn.of(methodDescription.getReturnType()))));
+ }
+ }
+
+ /**
+ * A base implementation of an abstract entry that defines a method.
+ */
+ abstract class ForDefinedMethod implements Record {
+
+ @Override
+ public void apply(ClassVisitor classVisitor, Implementation.Context implementationContext, AnnotationValueFilter.Factory annotationValueFilterFactory) {
+ MethodVisitor methodVisitor = classVisitor.visitMethod(getMethod().getActualModifiers(getSort().isImplemented(), getVisibility()),
+ getMethod().getInternalName(),
+ getMethod().getDescriptor(),
+ getMethod().getGenericSignature(),
+ getMethod().getExceptionTypes().asErasures().toInternalNames());
+ if (methodVisitor != null) {
+ ParameterList<?> parameterList = getMethod().getParameters();
+ if (parameterList.hasExplicitMetaData()) {
+ for (ParameterDescription parameterDescription : parameterList) {
+ methodVisitor.visitParameter(parameterDescription.getName(), parameterDescription.getModifiers());
+ }
+ }
+ applyHead(methodVisitor);
+ applyBody(methodVisitor, implementationContext, annotationValueFilterFactory);
+ methodVisitor.visitEnd();
+ }
+ }
+
+ /**
+ * Describes an entry that defines a method as byte code.
+ */
+ @EqualsAndHashCode(callSuper = false)
+ public static class WithBody extends ForDefinedMethod {
+
+ /**
+ * The implemented method.
+ */
+ private final MethodDescription methodDescription;
+
+ /**
+ * The byte code appender to apply.
+ */
+ private final ByteCodeAppender byteCodeAppender;
+
+ /**
+ * The method attribute appender to apply.
+ */
+ private final MethodAttributeAppender methodAttributeAppender;
+
+ /**
+ * The represented method's minimum visibility.
+ */
+ private final Visibility visibility;
+
+ /**
+ * Creates a new record for an implemented method without attributes or a modifier resolver.
+ *
+ * @param methodDescription The implemented method.
+ * @param byteCodeAppender The byte code appender to apply.
+ */
+ public WithBody(MethodDescription methodDescription, ByteCodeAppender byteCodeAppender) {
+ this(methodDescription, byteCodeAppender, MethodAttributeAppender.NoOp.INSTANCE, methodDescription.getVisibility());
+ }
+
+ /**
+ * Creates a new entry for a method that defines a method as byte code.
+ *
+ * @param methodDescription The implemented method.
+ * @param byteCodeAppender The byte code appender to apply.
+ * @param methodAttributeAppender The method attribute appender to apply.
+ * @param visibility The represented method's minimum visibility.
+ */
+ public WithBody(MethodDescription methodDescription,
+ ByteCodeAppender byteCodeAppender,
+ MethodAttributeAppender methodAttributeAppender,
+ Visibility visibility) {
+ this.methodDescription = methodDescription;
+ this.byteCodeAppender = byteCodeAppender;
+ this.methodAttributeAppender = methodAttributeAppender;
+ this.visibility = visibility;
+ }
+
+ @Override
+ public MethodDescription getMethod() {
+ return methodDescription;
+ }
+
+ @Override
+ public Sort getSort() {
+ return Sort.IMPLEMENTED;
+ }
+
+ @Override
+ public Visibility getVisibility() {
+ return visibility;
+ }
+
+ @Override
+ public void applyHead(MethodVisitor methodVisitor) {
+ /* do nothing */
+ }
+
+ @Override
+ public void applyBody(MethodVisitor methodVisitor, Implementation.Context implementationContext, AnnotationValueFilter.Factory annotationValueFilterFactory) {
+ applyAttributes(methodVisitor, annotationValueFilterFactory);
+ methodVisitor.visitCode();
+ ByteCodeAppender.Size size = applyCode(methodVisitor, implementationContext);
+ methodVisitor.visitMaxs(size.getOperandStackSize(), size.getLocalVariableSize());
+ }
+
+ @Override
+ public void applyAttributes(MethodVisitor methodVisitor, AnnotationValueFilter.Factory annotationValueFilterFactory) {
+ methodAttributeAppender.apply(methodVisitor, methodDescription, annotationValueFilterFactory.on(methodDescription));
+ }
+
+ @Override
+ public ByteCodeAppender.Size applyCode(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ return byteCodeAppender.apply(methodVisitor, implementationContext, methodDescription);
+ }
+
+ @Override
+ public Record prepend(ByteCodeAppender byteCodeAppender) {
+ return new WithBody(methodDescription,
+ new ByteCodeAppender.Compound(byteCodeAppender, this.byteCodeAppender),
+ methodAttributeAppender,
+ visibility);
+ }
+ }
+
+ /**
+ * Describes an entry that defines a method but without byte code and without an annotation value.
+ */
+ @EqualsAndHashCode(callSuper = false)
+ public static class WithoutBody extends ForDefinedMethod {
+
+ /**
+ * The implemented method.
+ */
+ private final MethodDescription methodDescription;
+
+ /**
+ * The method attribute appender to apply.
+ */
+ private final MethodAttributeAppender methodAttributeAppender;
+
+ /**
+ * The represented method's minimum visibility.
+ */
+ private final Visibility visibility;
+
+ /**
+ * Creates a new entry for a method that is defines but does not append byte code, i.e. is native or abstract.
+ *
+ * @param methodDescription The implemented method.
+ * @param methodAttributeAppender The method attribute appender to apply.
+ * @param visibility The represented method's minimum visibility.
+ */
+ public WithoutBody(MethodDescription methodDescription, MethodAttributeAppender methodAttributeAppender, Visibility visibility) {
+ this.methodDescription = methodDescription;
+ this.methodAttributeAppender = methodAttributeAppender;
+ this.visibility = visibility;
+ }
+
+ @Override
+ public MethodDescription getMethod() {
+ return methodDescription;
+ }
+
+ @Override
+ public Sort getSort() {
+ return Sort.DEFINED;
+ }
+
+ @Override
+ public Visibility getVisibility() {
+ return visibility;
+ }
+
+ @Override
+ public void applyHead(MethodVisitor methodVisitor) {
+ /* do nothing */
+ }
+
+ @Override
+ public void applyBody(MethodVisitor methodVisitor, Implementation.Context implementationContext, AnnotationValueFilter.Factory annotationValueFilterFactory) {
+ applyAttributes(methodVisitor, annotationValueFilterFactory);
+ }
+
+ @Override
+ public void applyAttributes(MethodVisitor methodVisitor, AnnotationValueFilter.Factory annotationValueFilterFactory) {
+ methodAttributeAppender.apply(methodVisitor, methodDescription, annotationValueFilterFactory.on(methodDescription));
+ }
+
+ @Override
+ public ByteCodeAppender.Size applyCode(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ throw new IllegalStateException("Cannot apply code for abstract method on " + methodDescription);
+ }
+
+ @Override
+ public Record prepend(ByteCodeAppender byteCodeAppender) {
+ throw new IllegalStateException("Cannot prepend code for abstract method on " + methodDescription);
+ }
+ }
+
+ /**
+ * Describes an entry that defines a method with a default annotation value.
+ */
+ @EqualsAndHashCode(callSuper = false)
+ public static class WithAnnotationDefaultValue extends ForDefinedMethod {
+
+ /**
+ * The implemented method.
+ */
+ private final MethodDescription methodDescription;
+
+ /**
+ * The annotation value to define.
+ */
+ private final AnnotationValue<?, ?> annotationValue;
+
+ /**
+ * The method attribute appender to apply.
+ */
+ private final MethodAttributeAppender methodAttributeAppender;
+
+ /**
+ * Creates a new entry for defining a method with a default annotation value.
+ *
+ * @param methodDescription The implemented method.
+ * @param annotationValue The annotation value to define.
+ * @param methodAttributeAppender The method attribute appender to apply.
+ */
+ public WithAnnotationDefaultValue(MethodDescription methodDescription,
+ AnnotationValue<?, ?> annotationValue,
+ MethodAttributeAppender methodAttributeAppender) {
+ this.methodDescription = methodDescription;
+ this.annotationValue = annotationValue;
+ this.methodAttributeAppender = methodAttributeAppender;
+ }
+
+ @Override
+ public MethodDescription getMethod() {
+ return methodDescription;
+ }
+
+ @Override
+ public Sort getSort() {
+ return Sort.DEFINED;
+ }
+
+ @Override
+ public Visibility getVisibility() {
+ return methodDescription.getVisibility();
+ }
+
+ @Override
+ public void applyHead(MethodVisitor methodVisitor) {
+ if (!methodDescription.isDefaultValue(annotationValue)) {
+ throw new IllegalStateException("Cannot set " + annotationValue + " as default for " + methodDescription);
+ }
+ AnnotationVisitor annotationVisitor = methodVisitor.visitAnnotationDefault();
+ AnnotationAppender.Default.apply(annotationVisitor,
+ methodDescription.getReturnType().asErasure(),
+ AnnotationAppender.NO_NAME,
+ annotationValue.resolve());
+ annotationVisitor.visitEnd();
+ }
+
+ @Override
+ public void applyBody(MethodVisitor methodVisitor, Implementation.Context implementationContext, AnnotationValueFilter.Factory annotationValueFilterFactory) {
+ methodAttributeAppender.apply(methodVisitor, methodDescription, annotationValueFilterFactory.on(methodDescription));
+ }
+
+ @Override
+ public void applyAttributes(MethodVisitor methodVisitor, AnnotationValueFilter.Factory annotationValueFilterFactory) {
+ throw new IllegalStateException("Cannot apply attributes for default value on " + methodDescription);
+ }
+
+ @Override
+ public ByteCodeAppender.Size applyCode(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ throw new IllegalStateException("Cannot apply code for default value on " + methodDescription);
+ }
+
+ @Override
+ public Record prepend(ByteCodeAppender byteCodeAppender) {
+ throw new IllegalStateException("Cannot prepend code for default value on " + methodDescription);
+ }
+ }
+
+ /**
+ * A record for a visibility bridge.
+ */
+ @EqualsAndHashCode(callSuper = false)
+ public static class OfVisibilityBridge extends ForDefinedMethod implements ByteCodeAppender {
+
+ /**
+ * The visibility bridge.
+ */
+ private final MethodDescription visibilityBridge;
+
+ /**
+ * The method the visibility bridge invokes.
+ */
+ private final MethodDescription bridgeTarget;
+
+ /**
+ * The type on which the bridge method is invoked.
+ */
+ private final TypeDescription bridgeType;
+
+ /**
+ * The attribute appender to apply to the visibility bridge.
+ */
+ private final MethodAttributeAppender attributeAppender;
+
+ /**
+ * Creates a new record for a visibility bridge.
+ *
+ * @param visibilityBridge The visibility bridge.
+ * @param bridgeTarget The method the visibility bridge invokes.
+ * @param bridgeType The type of the instrumented type.
+ * @param attributeAppender The attribute appender to apply to the visibility bridge.
+ */
+ protected OfVisibilityBridge(MethodDescription visibilityBridge,
+ MethodDescription bridgeTarget,
+ TypeDescription bridgeType,
+ MethodAttributeAppender attributeAppender) {
+ this.visibilityBridge = visibilityBridge;
+ this.bridgeTarget = bridgeTarget;
+ this.bridgeType = bridgeType;
+ this.attributeAppender = attributeAppender;
+ }
+
+ /**
+ * Creates a record for a visibility bridge.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param bridgeTarget The target method of the visibility bridge.
+ * @param attributeAppender The attribute appender to apply to the visibility bridge.
+ * @return A record describing the visibility bridge.
+ */
+ public static Record of(TypeDescription instrumentedType, MethodDescription bridgeTarget, MethodAttributeAppender attributeAppender) {
+ // Default method bridges must be dispatched on an implemented interface type, not considering the declaring type.
+ TypeDefinition bridgeType = null;
+ if (bridgeTarget.isDefaultMethod()) {
+ TypeDescription declaringType = bridgeTarget.getDeclaringType().asErasure();
+ for (TypeDescription interfaceType : instrumentedType.getInterfaces().asErasures().filter(isSubTypeOf(declaringType))) {
+ if (bridgeType == null || declaringType.isAssignableTo(bridgeType.asErasure())) {
+ bridgeType = interfaceType;
+ }
+ }
+ }
+ // Non-default method or default method that is inherited by a super class.
+ if (bridgeType == null) {
+ bridgeType = instrumentedType.getSuperClass();
+ }
+ return new OfVisibilityBridge(new VisibilityBridge(instrumentedType, bridgeTarget),
+ bridgeTarget,
+ bridgeType.asErasure(),
+ attributeAppender);
+ }
+
+ @Override
+ public MethodDescription getMethod() {
+ return visibilityBridge;
+ }
+
+ @Override
+ public Sort getSort() {
+ return Sort.IMPLEMENTED;
+ }
+
+ @Override
+ public Visibility getVisibility() {
+ return bridgeTarget.getVisibility();
+ }
+
+ @Override
+ public Record prepend(ByteCodeAppender byteCodeAppender) {
+ return new ForDefinedMethod.WithBody(visibilityBridge,
+ new ByteCodeAppender.Compound(this, byteCodeAppender),
+ attributeAppender,
+ bridgeTarget.getVisibility());
+ }
+
+ @Override
+ public void applyHead(MethodVisitor methodVisitor) {
+ /* do nothing */
+ }
+
+ @Override
+ public void applyBody(MethodVisitor methodVisitor, Implementation.Context implementationContext, AnnotationValueFilter.Factory annotationValueFilterFactory) {
+ applyAttributes(methodVisitor, annotationValueFilterFactory);
+ methodVisitor.visitCode();
+ ByteCodeAppender.Size size = applyCode(methodVisitor, implementationContext);
+ methodVisitor.visitMaxs(size.getOperandStackSize(), size.getLocalVariableSize());
+ }
+
+ @Override
+ public void applyAttributes(MethodVisitor methodVisitor, AnnotationValueFilter.Factory annotationValueFilterFactory) {
+ attributeAppender.apply(methodVisitor, visibilityBridge, annotationValueFilterFactory.on(visibilityBridge));
+ }
+
+ @Override
+ public Size applyCode(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ return apply(methodVisitor, implementationContext, visibilityBridge);
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodDescription instrumentedMethod) {
+ return new ByteCodeAppender.Simple(
+ MethodVariableAccess.allArgumentsOf(instrumentedMethod).prependThisReference(),
+ MethodInvocation.invoke(bridgeTarget).special(bridgeType),
+ MethodReturn.of(instrumentedMethod.getReturnType())
+ ).apply(methodVisitor, implementationContext, instrumentedMethod);
+ }
+
+ /**
+ * A method describing a visibility bridge.
+ */
+ protected static class VisibilityBridge extends MethodDescription.InDefinedShape.AbstractBase {
+
+ /**
+ * The instrumented type.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * The method that is the target of the bridge.
+ */
+ private final MethodDescription bridgeTarget;
+
+ /**
+ * Creates a new visibility bridge.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param bridgeTarget The method that is the target of the bridge.
+ */
+ protected VisibilityBridge(TypeDescription instrumentedType, MethodDescription bridgeTarget) {
+ this.instrumentedType = instrumentedType;
+ this.bridgeTarget = bridgeTarget;
+ }
+
+ @Override
+ public TypeDescription getDeclaringType() {
+ return instrumentedType;
+ }
+
+ @Override
+ public ParameterList<ParameterDescription.InDefinedShape> getParameters() {
+ return new ParameterList.Explicit.ForTypes(this, bridgeTarget.getParameters().asTypeList().asRawTypes());
+ }
+
+ @Override
+ public TypeDescription.Generic getReturnType() {
+ return bridgeTarget.getReturnType().asRawType();
+ }
+
+ @Override
+ public TypeList.Generic getExceptionTypes() {
+ return bridgeTarget.getExceptionTypes().asRawTypes();
+ }
+
+ @Override
+ public AnnotationValue<?, ?> getDefaultValue() {
+ return AnnotationValue.UNDEFINED;
+ }
+
+ @Override
+ public TypeList.Generic getTypeVariables() {
+ return new TypeList.Generic.Empty();
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return bridgeTarget.getDeclaredAnnotations();
+ }
+
+ @Override
+ public int getModifiers() {
+ return (bridgeTarget.getModifiers() | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_BRIDGE) & ~Opcodes.ACC_NATIVE;
+ }
+
+ @Override
+ public String getInternalName() {
+ return bridgeTarget.getName();
+ }
+ }
+ }
+ }
+
+ /**
+ * A wrapper that appends accessor bridges for a method's implementation. The bridges are only added if
+ * {@link net.bytebuddy.dynamic.scaffold.TypeWriter.MethodPool.Record#apply(ClassVisitor, Implementation.Context, AnnotationValueFilter.Factory)}
+ * is invoked such that bridges are not appended for methods that are rebased or redefined as such types already have bridge methods in place.
+ */
+ @EqualsAndHashCode
+ class AccessBridgeWrapper implements Record {
+
+ /**
+ * The delegate for implementing the bridge's target.
+ */
+ private final Record delegate;
+
+ /**
+ * The instrumented type that defines the bridge methods and the bridge target.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * The target of the bridge method.
+ */
+ private final MethodDescription bridgeTarget;
+
+ /**
+ * A collection of all tokens representing all bridge methods.
+ */
+ private final Set<MethodDescription.TypeToken> bridgeTypes;
+
+ /**
+ * The attribute appender being applied for the bridge target.
+ */
+ private final MethodAttributeAppender attributeAppender;
+
+ /**
+ * Creates a wrapper for adding accessor bridges.
+ *
+ * @param delegate The delegate for implementing the bridge's target.
+ * @param instrumentedType The instrumented type that defines the bridge methods and the bridge target.
+ * @param bridgeTarget The target of the bridge method.
+ * @param bridgeTypes A collection of all tokens representing all bridge methods.
+ * @param attributeAppender The attribute appender being applied for the bridge target.
+ */
+ protected AccessBridgeWrapper(Record delegate,
+ TypeDescription instrumentedType,
+ MethodDescription bridgeTarget,
+ Set<MethodDescription.TypeToken> bridgeTypes,
+ MethodAttributeAppender attributeAppender) {
+ this.delegate = delegate;
+ this.instrumentedType = instrumentedType;
+ this.bridgeTarget = bridgeTarget;
+ this.bridgeTypes = bridgeTypes;
+ this.attributeAppender = attributeAppender;
+ }
+
+ /**
+ * Wraps the given record in an accessor bridge wrapper if necessary.
+ *
+ * @param delegate The delegate for implementing the bridge's target.
+ * @param instrumentedType The instrumented type that defines the bridge methods and the bridge target.
+ * @param bridgeTarget The bridge methods' target methods.
+ * @param bridgeTypes A collection of all tokens representing all bridge methods.
+ * @param attributeAppender The attribute appender being applied for the bridge target.
+ * @return The given record wrapped by a bridge method wrapper if necessary.
+ */
+ public static Record of(Record delegate,
+ TypeDescription instrumentedType,
+ MethodDescription bridgeTarget,
+ Set<MethodDescription.TypeToken> bridgeTypes,
+ MethodAttributeAppender attributeAppender) {
+ Set<MethodDescription.TypeToken> compatibleBridgeTypes = new HashSet<MethodDescription.TypeToken>();
+ for (MethodDescription.TypeToken bridgeType : bridgeTypes) {
+ if (bridgeTarget.isBridgeCompatible(bridgeType)) {
+ compatibleBridgeTypes.add(bridgeType);
+ }
+ }
+ return compatibleBridgeTypes.isEmpty() || (instrumentedType.isInterface() && !delegate.getSort().isImplemented())
+ ? delegate
+ : new AccessBridgeWrapper(delegate, instrumentedType, bridgeTarget, compatibleBridgeTypes, attributeAppender);
+ }
+
+ @Override
+ public Sort getSort() {
+ return delegate.getSort();
+ }
+
+ @Override
+ public MethodDescription getMethod() {
+ return bridgeTarget;
+ }
+
+ @Override
+ public Visibility getVisibility() {
+ return delegate.getVisibility();
+ }
+
+ @Override
+ public Record prepend(ByteCodeAppender byteCodeAppender) {
+ return new AccessBridgeWrapper(delegate.prepend(byteCodeAppender), instrumentedType, bridgeTarget, bridgeTypes, attributeAppender);
+ }
+
+ @Override
+ public void apply(ClassVisitor classVisitor,
+ Implementation.Context implementationContext,
+ AnnotationValueFilter.Factory annotationValueFilterFactory) {
+ delegate.apply(classVisitor, implementationContext, annotationValueFilterFactory);
+ for (MethodDescription.TypeToken bridgeType : bridgeTypes) {
+ MethodDescription.InDefinedShape bridgeMethod = new AccessorBridge(bridgeTarget, bridgeType, instrumentedType);
+ MethodDescription.InDefinedShape bridgeTarget = new BridgeTarget(this.bridgeTarget, instrumentedType);
+ MethodVisitor methodVisitor = classVisitor.visitMethod(bridgeMethod.getActualModifiers(true, getVisibility()),
+ bridgeMethod.getInternalName(),
+ bridgeMethod.getDescriptor(),
+ MethodDescription.NON_GENERIC_SIGNATURE,
+ bridgeMethod.getExceptionTypes().asErasures().toInternalNames());
+ if (methodVisitor != null) {
+ attributeAppender.apply(methodVisitor, bridgeMethod, annotationValueFilterFactory.on(instrumentedType));
+ methodVisitor.visitCode();
+ ByteCodeAppender.Size size = new ByteCodeAppender.Simple(
+ MethodVariableAccess.allArgumentsOf(bridgeMethod).asBridgeOf(bridgeTarget).prependThisReference(),
+ MethodInvocation.invoke(bridgeTarget).virtual(instrumentedType),
+ bridgeTarget.getReturnType().asErasure().isAssignableTo(bridgeMethod.getReturnType().asErasure())
+ ? StackManipulation.Trivial.INSTANCE
+ : TypeCasting.to(bridgeMethod.getReturnType().asErasure()),
+ MethodReturn.of(bridgeMethod.getReturnType())
+ ).apply(methodVisitor, implementationContext, bridgeMethod);
+ methodVisitor.visitMaxs(size.getOperandStackSize(), size.getLocalVariableSize());
+ methodVisitor.visitEnd();
+ }
+ }
+ }
+
+ @Override
+ public void applyHead(MethodVisitor methodVisitor) {
+ delegate.applyHead(methodVisitor);
+ }
+
+ @Override
+ public void applyBody(MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ AnnotationValueFilter.Factory annotationValueFilterFactory) {
+ delegate.applyBody(methodVisitor, implementationContext, annotationValueFilterFactory);
+ }
+
+ @Override
+ public void applyAttributes(MethodVisitor methodVisitor, AnnotationValueFilter.Factory annotationValueFilterFactory) {
+ delegate.applyAttributes(methodVisitor, annotationValueFilterFactory);
+ }
+
+ @Override
+ public ByteCodeAppender.Size applyCode(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ return delegate.applyCode(methodVisitor, implementationContext);
+ }
+
+ /**
+ * A method representing an accessor bridge method.
+ */
+ protected static class AccessorBridge extends MethodDescription.InDefinedShape.AbstractBase {
+
+ /**
+ * The target method of the bridge.
+ */
+ private final MethodDescription bridgeTarget;
+
+ /**
+ * The bridge's type token.
+ */
+ private final MethodDescription.TypeToken bridgeType;
+
+ /**
+ * The instrumented type defining the bridge target.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * Creates a new accessor bridge method.
+ *
+ * @param bridgeTarget The target method of the bridge.
+ * @param bridgeType The bridge's type token.
+ * @param instrumentedType The instrumented type defining the bridge target.
+ */
+ protected AccessorBridge(MethodDescription bridgeTarget, TypeToken bridgeType, TypeDescription instrumentedType) {
+ this.bridgeTarget = bridgeTarget;
+ this.bridgeType = bridgeType;
+ this.instrumentedType = instrumentedType;
+ }
+
+ @Override
+ public TypeDescription getDeclaringType() {
+ return instrumentedType;
+ }
+
+ @Override
+ public ParameterList<ParameterDescription.InDefinedShape> getParameters() {
+ return new ParameterList.Explicit.ForTypes(this, bridgeType.getParameterTypes());
+ }
+
+ @Override
+ public TypeDescription.Generic getReturnType() {
+ return bridgeType.getReturnType().asGenericType();
+ }
+
+ @Override
+ public TypeList.Generic getExceptionTypes() {
+ return bridgeTarget.getExceptionTypes().accept(TypeDescription.Generic.Visitor.TypeErasing.INSTANCE);
+ }
+
+ @Override
+ public AnnotationValue<?, ?> getDefaultValue() {
+ return AnnotationValue.UNDEFINED;
+ }
+
+ @Override
+ public TypeList.Generic getTypeVariables() {
+ return new TypeList.Generic.Empty();
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return new AnnotationList.Empty();
+ }
+
+ @Override
+ public int getModifiers() {
+ return (bridgeTarget.getModifiers() | Opcodes.ACC_BRIDGE | Opcodes.ACC_SYNTHETIC) & ~(Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE);
+ }
+
+ @Override
+ public String getInternalName() {
+ return bridgeTarget.getInternalName();
+ }
+ }
+
+ /**
+ * A method representing a bridge's target method in its defined shape.
+ */
+ protected static class BridgeTarget extends MethodDescription.InDefinedShape.AbstractBase {
+
+ /**
+ * The target method of the bridge.
+ */
+ private final MethodDescription bridgeTarget;
+
+ /**
+ * The instrumented type defining the bridge target.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * Creates a new bridge target.
+ *
+ * @param bridgeTarget The target method of the bridge.
+ * @param instrumentedType The instrumented type defining the bridge target.
+ */
+ protected BridgeTarget(MethodDescription bridgeTarget, TypeDescription instrumentedType) {
+ this.bridgeTarget = bridgeTarget;
+ this.instrumentedType = instrumentedType;
+ }
+
+ @Override
+ public TypeDescription getDeclaringType() {
+ return instrumentedType;
+ }
+
+ @Override
+ public ParameterList<ParameterDescription.InDefinedShape> getParameters() {
+ return new ParameterList.ForTokens(this, bridgeTarget.getParameters().asTokenList(is(instrumentedType)));
+ }
+
+ @Override
+ public TypeDescription.Generic getReturnType() {
+ return bridgeTarget.getReturnType();
+ }
+
+ @Override
+ public TypeList.Generic getExceptionTypes() {
+ return bridgeTarget.getExceptionTypes();
+ }
+
+ @Override
+ public AnnotationValue<?, ?> getDefaultValue() {
+ return bridgeTarget.getDefaultValue();
+ }
+
+ @Override
+ public TypeList.Generic getTypeVariables() {
+ return bridgeTarget.getTypeVariables();
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return bridgeTarget.getDeclaredAnnotations();
+ }
+
+ @Override
+ public int getModifiers() {
+ return bridgeTarget.getModifiers();
+ }
+
+ @Override
+ public String getInternalName() {
+ return bridgeTarget.getInternalName();
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * A default implementation of a {@link net.bytebuddy.dynamic.scaffold.TypeWriter}.
+ *
+ * @param <S> The best known loaded type for the dynamically created type.
+ */
+ @EqualsAndHashCode
+ abstract class Default<S> implements TypeWriter<S> {
+
+ /**
+ * A folder for dumping class files or {@code null} if no dump should be generated.
+ */
+ private static final String DUMP_FOLDER;
+
+ /*
+ * Reads the dumping property that is set at program start up. This might cause an error because of security constraints.
+ */
+ static {
+ String dumpFolder;
+ try {
+ dumpFolder = AccessController.doPrivileged(new GetSystemPropertyAction(DUMP_PROPERTY));
+ } catch (RuntimeException exception) {
+ dumpFolder = null;
+ }
+ DUMP_FOLDER = dumpFolder;
+ }
+
+ /**
+ * The instrumented type to be created.
+ */
+ protected final TypeDescription instrumentedType;
+
+ /**
+ * The class file specified by the user.
+ */
+ protected final ClassFileVersion classFileVersion;
+
+ /**
+ * The field pool to use.
+ */
+ protected final FieldPool fieldPool;
+
+ /**
+ * The explicit auxiliary types to add to the created type.
+ */
+ protected final List<? extends DynamicType> auxiliaryTypes;
+
+ /**
+ * The instrumented type's declared fields.
+ */
+ protected final FieldList<FieldDescription.InDefinedShape> fields;
+
+ /**
+ * The instrumented type's methods that are declared or inherited.
+ */
+ protected final MethodList<?> methods;
+
+ /**
+ * The instrumented methods relevant to this type creation.
+ */
+ protected final MethodList<?> instrumentedMethods;
+
+ /**
+ * The loaded type initializer to apply onto the created type after loading.
+ */
+ protected final LoadedTypeInitializer loadedTypeInitializer;
+
+ /**
+ * The type initializer to include in the created type's type initializer.
+ */
+ protected final TypeInitializer typeInitializer;
+
+ /**
+ * The type attribute appender to apply onto the instrumented type.
+ */
+ protected final TypeAttributeAppender typeAttributeAppender;
+
+ /**
+ * The ASM visitor wrapper to apply onto the class writer.
+ */
+ protected final AsmVisitorWrapper asmVisitorWrapper;
+
+ /**
+ * The annotation value filter factory to apply.
+ */
+ protected final AnnotationValueFilter.Factory annotationValueFilterFactory;
+
+ /**
+ * The annotation retention to apply.
+ */
+ protected final AnnotationRetention annotationRetention;
+
+ /**
+ * The naming strategy for auxiliary types to apply.
+ */
+ protected final AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy;
+
+ /**
+ * The implementation context factory to apply.
+ */
+ protected final Implementation.Context.Factory implementationContextFactory;
+
+ /**
+ * Determines if a type should be explicitly validated.
+ */
+ protected final TypeValidation typeValidation;
+
+ /**
+ * The type pool to use for computing stack map frames, if required.
+ */
+ protected final TypePool typePool;
+
+ /**
+ * Creates a new default type writer.
+ *
+ * @param instrumentedType The instrumented type to be created.
+ * @param classFileVersion The class file specified by the user.
+ * @param fieldPool The field pool to use.
+ * @param auxiliaryTypes The explicit auxiliary types to add to the created type.
+ * @param fields The instrumented type's declared fields.
+ * @param methods The instrumented type's declared and virtually inhertied methods.
+ * @param instrumentedMethods The instrumented methods relevant to this type creation.
+ * @param loadedTypeInitializer The loaded type initializer to apply onto the created type after loading.
+ * @param typeInitializer The type initializer to include in the created type's type initializer.
+ * @param typeAttributeAppender The type attribute appender to apply onto the instrumented type.
+ * @param asmVisitorWrapper The ASM visitor wrapper to apply onto the class writer.
+ * @param annotationValueFilterFactory The annotation value filter factory to apply.
+ * @param annotationRetention The annotation retention to apply.
+ * @param auxiliaryTypeNamingStrategy The naming strategy for auxiliary types to apply.
+ * @param implementationContextFactory The implementation context factory to apply.
+ * @param typeValidation Determines if a type should be explicitly validated.
+ * @param typePool The type pool to use for computing stack map frames, if required.
+ */
+ protected Default(TypeDescription instrumentedType,
+ ClassFileVersion classFileVersion,
+ FieldPool fieldPool,
+ List<? extends DynamicType> auxiliaryTypes,
+ FieldList<FieldDescription.InDefinedShape> fields,
+ MethodList<?> methods,
+ MethodList<?> instrumentedMethods,
+ LoadedTypeInitializer loadedTypeInitializer,
+ TypeInitializer typeInitializer,
+ TypeAttributeAppender typeAttributeAppender,
+ AsmVisitorWrapper asmVisitorWrapper,
+ AnnotationValueFilter.Factory annotationValueFilterFactory,
+ AnnotationRetention annotationRetention,
+ AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
+ Implementation.Context.Factory implementationContextFactory,
+ TypeValidation typeValidation,
+ TypePool typePool) {
+ this.instrumentedType = instrumentedType;
+ this.classFileVersion = classFileVersion;
+ this.fieldPool = fieldPool;
+ this.auxiliaryTypes = auxiliaryTypes;
+ this.fields = fields;
+ this.methods = methods;
+ this.instrumentedMethods = instrumentedMethods;
+ this.loadedTypeInitializer = loadedTypeInitializer;
+ this.typeInitializer = typeInitializer;
+ this.typeAttributeAppender = typeAttributeAppender;
+ this.asmVisitorWrapper = asmVisitorWrapper;
+ this.auxiliaryTypeNamingStrategy = auxiliaryTypeNamingStrategy;
+ this.annotationValueFilterFactory = annotationValueFilterFactory;
+ this.annotationRetention = annotationRetention;
+ this.implementationContextFactory = implementationContextFactory;
+ this.typeValidation = typeValidation;
+ this.typePool = typePool;
+ }
+
+ /**
+ * Creates a type writer for creating a new type.
+ *
+ * @param methodRegistry The compiled method registry to use.
+ * @param fieldPool The field pool to use.
+ * @param typeAttributeAppender The type attribute appender to apply onto the instrumented type.
+ * @param asmVisitorWrapper The ASM visitor wrapper to apply onto the class writer.
+ * @param classFileVersion The class file version to use when no explicit class file version is applied.
+ * @param annotationValueFilterFactory The annotation value filter factory to apply.
+ * @param annotationRetention The annotation retention to apply.
+ * @param auxiliaryTypeNamingStrategy The naming strategy for auxiliary types to apply.
+ * @param implementationContextFactory The implementation context factory to apply.
+ * @param typeValidation Determines if a type should be explicitly validated.
+ * @param typePool The type pool to use for computing stack map frames, if required.
+ * @param <U> A loaded type that the instrumented type guarantees to subclass.
+ * @return A suitable type writer.
+ */
+ public static <U> TypeWriter<U> forCreation(MethodRegistry.Compiled methodRegistry,
+ FieldPool fieldPool,
+ TypeAttributeAppender typeAttributeAppender,
+ AsmVisitorWrapper asmVisitorWrapper,
+ ClassFileVersion classFileVersion,
+ AnnotationValueFilter.Factory annotationValueFilterFactory,
+ AnnotationRetention annotationRetention,
+ AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
+ Implementation.Context.Factory implementationContextFactory,
+ TypeValidation typeValidation,
+ TypePool typePool) {
+ return new ForCreation<U>(methodRegistry.getInstrumentedType(),
+ classFileVersion,
+ fieldPool,
+ methodRegistry,
+ Collections.<DynamicType>emptyList(),
+ methodRegistry.getInstrumentedType().getDeclaredFields(),
+ methodRegistry.getMethods(),
+ methodRegistry.getInstrumentedMethods(),
+ methodRegistry.getLoadedTypeInitializer(),
+ methodRegistry.getTypeInitializer(),
+ typeAttributeAppender,
+ asmVisitorWrapper,
+ annotationValueFilterFactory,
+ annotationRetention,
+ auxiliaryTypeNamingStrategy,
+ implementationContextFactory,
+ typeValidation,
+ typePool);
+ }
+
+ /**
+ * Creates a type writer for redefining a type.
+ *
+ * @param methodRegistry The compiled method registry to use.
+ * @param fieldPool The field pool to use.
+ * @param typeAttributeAppender The type attribute appender to apply onto the instrumented type.
+ * @param asmVisitorWrapper The ASM visitor wrapper to apply onto the class writer.
+ * @param classFileVersion The class file version to use when no explicit class file version is applied.
+ * @param annotationValueFilterFactory The annotation value filter factory to apply.
+ * @param annotationRetention The annotation retention to apply.
+ * @param auxiliaryTypeNamingStrategy The naming strategy for auxiliary types to apply.
+ * @param implementationContextFactory The implementation context factory to apply.
+ * @param typeValidation Determines if a type should be explicitly validated.
+ * @param typePool The type pool to use for computing stack map frames, if required.
+ * @param originalType The original type that is being redefined or rebased.
+ * @param classFileLocator The class file locator for locating the original type's class file.
+ * @param <U> A loaded type that the instrumented type guarantees to subclass.
+ * @return A suitable type writer.
+ */
+ public static <U> TypeWriter<U> forRedefinition(MethodRegistry.Prepared methodRegistry,
+ FieldPool fieldPool,
+ TypeAttributeAppender typeAttributeAppender,
+ AsmVisitorWrapper asmVisitorWrapper,
+ ClassFileVersion classFileVersion,
+ AnnotationValueFilter.Factory annotationValueFilterFactory,
+ AnnotationRetention annotationRetention,
+ AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
+ Implementation.Context.Factory implementationContextFactory,
+ TypeValidation typeValidation,
+ TypePool typePool,
+ TypeDescription originalType,
+ ClassFileLocator classFileLocator) {
+ return new ForInlining<U>(methodRegistry.getInstrumentedType(),
+ classFileVersion,
+ fieldPool,
+ methodRegistry,
+ SubclassImplementationTarget.Factory.LEVEL_TYPE,
+ Collections.<DynamicType>emptyList(),
+ methodRegistry.getInstrumentedType().getDeclaredFields(),
+ methodRegistry.getMethods(),
+ methodRegistry.getInstrumentedMethods(),
+ methodRegistry.getLoadedTypeInitializer(),
+ methodRegistry.getTypeInitializer(),
+ typeAttributeAppender,
+ asmVisitorWrapper,
+ annotationValueFilterFactory,
+ annotationRetention,
+ auxiliaryTypeNamingStrategy,
+ implementationContextFactory,
+ typeValidation,
+ typePool,
+ originalType,
+ classFileLocator,
+ MethodRebaseResolver.Disabled.INSTANCE);
+ }
+
+ /**
+ * Creates a type writer for rebasing a type.
+ *
+ * @param methodRegistry The compiled method registry to use.
+ * @param fieldPool The field pool to use.
+ * @param typeAttributeAppender The type attribute appender to apply onto the instrumented type.
+ * @param asmVisitorWrapper The ASM visitor wrapper to apply onto the class writer.
+ * @param classFileVersion The class file version to use when no explicit class file version is applied.
+ * @param annotationValueFilterFactory The annotation value filter factory to apply.
+ * @param annotationRetention The annotation retention to apply.
+ * @param auxiliaryTypeNamingStrategy The naming strategy for auxiliary types to apply.
+ * @param implementationContextFactory The implementation context factory to apply.
+ * @param typeValidation Determines if a type should be explicitly validated.
+ * @param typePool The type pool to use for computing stack map frames, if required.
+ * @param originalType The original type that is being redefined or rebased.
+ * @param classFileLocator The class file locator for locating the original type's class file.
+ * @param methodRebaseResolver The method rebase resolver to use for rebasing names.
+ * @param <U> A loaded type that the instrumented type guarantees to subclass.
+ * @return A suitable type writer.
+ */
+ public static <U> TypeWriter<U> forRebasing(MethodRegistry.Prepared methodRegistry,
+ FieldPool fieldPool,
+ TypeAttributeAppender typeAttributeAppender,
+ AsmVisitorWrapper asmVisitorWrapper,
+ ClassFileVersion classFileVersion,
+ AnnotationValueFilter.Factory annotationValueFilterFactory,
+ AnnotationRetention annotationRetention,
+ AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
+ Implementation.Context.Factory implementationContextFactory,
+ TypeValidation typeValidation,
+ TypePool typePool,
+ TypeDescription originalType,
+ ClassFileLocator classFileLocator,
+ MethodRebaseResolver methodRebaseResolver) {
+ return new ForInlining<U>(methodRegistry.getInstrumentedType(),
+ classFileVersion,
+ fieldPool,
+ methodRegistry,
+ new RebaseImplementationTarget.Factory(methodRebaseResolver),
+ methodRebaseResolver.getAuxiliaryTypes(),
+ methodRegistry.getInstrumentedType().getDeclaredFields(),
+ methodRegistry.getMethods(),
+ methodRegistry.getInstrumentedMethods(),
+ methodRegistry.getLoadedTypeInitializer(),
+ methodRegistry.getTypeInitializer(),
+ typeAttributeAppender,
+ asmVisitorWrapper,
+ annotationValueFilterFactory,
+ annotationRetention,
+ auxiliaryTypeNamingStrategy,
+ implementationContextFactory,
+ typeValidation,
+ typePool,
+ originalType,
+ classFileLocator,
+ methodRebaseResolver);
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Setting a debugging property should never change the program outcome")
+ public DynamicType.Unloaded<S> make(TypeResolutionStrategy.Resolved typeResolutionStrategy) {
+ UnresolvedType unresolvedType = create(typeResolutionStrategy.injectedInto(typeInitializer));
+ if (DUMP_FOLDER != null) {
+ try {
+ AccessController.doPrivileged(new ClassDumpAction(DUMP_FOLDER, instrumentedType, unresolvedType.getBinaryRepresentation()));
+ } catch (Exception exception) {
+ exception.printStackTrace();
+ }
+ }
+ return unresolvedType.toDynamicType(typeResolutionStrategy);
+ }
+
+ /**
+ * Creates an unresolved version of the dynamic type.
+ *
+ * @param typeInitializer The type initializer to use.
+ * @return An unresolved type.
+ */
+ protected abstract UnresolvedType create(TypeInitializer typeInitializer);
+
+ /**
+ * An unresolved type.
+ */
+ protected class UnresolvedType {
+
+ /**
+ * The type's binary representation.
+ */
+ private final byte[] binaryRepresentation;
+
+ /**
+ * A list of auxiliary types for this unresolved type.
+ */
+ private final List<? extends DynamicType> auxiliaryTypes;
+
+ /**
+ * Creates a new unresolved type.
+ *
+ * @param binaryRepresentation The type's binary representation.
+ * @param auxiliaryTypes A list of auxiliary types for this unresolved type.
+ */
+ protected UnresolvedType(byte[] binaryRepresentation, List<? extends DynamicType> auxiliaryTypes) {
+ this.binaryRepresentation = binaryRepresentation;
+ this.auxiliaryTypes = auxiliaryTypes;
+ }
+
+ /**
+ * Resolves this type to a dynamic type.
+ *
+ * @param typeResolutionStrategy The type resolution strategy to apply.
+ * @return A dynamic type representing the inlined type.
+ */
+ protected DynamicType.Unloaded<S> toDynamicType(TypeResolutionStrategy.Resolved typeResolutionStrategy) {
+ return new DynamicType.Default.Unloaded<S>(instrumentedType,
+ binaryRepresentation,
+ loadedTypeInitializer,
+ CompoundList.of(Default.this.auxiliaryTypes, auxiliaryTypes),
+ typeResolutionStrategy);
+ }
+
+ /**
+ * Returns the binary representation of this unresolved type.
+ *
+ * @return The binary representation of this unresolved type.
+ */
+ protected byte[] getBinaryRepresentation() {
+ return binaryRepresentation;
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private Default getOuter() {
+ return Default.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ @SuppressWarnings("unchecked")
+ public boolean equals(Object object) {
+ if (this == object) return true;
+ if (object == null || getClass() != object.getClass()) return false;
+ UnresolvedType that = (UnresolvedType) object; // Java 6 compilers cannot cast to a nested wildcard.
+ return Arrays.equals(binaryRepresentation, that.binaryRepresentation)
+ && Default.this.equals(that.getOuter())
+ && auxiliaryTypes.equals(that.auxiliaryTypes);
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ int result = Arrays.hashCode(binaryRepresentation);
+ result = 31 * result + auxiliaryTypes.hashCode();
+ result = 31 * result + Default.this.hashCode();
+ return result;
+ }
+ }
+
+ /**
+ * A class validator that validates that a class only defines members that are appropriate for the sort of the generated class.
+ */
+ protected static class ValidatingClassVisitor extends ClassVisitor {
+
+ /**
+ * Indicates that a method has no method parameters.
+ */
+ private static final String NO_PARAMETERS = "()";
+
+ /**
+ * Indicates that a method returns void.
+ */
+ private static final String RETURNS_VOID = "V";
+
+ /**
+ * The descriptor of the {@link String} type.
+ */
+ private static final String STRING_DESCRIPTOR = "Ljava/lang/String;";
+
+ /**
+ * Indicates that a field is ignored.
+ */
+ private static final FieldVisitor IGNORE_FIELD = null;
+
+ /**
+ * Indicates that a method is ignored.
+ */
+ private static final MethodVisitor IGNORE_METHOD = null;
+
+ /**
+ * The constraint to assert the members against. The constraint is first defined when the general class information is visited.
+ */
+ private Constraint constraint;
+
+ /**
+ * Creates a validating class visitor.
+ *
+ * @param classVisitor The class visitor to which any calls are delegated to.
+ */
+ protected ValidatingClassVisitor(ClassVisitor classVisitor) {
+ super(Opcodes.ASM5, classVisitor);
+ }
+
+ /**
+ * Adds a validating visitor if type validation is enabled.
+ *
+ * @param classVisitor The original class visitor.
+ * @param typeValidation The type validation state.
+ * @return A class visitor that applies type validation if this is required.
+ */
+ protected static ClassVisitor of(ClassVisitor classVisitor, TypeValidation typeValidation) {
+ return typeValidation.isEnabled()
+ ? new ValidatingClassVisitor(classVisitor)
+ : classVisitor;
+ }
+
+ @Override
+ public void visit(int version, int modifiers, String name, String signature, String superName, String[] interfaces) {
+ ClassFileVersion classFileVersion = ClassFileVersion.ofMinorMajor(version);
+ List<Constraint> constraints = new ArrayList<Constraint>();
+ constraints.add(new Constraint.ForClassFileVersion(classFileVersion));
+ if (name.endsWith('/' + PackageDescription.PACKAGE_CLASS_NAME)) {
+ constraints.add(Constraint.ForPackageType.INSTANCE);
+ } else if ((modifiers & Opcodes.ACC_ANNOTATION) != 0) {
+ if (!classFileVersion.isAtLeast(ClassFileVersion.JAVA_V5)) {
+ throw new IllegalStateException("Cannot define an annotation type for class file version " + classFileVersion);
+ }
+ constraints.add(classFileVersion.isAtLeast(ClassFileVersion.JAVA_V8)
+ ? Constraint.ForAnnotation.JAVA_8
+ : Constraint.ForAnnotation.CLASSIC);
+ } else if ((modifiers & Opcodes.ACC_INTERFACE) != 0) {
+ constraints.add(classFileVersion.isAtLeast(ClassFileVersion.JAVA_V8)
+ ? Constraint.ForInterface.JAVA_8
+ : Constraint.ForInterface.CLASSIC);
+ } else if ((modifiers & Opcodes.ACC_ABSTRACT) != 0) {
+ constraints.add(Constraint.ForClass.ABSTRACT);
+ } else {
+ constraints.add(Constraint.ForClass.MANIFEST);
+ }
+ constraint = new Constraint.Compound(constraints);
+ constraint.assertType(modifiers, interfaces != null, signature != null);
+ super.visit(version, modifiers, name, signature, superName, interfaces);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+ constraint.assertAnnotation();
+ return super.visitAnnotation(descriptor, visible);
+ }
+
+ @Override
+ public AnnotationVisitor visitTypeAnnotation(int typeReference, TypePath typePath, String descriptor, boolean visible) {
+ constraint.assertTypeAnnotation();
+ return super.visitTypeAnnotation(typeReference, typePath, descriptor, visible);
+ }
+
+ @Override
+ public FieldVisitor visitField(int modifiers, String name, String descriptor, String signature, Object defaultValue) {
+ if (defaultValue != null) {
+ Class<?> type;
+ switch (descriptor.charAt(0)) {
+ case 'Z':
+ case 'B':
+ case 'C':
+ case 'S':
+ case 'I':
+ type = Integer.class;
+ break;
+ case 'J':
+ type = Long.class;
+ break;
+ case 'F':
+ type = Float.class;
+ break;
+ case 'D':
+ type = Double.class;
+ break;
+ default:
+ if (!descriptor.equals(STRING_DESCRIPTOR)) {
+ throw new IllegalStateException("Cannot define a default value for type of field " + name);
+ }
+ type = String.class;
+ }
+ if (!type.isInstance(defaultValue)) {
+ throw new IllegalStateException("Field " + name + " defines an incompatible default value " + defaultValue);
+ } else if (type == Integer.class) {
+ int minimum, maximum;
+ switch (descriptor.charAt(0)) {
+ case 'Z':
+ minimum = 0;
+ maximum = 1;
+ break;
+ case 'B':
+ minimum = Byte.MIN_VALUE;
+ maximum = Byte.MAX_VALUE;
+ break;
+ case 'C':
+ minimum = Character.MIN_VALUE;
+ maximum = Character.MAX_VALUE;
+ break;
+ case 'S':
+ minimum = Short.MIN_VALUE;
+ maximum = Short.MAX_VALUE;
+ break;
+ default:
+ minimum = Integer.MIN_VALUE;
+ maximum = Integer.MAX_VALUE;
+ }
+ int value = (Integer) defaultValue;
+ if (value < minimum || value > maximum) {
+ throw new IllegalStateException("Field " + name + " defines an incompatible default value " + defaultValue);
+ }
+ }
+ }
+ constraint.assertField(name,
+ (modifiers & Opcodes.ACC_PUBLIC) != 0,
+ (modifiers & Opcodes.ACC_STATIC) != 0,
+ (modifiers & Opcodes.ACC_FINAL) != 0,
+ signature != null);
+ FieldVisitor fieldVisitor = super.visitField(modifiers, name, descriptor, signature, defaultValue);
+ return fieldVisitor == null
+ ? IGNORE_FIELD
+ : new ValidatingFieldVisitor(fieldVisitor);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int modifiers, String name, String descriptor, String signature, String[] exceptions) {
+ constraint.assertMethod(name,
+ (modifiers & Opcodes.ACC_ABSTRACT) != 0,
+ (modifiers & Opcodes.ACC_PUBLIC) != 0,
+ (modifiers & Opcodes.ACC_PRIVATE) != 0,
+ (modifiers & Opcodes.ACC_STATIC) != 0,
+ !name.equals(MethodDescription.CONSTRUCTOR_INTERNAL_NAME)
+ && !name.equals(MethodDescription.TYPE_INITIALIZER_INTERNAL_NAME)
+ && (modifiers & (Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC)) == 0,
+ name.equals(MethodDescription.CONSTRUCTOR_INTERNAL_NAME),
+ !descriptor.startsWith(NO_PARAMETERS) || descriptor.endsWith(RETURNS_VOID),
+ signature != null);
+ MethodVisitor methodVisitor = super.visitMethod(modifiers, name, descriptor, signature, exceptions);
+ return methodVisitor == null
+ ? IGNORE_METHOD
+ : new ValidatingMethodVisitor(methodVisitor, name);
+ }
+
+ /**
+ * A constraint for members that are legal for a given type.
+ */
+ protected interface Constraint {
+
+ /**
+ * Asserts if the type can legally represent a package description.
+ *
+ * @param modifier The modifier that is to be written to the type.
+ * @param definesInterfaces {@code true} if this type implements at least one interface.
+ * @param isGeneric {@code true} if this type defines a generic type signature.
+ */
+ void assertType(int modifier, boolean definesInterfaces, boolean isGeneric);
+
+ /**
+ * Asserts a field for being valid.
+ *
+ * @param name The name of the field.
+ * @param isPublic {@code true} if this field is public.
+ * @param isStatic {@code true} if this field is static.
+ * @param isFinal {@code true} if this field is final.
+ * @param isGeneric {@code true} if this field defines a generic signature.
+ */
+ void assertField(String name, boolean isPublic, boolean isStatic, boolean isFinal, boolean isGeneric);
+
+ /**
+ * Asserts a method for being valid.
+ *
+ * @param name The name of the method.
+ * @param isAbstract {@code true} if the method is abstract.
+ * @param isPublic {@code true} if this method is public.
+ * @param isPrivate {@code true} if this method is private.
+ * @param isStatic {@code true} if this method is static.
+ * @param isVirtual {@code true} if this method is virtual.
+ * @param isConstructor {@code true} if this method is a constructor.
+ * @param isDefaultValueIncompatible {@code true} if a method's signature cannot describe an annotation property method.
+ * @param isGeneric {@code true} if this method defines a generic signature.
+ */
+ void assertMethod(String name,
+ boolean isAbstract,
+ boolean isPublic,
+ boolean isPrivate,
+ boolean isStatic,
+ boolean isVirtual,
+ boolean isConstructor,
+ boolean isDefaultValueIncompatible,
+ boolean isGeneric);
+
+ /**
+ * Asserts the legitimacy of an annotation for the instrumented type.
+ */
+ void assertAnnotation();
+
+ /**
+ * Asserts the legitimacy of a type annotation for the instrumented type.
+ */
+ void assertTypeAnnotation();
+
+ /**
+ * Asserts if a default value is legal for a method.
+ *
+ * @param name The name of the method.
+ */
+ void assertDefaultValue(String name);
+
+ /**
+ * Asserts if it is legal to invoke a default method from a type.
+ */
+ void assertDefaultMethodCall();
+
+ /**
+ * Asserts the capability to store a type constant in the class's constant pool.
+ */
+ void assertTypeInConstantPool();
+
+ /**
+ * Asserts the capability to store a method type constant in the class's constant pool.
+ */
+ void assertMethodTypeInConstantPool();
+
+ /**
+ * Asserts the capability to store a method handle in the class's constant pool.
+ */
+ void assertHandleInConstantPool();
+
+ /**
+ * Asserts the capability to invoke a method dynamically.
+ */
+ void assertInvokeDynamic();
+
+ /**
+ * Asserts the capability of executing a subroutine.
+ */
+ void assertSubRoutine();
+
+ /**
+ * Represents the constraint of a class type.
+ */
+ enum ForClass implements Constraint {
+
+ /**
+ * Represents the constraints of a non-abstract class.
+ */
+ MANIFEST(true),
+
+ /**
+ * Represents the constraints of an abstract class.
+ */
+ ABSTRACT(false);
+
+ /**
+ * {@code true} if this instance represents the constraints a non-abstract class.
+ */
+ private final boolean manifestType;
+
+ /**
+ * Creates a new constraint for a class.
+ *
+ * @param manifestType {@code true} if this instance represents a non-abstract class.
+ */
+ ForClass(boolean manifestType) {
+ this.manifestType = manifestType;
+ }
+
+ @Override
+ public void assertType(int modifier, boolean definesInterfaces, boolean isGeneric) {
+ /* do nothing */
+ }
+
+ @Override
+ public void assertField(String name, boolean isPublic, boolean isStatic, boolean isFinal, boolean isGeneric) {
+ /* do nothing */
+ }
+
+ @Override
+ public void assertMethod(String name,
+ boolean isAbstract,
+ boolean isPublic,
+ boolean isPrivate,
+ boolean isStatic,
+ boolean isVirtual,
+ boolean isConstructor,
+ boolean isDefaultValueIncompatible,
+ boolean isGeneric) {
+ if (isAbstract && manifestType) {
+ throw new IllegalStateException("Cannot define abstract method '" + name + "' for non-abstract class");
+ }
+ }
+
+ @Override
+ public void assertAnnotation() {
+ /* do nothing */
+ }
+
+ @Override
+ public void assertTypeAnnotation() {
+ /* do nothing */
+ }
+
+ @Override
+ public void assertDefaultValue(String name) {
+ throw new IllegalStateException("Cannot define default value for '" + name + "' for non-annotation type");
+ }
+
+ @Override
+ public void assertDefaultMethodCall() {
+ /* do nothing */
+ }
+
+ @Override
+ public void assertTypeInConstantPool() {
+ /* do nothing */
+ }
+
+ @Override
+ public void assertMethodTypeInConstantPool() {
+ /* do nothing */
+ }
+
+ @Override
+ public void assertHandleInConstantPool() {
+ /* do nothing */
+ }
+
+ @Override
+ public void assertInvokeDynamic() {
+ /* do nothing */
+ }
+
+ @Override
+ public void assertSubRoutine() {
+ /* do nothing */
+ }
+ }
+
+ /**
+ * Represents the constraint of a package type.
+ */
+ enum ForPackageType implements Constraint {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public void assertField(String name, boolean isPublic, boolean isStatic, boolean isFinal, boolean isGeneric) {
+ throw new IllegalStateException("Cannot define a field for a package description type");
+ }
+
+ @Override
+ public void assertMethod(String name,
+ boolean isAbstract,
+ boolean isPublic,
+ boolean isPrivate,
+ boolean isStatic,
+ boolean isVirtual,
+ boolean isConstructor,
+ boolean isNoDefaultValue,
+ boolean isGeneric) {
+ throw new IllegalStateException("Cannot define a method for a package description type");
+ }
+
+ @Override
+ public void assertAnnotation() {
+ /* do nothing */
+ }
+
+ @Override
+ public void assertTypeAnnotation() {
+ /* do nothing */
+ }
+
+ @Override
+ public void assertDefaultValue(String name) {
+ /* do nothing, implicit by forbidding methods */
+ }
+
+ @Override
+ public void assertDefaultMethodCall() {
+ /* do nothing */
+ }
+
+ @Override
+ public void assertTypeInConstantPool() {
+ /* do nothing */
+ }
+
+ @Override
+ public void assertMethodTypeInConstantPool() {
+ /* do nothing */
+ }
+
+ @Override
+ public void assertHandleInConstantPool() {
+ /* do nothing */
+ }
+
+ @Override
+ public void assertInvokeDynamic() {
+ /* do nothing */
+ }
+
+ @Override
+ public void assertSubRoutine() {
+ /* do nothing */
+ }
+
+ @Override
+ public void assertType(int modifier, boolean definesInterfaces, boolean isGeneric) {
+ if (modifier != PackageDescription.PACKAGE_MODIFIERS) {
+ throw new IllegalStateException("A package description type must define " + PackageDescription.PACKAGE_MODIFIERS + " as modifier");
+ } else if (definesInterfaces) {
+ throw new IllegalStateException("Cannot implement interface for package type");
+ }
+ }
+ }
+
+ /**
+ * Represents the constraint of an interface type.
+ */
+ enum ForInterface implements Constraint {
+
+ /**
+ * An interface type with the constrains for the Java versions 5 to 7.
+ */
+ CLASSIC(true),
+
+ /**
+ * An interface type with the constrains for the Java versions 8+.
+ */
+ JAVA_8(false);
+
+ /**
+ * {@code true} if this instance represents a classic interface type (pre Java 8).
+ */
+ private final boolean classic;
+
+ /**
+ * Creates a constraint for an interface type.
+ *
+ * @param classic {@code true} if this instance represents a classic interface (pre Java 8).
+ */
+ ForInterface(boolean classic) {
+ this.classic = classic;
+ }
+
+ @Override
+ public void assertField(String name, boolean isPublic, boolean isStatic, boolean isFinal, boolean isGeneric) {
+ if (!isStatic || !isPublic || !isFinal) {
+ throw new IllegalStateException("Cannot only define public, static, final field '" + name + "' for interface type");
+ }
+ }
+
+ @Override
+ public void assertMethod(String name,
+ boolean isAbstract,
+ boolean isPublic,
+ boolean isPrivate,
+ boolean isStatic,
+ boolean isVirtual,
+ boolean isConstructor,
+ boolean isDefaultValueIncompatible,
+ boolean isGeneric) {
+ if (!name.equals(MethodDescription.TYPE_INITIALIZER_INTERNAL_NAME)) {
+ if (isConstructor) {
+ throw new IllegalStateException("Cannot define constructor for interface type");
+ } else if (classic && !isPublic) {
+ throw new IllegalStateException("Cannot define non-public method '" + name + "' for interface type");
+ } else if (classic && !isVirtual) {
+ throw new IllegalStateException("Cannot define non-virtual method '" + name + "' for a pre-Java 8 interface type");
+ } else if (classic && !isAbstract) {
+ throw new IllegalStateException("Cannot define default method '" + name + "' for pre-Java 8 interface type");
+ }
+ }
+ }
+
+ @Override
+ public void assertAnnotation() {
+ /* do nothing */
+ }
+
+ @Override
+ public void assertTypeAnnotation() {
+ /* do nothing */
+ }
+
+ @Override
+ public void assertDefaultValue(String name) {
+ throw new IllegalStateException("Cannot define default value for '" + name + "' for non-annotation type");
+ }
+
+ @Override
+ public void assertDefaultMethodCall() {
+ /* do nothing */
+ }
+
+ @Override
+ public void assertType(int modifier, boolean definesInterfaces, boolean isGeneric) {
+ /* do nothing */
+ }
+
+ @Override
+ public void assertTypeInConstantPool() {
+ /* do nothing */
+ }
+
+ @Override
+ public void assertMethodTypeInConstantPool() {
+ /* do nothing */
+ }
+
+ @Override
+ public void assertHandleInConstantPool() {
+ /* do nothing */
+ }
+
+ @Override
+ public void assertInvokeDynamic() {
+ /* do nothing */
+ }
+
+ @Override
+ public void assertSubRoutine() {
+ /* do nothing */
+ }
+ }
+
+ /**
+ * Represents the constraint of an annotation type.
+ */
+ enum ForAnnotation implements Constraint {
+
+ /**
+ * An annotation type with the constrains for the Java versions 5 to 7.
+ */
+ CLASSIC(true),
+
+ /**
+ * An annotation type with the constrains for the Java versions 8+.
+ */
+ JAVA_8(false);
+
+ /**
+ * {@code true} if this instance represents a classic annotation type (pre Java 8).
+ */
+ private final boolean classic;
+
+ /**
+ * Creates a constraint for an annotation type.
+ *
+ * @param classic {@code true} if this instance represents a classic annotation type (pre Java 8).
+ */
+ ForAnnotation(boolean classic) {
+ this.classic = classic;
+ }
+
+ @Override
+ public void assertField(String name, boolean isPublic, boolean isStatic, boolean isFinal, boolean isGeneric) {
+ if (!isStatic || !isPublic || !isFinal) {
+ throw new IllegalStateException("Cannot only define public, static, final field '" + name + "' for interface type");
+ }
+ }
+
+ @Override
+ public void assertMethod(String name,
+ boolean isAbstract,
+ boolean isPublic,
+ boolean isPrivate,
+ boolean isStatic,
+ boolean isVirtual,
+ boolean isConstructor,
+ boolean isDefaultValueIncompatible,
+ boolean isGeneric) {
+ if (!name.equals(MethodDescription.TYPE_INITIALIZER_INTERNAL_NAME)) {
+ if (isConstructor) {
+ throw new IllegalStateException("Cannot define constructor for interface type");
+ } else if (classic && !isVirtual) {
+ throw new IllegalStateException("Cannot define non-virtual method '" + name + "' for a pre-Java 8 annotation type");
+ } else if (!isStatic && isDefaultValueIncompatible) {
+ throw new IllegalStateException("Cannot define method '" + name + "' with the given signature as an annotation type method");
+ }
+ }
+ }
+
+ @Override
+ public void assertAnnotation() {
+ /* do nothing */
+ }
+
+ @Override
+ public void assertTypeAnnotation() {
+ /* do nothing */
+ }
+
+ @Override
+ public void assertDefaultValue(String name) {
+ /* do nothing */
+ }
+
+ @Override
+ public void assertDefaultMethodCall() {
+ /* do nothing */
+ }
+
+ @Override
+ public void assertType(int modifier, boolean definesInterfaces, boolean isGeneric) {
+ if ((modifier & Opcodes.ACC_INTERFACE) == 0) {
+ throw new IllegalStateException("Cannot define annotation type without interface modifier");
+ }
+ }
+
+ @Override
+ public void assertTypeInConstantPool() {
+ /* do nothing */
+ }
+
+ @Override
+ public void assertMethodTypeInConstantPool() {
+ /* do nothing */
+ }
+
+ @Override
+ public void assertHandleInConstantPool() {
+ /* do nothing */
+ }
+
+ @Override
+ public void assertInvokeDynamic() {
+ /* do nothing */
+ }
+
+ @Override
+ public void assertSubRoutine() {
+ /* do nothing */
+ }
+ }
+
+ /**
+ * Represents the constraint implied by a class file version.
+ */
+ @EqualsAndHashCode
+ class ForClassFileVersion implements Constraint {
+
+ /**
+ * The enforced class file version.
+ */
+ private final ClassFileVersion classFileVersion;
+
+ /**
+ * Creates a new constraint for the given class file version.
+ *
+ * @param classFileVersion The enforced class file version.
+ */
+ protected ForClassFileVersion(ClassFileVersion classFileVersion) {
+ this.classFileVersion = classFileVersion;
+ }
+
+ @Override
+ public void assertType(int modifiers, boolean definesInterfaces, boolean isGeneric) {
+ if ((modifiers & Opcodes.ACC_ANNOTATION) != 0 && !classFileVersion.isAtLeast(ClassFileVersion.JAVA_V5)) {
+ throw new IllegalStateException("Cannot define annotation type for class file version " + classFileVersion);
+ } else if (isGeneric && !classFileVersion.isAtLeast(ClassFileVersion.JAVA_V5)) {
+ throw new IllegalStateException("Cannot define a generic type for class file version " + classFileVersion);
+ }
+ }
+
+ @Override
+ public void assertField(String name, boolean isPublic, boolean isStatic, boolean isFinal, boolean isGeneric) {
+ if (isGeneric && !classFileVersion.isAtLeast(ClassFileVersion.JAVA_V5)) {
+ throw new IllegalStateException("Cannot define generic field '" + name + "' for class file version " + classFileVersion);
+ }
+ }
+
+ @Override
+ public void assertMethod(String name,
+ boolean isAbstract,
+ boolean isPublic,
+ boolean isPrivate,
+ boolean isStatic,
+ boolean isVirtual,
+ boolean isConstructor,
+ boolean isDefaultValueIncompatible,
+ boolean isGeneric) {
+ if (isGeneric && !classFileVersion.isAtLeast(ClassFileVersion.JAVA_V5)) {
+ throw new IllegalStateException("Cannot define generic method '" + name + "' for class file version " + classFileVersion);
+ } else if (!isVirtual && isAbstract) {
+ throw new IllegalStateException("Cannot define static or non-virtual method '" + name + "' to be abstract");
+ }
+ }
+
+ @Override
+ public void assertAnnotation() {
+ if (!classFileVersion.isAtLeast(ClassFileVersion.JAVA_V5)) {
+ throw new IllegalStateException("Cannot write annotations for class file version " + classFileVersion);
+ }
+ }
+
+ @Override
+ public void assertTypeAnnotation() {
+ if (!classFileVersion.isAtLeast(ClassFileVersion.JAVA_V5)) {
+ throw new IllegalStateException("Cannot write type annotations for class file version " + classFileVersion);
+ }
+ }
+
+ @Override
+ public void assertDefaultValue(String name) {
+ /* do nothing, implicitly checked by type assertion */
+ }
+
+ @Override
+ public void assertDefaultMethodCall() {
+ if (classFileVersion.isLessThan(ClassFileVersion.JAVA_V8)) {
+ throw new IllegalStateException("Cannot invoke default method for class file version " + classFileVersion);
+ }
+ }
+
+ @Override
+ public void assertTypeInConstantPool() {
+ if (!classFileVersion.isAtLeast(ClassFileVersion.JAVA_V5)) {
+ throw new IllegalStateException("Cannot write type to constant pool for class file version " + classFileVersion);
+ }
+ }
+
+ @Override
+ public void assertMethodTypeInConstantPool() {
+ if (!classFileVersion.isAtLeast(ClassFileVersion.JAVA_V7)) {
+ throw new IllegalStateException("Cannot write method type to constant pool for class file version " + classFileVersion);
+ }
+ }
+
+ @Override
+ public void assertHandleInConstantPool() {
+ if (!classFileVersion.isAtLeast(ClassFileVersion.JAVA_V7)) {
+ throw new IllegalStateException("Cannot write method handle to constant pool for class file version " + classFileVersion);
+ }
+ }
+
+ @Override
+ public void assertInvokeDynamic() {
+ if (!classFileVersion.isAtLeast(ClassFileVersion.JAVA_V7)) {
+ throw new IllegalStateException("Cannot write invoke dynamic instruction for class file version " + classFileVersion);
+ }
+ }
+
+ @Override
+ public void assertSubRoutine() {
+ if (!classFileVersion.isLessThan(ClassFileVersion.JAVA_V6)) {
+ throw new IllegalStateException("Cannot write subroutine for class file version " + classFileVersion);
+ }
+ }
+ }
+
+ /**
+ * A constraint implementation that summarizes several constraints.
+ */
+ @EqualsAndHashCode
+ class Compound implements Constraint {
+
+ /**
+ * A list of constraints that is enforced in the given order.
+ */
+ private final List<Constraint> constraints;
+
+ /**
+ * Creates a new compound constraint.
+ *
+ * @param constraints A list of constraints that is enforced in the given order.
+ */
+ public Compound(List<? extends Constraint> constraints) {
+ this.constraints = new ArrayList<Constraint>();
+ for (Constraint constraint : constraints) {
+ if (constraint instanceof Compound) {
+ this.constraints.addAll(((Compound) constraint).constraints);
+ } else {
+ this.constraints.add(constraint);
+ }
+ }
+ }
+
+ @Override
+ public void assertType(int modifier, boolean definesInterfaces, boolean isGeneric) {
+ for (Constraint constraint : constraints) {
+ constraint.assertType(modifier, definesInterfaces, isGeneric);
+ }
+ }
+
+ @Override
+ public void assertField(String name, boolean isPublic, boolean isStatic, boolean isFinal, boolean isGeneric) {
+ for (Constraint constraint : constraints) {
+ constraint.assertField(name, isPublic, isStatic, isFinal, isGeneric);
+ }
+ }
+
+ @Override
+ public void assertMethod(String name,
+ boolean isAbstract,
+ boolean isPublic,
+ boolean isPrivate,
+ boolean isStatic,
+ boolean isVirtual,
+ boolean isConstructor,
+ boolean isDefaultValueIncompatible,
+ boolean isGeneric) {
+ for (Constraint constraint : constraints) {
+ constraint.assertMethod(name,
+ isAbstract,
+ isPublic,
+ isPrivate,
+ isStatic,
+ isVirtual,
+ isConstructor,
+ isDefaultValueIncompatible,
+ isGeneric);
+ }
+ }
+
+ @Override
+ public void assertDefaultValue(String name) {
+ for (Constraint constraint : constraints) {
+ constraint.assertDefaultValue(name);
+ }
+ }
+
+ @Override
+ public void assertDefaultMethodCall() {
+ for (Constraint constraint : constraints) {
+ constraint.assertDefaultMethodCall();
+ }
+ }
+
+ @Override
+ public void assertAnnotation() {
+ for (Constraint constraint : constraints) {
+ constraint.assertAnnotation();
+ }
+ }
+
+ @Override
+ public void assertTypeAnnotation() {
+ for (Constraint constraint : constraints) {
+ constraint.assertTypeAnnotation();
+ }
+ }
+
+ @Override
+ public void assertTypeInConstantPool() {
+ for (Constraint constraint : constraints) {
+ constraint.assertTypeInConstantPool();
+ }
+ }
+
+ @Override
+ public void assertMethodTypeInConstantPool() {
+ for (Constraint constraint : constraints) {
+ constraint.assertMethodTypeInConstantPool();
+ }
+ }
+
+ @Override
+ public void assertHandleInConstantPool() {
+ for (Constraint constraint : constraints) {
+ constraint.assertHandleInConstantPool();
+ }
+ }
+
+ @Override
+ public void assertInvokeDynamic() {
+ for (Constraint constraint : constraints) {
+ constraint.assertInvokeDynamic();
+ }
+ }
+
+ @Override
+ public void assertSubRoutine() {
+ for (Constraint constraint : constraints) {
+ constraint.assertSubRoutine();
+ }
+ }
+ }
+ }
+
+ /**
+ * A field validator for checking default values.
+ */
+ protected class ValidatingFieldVisitor extends FieldVisitor {
+
+ /**
+ * Creates a validating field visitor.
+ *
+ * @param fieldVisitor The field visitor to which any calls are delegated to.
+ */
+ protected ValidatingFieldVisitor(FieldVisitor fieldVisitor) {
+ super(Opcodes.ASM5, fieldVisitor);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ constraint.assertAnnotation();
+ return super.visitAnnotation(desc, visible);
+ }
+ }
+
+ /**
+ * A method validator for checking default values.
+ */
+ protected class ValidatingMethodVisitor extends MethodVisitor {
+
+ /**
+ * The name of the method being visited.
+ */
+ private final String name;
+
+ /**
+ * Creates a validating method visitor.
+ *
+ * @param methodVisitor The method visitor to which any calls are delegated to.
+ * @param name The name of the method being visited.
+ */
+ protected ValidatingMethodVisitor(MethodVisitor methodVisitor, String name) {
+ super(Opcodes.ASM5, methodVisitor);
+ this.name = name;
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ constraint.assertAnnotation();
+ return super.visitAnnotation(desc, visible);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotationDefault() {
+ constraint.assertDefaultValue(name);
+ return super.visitAnnotationDefault();
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "SF_SWITCH_NO_DEFAULT", justification = "Fall through to default case is intentional")
+ public void visitLdcInsn(Object constant) {
+ if (constant instanceof Type) {
+ Type type = (Type) constant;
+ switch (type.getSort()) {
+ case Type.OBJECT:
+ case Type.ARRAY:
+ constraint.assertTypeInConstantPool();
+ break;
+ case Type.METHOD:
+ constraint.assertMethodTypeInConstantPool();
+ break;
+ }
+ } else if (constant instanceof Handle) {
+ constraint.assertHandleInConstantPool();
+ }
+ super.visitLdcInsn(constant);
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
+ if (isInterface && opcode == Opcodes.INVOKESPECIAL) {
+ constraint.assertDefaultMethodCall();
+ }
+ super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ }
+
+ @Override
+ public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethod, Object... bootstrapArgument) {
+ constraint.assertInvokeDynamic();
+ super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethod, bootstrapArgument);
+ }
+
+ @Override
+ public void visitJumpInsn(int opcode, Label label) {
+ if (opcode == Opcodes.JSR) {
+ constraint.assertSubRoutine();
+ }
+ super.visitJumpInsn(opcode, label);
+ }
+ }
+ }
+
+ /**
+ * A class writer that piggy-backs on Byte Buddy's {@link ClassFileLocator} to avoid class loading or look-up errors when redefining a class.
+ * This is not available when creating a new class where automatic frame computation is however not normally a requirement.
+ */
+ protected static class FrameComputingClassWriter extends ClassWriter {
+
+ /**
+ * The type pool to use for computing stack map frames, if required.
+ */
+ private final TypePool typePool;
+
+ /**
+ * Creates a new frame computing class writer.
+ *
+ * @param flags The flags to be handed to the writer.
+ * @param typePool The type pool to use for computing stack map frames, if required.
+ */
+ protected FrameComputingClassWriter(int flags, TypePool typePool) {
+ super(flags);
+ this.typePool = typePool;
+ }
+
+ /**
+ * Creates a new frame computing class writer.
+ *
+ * @param classReader The class reader from which the original class is read.
+ * @param flags The flags to be handed to the writer.
+ * @param typePool The type pool to use for computing stack map frames, if required.
+ */
+ protected FrameComputingClassWriter(ClassReader classReader, int flags, TypePool typePool) {
+ super(classReader, flags);
+ this.typePool = typePool;
+ }
+
+ @Override
+ protected String getCommonSuperClass(String leftTypeName, String rightTypeName) {
+ TypeDescription leftType = typePool.describe(leftTypeName.replace('/', '.')).resolve();
+ TypeDescription rightType = typePool.describe(rightTypeName.replace('/', '.')).resolve();
+ if (leftType.isAssignableFrom(rightType)) {
+ return leftType.getInternalName();
+ } else if (leftType.isAssignableTo(rightType)) {
+ return rightType.getInternalName();
+ } else if (leftType.isInterface() || rightType.isInterface()) {
+ return TypeDescription.OBJECT.getInternalName();
+ } else {
+ do {
+ leftType = leftType.getSuperClass().asErasure();
+ } while (!leftType.isAssignableFrom(rightType));
+ return leftType.getInternalName();
+ }
+ }
+ }
+
+ /**
+ * A type writer that inlines the created type into an existing class file.
+ *
+ * @param <U> The best known loaded type for the dynamically created type.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ public static class ForInlining<U> extends Default<U> {
+
+ /**
+ * Indicates that a type does not define a super type in its class file, i.e. the {@link Object} type.
+ */
+ private static final String NO_SUPER_TYPE = null;
+
+ /**
+ * Indicates that a field should be ignored.
+ */
+ private static final FieldVisitor IGNORE_FIELD = null;
+
+ /**
+ * Indicates that a method should be ignored.
+ */
+ private static final MethodVisitor IGNORE_METHOD = null;
+
+ /**
+ * Indicates that an annotation should be ignored.
+ */
+ private static final AnnotationVisitor IGNORE_ANNOTATION = null;
+
+ /**
+ * The method registry to use.
+ */
+ private final MethodRegistry.Prepared methodRegistry;
+
+ /**
+ * The implementation target factory to use.
+ */
+ private final Implementation.Target.Factory implementationTargetFactory;
+
+ /**
+ * The original type that is being redefined or rebased.
+ */
+ private final TypeDescription originalType;
+
+ /**
+ * The class file locator for locating the original type's class file.
+ */
+ private final ClassFileLocator classFileLocator;
+
+ /**
+ * The method rebase resolver to use for rebasing methods.
+ */
+ private final MethodRebaseResolver methodRebaseResolver;
+
+ /**
+ * Creates a new default type writer for creating a new type that is not based on an existing class file.
+ *
+ * @param instrumentedType The instrumented type to be created.
+ * @param classFileVersion The class file version to define auxiliary types in.
+ * @param fieldPool The field pool to use.
+ * @param methodRegistry The method registry to use.
+ * @param implementationTargetFactory The implementation target factory to use.
+ * @param explicitAuxiliaryTypes The explicit auxiliary types to add to the created type.
+ * @param fields The instrumented type's declared fields.
+ * @param methods The instrumented type's declared or virtually inherited methods.
+ * @param instrumentedMethods The instrumented methods relevant to this type creation.
+ * @param loadedTypeInitializer The loaded type initializer to apply onto the created type after loading.
+ * @param typeInitializer The type initializer to include in the created type's type initializer.
+ * @param typeAttributeAppender The type attribute appender to apply onto the instrumented type.
+ * @param asmVisitorWrapper The ASM visitor wrapper to apply onto the class writer.
+ * @param annotationValueFilterFactory The annotation value filter factory to apply.
+ * @param annotationRetention The annotation retention to apply.
+ * @param auxiliaryTypeNamingStrategy The naming strategy for auxiliary types to apply.
+ * @param implementationContextFactory The implementation context factory to apply.
+ * @param typeValidation Determines if a type should be explicitly validated.
+ * @param typePool The type pool to use for computing stack map frames, if required.
+ * @param originalType The original type that is being redefined or rebased.
+ * @param classFileLocator The class file locator for locating the original type's class file.
+ * @param methodRebaseResolver The method rebase resolver to use for rebasing methods.
+ */
+ protected ForInlining(TypeDescription instrumentedType,
+ ClassFileVersion classFileVersion,
+ FieldPool fieldPool,
+ MethodRegistry.Prepared methodRegistry,
+ Implementation.Target.Factory implementationTargetFactory,
+ List<DynamicType> explicitAuxiliaryTypes,
+ FieldList<FieldDescription.InDefinedShape> fields,
+ MethodList<?> methods,
+ MethodList<?> instrumentedMethods,
+ LoadedTypeInitializer loadedTypeInitializer,
+ TypeInitializer typeInitializer,
+ TypeAttributeAppender typeAttributeAppender,
+ AsmVisitorWrapper asmVisitorWrapper,
+ AnnotationValueFilter.Factory annotationValueFilterFactory,
+ AnnotationRetention annotationRetention,
+ AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
+ Implementation.Context.Factory implementationContextFactory,
+ TypeValidation typeValidation,
+ TypePool typePool,
+ TypeDescription originalType,
+ ClassFileLocator classFileLocator,
+ MethodRebaseResolver methodRebaseResolver) {
+ super(instrumentedType,
+ classFileVersion,
+ fieldPool,
+ explicitAuxiliaryTypes,
+ fields,
+ methods,
+ instrumentedMethods,
+ loadedTypeInitializer,
+ typeInitializer,
+ typeAttributeAppender,
+ asmVisitorWrapper,
+ annotationValueFilterFactory,
+ annotationRetention,
+ auxiliaryTypeNamingStrategy,
+ implementationContextFactory,
+ typeValidation,
+ typePool);
+ this.methodRegistry = methodRegistry;
+ this.implementationTargetFactory = implementationTargetFactory;
+ this.originalType = originalType;
+ this.classFileLocator = classFileLocator;
+ this.methodRebaseResolver = methodRebaseResolver;
+ }
+
+ @Override
+ protected UnresolvedType create(TypeInitializer typeInitializer) {
+ try {
+ int writerFlags = asmVisitorWrapper.mergeWriter(AsmVisitorWrapper.NO_FLAGS);
+ int readerFlags = asmVisitorWrapper.mergeReader(AsmVisitorWrapper.NO_FLAGS);
+ ClassReader classReader = new ClassReader(classFileLocator.locate(originalType.getName()).resolve());
+ ClassWriter classWriter = new FrameComputingClassWriter(classReader, writerFlags, typePool);
+ ContextRegistry contextRegistry = new ContextRegistry();
+ classReader.accept(writeTo(ValidatingClassVisitor.of(classWriter, typeValidation),
+ typeInitializer,
+ contextRegistry,
+ writerFlags,
+ readerFlags), readerFlags);
+ return new UnresolvedType(classWriter.toByteArray(), contextRegistry.getAuxiliaryTypes());
+ } catch (IOException exception) {
+ throw new RuntimeException("The class file could not be written", exception);
+ }
+ }
+
+ /**
+ * Creates a class visitor which weaves all changes and additions on the fly.
+ *
+ * @param classVisitor The class visitor to which this entry is to be written to.
+ * @param typeInitializer The type initializer to apply.
+ * @param contextRegistry A context registry to register the lazily created implementation context to.
+ * @param writerFlags The writer flags being used.
+ * @param readerFlags The reader flags being used.
+ * @return A class visitor which is capable of applying the changes.
+ */
+ private ClassVisitor writeTo(ClassVisitor classVisitor,
+ TypeInitializer typeInitializer,
+ ContextRegistry contextRegistry,
+ int writerFlags,
+ int readerFlags) {
+ classVisitor = new RedefinitionClassVisitor(classVisitor, typeInitializer, contextRegistry, writerFlags, readerFlags);
+ return originalType.getName().equals(instrumentedType.getName())
+ ? classVisitor
+ : new ClassRemapper(classVisitor, new SimpleRemapper(originalType.getInternalName(), instrumentedType.getInternalName()));
+ }
+
+ /**
+ * An initialization handler is responsible for handling the creation of the type initializer.
+ */
+ protected interface InitializationHandler {
+
+ /**
+ * Invoked upon completion of writing the instrumented type.
+ *
+ * @param classVisitor The class visitor to write any methods to.
+ * @param implementationContext The implementation context to use.
+ */
+ void complete(ClassVisitor classVisitor, Implementation.Context.ExtractableView implementationContext);
+
+ /**
+ * An initialization handler that creates a new type initializer.
+ */
+ class Creating extends TypeInitializer.Drain.Default implements InitializationHandler {
+
+ /**
+ * Creates a new creating initialization handler.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param methodPool The method pool to use.
+ * @param annotationValueFilterFactory The annotation value filter factory to use.
+ */
+ protected Creating(TypeDescription instrumentedType,
+ MethodPool methodPool,
+ AnnotationValueFilter.Factory annotationValueFilterFactory) {
+ super(instrumentedType, methodPool, annotationValueFilterFactory);
+ }
+
+ @Override
+ public void complete(ClassVisitor classVisitor, Implementation.Context.ExtractableView implementationContext) {
+ implementationContext.drain(this, classVisitor, annotationValueFilterFactory);
+ }
+ }
+
+ /**
+ * An initialization handler that appends code to a previously visited type initializer.
+ */
+ abstract class Appending extends MethodVisitor implements InitializationHandler, TypeInitializer.Drain {
+
+ /**
+ * The instrumented type.
+ */
+ protected final TypeDescription instrumentedType;
+
+ /**
+ * The method pool record for the type initializer.
+ */
+ protected final MethodPool.Record record;
+
+ /**
+ * The used annotation value filter factory.
+ */
+ protected final AnnotationValueFilter.Factory annotationValueFilterFactory;
+
+ /**
+ * The frame writer to use.
+ */
+ protected final FrameWriter frameWriter;
+
+ /**
+ * The currently recorded stack size.
+ */
+ protected int stackSize;
+
+ /**
+ * The currently recorded local variable length.
+ */
+ protected int localVariableLength;
+
+ /**
+ * Creates a new appending initialization handler.
+ *
+ * @param methodVisitor The underlying method visitor.
+ * @param instrumentedType The instrumented type.
+ * @param record The method pool record for the type initializer.
+ * @param annotationValueFilterFactory The used annotation value filter factory.
+ * @param requireFrames {@code true} if the visitor is required to add frames.
+ * @param expandFrames {@code true} if the visitor is required to expand any added frame.
+ */
+ protected Appending(MethodVisitor methodVisitor,
+ TypeDescription instrumentedType,
+ MethodPool.Record record,
+ AnnotationValueFilter.Factory annotationValueFilterFactory,
+ boolean requireFrames,
+ boolean expandFrames) {
+ super(Opcodes.ASM5, methodVisitor);
+ this.instrumentedType = instrumentedType;
+ this.record = record;
+ this.annotationValueFilterFactory = annotationValueFilterFactory;
+ if (!requireFrames) {
+ frameWriter = FrameWriter.NoOp.INSTANCE;
+ } else if (expandFrames) {
+ frameWriter = FrameWriter.Expanding.INSTANCE;
+ } else {
+ frameWriter = new FrameWriter.Active();
+ }
+ }
+
+ /**
+ * Resolves an initialization handler.
+ *
+ * @param enabled {@code true} if the implementation context is enabled, i.e. any {@link TypeInitializer} might be active.
+ * @param methodVisitor The delegation method visitor.
+ * @param instrumentedType The instrumented type.
+ * @param methodPool The method pool to use.
+ * @param annotationValueFilterFactory The annotation value filter factory to use.
+ * @param requireFrames {@code true} if frames must be computed.
+ * @param expandFrames {@code true} if frames must be expanded.
+ * @return An initialization handler which is also guaranteed to be a {@link MethodVisitor}.
+ */
+ protected static InitializationHandler of(boolean enabled,
+ MethodVisitor methodVisitor,
+ TypeDescription instrumentedType,
+ MethodPool methodPool,
+ AnnotationValueFilter.Factory annotationValueFilterFactory,
+ boolean requireFrames,
+ boolean expandFrames) {
+ return enabled
+ ? withDrain(methodVisitor, instrumentedType, methodPool, annotationValueFilterFactory, requireFrames, expandFrames)
+ : withoutDrain(methodVisitor, instrumentedType, methodPool, annotationValueFilterFactory, requireFrames, expandFrames);
+ }
+
+ /**
+ * Resolves an initialization handler with a drain.
+ *
+ * @param methodVisitor The delegation method visitor.
+ * @param instrumentedType The instrumented type.
+ * @param methodPool The method pool to use.
+ * @param annotationValueFilterFactory The annotation value filter factory to use.
+ * @param requireFrames {@code true} if frames must be computed.
+ * @param expandFrames {@code true} if frames must be expanded.
+ * @return An initialization handler which is also guaranteed to be a {@link MethodVisitor}.
+ */
+ private static WithDrain withDrain(MethodVisitor methodVisitor,
+ TypeDescription instrumentedType,
+ MethodPool methodPool,
+ AnnotationValueFilter.Factory annotationValueFilterFactory,
+ boolean requireFrames,
+ boolean expandFrames) {
+ MethodPool.Record record = methodPool.target(new MethodDescription.Latent.TypeInitializer(instrumentedType));
+ return record.getSort().isImplemented()
+ ? new WithDrain.WithActiveRecord(methodVisitor, instrumentedType, record, annotationValueFilterFactory, requireFrames, expandFrames)
+ : new WithDrain.WithoutActiveRecord(methodVisitor, instrumentedType, record, annotationValueFilterFactory, requireFrames, expandFrames);
+ }
+
+ /**
+ * Resolves an initialization handler without a drain.
+ *
+ * @param methodVisitor The delegation method visitor.
+ * @param instrumentedType The instrumented type.
+ * @param methodPool The method pool to use.
+ * @param annotationValueFilterFactory The annotation value filter factory to use.
+ * @param requireFrames {@code true} if frames must be computed.
+ * @param expandFrames {@code true} if frames must be expanded.
+ * @return An initialization handler which is also guaranteed to be a {@link MethodVisitor}.
+ */
+ private static WithoutDrain withoutDrain(MethodVisitor methodVisitor,
+ TypeDescription instrumentedType,
+ MethodPool methodPool,
+ AnnotationValueFilter.Factory annotationValueFilterFactory,
+ boolean requireFrames,
+ boolean expandFrames) {
+ MethodPool.Record record = methodPool.target(new MethodDescription.Latent.TypeInitializer(instrumentedType));
+ return record.getSort().isImplemented()
+ ? new WithoutDrain.WithActiveRecord(methodVisitor, instrumentedType, record, annotationValueFilterFactory, requireFrames, expandFrames)
+ : new WithoutDrain.WithoutActiveRecord(methodVisitor, instrumentedType, record, annotationValueFilterFactory);
+ }
+
+ @Override
+ public void visitCode() {
+ record.applyAttributes(mv, annotationValueFilterFactory);
+ super.visitCode();
+ onStart();
+ }
+
+ /**
+ * Invoked after the user code was visited.
+ */
+ protected abstract void onStart();
+
+ @Override
+ public void visitFrame(int type, int localVariableLength, Object[] localVariable, int stackSize, Object[] stack) {
+ super.visitFrame(type, localVariableLength, localVariable, stackSize, stack);
+ frameWriter.onFrame(type, localVariableLength);
+ }
+
+ @Override
+ public void visitMaxs(int stackSize, int localVariableLength) {
+ this.stackSize = stackSize;
+ this.localVariableLength = localVariableLength;
+ }
+
+ @Override
+ public void visitEnd() {
+ onEnd();
+ }
+
+ /**
+ * Invoked after the user code was completed.
+ */
+ protected abstract void onEnd();
+
+ @Override
+ public void apply(ClassVisitor classVisitor, TypeInitializer typeInitializer, Implementation.Context implementationContext) {
+ ByteCodeAppender.Size size = typeInitializer.apply(mv, implementationContext, new MethodDescription.Latent.TypeInitializer(instrumentedType));
+ stackSize = Math.max(stackSize, size.getOperandStackSize());
+ localVariableLength = Math.max(localVariableLength, size.getLocalVariableSize());
+ onComplete(implementationContext);
+ }
+
+ /**
+ * Invoked upon completion of writing the type initializer.
+ *
+ * @param implementationContext The implementation context to use.
+ */
+ protected abstract void onComplete(Implementation.Context implementationContext);
+
+ @Override
+ public void complete(ClassVisitor classVisitor, Implementation.Context.ExtractableView implementationContext) {
+ implementationContext.drain(this, classVisitor, annotationValueFilterFactory);
+ mv.visitMaxs(stackSize, localVariableLength);
+ mv.visitEnd();
+ }
+
+ /**
+ * A frame writer is responsible for adding empty frames on jumo instructions.
+ */
+ protected interface FrameWriter {
+
+ /**
+ * An empty array.
+ */
+ Object[] EMPTY = new Object[0];
+
+ /**
+ * Informs this frame writer of an observed frame.
+ *
+ * @param type The frame type.
+ * @param localVariableLength The length of the local variables array.
+ */
+ void onFrame(int type, int localVariableLength);
+
+ /**
+ * Emits an empty frame.
+ *
+ * @param methodVisitor The method visitor to write the frame to.
+ */
+ void emitFrame(MethodVisitor methodVisitor);
+
+ /**
+ * A non-operational frame writer.
+ */
+ enum NoOp implements FrameWriter {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public void onFrame(int type, int localVariableLength) {
+ /* do nothing */
+ }
+
+ @Override
+ public void emitFrame(MethodVisitor methodVisitor) {
+ /* do nothing */
+ }
+ }
+
+ /**
+ * A frame writer that creates an expanded frame.
+ */
+ enum Expanding implements FrameWriter {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public void onFrame(int type, int localVariableLength) {
+ /* do nothing */
+ }
+
+ @Override
+ public void emitFrame(MethodVisitor methodVisitor) {
+ methodVisitor.visitFrame(Opcodes.F_NEW, EMPTY.length, EMPTY, EMPTY.length, EMPTY);
+ }
+ }
+
+ /**
+ * An active frame writer that creates the most efficient frame.
+ */
+ class Active implements FrameWriter {
+
+ /**
+ * The current length of the current local variable array.
+ */
+ private int currentLocalVariableLength;
+
+ @Override
+ public void onFrame(int type, int localVariableLength) {
+ switch (type) {
+ case Opcodes.F_SAME:
+ case Opcodes.F_SAME1:
+ break;
+ case Opcodes.F_APPEND:
+ currentLocalVariableLength += localVariableLength;
+ break;
+ case Opcodes.F_CHOP:
+ currentLocalVariableLength -= localVariableLength;
+ break;
+ case Opcodes.F_NEW:
+ case Opcodes.F_FULL:
+ currentLocalVariableLength = localVariableLength;
+ break;
+ default:
+ throw new IllegalStateException("Unexpected frame type: " + type);
+ }
+ }
+
+ @Override
+ public void emitFrame(MethodVisitor methodVisitor) {
+ if (currentLocalVariableLength == 0) {
+ methodVisitor.visitFrame(Opcodes.F_SAME, EMPTY.length, EMPTY, EMPTY.length, EMPTY);
+ } else if (currentLocalVariableLength > 3) {
+ methodVisitor.visitFrame(Opcodes.F_FULL, EMPTY.length, EMPTY, EMPTY.length, EMPTY);
+ } else {
+ methodVisitor.visitFrame(Opcodes.F_CHOP, currentLocalVariableLength, EMPTY, EMPTY.length, EMPTY);
+ }
+ currentLocalVariableLength = 0;
+ }
+ }
+ }
+
+ /**
+ * An initialization handler that appends code to a previously visited type initializer without allowing active
+ * {@link TypeInitializer} registrations.
+ */
+ protected abstract static class WithoutDrain extends Appending {
+
+ /**
+ * Creates a new appending initialization handler without a drain.
+ *
+ * @param methodVisitor The underlying method visitor.
+ * @param instrumentedType The instrumented type.
+ * @param record The method pool record for the type initializer.
+ * @param annotationValueFilterFactory The used annotation value filter factory.
+ * @param requireFrames {@code true} if the visitor is required to add frames.
+ * @param expandFrames {@code true} if the visitor is required to expand any added frame.
+ */
+ protected WithoutDrain(MethodVisitor methodVisitor,
+ TypeDescription instrumentedType,
+ MethodPool.Record record,
+ AnnotationValueFilter.Factory annotationValueFilterFactory,
+ boolean requireFrames,
+ boolean expandFrames) {
+ super(methodVisitor, instrumentedType, record, annotationValueFilterFactory, requireFrames, expandFrames);
+ }
+
+ @Override
+ protected void onStart() {
+ /* do nothing */
+ }
+
+ @Override
+ protected void onEnd() {
+ /* do nothing */
+ }
+
+ /**
+ * An initialization handler that appends code to a previously visited type initializer without allowing active
+ * {@link TypeInitializer} registrations and without an active record.
+ */
+ protected static class WithoutActiveRecord extends WithoutDrain {
+
+ /**
+ * Creates a new appending initialization handler without a drain and without an active record.
+ *
+ * @param methodVisitor The underlying method visitor.
+ * @param instrumentedType The instrumented type.
+ * @param record The method pool record for the type initializer.
+ * @param annotationValueFilterFactory The used annotation value filter factory.
+ */
+ protected WithoutActiveRecord(MethodVisitor methodVisitor,
+ TypeDescription instrumentedType,
+ MethodPool.Record record,
+ AnnotationValueFilter.Factory annotationValueFilterFactory) {
+ super(methodVisitor, instrumentedType, record, annotationValueFilterFactory, false, false);
+ }
+
+ @Override
+ protected void onComplete(Implementation.Context implementationContext) {
+ /* do nothing */
+ }
+ }
+
+ /**
+ * An initialization handler that appends code to a previously visited type initializer without allowing active
+ * {@link TypeInitializer} registrations and with an active record.
+ */
+ protected static class WithActiveRecord extends WithoutDrain {
+
+ /**
+ * The label that indicates the beginning of the active record.
+ */
+ private final Label label;
+
+ /**
+ * Creates a new appending initialization handler without a drain and with an active record.
+ *
+ * @param methodVisitor The underlying method visitor.
+ * @param instrumentedType The instrumented type.
+ * @param record The method pool record for the type initializer.
+ * @param annotationValueFilterFactory The used annotation value filter factory.
+ * @param requireFrames {@code true} if the visitor is required to add frames.
+ * @param expandFrames {@code true} if the visitor is required to expand any added frame.
+ */
+ protected WithActiveRecord(MethodVisitor methodVisitor,
+ TypeDescription instrumentedType,
+ MethodPool.Record record,
+ AnnotationValueFilter.Factory annotationValueFilterFactory,
+ boolean requireFrames,
+ boolean expandFrames) {
+ super(methodVisitor, instrumentedType, record, annotationValueFilterFactory, requireFrames, expandFrames);
+ label = new Label();
+ }
+
+ @Override
+ public void visitInsn(int opcode) {
+ if (opcode == Opcodes.RETURN) {
+ mv.visitJumpInsn(Opcodes.GOTO, label);
+ } else {
+ super.visitInsn(opcode);
+ }
+ }
+
+ @Override
+ protected void onComplete(Implementation.Context implementationContext) {
+ mv.visitLabel(label);
+ frameWriter.emitFrame(mv);
+ ByteCodeAppender.Size size = record.applyCode(mv, implementationContext);
+ stackSize = Math.max(stackSize, size.getOperandStackSize());
+ localVariableLength = Math.max(localVariableLength, size.getLocalVariableSize());
+ }
+
+ }
+ }
+
+ /**
+ * An initialization handler that appends code to a previously visited type initializer with allowing active
+ * {@link TypeInitializer} registrations.
+ */
+ protected abstract static class WithDrain extends Appending {
+
+ /**
+ * A label marking the beginning of the appended code.
+ */
+ protected final Label appended;
+
+ /**
+ * A label marking the beginning og the original type initializer's code.
+ */
+ protected final Label original;
+
+ /**
+ * Creates a new appending initialization handler with a drain.
+ *
+ * @param methodVisitor The underlying method visitor.
+ * @param instrumentedType The instrumented type.
+ * @param record The method pool record for the type initializer.
+ * @param annotationValueFilterFactory The used annotation value filter factory.
+ * @param requireFrames {@code true} if the visitor is required to add frames.
+ * @param expandFrames {@code true} if the visitor is required to expand any added frame.
+ */
+ protected WithDrain(MethodVisitor methodVisitor,
+ TypeDescription instrumentedType,
+ MethodPool.Record record,
+ AnnotationValueFilter.Factory annotationValueFilterFactory,
+ boolean requireFrames,
+ boolean expandFrames) {
+ super(methodVisitor, instrumentedType, record, annotationValueFilterFactory, requireFrames, expandFrames);
+ appended = new Label();
+ original = new Label();
+ }
+
+ @Override
+ protected void onStart() {
+ mv.visitJumpInsn(Opcodes.GOTO, appended);
+ mv.visitLabel(original);
+ frameWriter.emitFrame(mv);
+ }
+
+ @Override
+ protected void onEnd() {
+ mv.visitLabel(appended);
+ frameWriter.emitFrame(mv);
+ }
+
+ @Override
+ protected void onComplete(Implementation.Context implementationContext) {
+ mv.visitJumpInsn(Opcodes.GOTO, original);
+ afterComplete(implementationContext);
+ }
+
+ /**
+ * Invoked after completion of writing the type initializer.
+ *
+ * @param implementationContext The implementation context to use.
+ */
+ protected abstract void afterComplete(Implementation.Context implementationContext);
+
+ /**
+ * A code appending initialization handler with a drain that does not apply an explicit record.
+ */
+ protected static class WithoutActiveRecord extends WithDrain {
+
+ /**
+ * Creates a new appending initialization handler with a drain and without an active record.
+ *
+ * @param methodVisitor The underlying method visitor.
+ * @param instrumentedType The instrumented type.
+ * @param record The method pool record for the type initializer.
+ * @param annotationValueFilterFactory The used annotation value filter factory.
+ * @param requireFrames {@code true} if the visitor is required to add frames.
+ * @param expandFrames {@code true} if the visitor is required to expand any added frame.
+ */
+ protected WithoutActiveRecord(MethodVisitor methodVisitor,
+ TypeDescription instrumentedType,
+ MethodPool.Record record,
+ AnnotationValueFilter.Factory annotationValueFilterFactory,
+ boolean requireFrames,
+ boolean expandFrames) {
+ super(methodVisitor, instrumentedType, record, annotationValueFilterFactory, requireFrames, expandFrames);
+ }
+
+ @Override
+ protected void afterComplete(Implementation.Context implementationContext) {
+ /* do nothing */
+ }
+ }
+
+ /**
+ * A code appending initialization handler with a drain that applies an explicit record.
+ */
+ protected static class WithActiveRecord extends WithDrain {
+
+ /**
+ * A label indicating the beginning of the record's code.
+ */
+ private final Label label;
+
+ /**
+ * Creates a new appending initialization handler with a drain and with an active record.
+ *
+ * @param methodVisitor The underlying method visitor.
+ * @param instrumentedType The instrumented type.
+ * @param record The method pool record for the type initializer.
+ * @param annotationValueFilterFactory The used annotation value filter factory.
+ * @param requireFrames {@code true} if the visitor is required to add frames.
+ * @param expandFrames {@code true} if the visitor is required to expand any added frame.
+ */
+ protected WithActiveRecord(MethodVisitor methodVisitor,
+ TypeDescription instrumentedType,
+ MethodPool.Record record,
+ AnnotationValueFilter.Factory annotationValueFilterFactory,
+ boolean requireFrames,
+ boolean expandFrames) {
+ super(methodVisitor, instrumentedType, record, annotationValueFilterFactory, requireFrames, expandFrames);
+ label = new Label();
+ }
+
+ @Override
+ public void visitInsn(int opcode) {
+ if (opcode == Opcodes.RETURN) {
+ mv.visitJumpInsn(Opcodes.GOTO, label);
+ } else {
+ super.visitInsn(opcode);
+ }
+ }
+
+ @Override
+ protected void afterComplete(Implementation.Context implementationContext) {
+ mv.visitLabel(label);
+ frameWriter.emitFrame(mv);
+ ByteCodeAppender.Size size = record.applyCode(mv, implementationContext);
+ stackSize = Math.max(stackSize, size.getOperandStackSize());
+ localVariableLength = Math.max(localVariableLength, size.getLocalVariableSize());
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * A context registry allows to extract auxiliary types from a lazily created implementation context.
+ */
+ protected static class ContextRegistry {
+
+ /**
+ * The implementation context that is used for creating a class or {@code null} if it was not registered.
+ */
+ private Implementation.Context.ExtractableView implementationContext;
+
+ /**
+ * Registers the implementation context.
+ *
+ * @param implementationContext The implementation context.
+ */
+ public void setImplementationContext(Implementation.Context.ExtractableView implementationContext) {
+ this.implementationContext = implementationContext;
+ }
+
+ /**
+ * Returns the auxiliary types that were registered during class creation. This method must only be called after
+ * a class was created.
+ *
+ * @return The auxiliary types that were registered during class creation
+ */
+ @SuppressFBWarnings(value = "UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", justification = "Lazy value definition is intended")
+ public List<DynamicType> getAuxiliaryTypes() {
+ return implementationContext.getAuxiliaryTypes();
+ }
+ }
+
+ /**
+ * A class visitor which is capable of applying a redefinition of an existing class file.
+ */
+ @SuppressFBWarnings(value = "UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", justification = "Field access order is implied by ASM")
+ protected class RedefinitionClassVisitor extends ClassVisitor {
+
+ /**
+ * The type initializer to apply.
+ */
+ private final TypeInitializer typeInitializer;
+
+ /**
+ * A context registry to register the lazily created implementation context to.
+ */
+ private final ContextRegistry contextRegistry;
+
+ /**
+ * The writer flags being used.
+ */
+ private final int writerFlags;
+
+ /**
+ * The reader flags being used.
+ */
+ private final int readerFlags;
+
+ /**
+ * A mapping of fields to write by their names.
+ */
+ private final LinkedHashMap<String, FieldDescription> declarableFields;
+
+ /**
+ * A mapping of methods to write by a concatenation of internal name and descriptor.
+ */
+ private final LinkedHashMap<String, MethodDescription> declarableMethods;
+
+ /**
+ * The method pool to use or {@code null} if the pool was not yet initialized.
+ */
+ private MethodPool methodPool;
+
+ /**
+ * The initialization handler to use or {@code null} if the handler was not yet initialized.
+ */
+ private InitializationHandler initializationHandler;
+
+ /**
+ * The implementation context for this class creation or {@code null} if it was not yet created.
+ */
+ private Implementation.Context.ExtractableView implementationContext;
+
+ /**
+ * Creates a class visitor which is capable of redefining an existent class on the fly.
+ *
+ * @param classVisitor The underlying class visitor to which writes are delegated.
+ * @param typeInitializer The type initializer to apply.
+ * @param contextRegistry A context registry to register the lazily created implementation context to.
+ * @param writerFlags The writer flags being used.
+ * @param readerFlags The reader flags being used.
+ */
+ protected RedefinitionClassVisitor(ClassVisitor classVisitor,
+ TypeInitializer typeInitializer,
+ ContextRegistry contextRegistry,
+ int writerFlags,
+ int readerFlags) {
+ super(Opcodes.ASM5, classVisitor);
+ this.typeInitializer = typeInitializer;
+ this.contextRegistry = contextRegistry;
+ this.writerFlags = writerFlags;
+ this.readerFlags = readerFlags;
+ declarableFields = new LinkedHashMap<String, FieldDescription>();
+ for (FieldDescription fieldDescription : fields) {
+ declarableFields.put(fieldDescription.getInternalName() + fieldDescription.getDescriptor(), fieldDescription);
+ }
+ declarableMethods = new LinkedHashMap<String, MethodDescription>();
+ for (MethodDescription methodDescription : instrumentedMethods) {
+ declarableMethods.put(methodDescription.getInternalName() + methodDescription.getDescriptor(), methodDescription);
+ }
+ }
+
+ @Override
+ public void visit(int classFileVersionNumber,
+ int modifiers,
+ String internalName,
+ String genericSignature,
+ String superClassInternalName,
+ String[] interfaceTypeInternalName) {
+ ClassFileVersion classFileVersion = ClassFileVersion.ofMinorMajor(classFileVersionNumber);
+ methodPool = methodRegistry.compile(implementationTargetFactory, classFileVersion);
+ initializationHandler = new InitializationHandler.Creating(instrumentedType, methodPool, annotationValueFilterFactory);
+ implementationContext = implementationContextFactory.make(instrumentedType,
+ auxiliaryTypeNamingStrategy,
+ typeInitializer,
+ classFileVersion,
+ ForInlining.this.classFileVersion);
+ contextRegistry.setImplementationContext(implementationContext);
+ cv = asmVisitorWrapper.wrap(instrumentedType,
+ cv,
+ implementationContext,
+ typePool,
+ fields,
+ methods,
+ writerFlags,
+ readerFlags);
+ super.visit(classFileVersionNumber,
+ instrumentedType.getActualModifiers((modifiers & Opcodes.ACC_SUPER) != 0 && !instrumentedType.isInterface())
+ // Anonymous types might not preserve their class file's final modifier via their inner class modifier.
+ | (((modifiers & Opcodes.ACC_FINAL) != 0 && instrumentedType.isAnonymousClass()) ? Opcodes.ACC_FINAL : 0),
+ instrumentedType.getInternalName(),
+ instrumentedType.getGenericSignature(),
+ instrumentedType.getSuperClass() == null
+ ? (instrumentedType.isInterface() ? TypeDescription.OBJECT.getInternalName() : NO_SUPER_TYPE)
+ : instrumentedType.getSuperClass().asErasure().getInternalName(),
+ instrumentedType.getInterfaces().asErasures().toInternalNames());
+ typeAttributeAppender.apply(cv, instrumentedType, annotationValueFilterFactory.on(instrumentedType));
+ }
+
+ @Override
+ public void visitInnerClass(String internalName, String outerName, String innerName, int modifiers) {
+ if (internalName.equals(instrumentedType.getInternalName())) {
+ modifiers = instrumentedType.getModifiers();
+ }
+ super.visitInnerClass(internalName, outerName, innerName, modifiers);
+ }
+
+ @Override
+ public AnnotationVisitor visitTypeAnnotation(int typeReference, TypePath typePath, String descriptor, boolean visible) {
+ return annotationRetention.isEnabled()
+ ? super.visitTypeAnnotation(typeReference, typePath, descriptor, visible)
+ : IGNORE_ANNOTATION;
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+ return annotationRetention.isEnabled()
+ ? super.visitAnnotation(descriptor, visible)
+ : IGNORE_ANNOTATION;
+ }
+
+ @Override
+ public FieldVisitor visitField(int modifiers,
+ String internalName,
+ String descriptor,
+ String genericSignature,
+ Object defaultValue) {
+ FieldDescription fieldDescription = declarableFields.remove(internalName + descriptor);
+ if (fieldDescription != null) {
+ FieldPool.Record record = fieldPool.target(fieldDescription);
+ if (!record.isImplicit()) {
+ return redefine(record, defaultValue);
+ }
+ }
+ return super.visitField(modifiers, internalName, descriptor, genericSignature, defaultValue);
+ }
+
+ /**
+ * Redefines a field using the given explicit field pool record and default value.
+ *
+ * @param record The field pool value to apply during visitation of the existing field.
+ * @param defaultValue The default value to write onto the field which might be {@code null}.
+ * @return A field visitor for visiting the existing field definition.
+ */
+ protected FieldVisitor redefine(FieldPool.Record record, Object defaultValue) {
+ FieldDescription instrumentedField = record.getField();
+ FieldVisitor fieldVisitor = super.visitField(instrumentedField.getActualModifiers(),
+ instrumentedField.getInternalName(),
+ instrumentedField.getDescriptor(),
+ instrumentedField.getGenericSignature(),
+ record.resolveDefault(defaultValue));
+ return fieldVisitor == null
+ ? IGNORE_FIELD
+ : new AttributeObtainingFieldVisitor(fieldVisitor, record);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int modifiers,
+ String internalName,
+ String descriptor,
+ String genericSignature,
+ String[] exceptionName) {
+ if (internalName.equals(MethodDescription.TYPE_INITIALIZER_INTERNAL_NAME)) {
+ MethodVisitor methodVisitor = super.visitMethod(modifiers, internalName, descriptor, genericSignature, exceptionName);
+ return methodVisitor == null
+ ? IGNORE_METHOD
+ : (MethodVisitor) (initializationHandler = InitializationHandler.Appending.of(implementationContext.isEnabled(),
+ methodVisitor,
+ instrumentedType,
+ methodPool,
+ annotationValueFilterFactory,
+ (writerFlags & ClassWriter.COMPUTE_FRAMES) == 0 && implementationContext.getClassFileVersion().isAtLeast(ClassFileVersion.JAVA_V6),
+ (readerFlags & ClassReader.EXPAND_FRAMES) != 0));
+ } else {
+ MethodDescription methodDescription = declarableMethods.remove(internalName + descriptor);
+ return methodDescription == null
+ ? super.visitMethod(modifiers, internalName, descriptor, genericSignature, exceptionName)
+ : redefine(methodDescription, (modifiers & Opcodes.ACC_ABSTRACT) != 0);
+ }
+ }
+
+ /**
+ * Redefines a given method if this is required by looking up a potential implementation from the
+ * {@link net.bytebuddy.dynamic.scaffold.TypeWriter.MethodPool}.
+ *
+ * @param methodDescription The method being considered for redefinition.
+ * @param abstractOrigin {@code true} if the original method is abstract, i.e. there is no implementation
+ * to preserve.
+ * @return A method visitor which is capable of consuming the original method.
+ */
+ protected MethodVisitor redefine(MethodDescription methodDescription, boolean abstractOrigin) {
+ MethodPool.Record record = methodPool.target(methodDescription);
+ if (!record.getSort().isDefined()) {
+ return super.visitMethod(methodDescription.getActualModifiers(),
+ methodDescription.getInternalName(),
+ methodDescription.getDescriptor(),
+ methodDescription.getGenericSignature(),
+ methodDescription.getExceptionTypes().asErasures().toInternalNames());
+ }
+ MethodDescription implementedMethod = record.getMethod();
+ MethodVisitor methodVisitor = super.visitMethod(ModifierContributor.Resolver.of(Collections.singleton(record.getVisibility()))
+ .resolve(implementedMethod.getActualModifiers(record.getSort().isImplemented())),
+ implementedMethod.getInternalName(),
+ implementedMethod.getDescriptor(),
+ implementedMethod.getGenericSignature(),
+ implementedMethod.getExceptionTypes().asErasures().toInternalNames());
+ if (methodVisitor == null) {
+ return IGNORE_METHOD;
+ } else if (abstractOrigin) {
+ return new AttributeObtainingMethodVisitor(methodVisitor, record);
+ } else if (methodDescription.isNative()) {
+ MethodRebaseResolver.Resolution resolution = methodRebaseResolver.resolve(implementedMethod.asDefined());
+ if (resolution.isRebased()) {
+ MethodVisitor rebasedMethodVisitor = super.visitMethod(resolution.getResolvedMethod().getActualModifiers(),
+ resolution.getResolvedMethod().getInternalName(),
+ resolution.getResolvedMethod().getDescriptor(),
+ resolution.getResolvedMethod().getGenericSignature(),
+ resolution.getResolvedMethod().getExceptionTypes().asErasures().toInternalNames());
+ if (rebasedMethodVisitor != null) {
+ rebasedMethodVisitor.visitEnd();
+ }
+ }
+ return new AttributeObtainingMethodVisitor(methodVisitor, record);
+ } else {
+ return new CodePreservingMethodVisitor(methodVisitor, record, methodRebaseResolver.resolve(implementedMethod.asDefined()));
+ }
+ }
+
+ @Override
+ public void visitEnd() {
+ for (FieldDescription fieldDescription : declarableFields.values()) {
+ fieldPool.target(fieldDescription).apply(cv, annotationValueFilterFactory);
+ }
+ for (MethodDescription methodDescription : declarableMethods.values()) {
+ methodPool.target(methodDescription).apply(cv, implementationContext, annotationValueFilterFactory);
+ }
+ initializationHandler.complete(cv, implementationContext);
+ super.visitEnd();
+ }
+
+ /**
+ * A field visitor that obtains all attributes and annotations of a field that is found in the
+ * class file but that discards all code.
+ */
+ protected class AttributeObtainingFieldVisitor extends FieldVisitor {
+
+ /**
+ * The field pool record to apply onto the field visitor.
+ */
+ private final FieldPool.Record record;
+
+ /**
+ * Creates a new attribute obtaining field visitor.
+ *
+ * @param fieldVisitor The field visitor to delegate to.
+ * @param record The field pool record to apply onto the field visitor.
+ */
+ protected AttributeObtainingFieldVisitor(FieldVisitor fieldVisitor, FieldPool.Record record) {
+ super(Opcodes.ASM5, fieldVisitor);
+ this.record = record;
+ }
+
+ @Override
+ public AnnotationVisitor visitTypeAnnotation(int typeReference, TypePath typePath, String descriptor, boolean visible) {
+ return annotationRetention.isEnabled()
+ ? super.visitTypeAnnotation(typeReference, typePath, descriptor, visible)
+ : IGNORE_ANNOTATION;
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+ return annotationRetention.isEnabled()
+ ? super.visitAnnotation(descriptor, visible)
+ : IGNORE_ANNOTATION;
+ }
+
+ @Override
+ public void visitEnd() {
+ record.apply(fv, annotationValueFilterFactory);
+ super.visitEnd();
+ }
+ }
+
+ /**
+ * A method visitor that preserves the code of a method in the class file by copying it into a rebased
+ * method while copying all attributes and annotations to the actual method.
+ */
+ protected class CodePreservingMethodVisitor extends MethodVisitor {
+
+ /**
+ * The method visitor of the actual method.
+ */
+ private final MethodVisitor actualMethodVisitor;
+
+ /**
+ * The method pool entry to apply.
+ */
+ private final MethodPool.Record record;
+
+ /**
+ * The resolution of a potential rebased method.
+ */
+ private final MethodRebaseResolver.Resolution resolution;
+
+ /**
+ * Creates a new code preserving method visitor.
+ *
+ * @param actualMethodVisitor The method visitor of the actual method.
+ * @param record The method pool entry to apply.
+ * @param resolution The resolution of the method rebase resolver in use.
+ */
+ protected CodePreservingMethodVisitor(MethodVisitor actualMethodVisitor,
+ MethodPool.Record record,
+ MethodRebaseResolver.Resolution resolution) {
+ super(Opcodes.ASM5, actualMethodVisitor);
+ this.actualMethodVisitor = actualMethodVisitor;
+ this.record = record;
+ this.resolution = resolution;
+ record.applyHead(actualMethodVisitor);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotationDefault() {
+ return IGNORE_ANNOTATION; // Annotation types can never be rebased.
+ }
+
+ @Override
+ public AnnotationVisitor visitTypeAnnotation(int typeReference, TypePath typePath, String descriptor, boolean visible) {
+ return annotationRetention.isEnabled()
+ ? super.visitTypeAnnotation(typeReference, typePath, descriptor, visible)
+ : IGNORE_ANNOTATION;
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+ return annotationRetention.isEnabled()
+ ? super.visitAnnotation(descriptor, visible)
+ : IGNORE_ANNOTATION;
+ }
+
+ @Override
+ public AnnotationVisitor visitParameterAnnotation(int index, String descriptor, boolean visible) {
+ return annotationRetention.isEnabled()
+ ? super.visitParameterAnnotation(index, descriptor, visible)
+ : IGNORE_ANNOTATION;
+ }
+
+ @Override
+ public void visitCode() {
+ record.applyBody(actualMethodVisitor, implementationContext, annotationValueFilterFactory);
+ actualMethodVisitor.visitEnd();
+ mv = resolution.isRebased()
+ ? cv.visitMethod(resolution.getResolvedMethod().getActualModifiers(),
+ resolution.getResolvedMethod().getInternalName(),
+ resolution.getResolvedMethod().getDescriptor(),
+ resolution.getResolvedMethod().getGenericSignature(),
+ resolution.getResolvedMethod().getExceptionTypes().asErasures().toInternalNames())
+ : IGNORE_METHOD;
+ super.visitCode();
+ }
+
+ @Override
+ public void visitMaxs(int stackSize, int localVariableLength) {
+ super.visitMaxs(stackSize, Math.max(localVariableLength, resolution.getResolvedMethod().getStackSize()));
+ }
+ }
+
+ /**
+ * A method visitor that obtains all attributes and annotations of a method that is found in the
+ * class file but that discards all code.
+ */
+ protected class AttributeObtainingMethodVisitor extends MethodVisitor {
+
+ /**
+ * The method visitor to which the actual method is to be written to.
+ */
+ private final MethodVisitor actualMethodVisitor;
+
+ /**
+ * The method pool entry to apply.
+ */
+ private final MethodPool.Record record;
+
+ /**
+ * Creates a new attribute obtaining method visitor.
+ *
+ * @param actualMethodVisitor The method visitor of the actual method.
+ * @param record The method pool entry to apply.
+ */
+ protected AttributeObtainingMethodVisitor(MethodVisitor actualMethodVisitor, MethodPool.Record record) {
+ super(Opcodes.ASM5, actualMethodVisitor);
+ this.actualMethodVisitor = actualMethodVisitor;
+ this.record = record;
+ record.applyHead(actualMethodVisitor);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotationDefault() {
+ return IGNORE_ANNOTATION;
+ }
+
+ @Override
+ public AnnotationVisitor visitTypeAnnotation(int typeReference, TypePath typePath, String descriptor, boolean visible) {
+ return annotationRetention.isEnabled()
+ ? super.visitTypeAnnotation(typeReference, typePath, descriptor, visible)
+ : IGNORE_ANNOTATION;
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+ return annotationRetention.isEnabled()
+ ? super.visitAnnotation(descriptor, visible)
+ : IGNORE_ANNOTATION;
+ }
+
+ @Override
+ public AnnotationVisitor visitParameterAnnotation(int index, String descriptor, boolean visible) {
+ return annotationRetention.isEnabled()
+ ? super.visitParameterAnnotation(index, descriptor, visible)
+ : IGNORE_ANNOTATION;
+ }
+
+ @Override
+ public void visitCode() {
+ mv = IGNORE_METHOD;
+ }
+
+ @Override
+ public void visitEnd() {
+ record.applyBody(actualMethodVisitor, implementationContext, annotationValueFilterFactory);
+ actualMethodVisitor.visitEnd();
+ }
+ }
+ }
+ }
+
+ /**
+ * A type writer that creates a class file that is not based upon another, existing class.
+ *
+ * @param <U> The best known loaded type for the dynamically created type.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ public static class ForCreation<U> extends Default<U> {
+
+ /**
+ * The method pool to use.
+ */
+ private final MethodPool methodPool;
+
+ /**
+ * Creates a new default type writer for creating a new type that is not based on an existing class file.
+ *
+ * @param instrumentedType The instrumented type to be created.
+ * @param classFileVersion The class file version to write the instrumented type in and to apply when creating auxiliary types.
+ * @param fieldPool The field pool to use.
+ * @param methodPool The method pool to use.
+ * @param auxiliaryTypes A list of auxiliary types to add to the created type.
+ * @param fields The instrumented type's declared fields.
+ * @param methods The instrumented type's declared and virtually inherited methods.
+ * @param instrumentedMethods The instrumented methods relevant to this type creation.
+ * @param loadedTypeInitializer The loaded type initializer to apply onto the created type after loading.
+ * @param typeInitializer The type initializer to include in the created type's type initializer.
+ * @param typeAttributeAppender The type attribute appender to apply onto the instrumented type.
+ * @param asmVisitorWrapper The ASM visitor wrapper to apply onto the class writer.
+ * @param annotationValueFilterFactory The annotation value filter factory to apply.
+ * @param annotationRetention The annotation retention to apply.
+ * @param auxiliaryTypeNamingStrategy The naming strategy for auxiliary types to apply.
+ * @param implementationContextFactory The implementation context factory to apply.
+ * @param typeValidation Determines if a type should be explicitly validated.
+ * @param typePool The type pool to use for computing stack map frames, if required.
+ */
+ protected ForCreation(TypeDescription instrumentedType,
+ ClassFileVersion classFileVersion,
+ FieldPool fieldPool,
+ MethodPool methodPool,
+ List<? extends DynamicType> auxiliaryTypes,
+ FieldList<FieldDescription.InDefinedShape> fields,
+ MethodList<?> methods,
+ MethodList<?> instrumentedMethods,
+ LoadedTypeInitializer loadedTypeInitializer,
+ TypeInitializer typeInitializer,
+ TypeAttributeAppender typeAttributeAppender,
+ AsmVisitorWrapper asmVisitorWrapper,
+ AnnotationValueFilter.Factory annotationValueFilterFactory,
+ AnnotationRetention annotationRetention,
+ AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
+ Implementation.Context.Factory implementationContextFactory,
+ TypeValidation typeValidation,
+ TypePool typePool) {
+ super(instrumentedType,
+ classFileVersion,
+ fieldPool,
+ auxiliaryTypes,
+ fields,
+ methods,
+ instrumentedMethods,
+ loadedTypeInitializer,
+ typeInitializer,
+ typeAttributeAppender,
+ asmVisitorWrapper,
+ annotationValueFilterFactory,
+ annotationRetention,
+ auxiliaryTypeNamingStrategy,
+ implementationContextFactory,
+ typeValidation,
+ typePool);
+ this.methodPool = methodPool;
+ }
+
+ @Override
+ protected UnresolvedType create(TypeInitializer typeInitializer) {
+ int writerFlags = asmVisitorWrapper.mergeWriter(AsmVisitorWrapper.NO_FLAGS);
+ ClassWriter classWriter = new FrameComputingClassWriter(writerFlags, typePool);
+ Implementation.Context.ExtractableView implementationContext = implementationContextFactory.make(instrumentedType,
+ auxiliaryTypeNamingStrategy,
+ typeInitializer,
+ classFileVersion,
+ classFileVersion);
+ ClassVisitor classVisitor = asmVisitorWrapper.wrap(instrumentedType,
+ ValidatingClassVisitor.of(classWriter, typeValidation),
+ implementationContext,
+ typePool,
+ fields,
+ methods,
+ writerFlags,
+ asmVisitorWrapper.mergeReader(AsmVisitorWrapper.NO_FLAGS));
+ classVisitor.visit(classFileVersion.getMinorMajorVersion(),
+ instrumentedType.getActualModifiers(!instrumentedType.isInterface()),
+ instrumentedType.getInternalName(),
+ instrumentedType.getGenericSignature(),
+ (instrumentedType.getSuperClass() == null
+ ? TypeDescription.OBJECT
+ : instrumentedType.getSuperClass().asErasure()).getInternalName(),
+ instrumentedType.getInterfaces().asErasures().toInternalNames());
+ typeAttributeAppender.apply(classVisitor, instrumentedType, annotationValueFilterFactory.on(instrumentedType));
+ for (FieldDescription fieldDescription : fields) {
+ fieldPool.target(fieldDescription).apply(classVisitor, annotationValueFilterFactory);
+ }
+ for (MethodDescription methodDescription : instrumentedMethods) {
+ methodPool.target(methodDescription).apply(classVisitor, implementationContext, annotationValueFilterFactory);
+ }
+ implementationContext.drain(new TypeInitializer.Drain.Default(instrumentedType,
+ methodPool,
+ annotationValueFilterFactory), classVisitor, annotationValueFilterFactory);
+ classVisitor.visitEnd();
+ return new UnresolvedType(classWriter.toByteArray(), implementationContext.getAuxiliaryTypes());
+ }
+ }
+
+ /**
+ * An action to write a class file to the dumping location.
+ */
+ @EqualsAndHashCode
+ protected static class ClassDumpAction implements PrivilegedExceptionAction<Void> {
+
+ /**
+ * Indicates that nothing is returned from this action.
+ */
+ private static final Void NOTHING = null;
+
+ /**
+ * The target folder for writing the class file to.
+ */
+ private final String target;
+
+ /**
+ * The instrumented type.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * The type's binary representation.
+ */
+ private final byte[] binaryRepresentation;
+
+ /**
+ * Creates a new class dump action.
+ *
+ * @param target The target folder for writing the class file to.
+ * @param instrumentedType The instrumented type.
+ * @param binaryRepresentation The type's binary representation.
+ */
+ protected ClassDumpAction(String target, TypeDescription instrumentedType, byte[] binaryRepresentation) {
+ this.target = target;
+ this.instrumentedType = instrumentedType;
+ this.binaryRepresentation = binaryRepresentation;
+ }
+
+ @Override
+ public Void run() throws Exception {
+ OutputStream outputStream = new FileOutputStream(new File(target, instrumentedType.getName() + "." + System.currentTimeMillis()));
+ try {
+ outputStream.write(binaryRepresentation);
+ return NOTHING;
+ } finally {
+ outputStream.close();
+ }
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/inline/AbstractInliningDynamicTypeBuilder.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/inline/AbstractInliningDynamicTypeBuilder.java
new file mode 100644
index 0000000..80df6dd
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/inline/AbstractInliningDynamicTypeBuilder.java
@@ -0,0 +1,95 @@
+package net.bytebuddy.dynamic.scaffold.inline;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.asm.AsmVisitorWrapper;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.TypeResolutionStrategy;
+import net.bytebuddy.dynamic.scaffold.*;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.attribute.AnnotationRetention;
+import net.bytebuddy.implementation.attribute.AnnotationValueFilter;
+import net.bytebuddy.implementation.attribute.TypeAttributeAppender;
+import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
+import net.bytebuddy.matcher.LatentMatcher;
+import net.bytebuddy.pool.TypePool;
+
+/**
+ * An abstract base implementation of a dynamic type builder that alters an existing type.
+ *
+ * @param <T> A loaded type that the dynamic type is guaranteed to be a subtype of.
+ */
+ at EqualsAndHashCode(callSuper = true)
+public abstract class AbstractInliningDynamicTypeBuilder<T> extends DynamicType.Builder.AbstractBase.Adapter<T> {
+
+ /**
+ * The original type that is being redefined or rebased.
+ */
+ protected final TypeDescription originalType;
+
+ /**
+ * The class file locator for locating the original type's class file.
+ */
+ protected final ClassFileLocator classFileLocator;
+
+
+ /**
+ * Creates an inlining dynamic type builder.
+ *
+ * @param instrumentedType An instrumented type representing the subclass.
+ * @param fieldRegistry The field pool to use.
+ * @param methodRegistry The method pool to use.
+ * @param typeAttributeAppender The type attribute appender to apply onto the instrumented type.
+ * @param asmVisitorWrapper The ASM visitor wrapper to apply onto the class writer.
+ * @param classFileVersion The class file version to use for types that are not based on an existing class file.
+ * @param auxiliaryTypeNamingStrategy The naming strategy to use for naming auxiliary types.
+ * @param annotationValueFilterFactory The annotation value filter factory to use.
+ * @param annotationRetention The annotation retention strategy to use.
+ * @param implementationContextFactory The implementation context factory to use.
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @param typeValidation Determines if a type should be explicitly validated.
+ * @param ignoredMethods A matcher for identifying methods that should be excluded from instrumentation.
+ * @param originalType The original type that is being redefined or rebased.
+ * @param classFileLocator The class file locator for locating the original type's class file.
+ */
+ protected AbstractInliningDynamicTypeBuilder(InstrumentedType.WithFlexibleName instrumentedType,
+ FieldRegistry fieldRegistry,
+ MethodRegistry methodRegistry,
+ TypeAttributeAppender typeAttributeAppender,
+ AsmVisitorWrapper asmVisitorWrapper,
+ ClassFileVersion classFileVersion,
+ AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
+ AnnotationValueFilter.Factory annotationValueFilterFactory,
+ AnnotationRetention annotationRetention,
+ Implementation.Context.Factory implementationContextFactory,
+ MethodGraph.Compiler methodGraphCompiler,
+ TypeValidation typeValidation,
+ LatentMatcher<? super MethodDescription> ignoredMethods,
+ TypeDescription originalType,
+ ClassFileLocator classFileLocator) {
+ super(instrumentedType,
+ fieldRegistry,
+ methodRegistry,
+ typeAttributeAppender,
+ asmVisitorWrapper,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ typeValidation,
+ ignoredMethods);
+ this.originalType = originalType;
+ this.classFileLocator = classFileLocator;
+ }
+
+ @Override
+ public DynamicType.Unloaded<T> make(TypeResolutionStrategy typeResolutionStrategy) {
+ return make(typeResolutionStrategy, TypePool.Default.of(classFileLocator));
+ }
+
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/inline/InliningImplementationMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/inline/InliningImplementationMatcher.java
new file mode 100644
index 0000000..38a1c27
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/inline/InliningImplementationMatcher.java
@@ -0,0 +1,68 @@
+package net.bytebuddy.dynamic.scaffold.inline;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.matcher.ElementMatchers;
+import net.bytebuddy.matcher.LatentMatcher;
+
+import static net.bytebuddy.matcher.ElementMatchers.*;
+
+/**
+ * A latent method matcher that identifies methods to instrument when redefining or rebasing a type.
+ */
+ at EqualsAndHashCode
+public class InliningImplementationMatcher implements LatentMatcher<MethodDescription> {
+
+ /**
+ * A method matcher that matches any ignored method.
+ */
+ private final LatentMatcher<? super MethodDescription> ignoredMethods;
+
+ /**
+ * A method matcher that matches any predefined method.
+ */
+ private final ElementMatcher<? super MethodDescription> predefinedMethodSignatures;
+
+ /**
+ * Creates a new inline implementation matcher.
+ *
+ * @param ignoredMethods A method matcher that matches any ignored method.
+ * @param predefinedMethodSignatures A method matcher that matches any predefined method.
+ */
+ protected InliningImplementationMatcher(LatentMatcher<? super MethodDescription> ignoredMethods,
+ ElementMatcher<? super MethodDescription> predefinedMethodSignatures) {
+ this.ignoredMethods = ignoredMethods;
+ this.predefinedMethodSignatures = predefinedMethodSignatures;
+ }
+
+ /**
+ * Creates a matcher where only overridable or declared methods are matched unless those are ignored. Methods that
+ * are declared by the target type are only matched if they are not ignored. Declared methods that are not found on the
+ * target type are always matched.
+ *
+ * @param ignoredMethods A method matcher that matches any ignored method.
+ * @param originalType The original type of the instrumentation before adding any user methods.
+ * @return A latent method matcher that identifies any method to instrument for a rebasement or redefinition.
+ */
+ protected static LatentMatcher<MethodDescription> of(LatentMatcher<? super MethodDescription> ignoredMethods, TypeDescription originalType) {
+ ElementMatcher.Junction<MethodDescription> predefinedMethodSignatures = none();
+ for (MethodDescription methodDescription : originalType.getDeclaredMethods()) {
+ ElementMatcher.Junction<MethodDescription> signature = methodDescription.isConstructor()
+ ? isConstructor()
+ : ElementMatchers.<MethodDescription>named(methodDescription.getName());
+ signature = signature.and(returns(methodDescription.getReturnType().asErasure()));
+ signature = signature.and(takesArguments(methodDescription.getParameters().asTypeList().asErasures()));
+ predefinedMethodSignatures = predefinedMethodSignatures.or(signature);
+ }
+ return new InliningImplementationMatcher(ignoredMethods, predefinedMethodSignatures);
+ }
+
+ @Override
+ public ElementMatcher<? super MethodDescription> resolve(TypeDescription typeDescription) {
+ return (ElementMatcher<? super MethodDescription>) not(ignoredMethods.resolve(typeDescription))
+ .and(isVirtual().and(not(isFinal())).or(isDeclaredBy(typeDescription)))
+ .or(isDeclaredBy(typeDescription).and(not(predefinedMethodSignatures)));
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/inline/MethodNameTransformer.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/inline/MethodNameTransformer.java
new file mode 100644
index 0000000..ea462f1
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/inline/MethodNameTransformer.java
@@ -0,0 +1,100 @@
+package net.bytebuddy.dynamic.scaffold.inline;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.utility.RandomString;
+
+/**
+ * A method name transformer provides a unique mapping of a method's name to an alternative name.
+ *
+ * @see MethodRebaseResolver
+ */
+public interface MethodNameTransformer {
+
+ /**
+ * Transforms a method's name to an alternative name. This name must not be equal to any existing method of the
+ * created class.
+ *
+ * @param methodDescription The original method.
+ * @return The alternative name.
+ */
+ String transform(MethodDescription methodDescription);
+
+ /**
+ * A method name transformer that adds a fixed suffix to an original method name, separated by a {@code $}.
+ */
+ @EqualsAndHashCode
+ class Suffixing implements MethodNameTransformer {
+
+ /**
+ * The default suffix to add to an original method name.
+ */
+ private static final String DEFAULT_SUFFIX = "original$";
+
+ /**
+ * The suffix to append to a method name.
+ */
+ private final String suffix;
+
+ /**
+ * Creates a new suffixing method name transformer which adds a default suffix with a random name component.
+ *
+ * @return A method name transformer that adds a randomized suffix to the original method name.
+ */
+ public static MethodNameTransformer withRandomSuffix() {
+ return new Suffixing(DEFAULT_SUFFIX + RandomString.make());
+ }
+
+ /**
+ * Creates a new suffixing method name transformer.
+ *
+ * @param suffix The suffix to add to the method name before the seed.
+ */
+ public Suffixing(String suffix) {
+ this.suffix = suffix;
+ }
+
+ @Override
+ public String transform(MethodDescription methodDescription) {
+ return String.format("%s$%s", methodDescription.getInternalName(), suffix);
+ }
+ }
+
+ /**
+ * A method name transformer that adds a fixed prefix to an original method name.
+ */
+ @EqualsAndHashCode
+ class Prefixing implements MethodNameTransformer {
+
+ /**
+ * The default prefix to add to an original method name.
+ */
+ private static final String DEFAULT_PREFIX = "original";
+
+ /**
+ * The prefix that is appended.
+ */
+ private final String prefix;
+
+ /**
+ * Creates a new prefixing method name transformer using a default prefix.
+ */
+ public Prefixing() {
+ this(DEFAULT_PREFIX);
+ }
+
+ /**
+ * Creates a new prefixing method name transformer.
+ *
+ * @param prefix The prefix being used.
+ */
+ public Prefixing(String prefix) {
+ this.prefix = prefix;
+ }
+
+ @Override
+ public String transform(MethodDescription methodDescription) {
+ return String.format("%s%s", prefix, methodDescription.getInternalName());
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/inline/MethodRebaseResolver.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/inline/MethodRebaseResolver.java
new file mode 100644
index 0000000..5aca8b6
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/inline/MethodRebaseResolver.java
@@ -0,0 +1,488 @@
+package net.bytebuddy.dynamic.scaffold.inline;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.annotation.AnnotationValue;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.implementation.MethodAccessorFactory;
+import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
+import net.bytebuddy.implementation.auxiliary.TrivialType;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.constant.NullConstant;
+import net.bytebuddy.utility.CompoundList;
+import org.objectweb.asm.Opcodes;
+
+import java.util.*;
+
+import static net.bytebuddy.matcher.ElementMatchers.is;
+
+/**
+ * A method rebase resolver is responsible for mapping methods of an instrumented type to an alternative signature.
+ * This way a method can exist in two versions within a class:
+ * <ol>
+ * <li>The rebased method which represents the original implementation as it is present in a class file.</li>
+ * <li>An overriden method which implements user code which is still able to invoke the original, rebased method.</li>
+ * </ol>
+ */
+public interface MethodRebaseResolver {
+
+ /**
+ * Checks if a method is eligible for rebasing and resolves this possibly rebased method.
+ *
+ * @param methodDescription A description of the method to resolve.
+ * @return A resolution for the given method.
+ */
+ Resolution resolve(MethodDescription.InDefinedShape methodDescription);
+
+ /**
+ * Returns a (potentially empty) list of auxiliary types that are required by this method rebase resolver.
+ *
+ * @return A list of auxiliary types that are required by this method rebase resolver.
+ */
+ List<DynamicType> getAuxiliaryTypes();
+
+ /**
+ * Returns a map of all rebasable methods' signature tokens to their resolution.
+ *
+ * @return A map of all rebasable methods' signature tokens to their resolution.
+ */
+ Map<MethodDescription.SignatureToken, Resolution> asTokenMap();
+
+ /**
+ * A method rebase resolver that preserves any method in its original form.
+ */
+ enum Disabled implements MethodRebaseResolver {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Resolution resolve(MethodDescription.InDefinedShape methodDescription) {
+ return new Resolution.Preserved(methodDescription);
+ }
+
+ @Override
+ public List<DynamicType> getAuxiliaryTypes() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public Map<MethodDescription.SignatureToken, Resolution> asTokenMap() {
+ return Collections.emptyMap();
+ }
+
+ }
+
+ /**
+ * A resolution for a method that was checked by a {@link MethodRebaseResolver}.
+ */
+ interface Resolution {
+
+ /**
+ * Checks if this resolution represents a rebased method.
+ *
+ * @return {@code true} if this resolution requires to rebase a method.
+ */
+ boolean isRebased();
+
+ /**
+ * Returns the resolved method if this resolution represents a rebased method or the original method.
+ *
+ * @return The resolved method if this resolution represents a rebased method or the original method.
+ */
+ MethodDescription.InDefinedShape getResolvedMethod();
+
+ /**
+ * A rebased method might require additional arguments in order to create a distinct signature. The
+ * stack manipulation that is returned from this method loads these arguments onto the operand stack. For
+ * a non-rebased method, this method throws an {@link java.lang.IllegalArgumentException}.
+ *
+ * @return A stack manipulation that loaded the additional arguments onto the stack, if any.
+ */
+ StackManipulation getAdditionalArguments();
+
+ /**
+ * A {@link MethodRebaseResolver.Resolution} of a non-rebased method.
+ */
+ @EqualsAndHashCode
+ class Preserved implements Resolution {
+
+ /**
+ * The preserved method.
+ */
+ private final MethodDescription.InDefinedShape methodDescription;
+
+ /**
+ * Creates a new {@link MethodRebaseResolver.Resolution} for
+ * a non-rebased method.
+ *
+ * @param methodDescription The preserved method.
+ */
+ public Preserved(MethodDescription.InDefinedShape methodDescription) {
+ this.methodDescription = methodDescription;
+ }
+
+ @Override
+ public boolean isRebased() {
+ return false;
+ }
+
+ @Override
+ public MethodDescription.InDefinedShape getResolvedMethod() {
+ return methodDescription;
+ }
+
+ @Override
+ public StackManipulation getAdditionalArguments() {
+ throw new IllegalStateException("Cannot process additional arguments for non-rebased method: " + methodDescription);
+ }
+ }
+
+ /**
+ * A {@link MethodRebaseResolver.Resolution} of a rebased method.
+ */
+ @EqualsAndHashCode
+ class ForRebasedMethod implements Resolution {
+
+ /**
+ * The rebased method.
+ */
+ private final MethodDescription.InDefinedShape methodDescription;
+
+ /**
+ * Creates a resolution for a rebased method.
+ *
+ * @param methodDescription The rebased method.
+ */
+ protected ForRebasedMethod(MethodDescription.InDefinedShape methodDescription) {
+ this.methodDescription = methodDescription;
+ }
+
+ /**
+ * Resolves a rebasement for the provided method.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param methodDescription The method to be rebased.
+ * @param methodNameTransformer The transformer to use for renaming the method.
+ * @return A resolution for rebasing the provided method.
+ */
+ public static Resolution of(TypeDescription instrumentedType,
+ MethodDescription.InDefinedShape methodDescription,
+ MethodNameTransformer methodNameTransformer) {
+ return new ForRebasedMethod(new RebasedMethod(instrumentedType, methodDescription, methodNameTransformer));
+ }
+
+ @Override
+ public boolean isRebased() {
+ return true;
+ }
+
+ @Override
+ public MethodDescription.InDefinedShape getResolvedMethod() {
+ return methodDescription;
+ }
+
+ @Override
+ public StackManipulation getAdditionalArguments() {
+ return StackManipulation.Trivial.INSTANCE;
+ }
+
+ /**
+ * A description of a rebased method.
+ */
+ protected static class RebasedMethod extends MethodDescription.InDefinedShape.AbstractBase {
+
+ /**
+ * The instrumented type.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * The method that is being rebased.
+ */
+ private final InDefinedShape methodDescription;
+
+ /**
+ * The transformer to use for renaming the method.
+ */
+ private final MethodNameTransformer methodNameTransformer;
+
+ /**
+ * Creates a new rebased method.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param methodDescription The method that is being rebased.
+ * @param methodNameTransformer The transformer to use for renaming the method.
+ */
+ protected RebasedMethod(TypeDescription instrumentedType, InDefinedShape methodDescription, MethodNameTransformer methodNameTransformer) {
+ this.instrumentedType = instrumentedType;
+ this.methodDescription = methodDescription;
+ this.methodNameTransformer = methodNameTransformer;
+ }
+
+ @Override
+ public TypeDescription.Generic getReturnType() {
+ return methodDescription.getReturnType().asRawType();
+ }
+
+ @Override
+ public ParameterList<ParameterDescription.InDefinedShape> getParameters() {
+ return new ParameterList.Explicit.ForTypes(this, methodDescription.getParameters().asTypeList().asRawTypes());
+ }
+
+ @Override
+ public TypeList.Generic getExceptionTypes() {
+ return methodDescription.getExceptionTypes().asRawTypes();
+ }
+
+ @Override
+ public AnnotationValue<?, ?> getDefaultValue() {
+ return AnnotationValue.UNDEFINED;
+ }
+
+ @Override
+ public TypeList.Generic getTypeVariables() {
+ return new TypeList.Generic.Empty();
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return new AnnotationList.Empty();
+ }
+
+ @Override
+ public TypeDescription getDeclaringType() {
+ return methodDescription.getDeclaringType();
+ }
+
+ @Override
+ public int getModifiers() {
+ return Opcodes.ACC_SYNTHETIC
+ | (methodDescription.isStatic() ? Opcodes.ACC_STATIC : EMPTY_MASK)
+ | (methodDescription.isNative() ? Opcodes.ACC_NATIVE : EMPTY_MASK)
+ | (instrumentedType.isInterface() ? Opcodes.ACC_PUBLIC : Opcodes.ACC_PRIVATE);
+ }
+
+ @Override
+ public String getInternalName() {
+ return methodNameTransformer.transform(methodDescription);
+ }
+ }
+ }
+
+ /**
+ * A {@link MethodRebaseResolver.Resolution} of a rebased constructor.
+ */
+ @EqualsAndHashCode
+ class ForRebasedConstructor implements Resolution {
+
+ /**
+ * The rebased constructor.
+ */
+ private final MethodDescription.InDefinedShape methodDescription;
+
+ /**
+ * Creates a new resolution for a rebased constructor.
+ *
+ * @param methodDescription The rebased constructor.
+ */
+ protected ForRebasedConstructor(MethodDescription.InDefinedShape methodDescription) {
+ this.methodDescription = methodDescription;
+ }
+
+ /**
+ * Resolves a constructor rebasement.
+ *
+ * @param methodDescription The constructor to rebase.
+ * @param placeholderType The placeholder type to use to distinguish the constructor's signature.
+ * @return A resolution of the provided constructor.
+ */
+ public static Resolution of(MethodDescription.InDefinedShape methodDescription, TypeDescription placeholderType) {
+ return new ForRebasedConstructor(new RebasedConstructor(methodDescription, placeholderType));
+ }
+
+ @Override
+ public boolean isRebased() {
+ return true;
+ }
+
+ @Override
+ public MethodDescription.InDefinedShape getResolvedMethod() {
+ return methodDescription;
+ }
+
+ @Override
+ public StackManipulation getAdditionalArguments() {
+ return NullConstant.INSTANCE;
+ }
+
+ /**
+ * An description of a rebased constructor.
+ */
+ protected static class RebasedConstructor extends MethodDescription.InDefinedShape.AbstractBase {
+
+ /**
+ * The constructor that is rebased.
+ */
+ private final InDefinedShape methodDescription;
+
+ /**
+ * The placeholder type that is used to distinguish the constructor's signature.
+ */
+ private final TypeDescription placeholderType;
+
+ /**
+ * Creates a new rebased constructor.
+ *
+ * @param methodDescription The constructor that is rebased.
+ * @param placeholderType The placeholder type that is used to distinguish the constructor's signature.
+ */
+ protected RebasedConstructor(InDefinedShape methodDescription, TypeDescription placeholderType) {
+ this.methodDescription = methodDescription;
+ this.placeholderType = placeholderType;
+ }
+
+ @Override
+ public TypeDescription.Generic getReturnType() {
+ return TypeDescription.Generic.VOID;
+ }
+
+ @Override
+ public ParameterList<ParameterDescription.InDefinedShape> getParameters() {
+ return new ParameterList.Explicit.ForTypes(this, CompoundList.of(methodDescription.getParameters().asTypeList().asErasures(), placeholderType));
+ }
+
+ @Override
+ public TypeList.Generic getExceptionTypes() {
+ return methodDescription.getExceptionTypes().asRawTypes();
+ }
+
+ @Override
+ public AnnotationValue<?, ?> getDefaultValue() {
+ return AnnotationValue.UNDEFINED;
+ }
+
+ @Override
+ public TypeList.Generic getTypeVariables() {
+ return new TypeList.Generic.Empty();
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return new AnnotationList.Empty();
+ }
+
+ @Override
+ public TypeDescription getDeclaringType() {
+ return methodDescription.getDeclaringType();
+ }
+
+ @Override
+ public int getModifiers() {
+ return Opcodes.ACC_SYNTHETIC | Opcodes.ACC_PRIVATE;
+ }
+
+ @Override
+ public String getInternalName() {
+ return MethodDescription.CONSTRUCTOR_INTERNAL_NAME;
+ }
+ }
+ }
+ }
+
+ /**
+ * A default implementation of a method rebase resolver.
+ */
+ @EqualsAndHashCode
+ class Default implements MethodRebaseResolver {
+
+ /**
+ * A mapping of rebased methods to their existing resolutions.
+ */
+ private final Map<MethodDescription.InDefinedShape, Resolution> resolutions;
+
+ /**
+ * A list of dynamic types that need to be appended to the created type in order to allow for the rebasement.
+ */
+ private final List<DynamicType> dynamicTypes;
+
+ /**
+ * Creates a new default method rebased resolver.
+ *
+ * @param resolutions A mapping of rebased methods to their existing resolutions.
+ * @param dynamicTypes A list of dynamic types that need to be appended to the created type in order to allow for the rebasement.
+ */
+ protected Default(Map<MethodDescription.InDefinedShape, Resolution> resolutions, List<DynamicType> dynamicTypes) {
+ this.resolutions = resolutions;
+ this.dynamicTypes = dynamicTypes;
+ }
+
+ /**
+ * Creates a new method rebase resolver.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param rebaseableMethodTokens Tokens describing all methods that can possibly be rebased.
+ * @param classFileVersion The class file version for the instrumentation.
+ * @param auxiliaryTypeNamingStrategy The naming strategy for naming a potential auxiliary type.
+ * @param methodNameTransformer A transformer for method names.
+ * @return A method rebase resolver that is capable of rebasing any of the provided methods.
+ */
+ public static MethodRebaseResolver make(TypeDescription instrumentedType,
+ Set<? extends MethodDescription.Token> rebaseableMethodTokens,
+ ClassFileVersion classFileVersion,
+ AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
+ MethodNameTransformer methodNameTransformer) {
+ DynamicType placeholderType = null;
+ Map<MethodDescription.InDefinedShape, Resolution> resolutions = new HashMap<MethodDescription.InDefinedShape, Resolution>();
+ for (MethodDescription.InDefinedShape instrumentedMethod : instrumentedType.getDeclaredMethods()) {
+ if (rebaseableMethodTokens.contains(instrumentedMethod.asToken(is(instrumentedType)))) {
+ Resolution resolution;
+ if (instrumentedMethod.isConstructor()) {
+ if (placeholderType == null) {
+ placeholderType = TrivialType.SIGNATURE_RELEVANT.make(auxiliaryTypeNamingStrategy.name(instrumentedType),
+ classFileVersion,
+ MethodAccessorFactory.Illegal.INSTANCE);
+ }
+ resolution = Resolution.ForRebasedConstructor.of(instrumentedMethod, placeholderType.getTypeDescription());
+ } else {
+ resolution = Resolution.ForRebasedMethod.of(instrumentedType, instrumentedMethod, methodNameTransformer);
+ }
+ resolutions.put(instrumentedMethod, resolution);
+ }
+ }
+ return placeholderType == null
+ ? new Default(resolutions, Collections.<DynamicType>emptyList())
+ : new Default(resolutions, Collections.singletonList(placeholderType));
+ }
+
+ @Override
+ public Resolution resolve(MethodDescription.InDefinedShape methodDescription) {
+ Resolution resolution = resolutions.get(methodDescription);
+ return resolution == null
+ ? new Resolution.Preserved(methodDescription)
+ : resolution;
+ }
+
+ @Override
+ public List<DynamicType> getAuxiliaryTypes() {
+ return dynamicTypes;
+ }
+
+ @Override
+ public Map<MethodDescription.SignatureToken, Resolution> asTokenMap() {
+ Map<MethodDescription.SignatureToken, Resolution> tokenMap = new HashMap<MethodDescription.SignatureToken, Resolution>();
+ for (Map.Entry<MethodDescription.InDefinedShape, Resolution> entry : resolutions.entrySet()) {
+ tokenMap.put(entry.getKey().asSignatureToken(), entry.getValue());
+ }
+ return tokenMap;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/inline/RebaseDynamicTypeBuilder.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/inline/RebaseDynamicTypeBuilder.java
new file mode 100644
index 0000000..89a5ba8
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/inline/RebaseDynamicTypeBuilder.java
@@ -0,0 +1,239 @@
+package net.bytebuddy.dynamic.scaffold.inline;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.asm.AsmVisitorWrapper;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.TypeResolutionStrategy;
+import net.bytebuddy.dynamic.scaffold.*;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.attribute.AnnotationRetention;
+import net.bytebuddy.implementation.attribute.AnnotationValueFilter;
+import net.bytebuddy.implementation.attribute.TypeAttributeAppender;
+import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.matcher.LatentMatcher;
+import net.bytebuddy.pool.TypePool;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static net.bytebuddy.matcher.ElementMatchers.is;
+
+/**
+ * A type builder that rebases an instrumented type.
+ *
+ * @param <T> A loaded type that the dynamic type is guaranteed to be a subtype of.
+ */
+ at EqualsAndHashCode(callSuper = true)
+public class RebaseDynamicTypeBuilder<T> extends AbstractInliningDynamicTypeBuilder<T> {
+
+ /**
+ * The method rebase resolver to use for determining the name of a rebased method.
+ */
+ private final MethodNameTransformer methodNameTransformer;
+
+ /**
+ * Creates a rebase dynamic type builder.
+ *
+ * @param instrumentedType An instrumented type representing the subclass.
+ * @param classFileVersion The class file version to use for types that are not based on an existing class file.
+ * @param auxiliaryTypeNamingStrategy The naming strategy to use for naming auxiliary types.
+ * @param annotationValueFilterFactory The annotation value filter factory to use.
+ * @param annotationRetention The annotation retention strategy to use.
+ * @param implementationContextFactory The implementation context factory to use.
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @param typeValidation Determines if a type should be explicitly validated.
+ * @param ignoredMethods A matcher for identifying methods that should be excluded from instrumentation.
+ * @param originalType The original type that is being redefined or rebased.
+ * @param classFileLocator The class file locator for locating the original type's class file.
+ * @param methodNameTransformer The method rebase resolver to use for determining the name of a rebased method.
+ */
+ public RebaseDynamicTypeBuilder(InstrumentedType.WithFlexibleName instrumentedType,
+ ClassFileVersion classFileVersion,
+ AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
+ AnnotationValueFilter.Factory annotationValueFilterFactory,
+ AnnotationRetention annotationRetention,
+ Implementation.Context.Factory implementationContextFactory,
+ MethodGraph.Compiler methodGraphCompiler,
+ TypeValidation typeValidation,
+ LatentMatcher<? super MethodDescription> ignoredMethods,
+ TypeDescription originalType,
+ ClassFileLocator classFileLocator,
+ MethodNameTransformer methodNameTransformer) {
+ this(instrumentedType,
+ new FieldRegistry.Default(),
+ new MethodRegistry.Default(),
+ annotationRetention.isEnabled()
+ ? new TypeAttributeAppender.ForInstrumentedType.Differentiating(originalType)
+ : TypeAttributeAppender.ForInstrumentedType.INSTANCE,
+ AsmVisitorWrapper.NoOp.INSTANCE,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ typeValidation,
+ ignoredMethods,
+ originalType,
+ classFileLocator,
+ methodNameTransformer);
+ }
+
+ /**
+ * Creates a rebase dynamic type builder.
+ *
+ * @param instrumentedType An instrumented type representing the subclass.
+ * @param fieldRegistry The field pool to use.
+ * @param methodRegistry The method pool to use.
+ * @param typeAttributeAppender The type attribute appender to apply onto the instrumented type.
+ * @param asmVisitorWrapper The ASM visitor wrapper to apply onto the class writer.
+ * @param classFileVersion The class file version to use for types that are not based on an existing class file.
+ * @param auxiliaryTypeNamingStrategy The naming strategy to use for naming auxiliary types.
+ * @param annotationValueFilterFactory The annotation value filter factory to use.
+ * @param annotationRetention The annotation retention strategy to use.
+ * @param implementationContextFactory The implementation context factory to use.
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @param typeValidation Determines if a type should be explicitly validated.
+ * @param ignoredMethods A matcher for identifying methods that should be excluded from instrumentation.
+ * @param originalType The original type that is being redefined or rebased.
+ * @param classFileLocator The class file locator for locating the original type's class file.
+ * @param methodNameTransformer The method rebase resolver to use for determining the name of a rebased method.
+ */
+ protected RebaseDynamicTypeBuilder(InstrumentedType.WithFlexibleName instrumentedType,
+ FieldRegistry fieldRegistry,
+ MethodRegistry methodRegistry,
+ TypeAttributeAppender typeAttributeAppender,
+ AsmVisitorWrapper asmVisitorWrapper,
+ ClassFileVersion classFileVersion,
+ AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
+ AnnotationValueFilter.Factory annotationValueFilterFactory,
+ AnnotationRetention annotationRetention,
+ Implementation.Context.Factory implementationContextFactory,
+ MethodGraph.Compiler methodGraphCompiler,
+ TypeValidation typeValidation,
+ LatentMatcher<? super MethodDescription> ignoredMethods,
+ TypeDescription originalType,
+ ClassFileLocator classFileLocator,
+ MethodNameTransformer methodNameTransformer) {
+ super(instrumentedType,
+ fieldRegistry,
+ methodRegistry,
+ typeAttributeAppender,
+ asmVisitorWrapper,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ typeValidation,
+ ignoredMethods,
+ originalType,
+ classFileLocator);
+ this.methodNameTransformer = methodNameTransformer;
+ }
+
+ @Override
+ protected DynamicType.Builder<T> materialize(InstrumentedType.WithFlexibleName instrumentedType,
+ FieldRegistry fieldRegistry,
+ MethodRegistry methodRegistry,
+ TypeAttributeAppender typeAttributeAppender,
+ AsmVisitorWrapper asmVisitorWrapper,
+ ClassFileVersion classFileVersion,
+ AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
+ AnnotationValueFilter.Factory annotationValueFilterFactory,
+ AnnotationRetention annotationRetention,
+ Implementation.Context.Factory implementationContextFactory,
+ MethodGraph.Compiler methodGraphCompiler,
+ TypeValidation typeValidation,
+ LatentMatcher<? super MethodDescription> ignoredMethods) {
+ return new RebaseDynamicTypeBuilder<T>(instrumentedType,
+ fieldRegistry,
+ methodRegistry,
+ typeAttributeAppender,
+ asmVisitorWrapper,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ typeValidation,
+ ignoredMethods,
+ originalType,
+ classFileLocator,
+ methodNameTransformer);
+ }
+
+ @Override
+ public DynamicType.Unloaded<T> make(TypeResolutionStrategy typeResolutionStrategy, TypePool typePool) {
+ MethodRegistry.Prepared methodRegistry = this.methodRegistry.prepare(instrumentedType,
+ methodGraphCompiler,
+ typeValidation,
+ InliningImplementationMatcher.of(ignoredMethods, originalType));
+ MethodRebaseResolver methodRebaseResolver = MethodRebaseResolver.Default.make(methodRegistry.getInstrumentedType(),
+ new HashSet<MethodDescription.Token>(originalType.getDeclaredMethods()
+ .asTokenList(is(originalType))
+ .filter(RebaseableMatcher.of(methodRegistry.getInstrumentedType(), methodRegistry.getInstrumentedMethods()))),
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ methodNameTransformer);
+ return TypeWriter.Default.<T>forRebasing(methodRegistry,
+ fieldRegistry.compile(methodRegistry.getInstrumentedType()),
+ typeAttributeAppender,
+ asmVisitorWrapper,
+ classFileVersion,
+ annotationValueFilterFactory,
+ annotationRetention,
+ auxiliaryTypeNamingStrategy,
+ implementationContextFactory,
+ typeValidation,
+ typePool,
+ originalType,
+ classFileLocator,
+ methodRebaseResolver).make(typeResolutionStrategy.resolve());
+ }
+
+ /**
+ * A matcher that filters any method that should not be rebased, i.e. that is not already defined by the original type.
+ */
+ @EqualsAndHashCode
+ protected static class RebaseableMatcher implements ElementMatcher<MethodDescription.Token> {
+
+ /**
+ * A set of method tokens representing all instrumented methods.
+ */
+ private final Set<MethodDescription.Token> tokens;
+
+ /**
+ * Creates a new matcher for identifying rebasable methods.
+ *
+ * @param tokens A set of method tokens representing all instrumented methods.
+ */
+ protected RebaseableMatcher(Set<MethodDescription.Token> tokens) {
+ this.tokens = tokens;
+ }
+
+ /**
+ * Returns a matcher that filters any method that should not be rebased.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param instrumentedMethods All instrumented methods.
+ * @return A suitable matcher that filters all methods that should not be rebased.
+ */
+ protected static ElementMatcher<MethodDescription.Token> of(TypeDescription instrumentedType, MethodList<?> instrumentedMethods) {
+ return new RebaseableMatcher(new HashSet<MethodDescription.Token>(instrumentedMethods.asTokenList(is(instrumentedType))));
+ }
+
+ @Override
+ public boolean matches(MethodDescription.Token target) {
+ return tokens.contains(target);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/inline/RebaseImplementationTarget.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/inline/RebaseImplementationTarget.java
new file mode 100644
index 0000000..bac2dc9
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/inline/RebaseImplementationTarget.java
@@ -0,0 +1,196 @@
+package net.bytebuddy.dynamic.scaffold.inline;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.scaffold.MethodGraph;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
+import org.objectweb.asm.MethodVisitor;
+
+import java.util.Map;
+
+/**
+ * An implementation target for redefining a given type while preserving the original methods within the
+ * instrumented type.
+ * <p> </p>
+ * Super method calls are merely emulated by this {@link Implementation.Target} in order
+ * to preserve Java's super call semantics a user would expect when invoking a {@code super}-prefixed method. This
+ * means that original methods are either moved to renamed {@code private} methods which are never dispatched
+ * virtually or they are invoked directly via the {@code INVOKESPECIAL} invocation to explicitly forbid a virtual
+ * dispatch.
+ */
+ at EqualsAndHashCode(callSuper = true)
+public class RebaseImplementationTarget extends Implementation.Target.AbstractBase {
+
+ /**
+ * A mapping of the instrumented type's declared methods by each method's token.
+ */
+ private final Map<MethodDescription.SignatureToken, MethodRebaseResolver.Resolution> rebaseableMethods;
+
+ /**
+ * Creates a rebase implementation target.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param methodGraph A method graph of the instrumented type.
+ * @param defaultMethodInvocation The default method invocation mode to apply.
+ * @param rebaseableMethods A mapping of the instrumented type's declared methods by each method's token.
+ */
+ protected RebaseImplementationTarget(TypeDescription instrumentedType,
+ MethodGraph.Linked methodGraph,
+ DefaultMethodInvocation defaultMethodInvocation,
+ Map<MethodDescription.SignatureToken, MethodRebaseResolver.Resolution> rebaseableMethods) {
+ super(instrumentedType, methodGraph, defaultMethodInvocation);
+ this.rebaseableMethods = rebaseableMethods;
+ }
+
+ /**
+ * Creates a new rebase implementation target.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param methodGraph A method graph of the instrumented type.
+ * @param classFileVersion The type's class file version.
+ * @param methodRebaseResolver A method rebase resolver to be used when calling a rebased method.
+ * @return An implementation target for the given input.
+ */
+ protected static Implementation.Target of(TypeDescription instrumentedType,
+ MethodGraph.Linked methodGraph,
+ ClassFileVersion classFileVersion,
+ MethodRebaseResolver methodRebaseResolver) {
+ return new RebaseImplementationTarget(instrumentedType, methodGraph, DefaultMethodInvocation.of(classFileVersion), methodRebaseResolver.asTokenMap());
+ }
+
+ @Override
+ public Implementation.SpecialMethodInvocation invokeSuper(MethodDescription.SignatureToken token) {
+ MethodRebaseResolver.Resolution resolution = rebaseableMethods.get(token);
+ return resolution == null
+ ? invokeSuper(methodGraph.getSuperClassGraph().locate(token))
+ : invokeSuper(resolution);
+ }
+
+ /**
+ * Creates a special method invocation for the given node.
+ *
+ * @param node The node for which a special method invocation is to be created.
+ * @return A special method invocation for the provided node.
+ */
+ private Implementation.SpecialMethodInvocation invokeSuper(MethodGraph.Node node) {
+ return node.getSort().isResolved()
+ ? Implementation.SpecialMethodInvocation.Simple.of(node.getRepresentative(), instrumentedType.getSuperClass().asErasure())
+ : Implementation.SpecialMethodInvocation.Illegal.INSTANCE;
+ }
+
+ /**
+ * Creates a special method invocation for the given rebase resolution.
+ *
+ * @param resolution The resolution for which a special method invocation is to be created.
+ * @return A special method invocation for the provided resolution.
+ */
+ private Implementation.SpecialMethodInvocation invokeSuper(MethodRebaseResolver.Resolution resolution) {
+ return resolution.isRebased()
+ ? RebasedMethodInvocation.of(resolution.getResolvedMethod(), instrumentedType, resolution.getAdditionalArguments())
+ : Implementation.SpecialMethodInvocation.Simple.of(resolution.getResolvedMethod(), instrumentedType);
+ }
+
+ @Override
+ public TypeDescription getOriginType() {
+ return instrumentedType;
+ }
+
+ /**
+ * A {@link Implementation.SpecialMethodInvocation} which invokes a rebased method
+ * as given by a {@link MethodRebaseResolver}.
+ */
+ protected static class RebasedMethodInvocation extends Implementation.SpecialMethodInvocation.AbstractBase {
+
+ /**
+ * The method to invoke via a special method invocation.
+ */
+ private final MethodDescription methodDescription;
+
+ /**
+ * The instrumented type on which the method should be invoked on.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * The stack manipulation to execute in order to invoke the rebased method.
+ */
+ private final StackManipulation stackManipulation;
+
+ /**
+ * Creates a new rebased method invocation.
+ *
+ * @param methodDescription The method to invoke via a special method invocation.
+ * @param instrumentedType The instrumented type on which the method should be invoked on.
+ * @param stackManipulation The stack manipulation to execute in order to invoke the rebased method.
+ */
+ protected RebasedMethodInvocation(MethodDescription methodDescription, TypeDescription instrumentedType, StackManipulation stackManipulation) {
+ this.methodDescription = methodDescription;
+ this.instrumentedType = instrumentedType;
+ this.stackManipulation = stackManipulation;
+ }
+
+ /**
+ * Creates a special method invocation for the given method.
+ *
+ * @param resolvedMethod The rebased method to be invoked.
+ * @param instrumentedType The instrumented type on which the method is to be invoked if it is non-static.
+ * @param additionalArguments Any additional arguments that are to be provided to the rebased method.
+ * @return A special method invocation of the rebased method.
+ */
+ protected static Implementation.SpecialMethodInvocation of(MethodDescription resolvedMethod,
+ TypeDescription instrumentedType,
+ StackManipulation additionalArguments) {
+ StackManipulation stackManipulation = resolvedMethod.isStatic()
+ ? MethodInvocation.invoke(resolvedMethod)
+ : MethodInvocation.invoke(resolvedMethod).special(instrumentedType);
+ return stackManipulation.isValid()
+ ? new RebasedMethodInvocation(resolvedMethod, instrumentedType, new Compound(additionalArguments, stackManipulation))
+ : Illegal.INSTANCE;
+ }
+
+ @Override
+ public MethodDescription getMethodDescription() {
+ return methodDescription;
+ }
+
+ @Override
+ public TypeDescription getTypeDescription() {
+ return instrumentedType;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ return stackManipulation.apply(methodVisitor, implementationContext);
+ }
+ }
+
+ /**
+ * A factory for creating a {@link RebaseImplementationTarget}.
+ */
+ @EqualsAndHashCode
+ public static class Factory implements Implementation.Target.Factory {
+
+ /**
+ * The method rebase resolver to use.
+ */
+ private final MethodRebaseResolver methodRebaseResolver;
+
+ /**
+ * Creates a new factory for a rebase implementation target.
+ *
+ * @param methodRebaseResolver The method rebase resolver to use.
+ */
+ public Factory(MethodRebaseResolver methodRebaseResolver) {
+ this.methodRebaseResolver = methodRebaseResolver;
+ }
+
+ @Override
+ public Implementation.Target make(TypeDescription instrumentedType, MethodGraph.Linked methodGraph, ClassFileVersion classFileVersion) {
+ return RebaseImplementationTarget.of(instrumentedType, methodGraph, classFileVersion, methodRebaseResolver);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/inline/RedefinitionDynamicTypeBuilder.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/inline/RedefinitionDynamicTypeBuilder.java
new file mode 100644
index 0000000..f896bbc
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/inline/RedefinitionDynamicTypeBuilder.java
@@ -0,0 +1,173 @@
+package net.bytebuddy.dynamic.scaffold.inline;
+
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.asm.AsmVisitorWrapper;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.TypeResolutionStrategy;
+import net.bytebuddy.dynamic.scaffold.*;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.attribute.AnnotationRetention;
+import net.bytebuddy.implementation.attribute.AnnotationValueFilter;
+import net.bytebuddy.implementation.attribute.TypeAttributeAppender;
+import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
+import net.bytebuddy.matcher.LatentMatcher;
+import net.bytebuddy.pool.TypePool;
+
+/**
+ * A type builder that redefines an instrumented type.
+ *
+ * @param <T> A loaded type that the dynamic type is guaranteed to be a subtype of.
+ */
+public class RedefinitionDynamicTypeBuilder<T> extends AbstractInliningDynamicTypeBuilder<T> {
+
+ /**
+ * Creates a redefinition dynamic type builder.
+ *
+ * @param instrumentedType An instrumented type representing the subclass.
+ * @param classFileVersion The class file version to use for types that are not based on an existing class file.
+ * @param auxiliaryTypeNamingStrategy The naming strategy to use for naming auxiliary types.
+ * @param annotationValueFilterFactory The annotation value filter factory to use.
+ * @param annotationRetention The annotation retention strategy to use.
+ * @param implementationContextFactory The implementation context factory to use.
+ * @param typeValidation Determines if a type should be explicitly validated.
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @param ignoredMethods A matcher for identifying methods that should be excluded from instrumentation.
+ * @param originalType The original type that is being redefined or rebased.
+ * @param classFileLocator The class file locator for locating the original type's class file.
+ */
+ public RedefinitionDynamicTypeBuilder(InstrumentedType.WithFlexibleName instrumentedType,
+ ClassFileVersion classFileVersion,
+ AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
+ AnnotationValueFilter.Factory annotationValueFilterFactory,
+ AnnotationRetention annotationRetention,
+ Implementation.Context.Factory implementationContextFactory,
+ MethodGraph.Compiler methodGraphCompiler,
+ TypeValidation typeValidation,
+ LatentMatcher<? super MethodDescription> ignoredMethods,
+ TypeDescription originalType,
+ ClassFileLocator classFileLocator) {
+ this(instrumentedType,
+ new FieldRegistry.Default(),
+ new MethodRegistry.Default(),
+ annotationRetention.isEnabled()
+ ? new TypeAttributeAppender.ForInstrumentedType.Differentiating(originalType)
+ : TypeAttributeAppender.ForInstrumentedType.INSTANCE,
+ AsmVisitorWrapper.NoOp.INSTANCE,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ typeValidation,
+ ignoredMethods,
+ originalType,
+ classFileLocator);
+ }
+
+ /**
+ * Creates a redefinition dynamic type builder.
+ *
+ * @param instrumentedType An instrumented type representing the subclass.
+ * @param fieldRegistry The field pool to use.
+ * @param methodRegistry The method pool to use.
+ * @param typeAttributeAppender The type attribute appender to apply onto the instrumented type.
+ * @param asmVisitorWrapper The ASM visitor wrapper to apply onto the class writer.
+ * @param classFileVersion The class file version to use for types that are not based on an existing class file.
+ * @param auxiliaryTypeNamingStrategy The naming strategy to use for naming auxiliary types.
+ * @param annotationValueFilterFactory The annotation value filter factory to use.
+ * @param annotationRetention The annotation retention strategy to use.
+ * @param implementationContextFactory The implementation context factory to use.
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @param typeValidation Determines if a type should be explicitly validated.
+ * @param ignoredMethods A matcher for identifying methods that should be excluded from instrumentation.
+ * @param originalType The original type that is being redefined or rebased.
+ * @param classFileLocator The class file locator for locating the original type's class file.
+ */
+ protected RedefinitionDynamicTypeBuilder(InstrumentedType.WithFlexibleName instrumentedType,
+ FieldRegistry fieldRegistry,
+ MethodRegistry methodRegistry,
+ TypeAttributeAppender typeAttributeAppender,
+ AsmVisitorWrapper asmVisitorWrapper,
+ ClassFileVersion classFileVersion,
+ AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
+ AnnotationValueFilter.Factory annotationValueFilterFactory,
+ AnnotationRetention annotationRetention,
+ Implementation.Context.Factory implementationContextFactory,
+ MethodGraph.Compiler methodGraphCompiler,
+ TypeValidation typeValidation,
+ LatentMatcher<? super MethodDescription> ignoredMethods,
+ TypeDescription originalType,
+ ClassFileLocator classFileLocator) {
+ super(instrumentedType,
+ fieldRegistry,
+ methodRegistry,
+ typeAttributeAppender,
+ asmVisitorWrapper,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ typeValidation,
+ ignoredMethods,
+ originalType,
+ classFileLocator);
+ }
+
+ @Override
+ protected DynamicType.Builder<T> materialize(InstrumentedType.WithFlexibleName instrumentedType,
+ FieldRegistry fieldRegistry,
+ MethodRegistry methodRegistry,
+ TypeAttributeAppender typeAttributeAppender,
+ AsmVisitorWrapper asmVisitorWrapper,
+ ClassFileVersion classFileVersion,
+ AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
+ AnnotationValueFilter.Factory annotationValueFilterFactory,
+ AnnotationRetention annotationRetention,
+ Implementation.Context.Factory implementationContextFactory,
+ MethodGraph.Compiler methodGraphCompiler,
+ TypeValidation typeValidation,
+ LatentMatcher<? super MethodDescription> ignoredMethods) {
+ return new RedefinitionDynamicTypeBuilder<T>(instrumentedType,
+ fieldRegistry,
+ methodRegistry,
+ typeAttributeAppender,
+ asmVisitorWrapper,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ typeValidation,
+ ignoredMethods,
+ originalType,
+ classFileLocator);
+ }
+
+ @Override
+ public DynamicType.Unloaded<T> make(TypeResolutionStrategy typeResolutionStrategy, TypePool typePool) {
+ MethodRegistry.Prepared methodRegistry = this.methodRegistry.prepare(instrumentedType,
+ methodGraphCompiler,
+ typeValidation,
+ InliningImplementationMatcher.of(ignoredMethods, originalType));
+ return TypeWriter.Default.<T>forRedefinition(methodRegistry,
+ fieldRegistry.compile(methodRegistry.getInstrumentedType()),
+ typeAttributeAppender,
+ asmVisitorWrapper,
+ classFileVersion,
+ annotationValueFilterFactory,
+ annotationRetention,
+ auxiliaryTypeNamingStrategy,
+ implementationContextFactory,
+ typeValidation,
+ typePool,
+ originalType,
+ classFileLocator).make(typeResolutionStrategy.resolve());
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/inline/package-info.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/inline/package-info.java
new file mode 100644
index 0000000..9872a28
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/inline/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * All classes and types in this package are related to creating a {@link net.bytebuddy.dynamic.DynamicType} by
+ * enhancing a given type.
+ */
+package net.bytebuddy.dynamic.scaffold.inline;
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/package-info.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/package-info.java
new file mode 100644
index 0000000..988747b
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * This package contains helper types and implementations that are responsible for the actual writing of a byte array
+ * representing a Java class. These utilities allow to write a Java type in a more modular manner and take away
+ * complexity from the actual implementations of {@link net.bytebuddy.dynamic.DynamicType.Builder}.
+ */
+package net.bytebuddy.dynamic.scaffold;
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/subclass/ConstructorStrategy.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/subclass/ConstructorStrategy.java
new file mode 100644
index 0000000..8e42391
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/subclass/ConstructorStrategy.java
@@ -0,0 +1,281 @@
+package net.bytebuddy.dynamic.scaffold.subclass;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.Transformer;
+import net.bytebuddy.dynamic.scaffold.MethodRegistry;
+import net.bytebuddy.implementation.SuperMethodCall;
+import net.bytebuddy.implementation.attribute.MethodAttributeAppender;
+import net.bytebuddy.matcher.LatentMatcher;
+import org.objectweb.asm.Opcodes;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static net.bytebuddy.matcher.ElementMatchers.*;
+
+/**
+ * A constructor strategy is responsible for creating bootstrap constructors for a
+ * {@link SubclassDynamicTypeBuilder}.
+ *
+ * @see net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy.Default
+ */
+public interface ConstructorStrategy {
+
+ /**
+ * Extracts constructors for a given super type. The extracted constructor signatures will then be imitated by the
+ * created dynamic type.
+ *
+ * @param instrumentedType The type for which the constructors should be created.
+ * @return A list of tokens that describe the constructors that are to be implemented.
+ */
+ List<MethodDescription.Token> extractConstructors(TypeDescription instrumentedType);
+
+ /**
+ * Returns a method registry that is capable of creating byte code for the constructors that were
+ * provided by the
+ * {@link net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy#extractConstructors(TypeDescription)}
+ * method of this instance.
+ *
+ * @param methodRegistry The original method registry.
+ * @return A method registry that is capable of providing byte code for the constructors that were added by this strategy.
+ */
+ MethodRegistry inject(MethodRegistry methodRegistry);
+
+ /**
+ * Default implementations of constructor strategies. Any such strategy offers to additionally apply an {@link MethodAttributeAppender.Factory}.
+ */
+ enum Default implements ConstructorStrategy {
+
+ /**
+ * This strategy is adding no constructors such that the instrumented type will by default not have any. This
+ * is legal by Java byte code requirements. However, if no constructor is added manually if this strategy is
+ * applied, the type is not constructable without using JVM non-public functionality.
+ */
+ NO_CONSTRUCTORS {
+ @Override
+ protected List<MethodDescription.Token> doExtractConstructors(TypeDescription superClass) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ protected MethodRegistry doInject(MethodRegistry methodRegistry, MethodAttributeAppender.Factory methodAttributeAppenderFactory) {
+ return methodRegistry;
+ }
+ },
+
+ /**
+ * This strategy is adding a default constructor that calls it's super types default constructor. If no such
+ * constructor is defined by the super class, an {@link IllegalArgumentException} is thrown. Note that the default
+ * constructor needs to be visible to its sub type for this strategy to work. The declared default constructor of
+ * the created class is declared public and without annotations.
+ */
+ DEFAULT_CONSTRUCTOR {
+ @Override
+ protected List<MethodDescription.Token> doExtractConstructors(TypeDescription instrumentedType) {
+ TypeDescription.Generic superClass = instrumentedType.getSuperClass();
+ MethodList<?> defaultConstructors = superClass == null
+ ? new MethodList.Empty<MethodDescription.InGenericShape>()
+ : superClass.getDeclaredMethods().filter(isConstructor().and(takesArguments(0)).<MethodDescription>and(isVisibleTo(instrumentedType)));
+ if (defaultConstructors.size() == 1) {
+ return Collections.singletonList(new MethodDescription.Token(Opcodes.ACC_PUBLIC));
+ } else {
+ throw new IllegalArgumentException(instrumentedType.getSuperClass() + " declares no constructor that is visible to " + instrumentedType);
+ }
+ }
+
+ @Override
+ protected MethodRegistry doInject(MethodRegistry methodRegistry, MethodAttributeAppender.Factory methodAttributeAppenderFactory) {
+ return methodRegistry.append(new LatentMatcher.Resolved<MethodDescription>(isConstructor()),
+ new MethodRegistry.Handler.ForImplementation(SuperMethodCall.INSTANCE),
+ methodAttributeAppenderFactory,
+ Transformer.NoOp.<MethodDescription>make());
+ }
+ },
+
+ /**
+ * This strategy is adding all constructors of the instrumented type's super class where each constructor is
+ * directly invoking its signature-equivalent super class constructor. Only constructors that are visible to the
+ * instrumented type are added, i.e. package-private constructors are only added if the super type is defined
+ * in the same package as the instrumented type and private constructors are always skipped.
+ */
+ IMITATE_SUPER_CLASS {
+ @Override
+ protected List<MethodDescription.Token> doExtractConstructors(TypeDescription instrumentedType) {
+ TypeDescription.Generic superClass = instrumentedType.getSuperClass();
+ return (superClass == null
+ ? new MethodList.Empty<MethodDescription.InGenericShape>()
+ : superClass.getDeclaredMethods().filter(isConstructor().and(isVisibleTo(instrumentedType)))).asTokenList(is(instrumentedType));
+ }
+
+ @Override
+ public MethodRegistry doInject(MethodRegistry methodRegistry, MethodAttributeAppender.Factory methodAttributeAppenderFactory) {
+ return methodRegistry.append(new LatentMatcher.Resolved<MethodDescription>(isConstructor()),
+ new MethodRegistry.Handler.ForImplementation(SuperMethodCall.INSTANCE),
+ methodAttributeAppenderFactory,
+ Transformer.NoOp.<MethodDescription>make());
+ }
+ },
+
+ /**
+ * This strategy is adding all constructors of the instrumented type's super class where each constructor is
+ * directly invoking its signature-equivalent super class constructor. Only {@code public} constructors are
+ * added.
+ */
+ IMITATE_SUPER_CLASS_PUBLIC {
+ @Override
+ protected List<MethodDescription.Token> doExtractConstructors(TypeDescription instrumentedType) {
+ TypeDescription.Generic superClass = instrumentedType.getSuperClass();
+ return (superClass == null
+ ? new MethodList.Empty<MethodDescription.InGenericShape>()
+ : superClass.getDeclaredMethods().filter(isPublic().and(isConstructor()))).asTokenList(is(instrumentedType));
+ }
+
+ @Override
+ public MethodRegistry doInject(MethodRegistry methodRegistry, MethodAttributeAppender.Factory methodAttributeAppenderFactory) {
+ return methodRegistry.append(new LatentMatcher.Resolved<MethodDescription>(isConstructor()),
+ new MethodRegistry.Handler.ForImplementation(SuperMethodCall.INSTANCE),
+ methodAttributeAppenderFactory,
+ Transformer.NoOp.<MethodDescription>make());
+ }
+ },
+
+ /**
+ * This strategy is adding all constructors of the instrumented type's super class where each constructor is
+ * directly invoking its signature-equivalent super class constructor. A constructor is added for any constructor
+ * of the super class that is invokable and is declared as {@code public}.
+ */
+ IMITATE_SUPER_CLASS_OPENING {
+ @Override
+ protected List<MethodDescription.Token> doExtractConstructors(TypeDescription instrumentedType) {
+ TypeDescription.Generic superClass = instrumentedType.getSuperClass();
+ return (superClass == null
+ ? new MethodList.Empty<MethodDescription.InGenericShape>()
+ : superClass.getDeclaredMethods().filter(isConstructor().and(isVisibleTo(instrumentedType)))).asTokenList(is(instrumentedType));
+ }
+
+ @Override
+ public MethodRegistry doInject(MethodRegistry methodRegistry, MethodAttributeAppender.Factory methodAttributeAppenderFactory) {
+ return methodRegistry.append(new LatentMatcher.Resolved<MethodDescription>(isConstructor()),
+ new MethodRegistry.Handler.ForImplementation(SuperMethodCall.INSTANCE),
+ methodAttributeAppenderFactory,
+ Transformer.NoOp.<MethodDescription>make());
+ }
+
+ @Override
+ protected int resolveModifier(int modifiers) {
+ return Opcodes.ACC_PUBLIC;
+ }
+ };
+
+ @Override
+ public List<MethodDescription.Token> extractConstructors(TypeDescription instrumentedType) {
+ List<MethodDescription.Token> tokens = doExtractConstructors(instrumentedType), stripped = new ArrayList<MethodDescription.Token>(tokens.size());
+ for (MethodDescription.Token token : tokens) {
+ stripped.add(new MethodDescription.Token(token.getName(),
+ resolveModifier(token.getModifiers()),
+ token.getTypeVariableTokens(),
+ token.getReturnType(),
+ token.getParameterTokens(),
+ token.getExceptionTypes(),
+ token.getAnnotations(),
+ token.getDefaultValue(),
+ TypeDescription.Generic.UNDEFINED));
+ }
+ return stripped;
+ }
+
+ /**
+ * Resolves a constructor's modifiers.
+ *
+ * @param modifiers The actual constructor's modifiers.
+ * @return The resolved modifiers.
+ */
+ protected int resolveModifier(int modifiers) {
+ return modifiers;
+ }
+
+ /**
+ * Extracts the relevant method tokens of the instrumented type's constructors.
+ *
+ * @param instrumentedType The type for which to extract the constructors.
+ * @return A list of relevant method tokens.
+ */
+ protected abstract List<MethodDescription.Token> doExtractConstructors(TypeDescription instrumentedType);
+
+ @Override
+ public MethodRegistry inject(MethodRegistry methodRegistry) {
+ return doInject(methodRegistry, MethodAttributeAppender.NoOp.INSTANCE);
+ }
+
+ /**
+ * Applies the actual injection with a method attribute appender factory supplied.
+ *
+ * @param methodRegistry The method registry into which to inject the constructors.
+ * @param methodAttributeAppenderFactory The method attribute appender to use.
+ * @return The resulting method registry.
+ */
+ protected abstract MethodRegistry doInject(MethodRegistry methodRegistry, MethodAttributeAppender.Factory methodAttributeAppenderFactory);
+
+ /**
+ * Returns a constructor strategy that supplies the supplied method attribute appender factory.
+ *
+ * @param methodAttributeAppenderFactory The method attribute appender factory to use.
+ * @return A copy of this constructor strategy with the method attribute appender factory applied.
+ */
+ public ConstructorStrategy with(MethodAttributeAppender.Factory methodAttributeAppenderFactory) {
+ return new WithMethodAttributeAppenderFactory(this, methodAttributeAppenderFactory);
+ }
+
+ /**
+ * Applies this constructor strategy while retaining any of the base constructor's annotations.
+ *
+ * @return A copy of this constructor strategy which retains any of the base constructor's annotations.
+ */
+ public ConstructorStrategy withInheritedAnnotations() {
+ return new WithMethodAttributeAppenderFactory(this, MethodAttributeAppender.ForInstrumentedMethod.EXCLUDING_RECEIVER);
+ }
+
+ /**
+ * A wrapper for a default constructor strategy which additionally applies a method attribute appender factory.
+ */
+ @EqualsAndHashCode
+ protected static class WithMethodAttributeAppenderFactory implements ConstructorStrategy {
+
+ /**
+ * The delegate default constructor strategy.
+ */
+ private final Default delegate;
+
+ /**
+ * The method attribute appender factory to apply.
+ */
+ private final MethodAttributeAppender.Factory methodAttributeAppenderFactory;
+
+ /**
+ * Creates a new wrapper for a default constructor strategy.
+ *
+ * @param delegate The delegate default constructor strategy.
+ * @param methodAttributeAppenderFactory The method attribute appender factory to apply.
+ */
+ protected WithMethodAttributeAppenderFactory(Default delegate, MethodAttributeAppender.Factory methodAttributeAppenderFactory) {
+ this.delegate = delegate;
+ this.methodAttributeAppenderFactory = methodAttributeAppenderFactory;
+ }
+
+ @Override
+ public List<MethodDescription.Token> extractConstructors(TypeDescription instrumentedType) {
+ return delegate.extractConstructors(instrumentedType);
+ }
+
+ @Override
+ public MethodRegistry inject(MethodRegistry methodRegistry) {
+ return delegate.doInject(methodRegistry, methodAttributeAppenderFactory);
+ }
+ }
+
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/subclass/SubclassDynamicTypeBuilder.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/subclass/SubclassDynamicTypeBuilder.java
new file mode 100644
index 0000000..6ea91f7
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/subclass/SubclassDynamicTypeBuilder.java
@@ -0,0 +1,221 @@
+package net.bytebuddy.dynamic.scaffold.subclass;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.asm.AsmVisitorWrapper;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.TypeResolutionStrategy;
+import net.bytebuddy.dynamic.scaffold.*;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.attribute.AnnotationRetention;
+import net.bytebuddy.implementation.attribute.AnnotationValueFilter;
+import net.bytebuddy.implementation.attribute.TypeAttributeAppender;
+import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.matcher.LatentMatcher;
+import net.bytebuddy.pool.TypePool;
+
+import static net.bytebuddy.matcher.ElementMatchers.*;
+
+/**
+ * A type builder that creates an instrumented type as a subclass, i.e. a type that is not based on an existing class file.
+ *
+ * @param <T> A loaded type that the dynamic type is guaranteed to be a subtype of.
+ */
+ at EqualsAndHashCode(callSuper = true)
+public class SubclassDynamicTypeBuilder<T> extends DynamicType.Builder.AbstractBase.Adapter<T> {
+
+ /**
+ * The constructor strategy to apply onto the instrumented type.
+ */
+ private final ConstructorStrategy constructorStrategy;
+
+ /**
+ * Creates a new type builder for creating a subclass.
+ *
+ * @param instrumentedType An instrumented type representing the subclass.
+ * @param classFileVersion The class file version to use for types that are not based on an existing class file.
+ * @param auxiliaryTypeNamingStrategy The naming strategy to use for naming auxiliary types.
+ * @param annotationValueFilterFactory The annotation value filter factory to use.
+ * @param annotationRetention The annotation retention strategy to use.
+ * @param implementationContextFactory The implementation context factory to use.
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @param typeValidation Determines if a type should be explicitly validated.
+ * @param ignoredMethods A matcher for identifying methods that should be excluded from instrumentation.
+ * @param constructorStrategy The constructor strategy to apply onto the instrumented type.
+ */
+ public SubclassDynamicTypeBuilder(InstrumentedType.WithFlexibleName instrumentedType,
+ ClassFileVersion classFileVersion,
+ AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
+ AnnotationValueFilter.Factory annotationValueFilterFactory,
+ AnnotationRetention annotationRetention,
+ Implementation.Context.Factory implementationContextFactory,
+ MethodGraph.Compiler methodGraphCompiler,
+ TypeValidation typeValidation,
+ LatentMatcher<? super MethodDescription> ignoredMethods,
+ ConstructorStrategy constructorStrategy) {
+ this(instrumentedType,
+ new FieldRegistry.Default(),
+ new MethodRegistry.Default(),
+ TypeAttributeAppender.ForInstrumentedType.INSTANCE,
+ AsmVisitorWrapper.NoOp.INSTANCE,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ typeValidation,
+ ignoredMethods,
+ constructorStrategy);
+ }
+
+ /**
+ * Creates a new type builder for creating a subclass.
+ *
+ * @param instrumentedType An instrumented type representing the subclass.
+ * @param fieldRegistry The field pool to use.
+ * @param methodRegistry The method pool to use.
+ * @param typeAttributeAppender The type attribute appender to apply onto the instrumented type.
+ * @param asmVisitorWrapper The ASM visitor wrapper to apply onto the class writer.
+ * @param classFileVersion The class file version to use for types that are not based on an existing class file.
+ * @param auxiliaryTypeNamingStrategy The naming strategy to use for naming auxiliary types.
+ * @param annotationValueFilterFactory The annotation value filter factory to use.
+ * @param annotationRetention The annotation retention strategy to use.
+ * @param implementationContextFactory The implementation context factory to use.
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @param typeValidation Determines if a type should be explicitly validated.
+ * @param ignoredMethods A matcher for identifying methods that should be excluded from instrumentation.
+ * @param constructorStrategy The constructor strategy to apply onto the instrumented type.
+ */
+ protected SubclassDynamicTypeBuilder(InstrumentedType.WithFlexibleName instrumentedType,
+ FieldRegistry fieldRegistry,
+ MethodRegistry methodRegistry,
+ TypeAttributeAppender typeAttributeAppender,
+ AsmVisitorWrapper asmVisitorWrapper,
+ ClassFileVersion classFileVersion,
+ AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
+ AnnotationValueFilter.Factory annotationValueFilterFactory,
+ AnnotationRetention annotationRetention,
+ Implementation.Context.Factory implementationContextFactory,
+ MethodGraph.Compiler methodGraphCompiler,
+ TypeValidation typeValidation,
+ LatentMatcher<? super MethodDescription> ignoredMethods,
+ ConstructorStrategy constructorStrategy) {
+ super(instrumentedType,
+ fieldRegistry,
+ methodRegistry,
+ typeAttributeAppender,
+ asmVisitorWrapper,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ typeValidation,
+ ignoredMethods);
+ this.constructorStrategy = constructorStrategy;
+ }
+
+ @Override
+ protected DynamicType.Builder<T> materialize(InstrumentedType.WithFlexibleName instrumentedType,
+ FieldRegistry fieldRegistry,
+ MethodRegistry methodRegistry,
+ TypeAttributeAppender typeAttributeAppender,
+ AsmVisitorWrapper asmVisitorWrapper,
+ ClassFileVersion classFileVersion,
+ AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
+ AnnotationValueFilter.Factory annotationValueFilterFactory,
+ AnnotationRetention annotationRetention,
+ Implementation.Context.Factory implementationContextFactory,
+ MethodGraph.Compiler methodGraphCompiler,
+ TypeValidation typeValidation,
+ LatentMatcher<? super MethodDescription> ignoredMethods) {
+ return new SubclassDynamicTypeBuilder<T>(instrumentedType,
+ fieldRegistry,
+ methodRegistry,
+ typeAttributeAppender,
+ asmVisitorWrapper,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ annotationValueFilterFactory,
+ annotationRetention,
+ implementationContextFactory,
+ methodGraphCompiler,
+ typeValidation,
+ ignoredMethods,
+ constructorStrategy);
+ }
+
+ @Override
+ public DynamicType.Unloaded<T> make(TypeResolutionStrategy typeResolutionStrategy) {
+ return make(typeResolutionStrategy, TypePool.ClassLoading.ofClassPath()); // Mimics the default behavior of ASM for least surprise.
+ }
+
+ @Override
+ public DynamicType.Unloaded<T> make(TypeResolutionStrategy typeResolutionStrategy, TypePool typePool) {
+ MethodRegistry.Compiled methodRegistry = constructorStrategy
+ .inject(this.methodRegistry)
+ .prepare(applyConstructorStrategy(instrumentedType), methodGraphCompiler, typeValidation, new InstrumentableMatcher(ignoredMethods))
+ .compile(SubclassImplementationTarget.Factory.SUPER_CLASS, classFileVersion);
+ return TypeWriter.Default.<T>forCreation(methodRegistry,
+ fieldRegistry.compile(methodRegistry.getInstrumentedType()),
+ typeAttributeAppender,
+ asmVisitorWrapper,
+ classFileVersion,
+ annotationValueFilterFactory,
+ annotationRetention,
+ auxiliaryTypeNamingStrategy,
+ implementationContextFactory,
+ typeValidation,
+ typePool).make(typeResolutionStrategy.resolve());
+ }
+
+ /**
+ * Applies this builder's constructor strategy to the given instrumented type.
+ *
+ * @param instrumentedType The instrumented type to apply the constructor onto.
+ * @return The instrumented type with the constructor strategy applied onto.
+ */
+ private InstrumentedType applyConstructorStrategy(InstrumentedType instrumentedType) {
+ if (!instrumentedType.isInterface()) {
+ for (MethodDescription.Token token : constructorStrategy.extractConstructors(instrumentedType)) {
+ instrumentedType = instrumentedType.withMethod(token);
+ }
+ }
+ return instrumentedType;
+ }
+
+ /**
+ * A matcher that locates all methods that are overridable and not ignored or that are directly defined on the instrumented type.
+ */
+ @EqualsAndHashCode
+ protected static class InstrumentableMatcher implements LatentMatcher<MethodDescription> {
+
+ /**
+ * A matcher for the ignored methods.
+ */
+ private final LatentMatcher<? super MethodDescription> ignoredMethods;
+
+ /**
+ * Creates a latent method matcher that matches all methods that are to be instrumented by a {@link SubclassDynamicTypeBuilder}.
+ *
+ * @param ignoredMethods A matcher for the ignored methods.
+ */
+ protected InstrumentableMatcher(LatentMatcher<? super MethodDescription> ignoredMethods) {
+ this.ignoredMethods = ignoredMethods;
+ }
+
+ @Override
+ public ElementMatcher<? super MethodDescription> resolve(TypeDescription typeDescription) {
+ // Casting is required by JDK 6.
+ return (ElementMatcher<? super MethodDescription>) isVirtual().and(not(isFinal()))
+ .and(isVisibleTo(typeDescription))
+ .and(not(ignoredMethods.resolve(typeDescription)))
+ .or(isDeclaredBy(typeDescription));
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/subclass/SubclassImplementationTarget.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/subclass/SubclassImplementationTarget.java
new file mode 100644
index 0000000..b4c911a
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/subclass/SubclassImplementationTarget.java
@@ -0,0 +1,152 @@
+package net.bytebuddy.dynamic.scaffold.subclass;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.scaffold.MethodGraph;
+import net.bytebuddy.implementation.Implementation;
+
+import static net.bytebuddy.matcher.ElementMatchers.hasSignature;
+import static net.bytebuddy.matcher.ElementMatchers.isVisibleTo;
+
+/**
+ * An implementation target for creating a subclass of a given type.
+ */
+ at EqualsAndHashCode(callSuper = true)
+public class SubclassImplementationTarget extends Implementation.Target.AbstractBase {
+
+ /**
+ * The origin type identifier to use.
+ */
+ protected final OriginTypeResolver originTypeResolver;
+
+ /**
+ * Creates a new subclass implementation target.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param methodGraph A method graph of the instrumented type.
+ * @param defaultMethodInvocation The default method invocation mode to apply.
+ * @param originTypeResolver A resolver for the origin type.
+ */
+ protected SubclassImplementationTarget(TypeDescription instrumentedType,
+ MethodGraph.Linked methodGraph,
+ DefaultMethodInvocation defaultMethodInvocation,
+ OriginTypeResolver originTypeResolver) {
+ super(instrumentedType, methodGraph, defaultMethodInvocation);
+ this.originTypeResolver = originTypeResolver;
+ }
+
+ @Override
+ public Implementation.SpecialMethodInvocation invokeSuper(MethodDescription.SignatureToken token) {
+ return token.getName().equals(MethodDescription.CONSTRUCTOR_INTERNAL_NAME)
+ ? invokeConstructor(token)
+ : invokeMethod(token);
+ }
+
+ /**
+ * Resolves a special method invocation for a constructor invocation.
+ *
+ * @param token A token describing the constructor to be invoked.
+ * @return A special method invocation for a constructor representing the given method token, if available.
+ */
+ private Implementation.SpecialMethodInvocation invokeConstructor(MethodDescription.SignatureToken token) {
+ TypeDescription.Generic superClass = instrumentedType.getSuperClass();
+ MethodList<?> candidates = superClass == null
+ ? new MethodList.Empty<MethodDescription.InGenericShape>()
+ : superClass.getDeclaredMethods().filter(hasSignature(token).and(isVisibleTo(instrumentedType)));
+ return candidates.size() == 1
+ ? Implementation.SpecialMethodInvocation.Simple.of(candidates.getOnly(), instrumentedType.getSuperClass().asErasure())
+ : Implementation.SpecialMethodInvocation.Illegal.INSTANCE;
+ }
+
+ /**
+ * Resolves a special method invocation for a non-constructor invocation.
+ *
+ * @param token A token describing the method to be invoked.
+ * @return A special method invocation for a method representing the given method token, if available.
+ */
+ private Implementation.SpecialMethodInvocation invokeMethod(MethodDescription.SignatureToken token) {
+ MethodGraph.Node methodNode = methodGraph.getSuperClassGraph().locate(token);
+ return methodNode.getSort().isUnique()
+ ? Implementation.SpecialMethodInvocation.Simple.of(methodNode.getRepresentative(), instrumentedType.getSuperClass().asErasure())
+ : Implementation.SpecialMethodInvocation.Illegal.INSTANCE;
+ }
+
+ @Override
+ public TypeDefinition getOriginType() {
+ return originTypeResolver.identify(instrumentedType);
+ }
+
+ /**
+ * Responsible for identifying the origin type that an implementation target represents when
+ * {@link Implementation.Target#getOriginType()} is invoked.
+ */
+ public enum OriginTypeResolver {
+
+ /**
+ * Identifies the super type of an instrumented type as the origin class.
+ */
+ SUPER_CLASS {
+ @Override
+ protected TypeDefinition identify(TypeDescription typeDescription) {
+ return typeDescription.getSuperClass();
+ }
+ },
+
+ /**
+ * Identifies the instrumented type as its own origin type.
+ */
+ LEVEL_TYPE {
+ @Override
+ protected TypeDefinition identify(TypeDescription typeDescription) {
+ return typeDescription;
+ }
+ };
+
+ /**
+ * Identifies the origin type to a given type description.
+ *
+ * @param typeDescription The type description for which an origin type should be identified.
+ * @return The origin type to the given type description.
+ */
+ protected abstract TypeDefinition identify(TypeDescription typeDescription);
+ }
+
+ /**
+ * A factory for creating a {@link net.bytebuddy.dynamic.scaffold.subclass.SubclassImplementationTarget}.
+ */
+ public enum Factory implements Implementation.Target.Factory {
+
+ /**
+ * A factory creating a subclass implementation target with a {@link OriginTypeResolver#SUPER_CLASS}.
+ */
+ SUPER_CLASS(OriginTypeResolver.SUPER_CLASS),
+
+ /**
+ * A factory creating a subclass implementation target with a {@link OriginTypeResolver#LEVEL_TYPE}.
+ */
+ LEVEL_TYPE(OriginTypeResolver.LEVEL_TYPE);
+
+ /**
+ * The origin type resolver that this factory hands to the created {@link SubclassImplementationTarget}.
+ */
+ private final OriginTypeResolver originTypeResolver;
+
+ /**
+ * Creates a new factory.
+ *
+ * @param originTypeResolver The origin type resolver that this factory hands to the created {@link SubclassImplementationTarget}.
+ */
+ Factory(OriginTypeResolver originTypeResolver) {
+ this.originTypeResolver = originTypeResolver;
+ }
+
+ @Override
+ public Implementation.Target make(TypeDescription instrumentedType, MethodGraph.Linked methodGraph, ClassFileVersion classFileVersion) {
+ return new SubclassImplementationTarget(instrumentedType, methodGraph, DefaultMethodInvocation.of(classFileVersion), originTypeResolver);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/subclass/package-info.java b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/subclass/package-info.java
new file mode 100644
index 0000000..28e795c
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/dynamic/scaffold/subclass/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * All classes and types in this package are related to creating a {@link net.bytebuddy.dynamic.DynamicType} by
+ * creating a subclass of a given type.
+ */
+package net.bytebuddy.dynamic.scaffold.subclass;
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/DefaultMethodCall.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/DefaultMethodCall.java
new file mode 100644
index 0000000..c760435
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/DefaultMethodCall.java
@@ -0,0 +1,232 @@
+package net.bytebuddy.implementation;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.member.MethodReturn;
+import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+import org.objectweb.asm.MethodVisitor;
+
+import java.util.*;
+
+/**
+ * This {@link Implementation} invokes a default method for the methods it instruments.
+ * A default method is potentially ambiguous if a method of identical signature is defined in several interfaces.
+ * For this reason, this implementation allows for the specification of <i>prioritized interfaces</i> whose default
+ * methods, if a method of equivalent signature is defined for a specific interface. All prioritized interfaces are
+ * searched for default methods in the order of their specification. If no prioritized interface defines a default method
+ * of equivalent signature to the given instrumented method, any non-prioritized interface is searched for a suitable
+ * default method. If exactly one of those interfaces defines a suitable default method, this method is invoked.
+ * If no method or more than one method is identified as a suitable default method, an exception is thrown.
+ * <p> </p>
+ * When it comes to default methods, the Java programming language specifies stronger requirements for the
+ * legitimacy of invoking a default method than the Java runtime. The Java compiler requires a method to be
+ * the most specific method in its defining type's type hierarchy, i.e. the method must not be overridden by another
+ * interface or class type. Additionally, the method must be invokable from an interface type which is directly
+ * implemented by the instrumented type. The Java runtime only requires the second condition to be fulfilled which
+ * is why this implementation only checks the later condition, as well.
+ */
+ at EqualsAndHashCode
+public class DefaultMethodCall implements Implementation {
+
+ /**
+ * A list of prioritized interfaces in the order in which a method should be attempted to be called.
+ */
+ private final List<TypeDescription> prioritizedInterfaces;
+
+ /**
+ * Creates a new {@link net.bytebuddy.implementation.DefaultMethodCall} implementation for a given list of
+ * prioritized interfaces.
+ *
+ * @param prioritizedInterfaces A list of prioritized interfaces in the order in which a method should be attempted to
+ * be called.
+ */
+ protected DefaultMethodCall(List<TypeDescription> prioritizedInterfaces) {
+ this.prioritizedInterfaces = prioritizedInterfaces;
+ }
+
+ /**
+ * Creates a {@link net.bytebuddy.implementation.DefaultMethodCall} implementation which searches the given list
+ * of interface types for a suitable default method in their order. If no such prioritized interface is suitable,
+ * because it is either not defined on the instrumented type or because it does not define a suitable default method,
+ * any remaining interface is searched for a suitable default method. If no or more than one method defines a
+ * suitable default method, an exception is thrown.
+ *
+ * @param prioritizedInterface A list of prioritized default method interfaces in their prioritization order.
+ * @return An implementation which calls an instrumented method's compatible default method that considers the given
+ * interfaces to be prioritized in their order.
+ */
+ public static Implementation prioritize(Class<?>... prioritizedInterface) {
+ return prioritize(new TypeList.ForLoadedTypes(prioritizedInterface));
+ }
+
+ /**
+ * Creates a {@link net.bytebuddy.implementation.DefaultMethodCall} implementation which searches the given list
+ * of interface types for a suitable default method in their order. If no such prioritized interface is suitable,
+ * because it is either not defined on the instrumented type or because it does not define a suitable default method,
+ * any remaining interface is searched for a suitable default method. If no or more than one method defines a
+ * suitable default method, an exception is thrown.
+ *
+ * @param prioritizedInterfaces A list of prioritized default method interfaces in their prioritization order.
+ * @return An implementation which calls an instrumented method's compatible default method that considers the given
+ * interfaces to be prioritized in their order.
+ */
+ public static Implementation prioritize(Iterable<? extends Class<?>> prioritizedInterfaces) {
+ List<Class<?>> list = new ArrayList<Class<?>>();
+ for (Class<?> prioritizedInterface : prioritizedInterfaces) {
+ list.add(prioritizedInterface);
+ }
+ return prioritize(new TypeList.ForLoadedTypes(list));
+ }
+
+ /**
+ * Creates a {@link net.bytebuddy.implementation.DefaultMethodCall} implementation which searches the given list
+ * of interface types for a suitable default method in their order. If no such prioritized interface is suitable,
+ * because it is either not defined on the instrumented type or because it does not define a suitable default method,
+ * any remaining interface is searched for a suitable default method. If no or more than one method defines a
+ * suitable default method, an exception is thrown.
+ *
+ * @param prioritizedInterface A list of prioritized default method interfaces in their prioritization order.
+ * @return An implementation which calls an instrumented method's compatible default method that considers the given
+ * interfaces to be prioritized in their order.
+ */
+ public static Implementation prioritize(TypeDescription... prioritizedInterface) {
+ return prioritize(Arrays.asList(prioritizedInterface));
+ }
+
+ /**
+ * Creates a {@link net.bytebuddy.implementation.DefaultMethodCall} implementation which searches the given list
+ * of interface types for a suitable default method in their order. If no such prioritized interface is suitable,
+ * because it is either not defined on the instrumented type or because it does not define a suitable default method,
+ * any remaining interface is searched for a suitable default method. If no or more than one method defines a
+ * suitable default method, an exception is thrown.
+ *
+ * @param prioritizedInterfaces A collection of prioritized default method interfaces in their prioritization order.
+ * @return An implementation which calls an instrumented method's compatible default method that considers the given
+ * interfaces to be prioritized in their order.
+ */
+ public static Implementation prioritize(Collection<? extends TypeDescription> prioritizedInterfaces) {
+ return new DefaultMethodCall(new ArrayList<TypeDescription>(prioritizedInterfaces));
+ }
+
+ /**
+ * Creates a {@link net.bytebuddy.implementation.DefaultMethodCall} implementation without prioritizing any
+ * interface. Instead, any interface that is defined for a given type is searched for a suitable default method. If
+ * exactly one interface defines a suitable default method, this method is invoked from the instrumented method.
+ * Otherwise, an exception is thrown.
+ *
+ * @return An implementation which calls an instrumented method's compatible default method if such a method
+ * is unambiguous.
+ */
+ public static Implementation unambiguousOnly() {
+ return new DefaultMethodCall(Collections.<TypeDescription>emptyList());
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return new Appender(implementationTarget, filterRelevant(implementationTarget.getInstrumentedType()));
+ }
+
+ /**
+ * Filters the relevant prioritized interfaces for a given type, i.e. finds the types that are
+ * directly declared on a given instrumented type.
+ *
+ * @param typeDescription The instrumented type for which the prioritized interfaces are to be filtered.
+ * @return A list of prioritized interfaces that are additionally implemented by the given type.
+ */
+ private List<TypeDescription> filterRelevant(TypeDescription typeDescription) {
+ List<TypeDescription> filtered = new ArrayList<TypeDescription>(prioritizedInterfaces.size());
+ Set<TypeDescription> relevant = new HashSet<TypeDescription>(typeDescription.getInterfaces().asErasures());
+ for (TypeDescription prioritizedInterface : prioritizedInterfaces) {
+ if (relevant.remove(prioritizedInterface)) {
+ filtered.add(prioritizedInterface);
+ }
+ }
+ return filtered;
+ }
+
+ /**
+ * The appender for implementing a {@link net.bytebuddy.implementation.DefaultMethodCall}.
+ */
+ @EqualsAndHashCode(exclude = "nonPrioritizedInterfaces")
+ protected static class Appender implements ByteCodeAppender {
+
+ /**
+ * The implementation target of this appender.
+ */
+ private final Target implementationTarget;
+
+ /**
+ * The relevant prioritized interfaces to be considered by this appender.
+ */
+ private final List<TypeDescription> prioritizedInterfaces;
+
+ /**
+ * The relevant non-prioritized interfaces to be considered by this appender.
+ */
+ private final Set<TypeDescription> nonPrioritizedInterfaces;
+
+ /**
+ * Creates a new appender for implementing a {@link net.bytebuddy.implementation.DefaultMethodCall}.
+ *
+ * @param implementationTarget The implementation target of this appender.
+ * @param prioritizedInterfaces The relevant prioritized interfaces to be considered by this appender.
+ */
+ protected Appender(Target implementationTarget, List<TypeDescription> prioritizedInterfaces) {
+ this.implementationTarget = implementationTarget;
+ this.prioritizedInterfaces = prioritizedInterfaces;
+ this.nonPrioritizedInterfaces = new HashSet<TypeDescription>(implementationTarget.getInstrumentedType().getInterfaces().asErasures());
+ nonPrioritizedInterfaces.removeAll(prioritizedInterfaces);
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ StackManipulation defaultMethodInvocation = locateDefault(instrumentedMethod);
+ if (!defaultMethodInvocation.isValid()) {
+ throw new IllegalStateException("Cannot invoke default method on " + instrumentedMethod);
+ }
+ StackManipulation.Size stackSize = new StackManipulation.Compound(
+ MethodVariableAccess.allArgumentsOf(instrumentedMethod).prependThisReference(),
+ defaultMethodInvocation,
+ MethodReturn.of(instrumentedMethod.getReturnType())
+ ).apply(methodVisitor, implementationContext);
+ return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+
+ /**
+ * Locates a special method invocation to be invoked from a given method.
+ *
+ * @param methodDescription The method that is currently instrumented.
+ * @return A potentially illegal stack manipulation representing the default method invocation for the
+ * given method.
+ */
+ private StackManipulation locateDefault(MethodDescription methodDescription) {
+ MethodDescription.SignatureToken methodToken = methodDescription.asSignatureToken();
+ SpecialMethodInvocation specialMethodInvocation = SpecialMethodInvocation.Illegal.INSTANCE;
+ for (TypeDescription typeDescription : prioritizedInterfaces) {
+ specialMethodInvocation = implementationTarget.invokeDefault(methodToken, typeDescription);
+ if (specialMethodInvocation.isValid()) {
+ return specialMethodInvocation;
+ }
+ }
+ for (TypeDescription typeDescription : nonPrioritizedInterfaces) {
+ SpecialMethodInvocation other = implementationTarget.invokeDefault(methodToken, typeDescription);
+ if (specialMethodInvocation.isValid() && other.isValid()) {
+ throw new IllegalStateException(methodDescription + " has an ambiguous default method with "
+ + other.getMethodDescription() + " and " + specialMethodInvocation.getMethodDescription());
+ }
+ specialMethodInvocation = other;
+ }
+ return specialMethodInvocation;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/ExceptionMethod.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/ExceptionMethod.java
new file mode 100644
index 0000000..ce7d85d
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/ExceptionMethod.java
@@ -0,0 +1,216 @@
+package net.bytebuddy.implementation;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.implementation.bytecode.*;
+import net.bytebuddy.implementation.bytecode.constant.TextConstant;
+import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
+import org.objectweb.asm.MethodVisitor;
+
+import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
+import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
+
+/**
+ * This implementation causes a {@link java.lang.Throwable} to be thrown when the instrumented method is invoked.
+ * Be aware that the Java Virtual machine does not care about exception declarations and will throw any
+ * {@link java.lang.Throwable} from any method even if the method does not declared a checked exception.
+ */
+ at EqualsAndHashCode
+public class ExceptionMethod implements Implementation, ByteCodeAppender {
+
+ /**
+ * The type of the exception that is thrown.
+ */
+ private final TypeDescription throwableType;
+
+ /**
+ * The construction delegation which is responsible for creating the exception to be thrown.
+ */
+ private final ConstructionDelegate constructionDelegate;
+
+ /**
+ * Creates a new instance of an implementation for throwing throwables.
+ *
+ * @param throwableType The type of the exception to be thrown.
+ * @param constructionDelegate A delegate that is responsible for calling the isThrowable's constructor.
+ */
+ public ExceptionMethod(TypeDescription throwableType, ConstructionDelegate constructionDelegate) {
+ this.throwableType = throwableType;
+ this.constructionDelegate = constructionDelegate;
+ }
+
+ /**
+ * Creates an implementation that creates a new instance of the given isThrowable type on each method invocation
+ * which is then thrown immediately. For this to be possible, the given type must define a default constructor
+ * which is visible from the instrumented type.
+ *
+ * @param exceptionType The type of the isThrowable.
+ * @return An implementation that will throw an instance of the isThrowable on each method invocation of the
+ * instrumented methods.
+ */
+ public static Implementation throwing(Class<? extends Throwable> exceptionType) {
+ return throwing(new TypeDescription.ForLoadedType(exceptionType));
+ }
+
+ /**
+ * Creates an implementation that creates a new instance of the given isThrowable type on each method invocation
+ * which is then thrown immediately. For this to be possible, the given type must define a default constructor
+ * which is visible from the instrumented type.
+ *
+ * @param exceptionType The type of the isThrowable.
+ * @return An implementation that will throw an instance of the isThrowable on each method invocation of the
+ * instrumented methods.
+ */
+ public static Implementation throwing(TypeDescription exceptionType) {
+ if (!exceptionType.isAssignableTo(Throwable.class)) {
+ throw new IllegalArgumentException(exceptionType + " does not extend throwable");
+ }
+ return new ExceptionMethod(exceptionType, new ConstructionDelegate.ForDefaultConstructor(exceptionType));
+ }
+
+ /**
+ * Creates an implementation that creates a new instance of the given isThrowable type on each method invocation
+ * which is then thrown immediately. For this to be possible, the given type must define a constructor that
+ * takes a single {@link java.lang.String} as its argument.
+ *
+ * @param exceptionType The type of the isThrowable.
+ * @param message The string that is handed to the constructor. Usually an exception message.
+ * @return An implementation that will throw an instance of the isThrowable on each method invocation of the
+ * instrumented methods.
+ */
+ public static Implementation throwing(Class<? extends Throwable> exceptionType, String message) {
+ return throwing(new TypeDescription.ForLoadedType(exceptionType), message);
+ }
+
+ /**
+ * Creates an implementation that creates a new instance of the given isThrowable type on each method invocation
+ * which is then thrown immediately. For this to be possible, the given type must define a constructor that
+ * takes a single {@link java.lang.String} as its argument.
+ *
+ * @param exceptionType The type of the isThrowable.
+ * @param message The string that is handed to the constructor. Usually an exception message.
+ * @return An implementation that will throw an instance of the isThrowable on each method invocation of the
+ * instrumented methods.
+ */
+ public static Implementation throwing(TypeDescription exceptionType, String message) {
+ if (!exceptionType.isAssignableTo(Throwable.class)) {
+ throw new IllegalArgumentException(exceptionType + " does not extend throwable");
+ }
+ return new ExceptionMethod(exceptionType, new ConstructionDelegate.ForStringConstructor(exceptionType, message));
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return this;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ StackManipulation.Size stackSize = new StackManipulation.Compound(
+ constructionDelegate.make(),
+ Throw.INSTANCE
+ ).apply(methodVisitor, implementationContext);
+ return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+
+ /**
+ * A construction delegate is responsible for calling a Throwable's constructor.
+ */
+ public interface ConstructionDelegate {
+
+ /**
+ * Creates a stack manipulation that creates pushes all constructor arguments onto the operand stack
+ * and subsequently calls the constructor.
+ *
+ * @return A stack manipulation for constructing a isThrowable.
+ */
+ StackManipulation make();
+
+ /**
+ * A construction delegate that calls the default constructor.
+ */
+ @EqualsAndHashCode
+ class ForDefaultConstructor implements ConstructionDelegate {
+
+ /**
+ * The type of the exception that is to be thrown.
+ */
+ private final TypeDescription exceptionType;
+
+ /**
+ * The constructor that is used for creating the exception.
+ */
+ private final MethodDescription targetConstructor;
+
+ /**
+ * Creates a new construction delegate that calls a default constructor.
+ *
+ * @param exceptionType The type of the isThrowable.
+ */
+ public ForDefaultConstructor(TypeDescription exceptionType) {
+ this.exceptionType = exceptionType;
+ this.targetConstructor = exceptionType.getDeclaredMethods()
+ .filter(isConstructor().and(takesArguments(0))).getOnly();
+ }
+
+ @Override
+ public StackManipulation make() {
+ return new StackManipulation.Compound(
+ TypeCreation.of(exceptionType),
+ Duplication.SINGLE,
+ MethodInvocation.invoke(targetConstructor));
+ }
+ }
+
+ /**
+ * A construction delegate that calls a constructor that takes a single string as its argument.
+ */
+ @EqualsAndHashCode
+ class ForStringConstructor implements ConstructionDelegate {
+
+ /**
+ * The type of the exception that is to be thrown.
+ */
+ private final TypeDescription exceptionType;
+
+ /**
+ * The constructor that is used for creating the exception.
+ */
+ private final MethodDescription targetConstructor;
+
+ /**
+ * The {@link java.lang.String} that is to be passed to the exception's constructor.
+ */
+ private final String message;
+
+ /**
+ * Creates a new construction delegate that calls a constructor by handing it the given string.
+ *
+ * @param exceptionType The type of the isThrowable.
+ * @param message The string that is handed to the constructor.
+ */
+ public ForStringConstructor(TypeDescription exceptionType, String message) {
+ this.exceptionType = exceptionType;
+ this.targetConstructor = exceptionType.getDeclaredMethods()
+ .filter(isConstructor().and(takesArguments(String.class))).getOnly();
+ this.message = message;
+ }
+
+ @Override
+ public StackManipulation make() {
+ return new StackManipulation.Compound(
+ TypeCreation.of(exceptionType),
+ Duplication.SINGLE,
+ new TextConstant(message),
+ MethodInvocation.invoke(targetConstructor));
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/FieldAccessor.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/FieldAccessor.java
new file mode 100644
index 0000000..bc6e5ef
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/FieldAccessor.java
@@ -0,0 +1,734 @@
+package net.bytebuddy.implementation;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.scaffold.FieldLocator;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.member.FieldAccess;
+import net.bytebuddy.implementation.bytecode.member.MethodReturn;
+import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+import org.objectweb.asm.MethodVisitor;
+
+import java.lang.reflect.Field;
+
+/**
+ * <p>
+ * Defines a method to access a given field by following the Java bean conventions for getters and setters:
+ * </p>
+ * <ul>
+ * <li>Getter: A method named {@code getFoo()} will be instrumented to read and return the value of a field {@code foo}
+ * or another field if one was specified explicitly. If a property is of type {@link java.lang.Boolean} or
+ * {@code boolean}, the name {@code isFoo()} is also permitted.</li>
+ * <li>Setter: A method named {@code setFoo(value)} will be instrumented to write the given argument {@code value}
+ * to a field {@code foo} or to another field if one was specified explicitly.</li>
+ * </ul>
+ * <p>
+ * Field accessors always implement a getter if a non-{@code void} value is returned from a method and attempt to define a setter
+ * otherwise. If a field accessor is not explicitly defined as a setter via {@link PropertyConfigurable}, an instrumented
+ * method must define exactly one parameter. Using the latter API, an explicit parameter index can be defined and a return
+ * value can be specified explicitly when {@code void} is not returned.
+ * </p>
+ */
+ at EqualsAndHashCode
+public abstract class FieldAccessor implements Implementation {
+
+ /**
+ * The field's location.
+ */
+ protected final FieldLocation fieldLocation;
+
+ /**
+ * The assigner to use.
+ */
+ protected final Assigner assigner;
+
+ /**
+ * Indicates if dynamic type castings should be attempted for incompatible assignments.
+ */
+ protected final Assigner.Typing typing;
+
+ /**
+ * Creates a new field accessor.
+ *
+ * @param fieldLocation The field's location.
+ * @param assigner The assigner to use.
+ * @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
+ */
+ protected FieldAccessor(FieldLocation fieldLocation, Assigner assigner, Assigner.Typing typing) {
+ this.fieldLocation = fieldLocation;
+ this.assigner = assigner;
+ this.typing = typing;
+ }
+
+ /**
+ * Defines a field accessor where any access is targeted to a field named {@code name}.
+ *
+ * @param name The name of the field to be accessed.
+ * @return A field accessor for a field of a given name.
+ */
+ public static OwnerTypeLocatable ofField(String name) {
+ return of(new FieldNameExtractor.ForFixedValue(name));
+ }
+
+ /**
+ * Defines a field accessor where any access is targeted to a field that matches the methods
+ * name with the Java specification for bean properties, i.e. a method {@code getFoo} or {@code setFoo(value)}
+ * will either read or write a field named {@code foo}.
+ *
+ * @return A field accessor that follows the Java naming conventions for bean properties.
+ */
+ public static OwnerTypeLocatable ofBeanProperty() {
+ return of(FieldNameExtractor.ForBeanProperty.INSTANCE);
+ }
+
+ /**
+ * Defines a custom strategy for determining the field that is accessed by this field accessor.
+ *
+ * @param fieldNameExtractor The field name extractor to use.
+ * @return A field accessor using the given field name extractor.
+ */
+ public static OwnerTypeLocatable of(FieldNameExtractor fieldNameExtractor) {
+ return new ForImplicitProperty(new FieldLocation.Relative(fieldNameExtractor));
+ }
+
+ /**
+ * Defines a field accessor where the specified field is accessed. The field must be within the hierarchy of the instrumented type.
+ *
+ * @param field The field being accessed.
+ * @return A field accessor for the given field.
+ */
+ public static AssignerConfigurable of(Field field) {
+ return of(new FieldDescription.ForLoadedField(field));
+ }
+
+ /**
+ * Defines a field accessor where the specified field is accessed. The field must be within the hierarchy of the instrumented type.
+ *
+ * @param fieldDescription The field being accessed.
+ * @return A field accessor for the given field.
+ */
+ public static AssignerConfigurable of(FieldDescription fieldDescription) {
+ return new ForImplicitProperty(new FieldLocation.Absolute(fieldDescription));
+ }
+
+ /**
+ * Creates a getter getter.
+ *
+ * @param fieldDescription The field to read the value from.
+ * @param instrumentedMethod The getter method.
+ * @return A stack manipulation that gets the field's value.
+ */
+ protected StackManipulation getter(FieldDescription fieldDescription, MethodDescription instrumentedMethod) {
+ return access(fieldDescription, instrumentedMethod, new StackManipulation.Compound(FieldAccess.forField(fieldDescription).read(),
+ assigner.assign(fieldDescription.getType(), instrumentedMethod.getReturnType(), typing)));
+ }
+
+ /**
+ * Creates a setter instruction.
+ *
+ * @param fieldDescription The field to set a value for.
+ * @param parameterDescription The parameter for what value is to be set.
+ * @return A stack manipulation that sets the field's value.
+ */
+ protected StackManipulation setter(FieldDescription fieldDescription, ParameterDescription parameterDescription) {
+ if (fieldDescription.isFinal() && parameterDescription.getDeclaringMethod().isMethod()) {
+ throw new IllegalArgumentException("Cannot set final field " + fieldDescription + " from " + parameterDescription.getDeclaringMethod());
+ }
+ return access(fieldDescription,
+ parameterDescription.getDeclaringMethod(),
+ new StackManipulation.Compound(MethodVariableAccess.load(parameterDescription),
+ assigner.assign(parameterDescription.getType(), fieldDescription.getType(), typing),
+ FieldAccess.forField(fieldDescription).write()));
+ }
+
+ /**
+ * Checks a field access and loads the {@code this} instance if necessary.
+ *
+ * @param fieldDescription The field to get a value
+ * @param instrumentedMethod The instrumented method.
+ * @param fieldAccess A stack manipulation describing the field access.
+ * @return An appropriate stack manipulation.
+ */
+ private StackManipulation access(FieldDescription fieldDescription, MethodDescription instrumentedMethod, StackManipulation fieldAccess) {
+ if (!fieldAccess.isValid()) {
+ throw new IllegalStateException("Incompatible type of " + fieldDescription + " and " + instrumentedMethod);
+ } else if (instrumentedMethod.isStatic() && !fieldDescription.isStatic()) {
+ throw new IllegalArgumentException("Cannot call instance field " + fieldDescription + " from static method " + instrumentedMethod);
+ }
+ return new StackManipulation.Compound(fieldDescription.isStatic()
+ ? StackManipulation.Trivial.INSTANCE
+ : MethodVariableAccess.loadThis(), fieldAccess);
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ /**
+ * A field location represents an identified field description which depends on the instrumented type and method.
+ */
+ protected interface FieldLocation {
+
+ /**
+ * Specifies a field locator factory to use.
+ *
+ * @param fieldLocatorFactory The field locator factory to use.
+ * @return An appropriate field location.
+ */
+ FieldLocation with(FieldLocator.Factory fieldLocatorFactory);
+
+ /**
+ * A prepared field location.
+ *
+ * @param instrumentedType The instrumented type.
+ * @return A prepared field location.
+ */
+ Prepared prepare(TypeDescription instrumentedType);
+
+ /**
+ * A prepared field location.
+ */
+ interface Prepared {
+
+ /**
+ * Resolves the field description to use.
+ *
+ * @param instrumentedMethod The instrumented method.
+ * @return The resolved field description.
+ */
+ FieldDescription resolve(MethodDescription instrumentedMethod);
+ }
+
+ /**
+ * An absolute field description representing a previously resolved field.
+ */
+ @EqualsAndHashCode
+ class Absolute implements FieldLocation, Prepared {
+
+ /**
+ * The field description.
+ */
+ private final FieldDescription fieldDescription;
+
+ /**
+ * Creates an absolute field location.
+ *
+ * @param fieldDescription The field description.
+ */
+ protected Absolute(FieldDescription fieldDescription) {
+ this.fieldDescription = fieldDescription;
+ }
+
+ @Override
+ public FieldLocation with(FieldLocator.Factory fieldLocatorFactory) {
+ throw new IllegalStateException("Cannot specify a field locator factory for an absolute field location");
+ }
+
+ @Override
+ public Prepared prepare(TypeDescription instrumentedType) {
+ if (!instrumentedType.isAssignableTo(fieldDescription.getDeclaringType().asErasure())) {
+ throw new IllegalStateException(fieldDescription + " is not declared by " + instrumentedType);
+ } else if (!fieldDescription.isVisibleTo(instrumentedType)) {
+ throw new IllegalStateException("Cannot access " + fieldDescription + " from " + instrumentedType);
+ }
+ return this;
+ }
+
+ @Override
+ public FieldDescription resolve(MethodDescription instrumentedMethod) {
+ return fieldDescription;
+ }
+ }
+
+ /**
+ * A relative field location where a field is located dynamically.
+ */
+ @EqualsAndHashCode
+ class Relative implements FieldLocation {
+
+ /**
+ * The field name extractor to use.
+ */
+ private final FieldNameExtractor fieldNameExtractor;
+
+ /**
+ * The field locator factory to use.
+ */
+ private final FieldLocator.Factory fieldLocatorFactory;
+
+ /**
+ * Creates a new relative field location.
+ *
+ * @param fieldNameExtractor The field name extractor to use.
+ */
+ protected Relative(FieldNameExtractor fieldNameExtractor) {
+ this(fieldNameExtractor, FieldLocator.ForClassHierarchy.Factory.INSTANCE);
+ }
+
+ /**
+ * Creates a new relative field location.
+ *
+ * @param fieldNameExtractor The field name extractor to use.
+ * @param fieldLocatorFactory The field locator factory to use.
+ */
+ private Relative(FieldNameExtractor fieldNameExtractor, FieldLocator.Factory fieldLocatorFactory) {
+ this.fieldNameExtractor = fieldNameExtractor;
+ this.fieldLocatorFactory = fieldLocatorFactory;
+ }
+
+ @Override
+ public FieldLocation with(FieldLocator.Factory fieldLocatorFactory) {
+ return new Relative(fieldNameExtractor, fieldLocatorFactory);
+ }
+
+ @Override
+ public FieldLocation.Prepared prepare(TypeDescription instrumentedType) {
+ return new Prepared(fieldNameExtractor, fieldLocatorFactory.make(instrumentedType));
+ }
+
+ /**
+ * A prepared version of a field location.
+ */
+ @EqualsAndHashCode
+ protected static class Prepared implements FieldLocation.Prepared {
+
+ /**
+ * The field name extractor to use.
+ */
+ private final FieldNameExtractor fieldNameExtractor;
+
+ /**
+ * The field locator factory to use.
+ */
+ private final FieldLocator fieldLocator;
+
+ /**
+ * Creates a new relative field location.
+ *
+ * @param fieldNameExtractor The field name extractor to use.
+ * @param fieldLocator The field locator to use.
+ */
+ protected Prepared(FieldNameExtractor fieldNameExtractor, FieldLocator fieldLocator) {
+ this.fieldNameExtractor = fieldNameExtractor;
+ this.fieldLocator = fieldLocator;
+ }
+
+ @Override
+ public FieldDescription resolve(MethodDescription instrumentedMethod) {
+ FieldLocator.Resolution resolution = fieldLocator.locate(fieldNameExtractor.resolve(instrumentedMethod));
+ if (!resolution.isResolved()) {
+ throw new IllegalStateException("Cannot resolve field for " + instrumentedMethod + " using " + fieldLocator);
+ }
+ return resolution.getField();
+ }
+ }
+ }
+ }
+
+ /**
+ * A field name extractor is responsible for determining a field name to a method that is implemented
+ * to access this method.
+ */
+ public interface FieldNameExtractor {
+
+ /**
+ * Extracts a field name to be accessed by a getter or setter method.
+ *
+ * @param methodDescription The method for which a field name is to be determined.
+ * @return The name of the field to be accessed by this method.
+ */
+ String resolve(MethodDescription methodDescription);
+
+ /**
+ * A {@link net.bytebuddy.implementation.FieldAccessor.FieldNameExtractor} that determines a field name
+ * according to the rules of Java bean naming conventions.
+ */
+ enum ForBeanProperty implements FieldNameExtractor {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public String resolve(MethodDescription methodDescription) {
+ String name = methodDescription.getInternalName();
+ int crop;
+ if (name.startsWith("get") || name.startsWith("set")) {
+ crop = 3;
+ } else if (name.startsWith("is")) {
+ crop = 2;
+ } else {
+ throw new IllegalArgumentException(methodDescription + " does not follow Java bean naming conventions");
+ }
+ name = name.substring(crop);
+ if (name.length() == 0) {
+ throw new IllegalArgumentException(methodDescription + " does not specify a bean name");
+ }
+ return Character.toLowerCase(name.charAt(0)) + name.substring(1);
+ }
+ }
+
+ /**
+ * A field name extractor that returns a fixed value.
+ */
+ @EqualsAndHashCode
+ class ForFixedValue implements FieldNameExtractor {
+
+ /**
+ * The name to return.
+ */
+ private final String name;
+
+ /**
+ * Creates a new field name extractor for a fixed value.
+ *
+ * @param name The name to return.
+ */
+ protected ForFixedValue(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String resolve(MethodDescription methodDescription) {
+ return name;
+ }
+ }
+ }
+
+ /**
+ * A field accessor that allows to define the access to be a field write of a given argument.
+ */
+ public interface PropertyConfigurable extends Implementation {
+
+ /**
+ * Creates a field accessor for the described field that serves as a setter for the supplied parameter index. The instrumented
+ * method must return {@code void} or a chained instrumentation must be supplied.
+ *
+ * @param index The index of the parameter for which to set the field's value.
+ * @return An instrumentation that sets the parameter's value to the described field.
+ */
+ Implementation.Composable setsArgumentAt(int index);
+ }
+
+ /**
+ * A field accessor that can be configured to use a given assigner and runtime type use configuration.
+ */
+ public interface AssignerConfigurable extends PropertyConfigurable {
+
+ /**
+ * Returns a field accessor that is identical to this field accessor but uses the given assigner
+ * and runtime type use configuration.
+ *
+ * @param assigner The assigner to use.
+ * @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
+ * @return This field accessor with the given assigner and runtime type use configuration.
+ */
+ PropertyConfigurable withAssigner(Assigner assigner, Assigner.Typing typing);
+ }
+
+ /**
+ * A field accessor that can be configured to locate a field in a specific manner.
+ */
+ public interface OwnerTypeLocatable extends AssignerConfigurable {
+
+ /**
+ * Determines that a field should only be considered when it was defined in a given type.
+ *
+ * @param type The type to be considered.
+ * @return This field accessor which will only considered fields that are defined in the given type.
+ */
+ AssignerConfigurable in(Class<?> type);
+
+ /**
+ * Determines that a field should only be considered when it was defined in a given type.
+ *
+ * @param typeDescription A description of the type to be considered.
+ * @return This field accessor which will only considered fields that are defined in the given type.
+ */
+ AssignerConfigurable in(TypeDescription typeDescription);
+
+ /**
+ * Determines that a field should only be considered when it was identified by a field locator that is
+ * produced by the given factory.
+ *
+ * @param fieldLocatorFactory A factory that will produce a field locator that will be used to find locate
+ * a field to be accessed.
+ * @return This field accessor which will only considered fields that are defined in the given type.
+ */
+ AssignerConfigurable in(FieldLocator.Factory fieldLocatorFactory);
+ }
+
+ /**
+ * A field accessor for an implicit property where a getter or setter property is infered from the signature.
+ */
+ protected static class ForImplicitProperty extends FieldAccessor implements OwnerTypeLocatable {
+
+ /**
+ * Creates a field accessor for an implicit property.
+ *
+ * @param fieldLocation The field's location.
+ */
+ protected ForImplicitProperty(FieldLocation fieldLocation) {
+ this(fieldLocation, Assigner.DEFAULT, Assigner.Typing.STATIC);
+ }
+
+ /**
+ * Creates a field accessor for an implicit property.
+ *
+ * @param fieldLocation The field's location.
+ * @param assigner The assigner to use.
+ * @param typing The typing to use.
+ */
+ private ForImplicitProperty(FieldLocation fieldLocation, Assigner assigner, Assigner.Typing typing) {
+ super(fieldLocation, assigner, typing);
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return new Appender(fieldLocation.prepare(implementationTarget.getInstrumentedType()));
+ }
+
+ @Override
+ public Composable setsArgumentAt(int index) {
+ if (index < 0) {
+ throw new IllegalArgumentException("A parameter index cannot be negative: " + index);
+ }
+ return new ForParameterSetter(fieldLocation, assigner, typing, index);
+ }
+
+ @Override
+ public PropertyConfigurable withAssigner(Assigner assigner, Assigner.Typing typing) {
+ return new ForImplicitProperty(fieldLocation, assigner, typing);
+ }
+
+ @Override
+ public AssignerConfigurable in(Class<?> type) {
+ return in(new TypeDescription.ForLoadedType(type));
+ }
+
+ @Override
+ public AssignerConfigurable in(TypeDescription typeDescription) {
+ return in(new FieldLocator.ForExactType.Factory(typeDescription));
+ }
+
+ @Override
+ public AssignerConfigurable in(FieldLocator.Factory fieldLocatorFactory) {
+ return new ForImplicitProperty(fieldLocation.with(fieldLocatorFactory), assigner, typing);
+ }
+
+ /**
+ * An byte code appender for an field accessor implementation.
+ */
+ protected class Appender implements ByteCodeAppender {
+
+ /**
+ * The field's location.
+ */
+ private final FieldLocation.Prepared fieldLocation;
+
+ /**
+ * Creates a new byte code appender for a field accessor implementation.
+ *
+ * @param fieldLocation The field's location.
+ */
+ protected Appender(FieldLocation.Prepared fieldLocation) {
+ this.fieldLocation = fieldLocation;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodDescription instrumentedMethod) {
+ if (!instrumentedMethod.isMethod()) {
+ throw new IllegalArgumentException(instrumentedMethod + " does not describe a field getter or setter");
+ }
+ FieldDescription fieldDescription = fieldLocation.resolve(instrumentedMethod);
+ StackManipulation implementation;
+ if (!instrumentedMethod.getReturnType().represents(void.class)) {
+ implementation = new StackManipulation.Compound(getter(fieldDescription, instrumentedMethod), MethodReturn.of(instrumentedMethod.getReturnType()));
+ } else if (instrumentedMethod.getReturnType().represents(void.class) && instrumentedMethod.getParameters().size() == 1) {
+ implementation = new StackManipulation.Compound(setter(fieldDescription, instrumentedMethod.getParameters().get(0)), MethodReturn.VOID);
+ } else {
+ throw new IllegalArgumentException("Method " + implementationContext + " is no bean property");
+ }
+ return new Size(implementation.apply(methodVisitor, implementationContext).getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private ForImplicitProperty getOuter() {
+ return ForImplicitProperty.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object object) {
+ if (this == object) return true;
+ if (object == null || getClass() != object.getClass()) return false;
+ Appender appender = (Appender) object;
+ return fieldLocation.equals(appender.fieldLocation) && ForImplicitProperty.this.equals(appender.getOuter());
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return fieldLocation.hashCode() + 31 * ForImplicitProperty.this.hashCode();
+ }
+ }
+ }
+
+ /**
+ * A field accessor that sets a parameters value of a given index.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ protected static class ForParameterSetter extends FieldAccessor implements Implementation.Composable {
+
+ /**
+ * The targeted parameter index.
+ */
+ private final int index;
+
+ /**
+ * The termination handler to apply.
+ */
+ private final TerminationHandler terminationHandler;
+
+ /**
+ * Creates a new field accessor.
+ *
+ * @param fieldLocation The field's location.
+ * @param assigner The assigner to use.
+ * @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
+ * @param index The targeted parameter index.
+ */
+ protected ForParameterSetter(FieldLocation fieldLocation, Assigner assigner, Assigner.Typing typing, int index) {
+ this(fieldLocation, assigner, typing, index, TerminationHandler.RETURNING);
+ }
+
+ /**
+ * Creates a new field accessor.
+ *
+ * @param fieldLocation The field's location.
+ * @param assigner The assigner to use.
+ * @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
+ * @param index The targeted parameter index.
+ * @param terminationHandler The termination handler to apply.
+ */
+ private ForParameterSetter(FieldLocation fieldLocation, Assigner assigner, Assigner.Typing typing, int index, TerminationHandler terminationHandler) {
+ super(fieldLocation, assigner, typing);
+ this.index = index;
+ this.terminationHandler = terminationHandler;
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return new Appender(fieldLocation.prepare(implementationTarget.getInstrumentedType()));
+ }
+
+ @Override
+ public Implementation andThen(Implementation implementation) {
+ return new Compound(new ForParameterSetter(fieldLocation,
+ assigner,
+ typing,
+ index, TerminationHandler.NON_OPERATIONAL), implementation);
+ }
+
+ /**
+ * A termination handler is responsible for handling a field accessor's return.
+ */
+ protected enum TerminationHandler {
+
+ /**
+ * Returns {@code void} or throws an exception if this is not the return type of the instrumented method.
+ */
+ RETURNING {
+ @Override
+ protected StackManipulation resolve(MethodDescription instrumentedMethod) {
+ if (!instrumentedMethod.getReturnType().represents(void.class)) {
+ throw new IllegalStateException("Cannot implement setter with return value for " + instrumentedMethod);
+ }
+ return MethodReturn.VOID;
+ }
+ },
+
+ /**
+ * Does not return from the method at all.
+ */
+ NON_OPERATIONAL {
+ @Override
+ protected StackManipulation resolve(MethodDescription instrumentedMethod) {
+ return StackManipulation.Trivial.INSTANCE;
+ }
+ };
+
+ /**
+ * Resolves the return instruction.
+ *
+ * @param instrumentedMethod The instrumented method.
+ * @return An appropriate stack manipulation.
+ */
+ protected abstract StackManipulation resolve(MethodDescription instrumentedMethod);
+ }
+
+ /**
+ * An appender for a field accessor that sets a parameter of a given index.
+ */
+ protected class Appender implements ByteCodeAppender {
+
+ /**
+ * The field's location.
+ */
+ private final FieldLocation.Prepared fieldLocation;
+
+ /**
+ * Creates a new byte code appender for a field accessor implementation.
+ *
+ * @param fieldLocation The field's location.
+ */
+ protected Appender(FieldLocation.Prepared fieldLocation) {
+ this.fieldLocation = fieldLocation;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodDescription instrumentedMethod) {
+ if (instrumentedMethod.getParameters().size() <= index) {
+ throw new IllegalStateException(instrumentedMethod + " does not define a parameter with index " + index);
+ } else {
+ return new Size(new StackManipulation.Compound(
+ setter(fieldLocation.resolve(instrumentedMethod), instrumentedMethod.getParameters().get(index)),
+ terminationHandler.resolve(instrumentedMethod)
+ ).apply(methodVisitor, implementationContext).getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private ForParameterSetter getOuter() {
+ return ForParameterSetter.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object object) {
+ if (this == object) return true;
+ if (object == null || getClass() != object.getClass()) return false;
+ ForParameterSetter.Appender appender = (ForParameterSetter.Appender) object;
+ return fieldLocation.equals(appender.fieldLocation) && ForParameterSetter.this.equals(appender.getOuter());
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return fieldLocation.hashCode() + 31 * ForParameterSetter.this.hashCode();
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/FixedValue.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/FixedValue.java
new file mode 100644
index 0000000..31ba4bc
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/FixedValue.java
@@ -0,0 +1,678 @@
+package net.bytebuddy.implementation;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.constant.*;
+import net.bytebuddy.implementation.bytecode.member.FieldAccess;
+import net.bytebuddy.implementation.bytecode.member.MethodReturn;
+import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+import net.bytebuddy.utility.JavaConstant;
+import net.bytebuddy.utility.JavaType;
+import net.bytebuddy.utility.RandomString;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+
+/**
+ * This implementation returns a fixed value for a method. Other than the {@link net.bytebuddy.implementation.StubMethod}
+ * implementation, this implementation allows to determine a specific value which must be assignable to the returning value
+ * of any instrumented method. Otherwise, an exception will be thrown.
+ *
+ * @see FieldAccessor
+ */
+ at EqualsAndHashCode
+public abstract class FixedValue implements Implementation {
+
+ /**
+ * The assigner that is used for assigning the fixed value to a method's return type.
+ */
+ protected final Assigner assigner;
+
+ /**
+ * Indicates if dynamic type castings should be attempted for incompatible assignments.
+ */
+ protected final Assigner.Typing typing;
+
+ /**
+ * Creates a new fixed value implementation.
+ *
+ * @param assigner The assigner to use for assigning the fixed value to the return type of the instrumented value.
+ * @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
+ */
+ protected FixedValue(Assigner assigner, Assigner.Typing typing) {
+ this.assigner = assigner;
+ this.typing = typing;
+ }
+
+ /**
+ * <p>
+ * Returns a fixed value from any intercepted method. The fixed value is stored in the constant pool if this is possible.
+ * Java is capable of storing any primitive value, {@link String} values and {@link Class} references in the constant pool.
+ * Since Java 7, {@code MethodHandle} as well as {@code MethodType} references are also supported. Alternatively, the fixed
+ * value is stored in a static field.
+ * </p>
+ * <p>
+ * When a value is stored in the class's constant pool, its identity is lost. If an object's identity is important, the
+ * {@link FixedValue#reference(Object)} method should be used instead.
+ * </p>
+ * <p>
+ * <b>Important</b>: When supplying a method handle or a method type, all types that are implied must be visible to the instrumented
+ * type or an {@link IllegalAccessException} will be thrown at runtime.
+ * </p>
+ *
+ * @param fixedValue The fixed value to return from the method.
+ * @return An implementation for the given {@code value}.
+ */
+ public static AssignerConfigurable value(Object fixedValue) {
+ Class<?> type = fixedValue.getClass();
+ if (type == String.class) {
+ return new ForPoolValue(new TextConstant((String) fixedValue), TypeDescription.STRING);
+ } else if (type == Class.class) {
+ return new ForPoolValue(ClassConstant.of(new TypeDescription.ForLoadedType((Class<?>) fixedValue)), TypeDescription.CLASS);
+ } else if (type == Boolean.class) {
+ return new ForPoolValue(IntegerConstant.forValue((Boolean) fixedValue), boolean.class);
+ } else if (type == Byte.class) {
+ return new ForPoolValue(IntegerConstant.forValue((Byte) fixedValue), byte.class);
+ } else if (type == Short.class) {
+ return new ForPoolValue(IntegerConstant.forValue((Short) fixedValue), short.class);
+ } else if (type == Character.class) {
+ return new ForPoolValue(IntegerConstant.forValue((Character) fixedValue), char.class);
+ } else if (type == Integer.class) {
+ return new ForPoolValue(IntegerConstant.forValue((Integer) fixedValue), int.class);
+ } else if (type == Long.class) {
+ return new ForPoolValue(LongConstant.forValue((Long) fixedValue), long.class);
+ } else if (type == Float.class) {
+ return new ForPoolValue(FloatConstant.forValue((Float) fixedValue), float.class);
+ } else if (type == Double.class) {
+ return new ForPoolValue(DoubleConstant.forValue((Double) fixedValue), double.class);
+ } else if (JavaType.METHOD_HANDLE.getTypeStub().isAssignableFrom(type)) {
+ return new ForPoolValue(new JavaConstantValue(JavaConstant.MethodHandle.ofLoaded(fixedValue)), type);
+ } else if (JavaType.METHOD_TYPE.getTypeStub().represents(type)) {
+ return new ForPoolValue(new JavaConstantValue(JavaConstant.MethodType.ofLoaded(fixedValue)), type);
+ } else {
+ return reference(fixedValue);
+ }
+ }
+
+ /**
+ * Other than {@link net.bytebuddy.implementation.FixedValue#value(Object)}, this function
+ * will create a fixed value implementation that will always defined a field in the instrumented class. As a result,
+ * object identity will be preserved between the given {@code value} and the value that is returned by
+ * instrumented methods. The field name can be explicitly determined. The field name is generated from the fixed value's
+ * hash code.
+ *
+ * @param fixedValue The fixed value to be returned by methods that are instrumented by this implementation.
+ * @return An implementation for the given {@code value}.
+ */
+ public static AssignerConfigurable reference(Object fixedValue) {
+ return new ForValue(fixedValue);
+ }
+
+ /**
+ * Other than {@link net.bytebuddy.implementation.FixedValue#value(Object)}, this function
+ * will create a fixed value implementation that will always defined a field in the instrumented class. As a result,
+ * object identity will be preserved between the given {@code value} and the value that is returned by
+ * instrumented methods. The field name can be explicitly determined.
+ *
+ * @param fixedValue The fixed value to be returned by methods that are instrumented by this implementation.
+ * @param fieldName The name of the field for storing the fixed value.
+ * @return An implementation for the given {@code value}.
+ */
+ public static AssignerConfigurable reference(Object fixedValue, String fieldName) {
+ return new ForValue(fieldName, fixedValue);
+ }
+
+ /**
+ * Returns the given type in form of a loaded type. The value is loaded from the written class's constant pool.
+ *
+ * @param fixedValue The type to return from the method.
+ * @return An implementation for the given {@code value}.
+ */
+ public static AssignerConfigurable value(TypeDescription fixedValue) {
+ return new ForPoolValue(ClassConstant.of(fixedValue), TypeDescription.CLASS);
+ }
+
+ /**
+ * Returns the loaded version of the given {@link JavaConstant}. The value is loaded from the written class's constant pool.
+ *
+ * @param fixedValue The type to return from the method.
+ * @return An implementation for the given {@code value}.
+ */
+ public static AssignerConfigurable value(JavaConstant fixedValue) {
+ return new ForPoolValue(fixedValue.asStackManipulation(), fixedValue.getType());
+ }
+
+ /**
+ * Returns the argument at the specified index.
+ *
+ * @param index The index of the argument to return.
+ * @return An implementation of a method that returns the argument at the specified index.
+ */
+ public static AssignerConfigurable argument(int index) {
+ if (index < 0) {
+ throw new IllegalArgumentException("Argument index cannot be negative: " + index);
+ }
+ return new ForArgument(index);
+ }
+
+ /**
+ * Returns {@code this} from an instrumented method.
+ *
+ * @return An implementation that returns {@code this} from a method.
+ */
+ public static AssignerConfigurable self() {
+ return new ForThisValue();
+ }
+
+ /**
+ * Returns a {@code null} value from an instrumented method.
+ *
+ * @return An implementation that returns {@code null} from a method.
+ */
+ public static Implementation nullValue() {
+ return ForNullValue.INSTANCE;
+ }
+
+ /**
+ * Returns the origin type from an instrumented method.
+ *
+ * @return An implementation that returns the origin type of the current instrumented type.
+ */
+ public static AssignerConfigurable originType() {
+ return new ForOriginType();
+ }
+
+ /**
+ * Blueprint method that for applying the actual implementation.
+ *
+ * @param methodVisitor The method visitor to which the implementation is applied to.
+ * @param implementationContext The implementation context for the given implementation.
+ * @param instrumentedMethod The instrumented method that is target of the implementation.
+ * @param fixedValueType A description of the type of the fixed value that is loaded by the
+ * {@code valueLoadingInstruction}.
+ * @param valueLoadingInstruction A stack manipulation that represents the loading of the fixed value onto the
+ * operand stack.
+ * @return A representation of the stack and variable array sized that are required for this implementation.
+ */
+ protected ByteCodeAppender.Size apply(MethodVisitor methodVisitor,
+ Context implementationContext,
+ MethodDescription instrumentedMethod,
+ TypeDescription.Generic fixedValueType,
+ StackManipulation valueLoadingInstruction) {
+ StackManipulation assignment = assigner.assign(fixedValueType, instrumentedMethod.getReturnType(), typing);
+ if (!assignment.isValid()) {
+ throw new IllegalArgumentException("Cannot return value of type " + fixedValueType + " for " + instrumentedMethod);
+ }
+ StackManipulation.Size stackSize = new StackManipulation.Compound(
+ valueLoadingInstruction,
+ assignment,
+ MethodReturn.of(instrumentedMethod.getReturnType())
+ ).apply(methodVisitor, implementationContext);
+ return new ByteCodeAppender.Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+
+ /**
+ * Represents a fixed value implementation that is using a default assigner for attempting to assign
+ * the fixed value to the return type of the instrumented method.
+ */
+ public interface AssignerConfigurable extends Implementation {
+
+ /**
+ * Defines an explicit assigner to this fixed value implementation.
+ *
+ * @param assigner The assigner to use for assigning the fixed value to the return type of the
+ * instrumented value.
+ * @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
+ * @return A fixed value implementation that makes use of the given assigner.
+ */
+ Implementation withAssigner(Assigner assigner, Assigner.Typing typing);
+ }
+
+ /**
+ * A fixed value of {@code null}.
+ */
+ protected enum ForNullValue implements Implementation, ByteCodeAppender {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return this;
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ if (instrumentedMethod.getReturnType().isPrimitive()) {
+ throw new IllegalStateException("Cannot return null from " + instrumentedMethod);
+ }
+ return new ByteCodeAppender.Simple(
+ NullConstant.INSTANCE,
+ MethodReturn.REFERENCE
+ ).apply(methodVisitor, implementationContext, instrumentedMethod);
+ }
+ }
+
+ /**
+ * A fixed value that appends the origin type of the instrumented type.
+ */
+ protected static class ForOriginType extends FixedValue implements AssignerConfigurable {
+
+ /**
+ * Creates a new fixed value appender for the origin type.
+ */
+ protected ForOriginType() {
+ this(Assigner.DEFAULT, Assigner.Typing.STATIC);
+ }
+
+ /**
+ * Creates a new fixed value appender for the origin type.
+ *
+ * @param assigner The assigner to use for assigning the fixed value to the return type of the instrumented value.
+ * @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
+ */
+ private ForOriginType(Assigner assigner, Assigner.Typing typing) {
+ super(assigner, typing);
+ }
+
+ @Override
+ public Implementation withAssigner(Assigner assigner, Assigner.Typing typing) {
+ return new ForOriginType(assigner, typing);
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return new Appender(implementationTarget.getOriginType().asErasure());
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ /**
+ * An appender for writing the origin type.
+ */
+ protected class Appender implements ByteCodeAppender {
+
+ /**
+ * The instrumented type.
+ */
+ private final TypeDescription originType;
+
+ /**
+ * Creates a new appender.
+ *
+ * @param originType The instrumented type.
+ */
+ protected Appender(TypeDescription originType) {
+ this.originType = originType;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ return ForOriginType.this.apply(methodVisitor,
+ implementationContext,
+ instrumentedMethod,
+ TypeDescription.CLASS.asGenericType(),
+ ClassConstant.of(originType));
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private ForOriginType getOuter() {
+ return ForOriginType.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Appender appender = (Appender) o;
+ return originType.equals(appender.originType)
+ && getOuter().equals(appender.getOuter());
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return 31 * getOuter().hashCode() + originType.hashCode();
+ }
+ }
+ }
+
+ /**
+ * A fixed value of {@code this}.
+ */
+ protected static class ForThisValue extends FixedValue implements AssignerConfigurable {
+
+ /**
+ * Creates an implementation that returns the instance of the instrumented type.
+ */
+ protected ForThisValue() {
+ super(Assigner.DEFAULT, Assigner.Typing.STATIC);
+ }
+
+ /**
+ * Creates an implementation that returns the instance of the instrumented type.
+ *
+ * @param assigner The assigner to use for assigning the fixed value to the return type of the instrumented value.
+ * @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
+ */
+ private ForThisValue(Assigner assigner, Assigner.Typing typing) {
+ super(assigner, typing);
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return new Appender(implementationTarget.getInstrumentedType());
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public Implementation withAssigner(Assigner assigner, Assigner.Typing typing) {
+ return new ForThisValue(assigner, typing);
+ }
+
+ /**
+ * A byte code appender for returning {@code this}.
+ */
+ @EqualsAndHashCode
+ protected static class Appender implements ByteCodeAppender {
+
+ /**
+ * The instrumented type.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * Creates a new byte code appender for returning {@code this}.
+ *
+ * @param instrumentedType The instrumented type.
+ */
+ protected Appender(TypeDescription instrumentedType) {
+ this.instrumentedType = instrumentedType;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ if (instrumentedMethod.isStatic() || !instrumentedType.isAssignableTo(instrumentedMethod.getReturnType().asErasure())) {
+ throw new IllegalStateException("Cannot return 'this' from " + instrumentedMethod);
+ }
+ return new ByteCodeAppender.Simple(
+ MethodVariableAccess.loadThis(),
+ MethodReturn.REFERENCE
+ ).apply(methodVisitor, implementationContext, instrumentedMethod);
+ }
+ }
+ }
+
+ /**
+ * A fixed value implementation that returns a method's argument.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ protected static class ForArgument extends FixedValue implements AssignerConfigurable, ByteCodeAppender {
+
+ /**
+ * The argument index.
+ */
+ private final int index;
+
+ /**
+ * Creates a new fixed value implementation that returns a method's argument.
+ *
+ * @param index The argument's index.
+ */
+ protected ForArgument(int index) {
+ this(Assigner.DEFAULT, Assigner.Typing.STATIC, index);
+ }
+
+ /**
+ * Creates a new fixed value implementation that returns a method's argument.
+ *
+ * @param assigner The assigner to use for assigning the fixed value to the return type of the
+ * instrumented value.
+ * @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
+ * @param index The argument's index.
+ */
+ private ForArgument(Assigner assigner, Assigner.Typing typing, int index) {
+ super(assigner, typing);
+ this.index = index;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ if (instrumentedMethod.getParameters().size() <= index) {
+ throw new IllegalStateException(instrumentedMethod + " does not define a parameter with index " + index);
+ }
+ ParameterDescription parameterDescription = instrumentedMethod.getParameters().get(index);
+ StackManipulation stackManipulation = new StackManipulation.Compound(
+ MethodVariableAccess.load(parameterDescription),
+ assigner.assign(parameterDescription.getType(), instrumentedMethod.getReturnType(), typing),
+ MethodReturn.of(instrumentedMethod.getReturnType())
+ );
+ if (!stackManipulation.isValid()) {
+ throw new IllegalStateException("Cannot assign " + instrumentedMethod.getReturnType() + " to " + parameterDescription);
+ }
+ return new Size(stackManipulation.apply(methodVisitor, implementationContext).getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return this;
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public Implementation withAssigner(Assigner assigner, Assigner.Typing typing) {
+ return new ForArgument(assigner, typing, index);
+ }
+ }
+
+ /**
+ * A fixed value implementation that represents its fixed value as a value that is written to the instrumented
+ * class's constant pool.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ protected static class ForPoolValue extends FixedValue implements AssignerConfigurable, ByteCodeAppender {
+
+ /**
+ * The stack manipulation which is responsible for loading the fixed value onto the operand stack.
+ */
+ private final StackManipulation valueLoadInstruction;
+
+ /**
+ * The type of the fixed value.
+ */
+ private final TypeDescription loadedType;
+
+ /**
+ * Creates a new constant pool fixed value implementation.
+ *
+ * @param valueLoadInstruction The instruction that is responsible for loading the constant pool value onto the
+ * operand stack.
+ * @param loadedType A type description representing the loaded type.
+ */
+ protected ForPoolValue(StackManipulation valueLoadInstruction, Class<?> loadedType) {
+ this(valueLoadInstruction, new TypeDescription.ForLoadedType(loadedType));
+ }
+
+ /**
+ * Creates a new constant pool fixed value implementation.
+ *
+ * @param valueLoadInstruction The instruction that is responsible for loading the constant pool value onto the
+ * operand stack.
+ * @param loadedType A type description representing the loaded type.
+ */
+ protected ForPoolValue(StackManipulation valueLoadInstruction, TypeDescription loadedType) {
+ this(Assigner.DEFAULT, Assigner.Typing.STATIC, valueLoadInstruction, loadedType);
+ }
+
+ /**
+ * Creates a new constant pool fixed value implementation.
+ *
+ * @param valueLoadInstruction The instruction that is responsible for loading the constant pool value onto the
+ * operand stack.
+ * @param loadedType A type description representing the loaded type.
+ * @param assigner The assigner to use for assigning the fixed value to the return type of the
+ * instrumented value.
+ * @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
+ */
+ private ForPoolValue(Assigner assigner, Assigner.Typing typing, StackManipulation valueLoadInstruction, TypeDescription loadedType) {
+ super(assigner, typing);
+ this.valueLoadInstruction = valueLoadInstruction;
+ this.loadedType = loadedType;
+ }
+
+ @Override
+ public Implementation withAssigner(Assigner assigner, Assigner.Typing typing) {
+ return new ForPoolValue(assigner, typing, valueLoadInstruction, loadedType);
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return this;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ return apply(methodVisitor, implementationContext, instrumentedMethod, loadedType.asGenericType(), valueLoadInstruction);
+ }
+ }
+
+ /**
+ * A fixed value implementation that represents its fixed value as a static field of the instrumented class.
+ */
+ @EqualsAndHashCode(callSuper = true, exclude = "fieldType")
+ protected static class ForValue extends FixedValue implements AssignerConfigurable {
+
+ /**
+ * The prefix of the static field that is created for storing the fixed value.
+ */
+ private static final String PREFIX = "value";
+
+ /**
+ * The name of the field in which the fixed value is stored.
+ */
+ private final String fieldName;
+
+ /**
+ * The value that is to be stored in the static field.
+ */
+ private final Object value;
+
+ /**
+ * The type if the field for storing the fixed value.
+ */
+ private final TypeDescription.Generic fieldType;
+
+ /**
+ * Creates a new static field fixed value implementation with a random name for the field containing the fixed
+ * value.
+ *
+ * @param value The fixed value to be returned.
+ */
+ protected ForValue(Object value) {
+ this(String.format("%s$%s", PREFIX, RandomString.hashOf(value.hashCode())), value);
+ }
+
+ /**
+ * Creates a new static field fixed value implementation.
+ *
+ * @param fieldName The name of the field for storing the fixed value.
+ * @param value The fixed value to be returned.
+ */
+ protected ForValue(String fieldName, Object value) {
+ this(Assigner.DEFAULT, Assigner.Typing.STATIC, fieldName, value);
+ }
+
+ /**
+ * Creates a new static field fixed value implementation.
+ *
+ * @param fieldName The name of the field for storing the fixed value.
+ * @param value The fixed value to be returned.
+ * @param assigner The assigner to use for assigning the fixed value to the return type of the
+ * instrumented value.
+ * @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
+ */
+ private ForValue(Assigner assigner, Assigner.Typing typing, String fieldName, Object value) {
+ super(assigner, typing);
+ this.fieldName = fieldName;
+ this.value = value;
+ fieldType = new TypeDescription.Generic.OfNonGenericType.ForLoadedType(value.getClass());
+ }
+
+ @Override
+ public Implementation withAssigner(Assigner assigner, Assigner.Typing typing) {
+ return new ForValue(assigner, typing, fieldName, value);
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType
+ .withField(new FieldDescription.Token(fieldName, Opcodes.ACC_SYNTHETIC | Opcodes.ACC_STATIC | Opcodes.ACC_PUBLIC, fieldType))
+ .withInitializer(new LoadedTypeInitializer.ForStaticField(fieldName, value));
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return new StaticFieldByteCodeAppender(implementationTarget.getInstrumentedType());
+ }
+
+ /**
+ * A byte code appender for returning the fixed value that was stored in a static field.
+ */
+ @EqualsAndHashCode
+ private class StaticFieldByteCodeAppender implements ByteCodeAppender {
+
+ /**
+ * The stack manipulation that loads the fixed value onto the operand stack.
+ */
+ private final StackManipulation fieldGetAccess;
+
+ /**
+ * Creates a new byte code appender for returning a value of a static field from an instrumented method.
+ *
+ * @param instrumentedType The instrumented type that is subject of the instrumentation.
+ */
+ private StaticFieldByteCodeAppender(TypeDescription instrumentedType) {
+ fieldGetAccess = FieldAccess.forField(instrumentedType.getDeclaredFields().filter((named(fieldName))).getOnly()).read();
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ return ForValue.this.apply(methodVisitor, implementationContext, instrumentedMethod, fieldType, fieldGetAccess);
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/Implementation.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/Implementation.java
new file mode 100644
index 0000000..a54ca8a
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/Implementation.java
@@ -0,0 +1,1582 @@
+package net.bytebuddy.implementation;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.annotation.AnnotationValue;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.dynamic.scaffold.MethodGraph;
+import net.bytebuddy.dynamic.scaffold.TypeInitializer;
+import net.bytebuddy.dynamic.scaffold.TypeWriter;
+import net.bytebuddy.implementation.attribute.AnnotationValueFilter;
+import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.member.FieldAccess;
+import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
+import net.bytebuddy.implementation.bytecode.member.MethodReturn;
+import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+import net.bytebuddy.utility.RandomString;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.*;
+
+/**
+ * An implementation is responsible for implementing methods of a dynamically created type as byte code. An
+ * implementation is applied in two stages:
+ * <ol>
+ * <li>The implementation is able to prepare an instrumented type by adding fields and/or helper methods that are
+ * required for the methods implemented by this implementation. Furthermore,
+ * {@link LoadedTypeInitializer}s and byte code for the type initializer can be registered for the instrumented
+ * type.</li>
+ * <li>Any implementation is required to supply a byte code appender that is responsible for providing the byte code
+ * to the instrumented methods that were delegated to this implementation. This byte code appender is also
+ * be responsible for providing implementations for the methods added in step <i>1</i>.</li>
+ * </ol>
+ * <p> </p>
+ * An implementation should provide meaningful implementations of both {@link java.lang.Object#equals(Object)}
+ * and {@link Object#hashCode()} if it wants to avoid to be used twice within the creation of a dynamic type. For two
+ * equal implementations only one will be applied on the creation of a dynamic type.
+ */
+public interface Implementation extends InstrumentedType.Prepareable {
+
+ /**
+ * Creates a byte code appender that determines the implementation of the instrumented type's methods.
+ *
+ * @param implementationTarget The target of the current implementation.
+ * @return A byte code appender for implementing methods delegated to this implementation. This byte code appender
+ * is also responsible for handling methods that were added by this implementation on the call to
+ * {@link Implementation#prepare(InstrumentedType)}.
+ */
+ ByteCodeAppender appender(Target implementationTarget);
+
+ /**
+ * Represents an implementation that can be chained together with another implementation.
+ */
+ interface Composable extends Implementation {
+
+ /**
+ * Appends the supplied implementation to this implementation.
+ *
+ * @param implementation The subsequent implementation.
+ * @return An implementation that combines this implementation with the provided one.
+ */
+ Implementation andThen(Implementation implementation);
+ }
+
+ /**
+ * Represents an type-specific method invocation on the current instrumented type which is not legal from outside
+ * the type such as a super method or default method invocation. Legal instances of special method invocations must
+ * be equal to one another if they represent the same invocation target.
+ */
+ interface SpecialMethodInvocation extends StackManipulation {
+
+ /**
+ * Returns the method that represents this special method invocation. This method can be different even for
+ * equal special method invocations, dependant on the method that was used to request such an invocation by the
+ * means of a {@link Implementation.Target}.
+ *
+ * @return The method description that describes this instances invocation target.
+ */
+ MethodDescription getMethodDescription();
+
+ /**
+ * Returns the target type the represented method is invoked on.
+ *
+ * @return The type the represented method is invoked on.
+ */
+ TypeDescription getTypeDescription();
+
+ /**
+ * A canonical implementation of an illegal {@link Implementation.SpecialMethodInvocation}.
+ */
+ enum Illegal implements SpecialMethodInvocation {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public boolean isValid() {
+ return false;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
+ throw new IllegalStateException("Cannot implement an undefined method");
+ }
+
+ @Override
+ public MethodDescription getMethodDescription() {
+ throw new IllegalStateException("An illegal special method invocation must not be applied");
+ }
+
+ @Override
+ public TypeDescription getTypeDescription() {
+ throw new IllegalStateException("An illegal special method invocation must not be applied");
+ }
+ }
+
+ /**
+ * An abstract base implementation of a valid special method invocation.
+ */
+ abstract class AbstractBase implements SpecialMethodInvocation {
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return 31 * getMethodDescription().asSignatureToken().hashCode() + getTypeDescription().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (!(other instanceof SpecialMethodInvocation)) return false;
+ SpecialMethodInvocation specialMethodInvocation = (SpecialMethodInvocation) other;
+ return getMethodDescription().asSignatureToken().equals(specialMethodInvocation.getMethodDescription().asSignatureToken())
+ && getTypeDescription().equals(((SpecialMethodInvocation) other).getTypeDescription());
+ }
+ }
+
+ /**
+ * A canonical implementation of a {@link SpecialMethodInvocation}.
+ */
+ class Simple extends SpecialMethodInvocation.AbstractBase {
+
+ /**
+ * The method description that is represented by this legal special method invocation.
+ */
+ private final MethodDescription methodDescription;
+
+ /**
+ * The type description that is represented by this legal special method invocation.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * A stack manipulation representing the method's invocation on the type description.
+ */
+ private final StackManipulation stackManipulation;
+
+ /**
+ * Creates a new legal special method invocation.
+ *
+ * @param methodDescription The method that represents the special method invocation.
+ * @param typeDescription The type on which the method should be invoked on by an {@code INVOKESPECIAL}
+ * invocation.
+ * @param stackManipulation The stack manipulation that represents this special method invocation.
+ */
+ protected Simple(MethodDescription methodDescription, TypeDescription typeDescription, StackManipulation stackManipulation) {
+ this.methodDescription = methodDescription;
+ this.typeDescription = typeDescription;
+ this.stackManipulation = stackManipulation;
+ }
+
+ /**
+ * Creates a special method invocation for a given invocation target.
+ *
+ * @param methodDescription The method that represents the special method invocation.
+ * @param typeDescription The type on which the method should be invoked on by an {@code INVOKESPECIAL}
+ * invocation.
+ * @return A special method invocation representing a legal invocation if the method can be invoked
+ * specially on the target type or an illegal invocation if this is not possible.
+ */
+ public static SpecialMethodInvocation of(MethodDescription methodDescription, TypeDescription typeDescription) {
+ StackManipulation stackManipulation = MethodInvocation.invoke(methodDescription).special(typeDescription);
+ return stackManipulation.isValid()
+ ? new Simple(methodDescription, typeDescription, stackManipulation)
+ : SpecialMethodInvocation.Illegal.INSTANCE;
+ }
+
+ @Override
+ public MethodDescription getMethodDescription() {
+ return methodDescription;
+ }
+
+ @Override
+ public TypeDescription getTypeDescription() {
+ return typeDescription;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
+ return stackManipulation.apply(methodVisitor, implementationContext);
+ }
+ }
+ }
+
+ /**
+ * The target of an implementation. Implementation targets must be immutable and can be queried without altering
+ * the implementation result. An implementation target provides information on the type that is to be created
+ * where it is the implementation's responsibility to cache expensive computations, especially such computations
+ * that require reflective look-up.
+ */
+ interface Target {
+
+ /**
+ * Returns a description of the instrumented type.
+ *
+ * @return A description of the instrumented type.
+ */
+ TypeDescription getInstrumentedType();
+
+ /**
+ * Identifies the origin type of an implementation. The origin type describes the type that is subject to
+ * any form of enhancement. If a subclass of a given type is generated, the base type of this subclass
+ * describes the origin type. If a given type is redefined or rebased, the origin type is described by the
+ * instrumented type itself.
+ *
+ * @return The origin type of this implementation.
+ */
+ TypeDefinition getOriginType();
+
+ /**
+ * Creates a special method invocation for invoking the super method of the given method.
+ *
+ * @param token A token of the method that is to be invoked as a super method.
+ * @return The corresponding special method invocation which might be illegal if the requested invocation is not legal.
+ */
+ SpecialMethodInvocation invokeSuper(MethodDescription.SignatureToken token);
+
+ /**
+ * Creates a special method invocation for invoking a default method with the given token. The default method call must
+ * not be ambiguous or an illegal special method invocation is returned.
+ *
+ * @param token A token of the method that is to be invoked as a default method.
+ * @return The corresponding default method invocation which might be illegal if the requested invocation is not legal or ambiguous.
+ */
+ SpecialMethodInvocation invokeDefault(MethodDescription.SignatureToken token);
+
+ /**
+ * Creates a special method invocation for invoking a default method.
+ *
+ * @param targetType The interface on which the default method is to be invoked.
+ * @param token A token that uniquely describes the method to invoke.
+ * @return The corresponding special method invocation which might be illegal if the requested invocation is
+ * not legal.
+ */
+ SpecialMethodInvocation invokeDefault(MethodDescription.SignatureToken token, TypeDescription targetType);
+
+ /**
+ * Invokes a dominant method, i.e. if the method token can be invoked as a super method invocation, this invocation is considered dominant.
+ * Alternatively, a method invocation is attempted on an interface type as a default method invocation only if this invocation is not ambiguous
+ * for several interfaces.
+ *
+ * @param token The method token representing the method to be invoked.
+ * @return A special method invocation for a method representing the method token.
+ */
+ SpecialMethodInvocation invokeDominant(MethodDescription.SignatureToken token);
+
+ /**
+ * A factory for creating an {@link Implementation.Target}.
+ */
+ interface Factory {
+
+ /**
+ * Creates an implementation target.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param methodGraph A method graph of the instrumented type.
+ * @param classFileVersion The type's class file version.
+ * @return An implementation target for the instrumented type.
+ */
+ Target make(TypeDescription instrumentedType, MethodGraph.Linked methodGraph, ClassFileVersion classFileVersion);
+ }
+
+ /**
+ * An abstract base implementation for an {@link Implementation.Target}.
+ */
+ @EqualsAndHashCode
+ abstract class AbstractBase implements Target {
+
+ /**
+ * The instrumented type.
+ */
+ protected final TypeDescription instrumentedType;
+
+ /**
+ * The instrumented type's method graph.
+ */
+ protected final MethodGraph.Linked methodGraph;
+
+ /**
+ * The default method invocation mode to apply.
+ */
+ protected final DefaultMethodInvocation defaultMethodInvocation;
+
+ /**
+ * Creates a new implementation target.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param methodGraph The instrumented type's method graph.
+ * @param defaultMethodInvocation The default method invocation mode to apply.
+ */
+ protected AbstractBase(TypeDescription instrumentedType, MethodGraph.Linked methodGraph, DefaultMethodInvocation defaultMethodInvocation) {
+ this.instrumentedType = instrumentedType;
+ this.methodGraph = methodGraph;
+ this.defaultMethodInvocation = defaultMethodInvocation;
+ }
+
+ @Override
+ public TypeDescription getInstrumentedType() {
+ return instrumentedType;
+ }
+
+ @Override
+ public SpecialMethodInvocation invokeDefault(MethodDescription.SignatureToken token) {
+ SpecialMethodInvocation specialMethodInvocation = SpecialMethodInvocation.Illegal.INSTANCE;
+ for (TypeDescription interfaceType : instrumentedType.getInterfaces().asErasures()) {
+ SpecialMethodInvocation invocation = invokeDefault(token, interfaceType);
+ if (invocation.isValid()) {
+ if (specialMethodInvocation.isValid()) {
+ return SpecialMethodInvocation.Illegal.INSTANCE;
+ } else {
+ specialMethodInvocation = invocation;
+ }
+ }
+ }
+ return specialMethodInvocation;
+ }
+
+ @Override
+ public SpecialMethodInvocation invokeDefault(MethodDescription.SignatureToken token, TypeDescription targetType) {
+ return defaultMethodInvocation.apply(methodGraph.getInterfaceGraph(targetType).locate(token), targetType);
+ }
+
+ @Override
+ public SpecialMethodInvocation invokeDominant(MethodDescription.SignatureToken token) {
+ SpecialMethodInvocation specialMethodInvocation = invokeSuper(token);
+ return specialMethodInvocation.isValid()
+ ? specialMethodInvocation
+ : invokeDefault(token);
+ }
+
+ /**
+ * Determines if default method invocations are possible.
+ */
+ protected enum DefaultMethodInvocation {
+
+ /**
+ * Permits default method invocations, if an interface declaring a default method is possible.
+ */
+ ENABLED {
+ @Override
+ protected SpecialMethodInvocation apply(MethodGraph.Node node, TypeDescription targetType) {
+ return node.getSort().isUnique()
+ ? SpecialMethodInvocation.Simple.of(node.getRepresentative(), targetType)
+ : SpecialMethodInvocation.Illegal.INSTANCE;
+ }
+ },
+
+ /**
+ * Does not permit default method invocations.
+ */
+ DISABLED {
+ @Override
+ protected SpecialMethodInvocation apply(MethodGraph.Node node, TypeDescription targetType) {
+ return SpecialMethodInvocation.Illegal.INSTANCE;
+ }
+ };
+
+ /**
+ * Resolves a default method invocation depending on the class file version permitting such calls.
+ *
+ * @param classFileVersion The class file version to resolve for.
+ * @return A suitable default method invocation mode.
+ */
+ public static DefaultMethodInvocation of(ClassFileVersion classFileVersion) {
+ return classFileVersion.isAtLeast(ClassFileVersion.JAVA_V8)
+ ? ENABLED
+ : DISABLED;
+ }
+
+ /**
+ * Resolves a default method invocation for a given node.
+ *
+ * @param node The node representing the default method call.
+ * @param targetType The target type defining the default method.
+ * @return A suitable special method invocation.
+ */
+ protected abstract SpecialMethodInvocation apply(MethodGraph.Node node, TypeDescription targetType);
+ }
+ }
+ }
+
+ /**
+ * The context for an implementation application. An implementation context represents a mutable data structure
+ * where any registration is irrevocable. Calling methods on an implementation context should be considered equally
+ * sensitive as calling a {@link org.objectweb.asm.MethodVisitor}. As such, an implementation context and a
+ * {@link org.objectweb.asm.MethodVisitor} are complementary for creating an new Java type.
+ */
+ interface Context extends MethodAccessorFactory {
+
+ /**
+ * Registers an auxiliary type as required for the current implementation. Registering a type will cause the
+ * creation of this type even if this type is not effectively used for the current implementation.
+ *
+ * @param auxiliaryType The auxiliary type that is required for the current implementation.
+ * @return A description of the registered auxiliary type.
+ */
+ TypeDescription register(AuxiliaryType auxiliaryType);
+
+ /**
+ * Caches a single value by storing it in form of a {@code private}, {@code final} and {@code static} field.
+ * By caching values, expensive instance creations can be avoided and object identity can be preserved.
+ * The field is initiated in a generated class's static initializer.
+ *
+ * @param fieldValue A stack manipulation for creating the value that is to be cached in a {@code static} field.
+ * After executing the stack manipulation, exactly one value must be put onto the operand
+ * stack which is assignable to the given {@code fieldType}.
+ * @param fieldType The type of the field for storing the cached value. This field's type determines the value
+ * that is put onto the operand stack by this method's returned stack manipulation.
+ * @return A description of a field that was defined on the instrumented type which contains the given value.
+ */
+ FieldDescription.InDefinedShape cache(StackManipulation fieldValue, TypeDescription fieldType);
+
+ /**
+ * Returns the instrumented type of the current implementation. The instrumented type is exposed with the intend of allowing optimal
+ * byte code generation and not for implementing checks or changing the behavior of a {@link StackManipulation}.
+ *
+ * @return The instrumented type of the current implementation.
+ */
+ TypeDescription getInstrumentedType();
+
+ /**
+ * Returns the class file version of the currently created dynamic type.
+ *
+ * @return The class file version of the currently created dynamic type.
+ */
+ ClassFileVersion getClassFileVersion();
+
+ /**
+ * Represents an extractable view of an {@link Implementation.Context} which
+ * allows the retrieval of any registered auxiliary type.
+ */
+ interface ExtractableView extends Context {
+
+ /**
+ * Returns {@code true} if this implementation context permits the registration of any implicit type initializers.
+ *
+ * @return {@code true} if this implementation context permits the registration of any implicit type initializers.
+ */
+ boolean isEnabled();
+
+ /**
+ * Returns any {@link net.bytebuddy.implementation.auxiliary.AuxiliaryType} that was registered
+ * with this {@link Implementation.Context}.
+ *
+ * @return A list of all manifested registered auxiliary types.
+ */
+ List<DynamicType> getAuxiliaryTypes();
+
+ /**
+ * Writes any information that was registered with an {@link Implementation.Context}
+ * to the provided class visitor. This contains any fields for value caching, any accessor method and it
+ * writes the type initializer. The type initializer must therefore never be written manually.
+ *
+ * @param drain The drain to write the type initializer to.
+ * @param classVisitor The class visitor to which the extractable view is to be written.
+ * @param annotationValueFilterFactory The annotation value filter factory to apply when writing annotation.
+ */
+ void drain(TypeInitializer.Drain drain, ClassVisitor classVisitor, AnnotationValueFilter.Factory annotationValueFilterFactory);
+
+ /**
+ * An abstract base implementation of an extractable view of an implementation context.
+ */
+ @EqualsAndHashCode
+ abstract class AbstractBase implements ExtractableView {
+
+ /**
+ * The instrumented type.
+ */
+ protected final TypeDescription instrumentedType;
+
+ /**
+ * The class file version of the dynamic type.
+ */
+ protected final ClassFileVersion classFileVersion;
+
+ /**
+ * Create a new extractable view.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param classFileVersion The class file version of the dynamic type.
+ */
+ protected AbstractBase(TypeDescription instrumentedType, ClassFileVersion classFileVersion) {
+ this.instrumentedType = instrumentedType;
+ this.classFileVersion = classFileVersion;
+ }
+
+ @Override
+ public TypeDescription getInstrumentedType() {
+ return instrumentedType;
+ }
+
+ @Override
+ public ClassFileVersion getClassFileVersion() {
+ return classFileVersion;
+ }
+ }
+ }
+
+ /**
+ * A factory for creating a new implementation context.
+ */
+ interface Factory {
+
+ /**
+ * Creates a new implementation context.
+ *
+ * @param instrumentedType The description of the type that is currently subject of creation.
+ * @param auxiliaryTypeNamingStrategy The naming strategy for naming an auxiliary type.
+ * @param typeInitializer The type initializer of the created instrumented type.
+ * @param classFileVersion The class file version of the created class.
+ * @param auxiliaryClassFileVersion The class file version of any auxiliary classes.
+ * @return An implementation context in its extractable view.
+ */
+ ExtractableView make(TypeDescription instrumentedType,
+ AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
+ TypeInitializer typeInitializer,
+ ClassFileVersion classFileVersion,
+ ClassFileVersion auxiliaryClassFileVersion);
+ }
+
+ /**
+ * An implementation context that does not allow for any injections into the static initializer block. This can be useful when
+ * redefining a class when it is not allowed to add methods to a class what is an implicit requirement when copying the static
+ * initializer block into another method.
+ */
+ class Disabled extends ExtractableView.AbstractBase {
+
+ /**
+ * Creates a new disabled implementation context.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param classFileVersion The class file version to create the class in.
+ */
+ protected Disabled(TypeDescription instrumentedType, ClassFileVersion classFileVersion) {
+ super(instrumentedType, classFileVersion);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return false;
+ }
+
+ @Override
+ public List<DynamicType> getAuxiliaryTypes() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public void drain(TypeInitializer.Drain drain, ClassVisitor classVisitor, AnnotationValueFilter.Factory annotationValueFilterFactory) {
+ drain.apply(classVisitor, TypeInitializer.None.INSTANCE, this);
+ }
+
+ @Override
+ public TypeDescription register(AuxiliaryType auxiliaryType) {
+ throw new IllegalStateException("Registration of auxiliary types was disabled: " + auxiliaryType);
+ }
+
+ @Override
+ public MethodDescription.InDefinedShape registerAccessorFor(SpecialMethodInvocation specialMethodInvocation, AccessType accessType) {
+ throw new IllegalStateException("Registration of method accessors was disabled: " + specialMethodInvocation.getMethodDescription());
+ }
+
+ @Override
+ public MethodDescription.InDefinedShape registerGetterFor(FieldDescription fieldDescription, AccessType accessType) {
+ throw new IllegalStateException("Registration of field accessor was disabled: " + fieldDescription);
+ }
+
+ @Override
+ public MethodDescription.InDefinedShape registerSetterFor(FieldDescription fieldDescription, AccessType accessType) {
+ throw new IllegalStateException("Registration of field accessor was disabled: " + fieldDescription);
+ }
+
+ @Override
+ public FieldDescription.InDefinedShape cache(StackManipulation fieldValue, TypeDescription fieldType) {
+ throw new IllegalStateException("Field values caching was disabled: " + fieldType);
+ }
+
+ /**
+ * A factory for creating a {@link net.bytebuddy.implementation.Implementation.Context.Disabled}.
+ */
+ public enum Factory implements Context.Factory {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public ExtractableView make(TypeDescription instrumentedType,
+ AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
+ TypeInitializer typeInitializer,
+ ClassFileVersion classFileVersion,
+ ClassFileVersion auxiliaryClassFileVersion) {
+ if (typeInitializer.isDefined()) {
+ throw new IllegalStateException("Cannot define type initializer which was explicitly disabled: " + typeInitializer);
+ }
+ return new Disabled(instrumentedType, classFileVersion);
+ }
+ }
+ }
+
+ /**
+ * A default implementation of an {@link Implementation.Context.ExtractableView}
+ * which serves as its own {@link MethodAccessorFactory}.
+ */
+ class Default extends ExtractableView.AbstractBase {
+
+ /**
+ * The name suffix to be appended to an accessor method.
+ */
+ public static final String ACCESSOR_METHOD_SUFFIX = "accessor";
+
+ /**
+ * The name prefix to be prepended to a field storing a cached value.
+ */
+ public static final String FIELD_CACHE_PREFIX = "cachedValue";
+
+ /**
+ * The naming strategy for naming auxiliary types that are registered.
+ */
+ private final AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy;
+
+ /**
+ * The type initializer of the created instrumented type.
+ */
+ private final TypeInitializer typeInitializer;
+
+ /**
+ * The class file version to use for auxiliary classes.
+ */
+ private final ClassFileVersion auxiliaryClassFileVersion;
+
+ /**
+ * A mapping of special method invocations to their accessor methods that each invoke their mapped invocation.
+ */
+ private final Map<SpecialMethodInvocation, DelegationRecord> registeredAccessorMethods;
+
+ /**
+ * The registered getters.
+ */
+ private final Map<FieldDescription, DelegationRecord> registeredGetters;
+
+ /**
+ * The registered setters.
+ */
+ private final Map<FieldDescription, DelegationRecord> registeredSetters;
+
+ /**
+ * A map of registered auxiliary types to their dynamic type representation.
+ */
+ private final Map<AuxiliaryType, DynamicType> auxiliaryTypes;
+
+ /**
+ * A map of already registered field caches to their field representation.
+ */
+ private final Map<FieldCacheEntry, FieldDescription.InDefinedShape> registeredFieldCacheEntries;
+
+ /**
+ * A random suffix to append to the names of accessor methods.
+ */
+ private final String suffix;
+
+ /**
+ * If {@code false}, the type initializer for this instance was already drained what prohibits the registration of additional cached field values.
+ */
+ private boolean fieldCacheCanAppendEntries;
+
+ /**
+ * Creates a new default implementation context.
+ *
+ * @param instrumentedType The description of the type that is currently subject of creation.
+ * @param classFileVersion The class file version of the created class.
+ * @param auxiliaryTypeNamingStrategy The naming strategy for naming an auxiliary type.
+ * @param typeInitializer The type initializer of the created instrumented type.
+ * @param auxiliaryClassFileVersion The class file version to use for auxiliary classes.
+ */
+ protected Default(TypeDescription instrumentedType,
+ ClassFileVersion classFileVersion,
+ AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
+ TypeInitializer typeInitializer,
+ ClassFileVersion auxiliaryClassFileVersion) {
+ super(instrumentedType, classFileVersion);
+ this.auxiliaryTypeNamingStrategy = auxiliaryTypeNamingStrategy;
+ this.typeInitializer = typeInitializer;
+ this.auxiliaryClassFileVersion = auxiliaryClassFileVersion;
+ registeredAccessorMethods = new HashMap<SpecialMethodInvocation, DelegationRecord>();
+ registeredGetters = new HashMap<FieldDescription, DelegationRecord>();
+ registeredSetters = new HashMap<FieldDescription, DelegationRecord>();
+ auxiliaryTypes = new HashMap<AuxiliaryType, DynamicType>();
+ registeredFieldCacheEntries = new HashMap<FieldCacheEntry, FieldDescription.InDefinedShape>();
+ suffix = RandomString.make();
+ fieldCacheCanAppendEntries = true;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+
+ @Override
+ public MethodDescription.InDefinedShape registerAccessorFor(SpecialMethodInvocation specialMethodInvocation, AccessType accessType) {
+ DelegationRecord record = registeredAccessorMethods.get(specialMethodInvocation);
+ record = record == null
+ ? new AccessorMethodDelegation(instrumentedType, suffix, accessType, specialMethodInvocation)
+ : record.with(accessType);
+ registeredAccessorMethods.put(specialMethodInvocation, record);
+ return record.getMethod();
+ }
+
+ @Override
+ public MethodDescription.InDefinedShape registerGetterFor(FieldDescription fieldDescription, AccessType accessType) {
+ DelegationRecord record = registeredGetters.get(fieldDescription);
+ record = record == null
+ ? new FieldGetterDelegation(instrumentedType, suffix, accessType, fieldDescription)
+ : record.with(accessType);
+ registeredGetters.put(fieldDescription, record);
+ return record.getMethod();
+ }
+
+ @Override
+ public MethodDescription.InDefinedShape registerSetterFor(FieldDescription fieldDescription, AccessType accessType) {
+ DelegationRecord record = registeredSetters.get(fieldDescription);
+ record = record == null
+ ? new FieldSetterDelegation(instrumentedType, suffix, accessType, fieldDescription)
+ : record.with(accessType);
+ registeredSetters.put(fieldDescription, record);
+ return record.getMethod();
+ }
+
+ @Override
+ public TypeDescription register(AuxiliaryType auxiliaryType) {
+ DynamicType dynamicType = auxiliaryTypes.get(auxiliaryType);
+ if (dynamicType == null) {
+ dynamicType = auxiliaryType.make(auxiliaryTypeNamingStrategy.name(instrumentedType), auxiliaryClassFileVersion, this);
+ auxiliaryTypes.put(auxiliaryType, dynamicType);
+ }
+ return dynamicType.getTypeDescription();
+ }
+
+ @Override
+ public List<DynamicType> getAuxiliaryTypes() {
+ return new ArrayList<DynamicType>(auxiliaryTypes.values());
+ }
+
+ @Override
+ public FieldDescription.InDefinedShape cache(StackManipulation fieldValue, TypeDescription fieldType) {
+ FieldCacheEntry fieldCacheEntry = new FieldCacheEntry(fieldValue, fieldType);
+ FieldDescription.InDefinedShape fieldCache = registeredFieldCacheEntries.get(fieldCacheEntry);
+ if (fieldCache != null) {
+ return fieldCache;
+ }
+ if (!fieldCacheCanAppendEntries) {
+ throw new IllegalStateException("Cached values cannot be registered after defining the type initializer for " + instrumentedType);
+ }
+ fieldCache = new CacheValueField(instrumentedType, fieldType.asGenericType(), suffix, fieldValue.hashCode());
+ registeredFieldCacheEntries.put(fieldCacheEntry, fieldCache);
+ return fieldCache;
+ }
+
+ @Override
+ public void drain(TypeInitializer.Drain drain,
+ ClassVisitor classVisitor,
+ AnnotationValueFilter.Factory annotationValueFilterFactory) {
+ fieldCacheCanAppendEntries = false;
+ TypeInitializer typeInitializer = this.typeInitializer;
+ for (Map.Entry<FieldCacheEntry, FieldDescription.InDefinedShape> entry : registeredFieldCacheEntries.entrySet()) {
+ FieldVisitor fieldVisitor = classVisitor.visitField(entry.getValue().getModifiers(),
+ entry.getValue().getInternalName(),
+ entry.getValue().getDescriptor(),
+ entry.getValue().getGenericSignature(),
+ FieldDescription.NO_DEFAULT_VALUE);
+ if (fieldVisitor != null) {
+ fieldVisitor.visitEnd();
+ typeInitializer = typeInitializer.expandWith(entry.getKey().storeIn(entry.getValue()));
+ }
+ }
+ drain.apply(classVisitor, typeInitializer, this);
+ for (TypeWriter.MethodPool.Record record : registeredAccessorMethods.values()) {
+ record.apply(classVisitor, this, annotationValueFilterFactory);
+ }
+ for (TypeWriter.MethodPool.Record record : registeredGetters.values()) {
+ record.apply(classVisitor, this, annotationValueFilterFactory);
+ }
+ for (TypeWriter.MethodPool.Record record : registeredSetters.values()) {
+ record.apply(classVisitor, this, annotationValueFilterFactory);
+ }
+ }
+
+ /**
+ * A description of a field that stores a cached value.
+ */
+ protected static class CacheValueField extends FieldDescription.InDefinedShape.AbstractBase {
+
+ /**
+ * The instrumented type.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * The type of the cache's field.
+ */
+ private final TypeDescription.Generic fieldType;
+
+ /**
+ * The suffix to use for the cache field's name.
+ */
+ private final String suffix;
+
+ /**
+ * The hash value of the field's value for creating a unique field name.
+ */
+ private final int hashCode;
+
+ /**
+ * Creates a new cache value field.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param fieldType The type of the cache's field.
+ * @param suffix The suffix to use for the cache field's name.
+ * @param hashCode The hash value of the field's value for creating a unique field name.
+ */
+ protected CacheValueField(TypeDescription instrumentedType, TypeDescription.Generic fieldType, String suffix, int hashCode) {
+ this.instrumentedType = instrumentedType;
+ this.fieldType = fieldType;
+ this.suffix = suffix;
+ this.hashCode = hashCode;
+ }
+
+ @Override
+ public TypeDescription.Generic getType() {
+ return fieldType;
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return new AnnotationList.Empty();
+ }
+
+ @Override
+ public TypeDescription getDeclaringType() {
+ return instrumentedType;
+ }
+
+ @Override
+ public int getModifiers() {
+ return Opcodes.ACC_SYNTHETIC | Opcodes.ACC_FINAL | Opcodes.ACC_STATIC | (instrumentedType.isInterface()
+ ? Opcodes.ACC_PUBLIC
+ : Opcodes.ACC_PRIVATE);
+ }
+
+ @Override
+ public String getName() {
+ return String.format("%s$%s$%s", FIELD_CACHE_PREFIX, suffix, RandomString.hashOf(hashCode));
+ }
+ }
+
+ /**
+ * A field cache entry for uniquely identifying a cached field. A cached field is described by the stack
+ * manipulation that loads the field's value onto the operand stack and the type of the field.
+ */
+ @EqualsAndHashCode
+ protected static class FieldCacheEntry implements StackManipulation {
+
+ /**
+ * The field value that is represented by this field cache entry.
+ */
+ private final StackManipulation fieldValue;
+
+ /**
+ * The field type that is represented by this field cache entry.
+ */
+ private final TypeDescription fieldType;
+
+ /**
+ * Creates a new field cache entry.
+ *
+ * @param fieldValue The field value that is represented by this field cache entry.
+ * @param fieldType The field type that is represented by this field cache entry.
+ */
+ protected FieldCacheEntry(StackManipulation fieldValue, TypeDescription fieldType) {
+ this.fieldValue = fieldValue;
+ this.fieldType = fieldType;
+ }
+
+ /**
+ * Returns a stack manipulation where the represented value is stored in the given field.
+ *
+ * @param fieldDescription A static field in which the value is to be stored.
+ * @return A byte code appender that represents this storage.
+ */
+ protected ByteCodeAppender storeIn(FieldDescription fieldDescription) {
+ return new ByteCodeAppender.Simple(this, FieldAccess.forField(fieldDescription).write());
+ }
+
+ /**
+ * Returns the field type that is represented by this field cache entry.
+ *
+ * @return The field type that is represented by this field cache entry.
+ */
+ protected TypeDescription getFieldType() {
+ return fieldType;
+ }
+
+ @Override
+ public boolean isValid() {
+ return fieldValue.isValid();
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
+ return fieldValue.apply(methodVisitor, implementationContext);
+ }
+ }
+
+ /**
+ * A base implementation of a method that accesses a property of an instrumented type.
+ */
+ protected abstract static class AbstractPropertyAccessorMethod extends MethodDescription.InDefinedShape.AbstractBase {
+
+ @Override
+ public int getModifiers() {
+ return Opcodes.ACC_SYNTHETIC | getBaseModifiers() | (getDeclaringType().isInterface()
+ ? Opcodes.ACC_PUBLIC
+ : Opcodes.ACC_FINAL);
+ }
+
+ /**
+ * Returns the base modifiers, i.e. the modifiers that define the accessed property's features.
+ *
+ * @return Returns the base modifiers of the represented methods.
+ */
+ protected abstract int getBaseModifiers();
+ }
+
+ /**
+ * A description of an accessor method to access another method from outside the instrumented type.
+ */
+ protected static class AccessorMethod extends AbstractPropertyAccessorMethod {
+
+ /**
+ * The instrumented type.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * The method that is being accessed.
+ */
+ private final MethodDescription methodDescription;
+
+ /**
+ * The suffix to append to the accessor method's name.
+ */
+ private final String suffix;
+
+ /**
+ * Creates a new accessor method.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param methodDescription The method that is being accessed.
+ * @param suffix The suffix to append to the accessor method's name.
+ */
+ protected AccessorMethod(TypeDescription instrumentedType, MethodDescription methodDescription, String suffix) {
+ this.instrumentedType = instrumentedType;
+ this.methodDescription = methodDescription;
+ this.suffix = suffix;
+ }
+
+ @Override
+ public TypeDescription.Generic getReturnType() {
+ return methodDescription.getReturnType().asRawType();
+ }
+
+ @Override
+ public ParameterList<ParameterDescription.InDefinedShape> getParameters() {
+ return new ParameterList.Explicit.ForTypes(this, methodDescription.getParameters().asTypeList().asRawTypes());
+ }
+
+ @Override
+ public TypeList.Generic getExceptionTypes() {
+ return methodDescription.getExceptionTypes().asRawTypes();
+ }
+
+ @Override
+ public AnnotationValue<?, ?> getDefaultValue() {
+ return AnnotationValue.UNDEFINED;
+ }
+
+ @Override
+ public TypeList.Generic getTypeVariables() {
+ return new TypeList.Generic.Empty();
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return new AnnotationList.Empty();
+ }
+
+ @Override
+ public TypeDescription getDeclaringType() {
+ return instrumentedType;
+ }
+
+ @Override
+ public int getBaseModifiers() {
+ return methodDescription.isStatic()
+ ? Opcodes.ACC_STATIC
+ : EMPTY_MASK;
+ }
+
+ @Override
+ public String getInternalName() {
+ return String.format("%s$%s$%s", methodDescription.getInternalName(), ACCESSOR_METHOD_SUFFIX, suffix);
+ }
+ }
+
+ /**
+ * A description of a field getter method.
+ */
+ protected static class FieldGetter extends AbstractPropertyAccessorMethod {
+
+ /**
+ * The instrumented type.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * The field for which a getter is described.
+ */
+ private final FieldDescription fieldDescription;
+
+ /**
+ * The name suffix for the field getter method.
+ */
+ private final String suffix;
+
+ /**
+ * Creates a new field getter.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param fieldDescription The field for which a getter is described.
+ * @param suffix The name suffix for the field getter method.
+ */
+ protected FieldGetter(TypeDescription instrumentedType, FieldDescription fieldDescription, String suffix) {
+ this.instrumentedType = instrumentedType;
+ this.fieldDescription = fieldDescription;
+ this.suffix = suffix;
+ }
+
+ @Override
+ public TypeDescription.Generic getReturnType() {
+ return fieldDescription.getType().asRawType();
+ }
+
+ @Override
+ public ParameterList<ParameterDescription.InDefinedShape> getParameters() {
+ return new ParameterList.Empty<ParameterDescription.InDefinedShape>();
+ }
+
+ @Override
+ public TypeList.Generic getExceptionTypes() {
+ return new TypeList.Generic.Empty();
+ }
+
+ @Override
+ public AnnotationValue<?, ?> getDefaultValue() {
+ return AnnotationValue.UNDEFINED;
+ }
+
+ @Override
+ public TypeList.Generic getTypeVariables() {
+ return new TypeList.Generic.Empty();
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return new AnnotationList.Empty();
+ }
+
+ @Override
+ public TypeDescription getDeclaringType() {
+ return instrumentedType;
+ }
+
+ @Override
+ protected int getBaseModifiers() {
+ return fieldDescription.isStatic()
+ ? Opcodes.ACC_STATIC
+ : EMPTY_MASK;
+ }
+
+ @Override
+ public String getInternalName() {
+ return String.format("%s$%s$%s", fieldDescription.getName(), ACCESSOR_METHOD_SUFFIX, suffix);
+ }
+ }
+
+ /**
+ * A description of a field setter method.
+ */
+ protected static class FieldSetter extends AbstractPropertyAccessorMethod {
+
+ /**
+ * The instrumented type.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * The field for which a setter is described.
+ */
+ private final FieldDescription fieldDescription;
+
+ /**
+ * The name suffix for the field setter method.
+ */
+ private final String suffix;
+
+ /**
+ * Creates a new field setter.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param fieldDescription The field for which a setter is described.
+ * @param suffix The name suffix for the field setter method.
+ */
+ protected FieldSetter(TypeDescription instrumentedType, FieldDescription fieldDescription, String suffix) {
+ this.instrumentedType = instrumentedType;
+ this.fieldDescription = fieldDescription;
+ this.suffix = suffix;
+ }
+
+ @Override
+ public TypeDescription.Generic getReturnType() {
+ return TypeDescription.Generic.VOID;
+ }
+
+ @Override
+ public ParameterList<ParameterDescription.InDefinedShape> getParameters() {
+ return new ParameterList.Explicit.ForTypes(this, Collections.singletonList(fieldDescription.getType().asRawType()));
+ }
+
+ @Override
+ public TypeList.Generic getExceptionTypes() {
+ return new TypeList.Generic.Empty();
+ }
+
+ @Override
+ public AnnotationValue<?, ?> getDefaultValue() {
+ return AnnotationValue.UNDEFINED;
+ }
+
+ @Override
+ public TypeList.Generic getTypeVariables() {
+ return new TypeList.Generic.Empty();
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return new AnnotationList.Empty();
+ }
+
+ @Override
+ public TypeDescription getDeclaringType() {
+ return instrumentedType;
+ }
+
+ @Override
+ protected int getBaseModifiers() {
+ return fieldDescription.isStatic()
+ ? Opcodes.ACC_STATIC
+ : EMPTY_MASK;
+ }
+
+ @Override
+ public String getInternalName() {
+ return String.format("%s$%s$%s", fieldDescription.getName(), ACCESSOR_METHOD_SUFFIX, suffix);
+ }
+ }
+
+ /**
+ * An abstract method pool entry that delegates the implementation of a method to itself.
+ */
+ @EqualsAndHashCode(callSuper = false)
+ protected abstract static class DelegationRecord extends TypeWriter.MethodPool.Record.ForDefinedMethod implements ByteCodeAppender {
+
+ /**
+ * The delegation method.
+ */
+ protected final MethodDescription.InDefinedShape methodDescription;
+
+ /**
+ * The record's visibility.
+ */
+ protected final Visibility visibility;
+
+ /**
+ * Creates a new delegation record.
+ *
+ * @param methodDescription The delegation method.
+ * @param visibility The method's actual visibility.
+ */
+ protected DelegationRecord(MethodDescription.InDefinedShape methodDescription, Visibility visibility) {
+ this.methodDescription = methodDescription;
+ this.visibility = visibility;
+ }
+
+ /**
+ * Returns this delegation record with the minimal visibility represented by the supplied access type.
+ *
+ * @param accessType The access type to enforce.
+ * @return A new version of this delegation record with the minimal implied visibility.
+ */
+ protected abstract DelegationRecord with(AccessType accessType);
+
+ @Override
+ public MethodDescription.InDefinedShape getMethod() {
+ return methodDescription;
+ }
+
+ @Override
+ public Sort getSort() {
+ return Sort.IMPLEMENTED;
+ }
+
+ @Override
+ public Visibility getVisibility() {
+ return visibility;
+ }
+
+ @Override
+ public void applyHead(MethodVisitor methodVisitor) {
+ /* do nothing */
+ }
+
+ @Override
+ public void applyBody(MethodVisitor methodVisitor, Context implementationContext, AnnotationValueFilter.Factory annotationValueFilterFactory) {
+ methodVisitor.visitCode();
+ Size size = applyCode(methodVisitor, implementationContext);
+ methodVisitor.visitMaxs(size.getOperandStackSize(), size.getLocalVariableSize());
+ }
+
+ @Override
+ public void applyAttributes(MethodVisitor methodVisitor, AnnotationValueFilter.Factory annotationValueFilterFactory) {
+ /* do nothing */
+ }
+
+ @Override
+ public Size applyCode(MethodVisitor methodVisitor, Context implementationContext) {
+ return apply(methodVisitor, implementationContext, getMethod());
+ }
+
+ @Override
+ public TypeWriter.MethodPool.Record prepend(ByteCodeAppender byteCodeAppender) {
+ throw new UnsupportedOperationException("Cannot prepend code to a delegation for " + methodDescription);
+ }
+ }
+
+ /**
+ * An implementation of a {@link TypeWriter.MethodPool.Record} for implementing
+ * an accessor method.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ protected static class AccessorMethodDelegation extends DelegationRecord {
+
+ /**
+ * The stack manipulation that represents the requested special method invocation.
+ */
+ private final StackManipulation accessorMethodInvocation;
+
+ /**
+ * Creates a delegation to an accessor method.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param suffix The suffix to append to the method.
+ * @param accessType The access type.
+ * @param specialMethodInvocation The actual method's invocation.
+ */
+ protected AccessorMethodDelegation(TypeDescription instrumentedType,
+ String suffix,
+ AccessType accessType,
+ SpecialMethodInvocation specialMethodInvocation) {
+ this(new AccessorMethod(instrumentedType, specialMethodInvocation.getMethodDescription(), suffix),
+ accessType.getVisibility(),
+ specialMethodInvocation);
+ }
+
+ /**
+ * Creates a delegation to an accessor method.
+ *
+ * @param methodDescription The accessor method.
+ * @param visibility The method's visibility.
+ * @param accessorMethodInvocation The actual method's invocation.
+ */
+ private AccessorMethodDelegation(MethodDescription.InDefinedShape methodDescription,
+ Visibility visibility,
+ StackManipulation accessorMethodInvocation) {
+ super(methodDescription, visibility);
+ this.accessorMethodInvocation = accessorMethodInvocation;
+ }
+
+ @Override
+ protected DelegationRecord with(AccessType accessType) {
+ return new AccessorMethodDelegation(methodDescription, visibility.expandTo(accessType.getVisibility()), accessorMethodInvocation);
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodDescription instrumentedMethod) {
+ StackManipulation.Size stackSize = new StackManipulation.Compound(
+ MethodVariableAccess.allArgumentsOf(instrumentedMethod).prependThisReference(),
+ accessorMethodInvocation,
+ MethodReturn.of(instrumentedMethod.getReturnType())
+ ).apply(methodVisitor, implementationContext);
+ return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+ }
+
+ /**
+ * An implementation for a field getter.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ protected static class FieldGetterDelegation extends DelegationRecord {
+
+ /**
+ * The field to read from.
+ */
+ private final FieldDescription fieldDescription;
+
+ /**
+ * Creates a new field getter implementation.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param suffix The suffix to use for the setter method.
+ * @param accessType The method's access type.
+ * @param fieldDescription The field to write to.
+ */
+ protected FieldGetterDelegation(TypeDescription instrumentedType, String suffix, AccessType accessType, FieldDescription fieldDescription) {
+ this(new FieldGetter(instrumentedType, fieldDescription, suffix), accessType.getVisibility(), fieldDescription);
+ }
+
+ /**
+ * Creates a new field getter implementation.
+ *
+ * @param methodDescription The delegation method.
+ * @param visibility The delegation method's visibility.
+ * @param fieldDescription The field to read.
+ */
+ private FieldGetterDelegation(MethodDescription.InDefinedShape methodDescription, Visibility visibility, FieldDescription fieldDescription) {
+ super(methodDescription, visibility);
+ this.fieldDescription = fieldDescription;
+ }
+
+ @Override
+ protected DelegationRecord with(AccessType accessType) {
+ return new FieldGetterDelegation(methodDescription, visibility.expandTo(accessType.getVisibility()), fieldDescription);
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ StackManipulation.Size stackSize = new StackManipulation.Compound(
+ fieldDescription.isStatic()
+ ? StackManipulation.Trivial.INSTANCE
+ : MethodVariableAccess.loadThis(),
+ FieldAccess.forField(fieldDescription).read(),
+ MethodReturn.of(fieldDescription.getType())
+ ).apply(methodVisitor, implementationContext);
+ return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+ }
+
+ /**
+ * An implementation for a field setter.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ protected static class FieldSetterDelegation extends DelegationRecord {
+
+ /**
+ * The field to write to.
+ */
+ private final FieldDescription fieldDescription;
+
+ /**
+ * Creates a new field setter implementation.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param suffix The suffix to use for the setter method.
+ * @param accessType The method's access type.
+ * @param fieldDescription The field to write to.
+ */
+ protected FieldSetterDelegation(TypeDescription instrumentedType, String suffix, AccessType accessType, FieldDescription fieldDescription) {
+ this(new FieldSetter(instrumentedType, fieldDescription, suffix), accessType.getVisibility(), fieldDescription);
+ }
+
+ /**
+ * Creates a new field setter.
+ *
+ * @param methodDescription The field accessor method.
+ * @param visibility The delegation method's visibility.
+ * @param fieldDescription The field to write to.
+ */
+ private FieldSetterDelegation(MethodDescription.InDefinedShape methodDescription, Visibility visibility, FieldDescription fieldDescription) {
+ super(methodDescription, visibility);
+ this.fieldDescription = fieldDescription;
+ }
+
+ @Override
+ protected DelegationRecord with(AccessType accessType) {
+ return new FieldSetterDelegation(methodDescription, visibility.expandTo(accessType.getVisibility()), fieldDescription);
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ StackManipulation.Size stackSize = new StackManipulation.Compound(
+ MethodVariableAccess.allArgumentsOf(instrumentedMethod).prependThisReference(),
+ FieldAccess.forField(fieldDescription).write(),
+ MethodReturn.VOID
+ ).apply(methodVisitor, implementationContext);
+ return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+ }
+
+ /**
+ * A factory for creating a {@link net.bytebuddy.implementation.Implementation.Context.Default}.
+ */
+ public enum Factory implements ExtractableView.Factory {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public ExtractableView make(TypeDescription instrumentedType,
+ AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
+ TypeInitializer typeInitializer,
+ ClassFileVersion classFileVersion,
+ ClassFileVersion auxiliaryClassFileVersion) {
+ return new Default(instrumentedType, classFileVersion, auxiliaryTypeNamingStrategy, typeInitializer, auxiliaryClassFileVersion);
+ }
+ }
+ }
+ }
+
+ /**
+ * A compound implementation that allows to combine several implementations.
+ * <p> </p>
+ * Note that the combination of two implementation might break the contract for implementing
+ * {@link java.lang.Object#equals(Object)} and {@link Object#hashCode()} as described for
+ * {@link Implementation}.
+ *
+ * @see Implementation
+ */
+ @EqualsAndHashCode
+ class Compound implements Implementation {
+
+ /**
+ * All implementation that are represented by this compound implementation.
+ */
+ private final List<Implementation> implementations;
+
+ /**
+ * Creates a new immutable compound implementation.
+ *
+ * @param implementation The implementations to combine in their order.
+ */
+ public Compound(Implementation... implementation) {
+ this(Arrays.asList(implementation));
+ }
+
+ /**
+ * Creates a new immutable compound implementation.
+ *
+ * @param implementations The implementations to combine in their order.
+ */
+ public Compound(List<? extends Implementation> implementations) {
+ this.implementations = new ArrayList<Implementation>();
+ for (Implementation implementation : implementations) {
+ if (implementation instanceof Compound) {
+ this.implementations.addAll(((Compound) implementation).implementations);
+ } else {
+ this.implementations.add(implementation);
+ }
+ }
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ for (Implementation implementation : implementations) {
+ instrumentedType = implementation.prepare(instrumentedType);
+ }
+ return instrumentedType;
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ ByteCodeAppender[] byteCodeAppender = new ByteCodeAppender[implementations.size()];
+ int index = 0;
+ for (Implementation implementation : implementations) {
+ byteCodeAppender[index++] = implementation.appender(implementationTarget);
+ }
+ return new ByteCodeAppender.Compound(byteCodeAppender);
+ }
+ }
+
+ /**
+ * A simple implementation that does not register any members with the instrumented type.
+ */
+ @EqualsAndHashCode
+ class Simple implements Implementation {
+
+ /**
+ * The byte code appender to emmit.
+ */
+ private final ByteCodeAppender byteCodeAppender;
+
+ /**
+ * Creates a new simple implementation for the given byte code appenders.
+ *
+ * @param byteCodeAppender The byte code appenders to apply in their order of application.
+ */
+ public Simple(ByteCodeAppender... byteCodeAppender) {
+ this.byteCodeAppender = new ByteCodeAppender.Compound(byteCodeAppender);
+ }
+
+ /**
+ * Creates a new simple instrumentation for the given stack manipulations which are summarized in a
+ * byte code appender that defines any requested method by these manipulations.
+ *
+ * @param stackManipulation The stack manipulation to apply in their order of application.
+ */
+ public Simple(StackManipulation... stackManipulation) {
+ byteCodeAppender = new ByteCodeAppender.Simple(stackManipulation);
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return byteCodeAppender;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/InvocationHandlerAdapter.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/InvocationHandlerAdapter.java
new file mode 100644
index 0000000..027e9a9
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/InvocationHandlerAdapter.java
@@ -0,0 +1,426 @@
+package net.bytebuddy.implementation;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.dynamic.scaffold.FieldLocator;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.collection.ArrayFactory;
+import net.bytebuddy.implementation.bytecode.constant.MethodConstant;
+import net.bytebuddy.implementation.bytecode.member.FieldAccess;
+import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
+import net.bytebuddy.implementation.bytecode.member.MethodReturn;
+import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+import net.bytebuddy.utility.RandomString;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.lang.reflect.InvocationHandler;
+import java.util.ArrayList;
+import java.util.List;
+
+import static net.bytebuddy.matcher.ElementMatchers.genericFieldType;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+
+/**
+ * An adapter for adapting an {@link java.lang.reflect.InvocationHandler}. The adapter allows the invocation handler
+ * to also intercept method calls to non-interface methods.
+ */
+ at EqualsAndHashCode
+public abstract class InvocationHandlerAdapter implements Implementation {
+
+ /**
+ * A type description of the {@link InvocationHandler}.
+ */
+ private static final TypeDescription.Generic INVOCATION_HANDLER_TYPE = new TypeDescription.Generic.OfNonGenericType.ForLoadedType(InvocationHandler.class);
+
+ /**
+ * Indicates that a value should not be cached.
+ */
+ private static final boolean NO_CACHING = false;
+
+ /**
+ * Indicates that a {@link java.lang.reflect.Method} should be cached.
+ */
+ protected static final boolean CACHING = true;
+
+ /**
+ * The name of the field for storing an invocation handler.
+ */
+ protected final String fieldName;
+
+ /**
+ * The assigner that is used for assigning the return invocation handler's return value to the
+ * intercepted method's return value.
+ */
+ protected final Assigner assigner;
+
+ /**
+ * Determines if the {@link java.lang.reflect.Method} instances that are handed to the intercepted methods are
+ * cached in {@code static} fields.
+ */
+ protected final boolean cacheMethods;
+
+ /**
+ * Creates a new invocation handler for a given field.
+ *
+ * @param fieldName The name of the field.
+ * @param cacheMethods Determines if the {@link java.lang.reflect.Method} instances that are handed to the
+ * intercepted methods are cached in {@code static} fields.
+ * @param assigner The assigner to apply when defining this implementation.
+ */
+ protected InvocationHandlerAdapter(String fieldName, boolean cacheMethods, Assigner assigner) {
+ this.fieldName = fieldName;
+ this.cacheMethods = cacheMethods;
+ this.assigner = assigner;
+ }
+
+ /**
+ * Creates an implementation for any instance of an {@link java.lang.reflect.InvocationHandler} that delegates
+ * all method interceptions to the given instance which will be stored in a {@code static} field.
+ *
+ * @param invocationHandler The invocation handler to which all method calls are delegated.
+ * @return An implementation that delegates all method interceptions to the given invocation handler.
+ */
+ public static InvocationHandlerAdapter of(InvocationHandler invocationHandler) {
+ return of(invocationHandler, String.format("%s$%s", ForInstance.PREFIX, RandomString.hashOf(invocationHandler.hashCode())));
+ }
+
+ /**
+ * Creates an implementation for any instance of an {@link java.lang.reflect.InvocationHandler} that delegates
+ * all method interceptions to the given instance which will be stored in a {@code static} field.
+ *
+ * @param invocationHandler The invocation handler to which all method calls are delegated.
+ * @param fieldName The name of the field.
+ * @return An implementation that delegates all method interceptions to the given invocation handler.
+ */
+ public static InvocationHandlerAdapter of(InvocationHandler invocationHandler, String fieldName) {
+ return new ForInstance(fieldName, CACHING, Assigner.DEFAULT, invocationHandler);
+ }
+
+ /**
+ * Creates an implementation for any {@link java.lang.reflect.InvocationHandler} that delegates
+ * all method interceptions to a field with the given name. This field has to be of a subtype of invocation
+ * handler and needs to be set before any invocations are intercepted. Otherwise, a {@link java.lang.NullPointerException}
+ * will be thrown.
+ *
+ * @param name The name of the field.
+ * @return An implementation that delegates all method interceptions to an instance field of the given name.
+ */
+ public static InvocationHandlerAdapter toField(String name) {
+ return toField(name, FieldLocator.ForClassHierarchy.Factory.INSTANCE);
+ }
+
+ /**
+ * Creates an implementation for any {@link java.lang.reflect.InvocationHandler} that delegates
+ * all method interceptions to a field with the given name. This field has to be of a subtype of invocation
+ * handler and needs to be set before any invocations are intercepted. Otherwise, a {@link java.lang.NullPointerException}
+ * will be thrown.
+ *
+ * @param name The name of the field.
+ * @param fieldLocatorFactory The field locator factory
+ * @return An implementation that delegates all method interceptions to an instance field of the given name.
+ */
+ public static InvocationHandlerAdapter toField(String name, FieldLocator.Factory fieldLocatorFactory) {
+ return new ForField(name, CACHING, Assigner.DEFAULT, fieldLocatorFactory);
+ }
+
+ /**
+ * Returns a list of stack manipulations that loads all arguments of an instrumented method.
+ *
+ * @param instrumentedMethod The method that is instrumented.
+ * @return A list of stack manipulation that loads all arguments of an instrumented method.
+ */
+ private List<StackManipulation> argumentValuesOf(MethodDescription instrumentedMethod) {
+ TypeList.Generic parameterTypes = instrumentedMethod.getParameters().asTypeList();
+ List<StackManipulation> instruction = new ArrayList<StackManipulation>(parameterTypes.size());
+ int currentIndex = 1;
+ for (TypeDescription.Generic parameterType : parameterTypes) {
+ instruction.add(new StackManipulation.Compound(
+ MethodVariableAccess.of(parameterType).loadFrom(currentIndex),
+ assigner.assign(parameterType, TypeDescription.Generic.OBJECT, Assigner.Typing.STATIC)));
+ currentIndex += parameterType.getStackSize().getSize();
+ }
+ return instruction;
+ }
+
+ /**
+ * By default, any {@link java.lang.reflect.Method} instance that is handed over to an
+ * {@link java.lang.reflect.InvocationHandler} is cached in a static field. By invoking this method,
+ * this feature can be disabled.
+ *
+ * @return A similar invocation handler adapter that applies caching.
+ */
+ public abstract AssignerConfigurable withoutMethodCache();
+
+ /**
+ * Applies an implementation that delegates to a invocation handler.
+ *
+ * @param methodVisitor The method visitor for writing the byte code to.
+ * @param implementationContext The implementation context for the current implementation.
+ * @param instrumentedMethod The method that is instrumented.
+ * @param preparingManipulation A stack manipulation that applies any preparation to the operand stack.
+ * @param fieldDescription The field that contains the value for the invocation handler.
+ * @return The size of the applied assignment.
+ */
+ protected ByteCodeAppender.Size apply(MethodVisitor methodVisitor,
+ Context implementationContext,
+ MethodDescription instrumentedMethod,
+ StackManipulation preparingManipulation,
+ FieldDescription fieldDescription) {
+ if (instrumentedMethod.isStatic()) {
+ throw new IllegalStateException("It is not possible to apply an invocation handler onto the static method " + instrumentedMethod);
+ }
+ StackManipulation.Size stackSize = new StackManipulation.Compound(
+ preparingManipulation,
+ FieldAccess.forField(fieldDescription).read(),
+ MethodVariableAccess.loadThis(),
+ cacheMethods
+ ? MethodConstant.forMethod(instrumentedMethod.asDefined()).cached()
+ : MethodConstant.forMethod(instrumentedMethod.asDefined()),
+ ArrayFactory.forType(TypeDescription.Generic.OBJECT).withValues(argumentValuesOf(instrumentedMethod)),
+ MethodInvocation.invoke(INVOCATION_HANDLER_TYPE.getDeclaredMethods().getOnly()),
+ assigner.assign(TypeDescription.Generic.OBJECT, instrumentedMethod.getReturnType(), Assigner.Typing.DYNAMIC),
+ MethodReturn.of(instrumentedMethod.getReturnType())
+ ).apply(methodVisitor, implementationContext);
+ return new ByteCodeAppender.Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+
+ /**
+ * Allows for the configuration of an {@link net.bytebuddy.implementation.bytecode.assign.Assigner}
+ * of an {@link net.bytebuddy.implementation.InvocationHandlerAdapter}.
+ */
+ protected interface AssignerConfigurable extends Implementation {
+
+ /**
+ * Configures an assigner to use with this invocation handler adapter.
+ *
+ * @param assigner The assigner to apply when defining this implementation.
+ * @return This instrumentation with the given {@code assigner} configured.
+ */
+ Implementation withAssigner(Assigner assigner);
+ }
+
+ /**
+ * An implementation of an {@link net.bytebuddy.implementation.InvocationHandlerAdapter} that delegates method
+ * invocations to an adapter that is stored in a static field.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ protected static class ForInstance extends InvocationHandlerAdapter implements AssignerConfigurable {
+
+ /**
+ * The prefix for field that are created for storing the instrumented value.
+ */
+ private static final String PREFIX = "invocationHandler";
+
+ /**
+ * The invocation handler to which method interceptions are to be delegated.
+ */
+ protected final InvocationHandler invocationHandler;
+
+ /**
+ * Creates a new invocation handler adapter for delegating invocations to an invocation handler that is stored
+ * in a static field.
+ *
+ * @param fieldName The name of the field.
+ * @param cacheMethods Determines if the {@link java.lang.reflect.Method} instances that are handed to the
+ * intercepted methods are cached in {@code static} fields.
+ * @param assigner The assigner to apply when defining this implementation.
+ * @param invocationHandler The invocation handler to which all method calls are delegated.
+ */
+ protected ForInstance(String fieldName, boolean cacheMethods, Assigner assigner, InvocationHandler invocationHandler) {
+ super(fieldName, cacheMethods, assigner);
+ this.invocationHandler = invocationHandler;
+ }
+
+ @Override
+ public AssignerConfigurable withoutMethodCache() {
+ return new ForInstance(fieldName, NO_CACHING, assigner, invocationHandler);
+ }
+
+ @Override
+ public Implementation withAssigner(Assigner assigner) {
+ return new ForInstance(fieldName, cacheMethods, assigner, invocationHandler);
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType
+ .withField(new FieldDescription.Token(fieldName, Opcodes.ACC_SYNTHETIC | Opcodes.ACC_STATIC | Opcodes.ACC_PUBLIC, INVOCATION_HANDLER_TYPE))
+ .withInitializer(new LoadedTypeInitializer.ForStaticField(fieldName, invocationHandler));
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return new Appender(implementationTarget.getInstrumentedType());
+ }
+
+ /**
+ * An appender for implementing the {@link ForInstance}.
+ */
+ protected class Appender implements ByteCodeAppender {
+
+ /**
+ * The instrumented type for which the methods are being intercepted.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * Creates a new appender.
+ *
+ * @param instrumentedType The type that is instrumented.
+ */
+ protected Appender(TypeDescription instrumentedType) {
+ this.instrumentedType = instrumentedType;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ return ForInstance.this.apply(methodVisitor,
+ implementationContext,
+ instrumentedMethod,
+ StackManipulation.Trivial.INSTANCE,
+ instrumentedType.getDeclaredFields().filter(named(fieldName).and(genericFieldType(INVOCATION_HANDLER_TYPE))).getOnly());
+ }
+
+ /**
+ * Returns the outer class.
+ *
+ * @return The outer class of this instance.
+ */
+ private InvocationHandlerAdapter getInvocationHandlerAdapter() {
+ return ForInstance.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && instrumentedType.equals(((Appender) other).instrumentedType)
+ && ForInstance.this.equals(((Appender) other).getInvocationHandlerAdapter());
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return 31 * ForInstance.this.hashCode() + instrumentedType.hashCode();
+ }
+ }
+ }
+
+ /**
+ * An implementation of an {@link net.bytebuddy.implementation.InvocationHandlerAdapter} that delegates method
+ * invocations to an adapter that is stored in an instance field.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ protected static class ForField extends InvocationHandlerAdapter implements AssignerConfigurable {
+
+ /**
+ * The field locator factory to use.
+ */
+ private final FieldLocator.Factory fieldLocatorFactory;
+
+ /**
+ * Creates a new invocation handler adapter that loads its value from a field.
+ *
+ * @param fieldName The name of the field.
+ * @param cacheMethods Determines if the {@link java.lang.reflect.Method} instances that are handed to the
+ * intercepted methods are cached in {@code static} fields.
+ * @param assigner The assigner to apply when defining this implementation.
+ * @param fieldLocatorFactory The field locator factory to use.
+ */
+ protected ForField(String fieldName, boolean cacheMethods, Assigner assigner, FieldLocator.Factory fieldLocatorFactory) {
+ super(fieldName, cacheMethods, assigner);
+ this.fieldLocatorFactory = fieldLocatorFactory;
+ }
+
+ @Override
+ public AssignerConfigurable withoutMethodCache() {
+ return new ForField(fieldName, NO_CACHING, assigner, fieldLocatorFactory);
+ }
+
+ @Override
+ public Implementation withAssigner(Assigner assigner) {
+ return new ForField(fieldName, cacheMethods, assigner, fieldLocatorFactory);
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ FieldLocator.Resolution resolution = fieldLocatorFactory.make(implementationTarget.getInstrumentedType()).locate(fieldName);
+ if (!resolution.isResolved()) {
+ throw new IllegalStateException("Could not find a field named '" + fieldName + "' for " + implementationTarget.getInstrumentedType());
+ } else if (!resolution.getField().getType().asErasure().isAssignableTo(InvocationHandler.class)) {
+ throw new IllegalStateException("Field " + resolution.getField() + " does not declare a type that is assignable to invocation handler");
+ }
+ return new Appender(implementationTarget.getInstrumentedType(), resolution.getField());
+ }
+
+ /**
+ * An appender for implementing the {@link ForField}.
+ */
+ protected class Appender implements ByteCodeAppender {
+
+ /**
+ * The type that is subject of the instrumentation.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * The field that contains the invocation handler.
+ */
+ private final FieldDescription fieldDescription;
+
+ /**
+ * Creates a new appender.
+ *
+ * @param instrumentedType The type that is instrumented.
+ * @param fieldDescription The field that contains the invocation handler.
+ */
+ protected Appender(TypeDescription instrumentedType, FieldDescription fieldDescription) {
+ this.instrumentedType = instrumentedType;
+ this.fieldDescription = fieldDescription;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ return ForField.this.apply(methodVisitor,
+ implementationContext,
+ instrumentedMethod,
+ fieldDescription.isStatic()
+ ? StackManipulation.Trivial.INSTANCE
+ : MethodVariableAccess.loadThis(),
+ fieldDescription);
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && instrumentedType.equals(((Appender) other).instrumentedType)
+ && fieldDescription.equals(((Appender) other).fieldDescription)
+ && ForField.this.equals(((Appender) other).getInvocationHandlerAdapter());
+ }
+
+ /**
+ * Returns the outer class.
+ *
+ * @return The outer class.
+ */
+ private InvocationHandlerAdapter getInvocationHandlerAdapter() {
+ return ForField.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return 31 * (31 * ForField.this.hashCode() + instrumentedType.hashCode()) + fieldDescription.hashCode();
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/InvokeDynamic.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/InvokeDynamic.java
new file mode 100644
index 0000000..f25afe3
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/InvokeDynamic.java
@@ -0,0 +1,2984 @@
+package net.bytebuddy.implementation;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.enumeration.EnumerationDescription;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.dynamic.scaffold.FieldLocator;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.implementation.bytecode.Removal;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.constant.*;
+import net.bytebuddy.implementation.bytecode.member.FieldAccess;
+import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
+import net.bytebuddy.implementation.bytecode.member.MethodReturn;
+import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+import net.bytebuddy.utility.CompoundList;
+import net.bytebuddy.utility.JavaConstant;
+import net.bytebuddy.utility.JavaType;
+import net.bytebuddy.utility.RandomString;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+
+/**
+ * An implementation that applies a
+ * <a href="http://docs.oracle.com/javase/8/docs/api/java/lang/invoke/package-summary.html">dynamic method invocation</a>.
+ */
+ at EqualsAndHashCode
+public class InvokeDynamic implements Implementation.Composable {
+
+ /**
+ * The bootstrap method.
+ */
+ protected final MethodDescription.InDefinedShape bootstrapMethod;
+
+ /**
+ * The arguments that are provided to the bootstrap method.
+ */
+ protected final List<?> handleArguments;
+
+ /**
+ * The target provided that identifies the method to be bootstrapped.
+ */
+ protected final InvocationProvider invocationProvider;
+
+ /**
+ * A handler that handles the method return.
+ */
+ protected final TerminationHandler terminationHandler;
+
+ /**
+ * The assigner to be used.
+ */
+ protected final Assigner assigner;
+
+ /**
+ * Indicates if dynamic type castings should be attempted for incompatible assignments.
+ */
+ protected final Assigner.Typing typing;
+
+ /**
+ * Creates a new invoke dynamic implementation.
+ *
+ * @param bootstrapMethod The bootstrap method.
+ * @param handleArguments The arguments that are provided to the bootstrap method.
+ * @param invocationProvider The target provided that identifies the method to be bootstrapped.
+ * @param terminationHandler A handler that handles the method return.
+ * @param assigner The assigner to be used.
+ * @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
+ */
+ protected InvokeDynamic(MethodDescription.InDefinedShape bootstrapMethod,
+ List<?> handleArguments,
+ InvocationProvider invocationProvider,
+ TerminationHandler terminationHandler,
+ Assigner assigner,
+ Assigner.Typing typing) {
+ this.bootstrapMethod = bootstrapMethod;
+ this.handleArguments = handleArguments;
+ this.invocationProvider = invocationProvider;
+ this.terminationHandler = terminationHandler;
+ this.assigner = assigner;
+ this.typing = typing;
+ }
+
+ /**
+ * Implements the instrumented method with a dynamic method invocation which is linked at runtime using the
+ * specified bootstrap method.
+ *
+ * @param method The bootstrap method that is used to link the instrumented method.
+ * @param rawArgument The arguments that are handed to the bootstrap method. Any argument must be saved in the
+ * constant pool, i.e. primitive types (represented as their wrapper types) with a size of
+ * at least 32 bit, {@link java.lang.String} types, {@link java.lang.Class} types as well
+ * as {@code MethodType} and {@code MethodHandle} instances. In order to avoid class loading,
+ * it is also possible to supply unloaded types as {@link TypeDescription},
+ * {@link JavaConstant.MethodHandle} or
+ * {@link JavaConstant.MethodType} instances.
+ * instrumented method are passed to the bootstrapped method unless explicit parameters are specified.
+ * @return An implementation where a {@code this} reference, if available, and all arguments of the
+ * instrumented method are passed to the bootstrapped method unless explicit parameters are specified.
+ */
+ public static WithImplicitTarget bootstrap(Method method, Object... rawArgument) {
+ return bootstrap(new MethodDescription.ForLoadedMethod(method), rawArgument);
+ }
+
+ /**
+ * Implements the instrumented method with a dynamic method invocation which is linked at runtime using the
+ * specified bootstrap method.
+ *
+ * @param method The bootstrap method that is used to link the instrumented method.
+ * @param rawArguments The arguments that are handed to the bootstrap method. Any argument must be saved in the
+ * constant pool, i.e. primitive types (represented as their wrapper types) with a size of
+ * at least 32 bit, {@link java.lang.String} types, {@link java.lang.Class} types as well
+ * as {@code MethodType} and {@code MethodHandle} instances. In order to avoid class loading,
+ * it is also possible to supply unloaded types as {@link TypeDescription},
+ * {@link JavaConstant.MethodHandle} or
+ * {@link JavaConstant.MethodType} instances.
+ * instrumented method are passed to the bootstrapped method unless explicit parameters are specified.
+ * @return An implementation where a {@code this} reference, if available, and all arguments of the
+ * instrumented method are passed to the bootstrapped method unless explicit parameters are specified.
+ */
+ public static WithImplicitTarget bootstrap(Method method, List<?> rawArguments) {
+ return bootstrap(new MethodDescription.ForLoadedMethod(method), rawArguments);
+ }
+
+ /**
+ * Implements the instrumented method with a dynamic method invocation which is linked at runtime using the
+ * specified bootstrap constructor.
+ *
+ * @param constructor The bootstrap constructor that is used to link the instrumented method.
+ * @param rawArgument The arguments that are handed to the bootstrap method. Any argument must be saved in the
+ * constant pool, i.e. primitive types (represented as their wrapper types) with a size of
+ * at least 32 bit, {@link java.lang.String} types, {@link java.lang.Class} types as well
+ * as {@code MethodType} and {@code MethodHandle} instances. In order to avoid class loading,
+ * it is also possible to supply unloaded types as {@link TypeDescription},
+ * {@link JavaConstant.MethodHandle} or
+ * {@link JavaConstant.MethodType} instances.
+ * @return An implementation where a {@code this} reference, if available, and all arguments of the
+ * instrumented method are passed to the bootstrapped method unless explicit parameters are specified.
+ */
+ public static WithImplicitTarget bootstrap(Constructor<?> constructor, Object... rawArgument) {
+ return bootstrap(new MethodDescription.ForLoadedConstructor(constructor), rawArgument);
+ }
+
+ /**
+ * Implements the instrumented method with a dynamic method invocation which is linked at runtime using the
+ * specified bootstrap constructor.
+ *
+ * @param constructor The bootstrap constructor that is used to link the instrumented method.
+ * @param rawArguments The arguments that are handed to the bootstrap method. Any argument must be saved in the
+ * constant pool, i.e. primitive types (represented as their wrapper types) with a size of
+ * at least 32 bit, {@link java.lang.String} types, {@link java.lang.Class} types as well
+ * as {@code MethodType} and {@code MethodHandle} instances. In order to avoid class loading,
+ * it is also possible to supply unloaded types as {@link TypeDescription},
+ * {@link JavaConstant.MethodHandle} or
+ * {@link JavaConstant.MethodType} instances.
+ * @return An implementation where a {@code this} reference, if available, and all arguments of the
+ * instrumented method are passed to the bootstrapped method unless explicit parameters are specified.
+ */
+ public static WithImplicitTarget bootstrap(Constructor<?> constructor, List<?> rawArguments) {
+ return bootstrap(new MethodDescription.ForLoadedConstructor(constructor), rawArguments);
+ }
+
+ /**
+ * Implements the instrumented method with a dynamic method invocation which is linked at runtime using the
+ * specified bootstrap method or constructor.
+ *
+ * @param bootstrapMethod The bootstrap method or constructor that is used to link the instrumented method.
+ * @param rawArgument The arguments that are handed to the bootstrap method. Any argument must be saved in the
+ * constant pool, i.e. primitive types (represented as their wrapper types) with a size of
+ * at least 32 bit, {@link java.lang.String} types, {@link java.lang.Class} types as well
+ * as {@code MethodType} and {@code MethodHandle} instances. In order to avoid class loading,
+ * it is also possible to supply unloaded types as {@link TypeDescription},
+ * {@link JavaConstant.MethodHandle} or
+ * {@link JavaConstant.MethodType} instances.
+ * @return An implementation where a {@code this} reference, if available, and all arguments of the
+ * instrumented method are passed to the bootstrapped method unless explicit parameters are specified.
+ */
+ public static WithImplicitTarget bootstrap(MethodDescription.InDefinedShape bootstrapMethod, Object... rawArgument) {
+ return bootstrap(bootstrapMethod, Arrays.asList(rawArgument));
+ }
+
+ /**
+ * Implements the instrumented method with a dynamic method invocation which is linked at runtime using the
+ * specified bootstrap method or constructor.
+ *
+ * @param bootstrapMethod The bootstrap method or constructor that is used to link the instrumented method.
+ * @param rawArguments The arguments that are handed to the bootstrap method. Any argument must be saved in the
+ * constant pool, i.e. primitive types (represented as their wrapper types) with a size of
+ * at least 32 bit, {@link java.lang.String} types, {@link java.lang.Class} types as well
+ * as {@code MethodType} and {@code MethodHandle} instances. In order to avoid class loading,
+ * it is also possible to supply unloaded types as {@link TypeDescription},
+ * {@link JavaConstant.MethodHandle} or
+ * {@link JavaConstant.MethodType} instances.
+ * @return An implementation where a {@code this} reference, if available, and all arguments of the
+ * instrumented method are passed to the bootstrapped method unless explicit parameters are specified.
+ */
+ public static WithImplicitTarget bootstrap(MethodDescription.InDefinedShape bootstrapMethod, List<?> rawArguments) {
+ List<Object> arguments = new ArrayList<Object>(rawArguments.size());
+ for (Object argument : rawArguments) {
+ if (argument instanceof Class) {
+ argument = new TypeDescription.ForLoadedType((Class<?>) argument);
+ } else if (JavaType.METHOD_HANDLE.getTypeStub().isInstance(argument)) {
+ argument = JavaConstant.MethodHandle.ofLoaded(argument);
+ } else if (JavaType.METHOD_TYPE.getTypeStub().isInstance(argument)) {
+ argument = JavaConstant.MethodType.ofLoaded(argument);
+ }
+ arguments.add(argument);
+ }
+ if (!bootstrapMethod.isBootstrap(arguments)) {
+ throw new IllegalArgumentException("Not a valid bootstrap method " + bootstrapMethod + " for " + arguments);
+ }
+ List<Object> serializedArguments = new ArrayList<Object>(arguments.size());
+ for (Object anArgument : arguments) {
+ if (anArgument instanceof TypeDescription) {
+ anArgument = Type.getType(((TypeDescription) anArgument).getDescriptor());
+ } else if (anArgument instanceof JavaConstant) {
+ anArgument = ((JavaConstant) anArgument).asConstantPoolValue();
+ }
+ serializedArguments.add(anArgument);
+ }
+ return new WithImplicitTarget(bootstrapMethod,
+ serializedArguments,
+ new InvocationProvider.Default(),
+ TerminationHandler.RETURNING,
+ Assigner.DEFAULT,
+ Assigner.Typing.STATIC);
+ }
+
+ /**
+ * Requires the bootstrap method to bootstrap a method that takes the specified {@code boolean} arguments
+ * as its next parameters.
+ *
+ * @param value The arguments to pass to the bootstrapped method.
+ * @return This invoke dynamic implementation where the bootstrapped method is passed the specified arguments.
+ */
+ public InvokeDynamic withBooleanValue(boolean... value) {
+ List<InvocationProvider.ArgumentProvider> argumentProviders = new ArrayList<InvocationProvider.ArgumentProvider>(value.length);
+ for (boolean aValue : value) {
+ argumentProviders.add(new InvocationProvider.ArgumentProvider.ForBooleanConstant(aValue));
+ }
+ return new InvokeDynamic(bootstrapMethod,
+ handleArguments,
+ invocationProvider.appendArguments(argumentProviders),
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ /**
+ * Requires the bootstrap method to bootstrap a method that takes the specified {@code byte} arguments
+ * as its next parameters.
+ *
+ * @param value The arguments to pass to the bootstrapped method.
+ * @return This invoke dynamic implementation where the bootstrapped method is passed the specified arguments.
+ */
+ public InvokeDynamic withByteValue(byte... value) {
+ List<InvocationProvider.ArgumentProvider> argumentProviders = new ArrayList<InvocationProvider.ArgumentProvider>(value.length);
+ for (byte aValue : value) {
+ argumentProviders.add(new InvocationProvider.ArgumentProvider.ForByteConstant(aValue));
+ }
+ return new InvokeDynamic(bootstrapMethod,
+ handleArguments,
+ invocationProvider.appendArguments(argumentProviders),
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ /**
+ * Requires the bootstrap method to bootstrap a method that takes the specified {@code short} arguments
+ * as its next parameters.
+ *
+ * @param value The arguments to pass to the bootstrapped method.
+ * @return This invoke dynamic implementation where the bootstrapped method is passed the specified arguments.
+ */
+ public InvokeDynamic withShortValue(short... value) {
+ List<InvocationProvider.ArgumentProvider> argumentProviders = new ArrayList<InvocationProvider.ArgumentProvider>(value.length);
+ for (short aValue : value) {
+ argumentProviders.add(new InvocationProvider.ArgumentProvider.ForShortConstant(aValue));
+ }
+ return new InvokeDynamic(bootstrapMethod,
+ handleArguments,
+ invocationProvider.appendArguments(argumentProviders),
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ /**
+ * Requires the bootstrap method to bootstrap a method that takes the specified {@code char} arguments
+ * as its next parameters.
+ *
+ * @param value The arguments to pass to the bootstrapped method.
+ * @return This invoke dynamic implementation where the bootstrapped method is passed the specified arguments.
+ */
+ public InvokeDynamic withCharacterValue(char... value) {
+ List<InvocationProvider.ArgumentProvider> argumentProviders = new ArrayList<InvocationProvider.ArgumentProvider>(value.length);
+ for (char aValue : value) {
+ argumentProviders.add(new InvocationProvider.ArgumentProvider.ForCharacterConstant(aValue));
+ }
+ return new InvokeDynamic(bootstrapMethod,
+ handleArguments,
+ invocationProvider.appendArguments(argumentProviders),
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ /**
+ * Requires the bootstrap method to bootstrap a method that takes the specified {@code int} arguments
+ * as its next parameters.
+ *
+ * @param value The arguments to pass to the bootstrapped method.
+ * @return This invoke dynamic implementation where the bootstrapped method is passed the specified arguments.
+ */
+ public InvokeDynamic withIntegerValue(int... value) {
+ List<InvocationProvider.ArgumentProvider> argumentProviders = new ArrayList<InvocationProvider.ArgumentProvider>(value.length);
+ for (int aValue : value) {
+ argumentProviders.add(new InvocationProvider.ArgumentProvider.ForIntegerConstant(aValue));
+ }
+ return new InvokeDynamic(bootstrapMethod,
+ handleArguments,
+ invocationProvider.appendArguments(argumentProviders),
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ /**
+ * Requires the bootstrap method to bootstrap a method that takes the specified {@code long} arguments
+ * as its next parameters.
+ *
+ * @param value The arguments to pass to the bootstrapped method.
+ * @return This invoke dynamic implementation where the bootstrapped method is passed the specified arguments.
+ */
+ public InvokeDynamic withLongValue(long... value) {
+ List<InvocationProvider.ArgumentProvider> argumentProviders = new ArrayList<InvocationProvider.ArgumentProvider>(value.length);
+ for (long aValue : value) {
+ argumentProviders.add(new InvocationProvider.ArgumentProvider.ForLongConstant(aValue));
+ }
+ return new InvokeDynamic(bootstrapMethod,
+ handleArguments,
+ invocationProvider.appendArguments(argumentProviders),
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ /**
+ * Requires the bootstrap method to bootstrap a method that takes the specified {@code float} arguments
+ * as its next parameters.
+ *
+ * @param value The arguments to pass to the bootstrapped method.
+ * @return This invoke dynamic implementation where the bootstrapped method is passed the specified arguments.
+ */
+ public InvokeDynamic withFloatValue(float... value) {
+ List<InvocationProvider.ArgumentProvider> argumentProviders = new ArrayList<InvocationProvider.ArgumentProvider>(value.length);
+ for (float aValue : value) {
+ argumentProviders.add(new InvocationProvider.ArgumentProvider.ForFloatConstant(aValue));
+ }
+ return new InvokeDynamic(bootstrapMethod,
+ handleArguments,
+ invocationProvider.appendArguments(argumentProviders),
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ /**
+ * Requires the bootstrap method to bootstrap a method that takes the specified {@code double} arguments
+ * as its next parameters.
+ *
+ * @param value The arguments to pass to the bootstrapped method.
+ * @return This invoke dynamic implementation where the bootstrapped method is passed the specified arguments.
+ */
+ public InvokeDynamic withDoubleValue(double... value) {
+ List<InvocationProvider.ArgumentProvider> argumentProviders = new ArrayList<InvocationProvider.ArgumentProvider>(value.length);
+ for (double aValue : value) {
+ argumentProviders.add(new InvocationProvider.ArgumentProvider.ForDoubleConstant(aValue));
+ }
+ return new InvokeDynamic(bootstrapMethod,
+ handleArguments,
+ invocationProvider.appendArguments(argumentProviders),
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ /**
+ * <p>
+ * Requires the bootstrap method to bootstrap a method that takes the specified arguments as its next parameters.
+ * Note that any primitive parameters are passed as their wrapper types. Furthermore, values that can be stored
+ * in the instrumented class's constant pool might be of different object identity when passed to the
+ * bootstrapped method or might not be visible to the the created class what later results in a runtime error.
+ * </p>
+ *
+ * @param value The arguments to pass to the bootstrapped method.
+ * @return This invoke dynamic implementation where the bootstrapped method is passed the specified arguments.
+ */
+ public InvokeDynamic withValue(Object... value) {
+ List<InvocationProvider.ArgumentProvider> argumentProviders = new ArrayList<InvocationProvider.ArgumentProvider>(value.length);
+ for (Object aValue : value) {
+ argumentProviders.add(InvocationProvider.ArgumentProvider.ConstantPoolWrapper.of(aValue));
+ }
+ return new InvokeDynamic(bootstrapMethod,
+ handleArguments,
+ invocationProvider.appendArguments(argumentProviders),
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ /**
+ * <p>
+ * Requires the bootstrap method to bootstrap a method that takes the specified argument as its next parameter while
+ * allowing to specify the value to be of a different type than the actual instance type.
+ * </p>
+ *
+ * @param value The arguments to pass to the bootstrapped method.
+ * @return This invoke dynamic implementation where the bootstrapped method is passed the specified arguments.
+ */
+ public WithImplicitType withReference(Object value) {
+ return new WithImplicitType.OfInstance(bootstrapMethod,
+ handleArguments,
+ invocationProvider,
+ terminationHandler,
+ assigner,
+ typing,
+ value);
+ }
+
+ /**
+ * Requires the bootstrap method to bootstrap a method that takes the specified arguments as its next parameters.
+ * Note that any primitive parameters are passed as their wrapper types. Any value that is passed to the
+ * bootstrapped method is guaranteed to be of the same object identity.
+ *
+ * @param value The arguments to pass to the bootstrapped method.
+ * @return This invoke dynamic implementation where the bootstrapped method is passed the specified arguments.
+ */
+ public InvokeDynamic withReference(Object... value) {
+ List<InvocationProvider.ArgumentProvider> argumentProviders = new ArrayList<InvocationProvider.ArgumentProvider>(value.length);
+ for (Object aValue : value) {
+ argumentProviders.add(InvocationProvider.ArgumentProvider.ForInstance.of(aValue));
+ }
+ return new InvokeDynamic(bootstrapMethod,
+ handleArguments,
+ invocationProvider.appendArguments(argumentProviders),
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ /**
+ * Hands the provided types to the dynamically bound method. The type is stored in the generated class's
+ * constant pool and is loaded at invocation time. For this to be possible, the created class's
+ * class loader must be able to see the provided type.
+ *
+ * @param typeDescription The classes to provide to the bound method as an argument.
+ * @return This invoke dynamic implementation where the bootstrapped method is passed the specified type.
+ */
+ public InvokeDynamic withType(TypeDescription... typeDescription) {
+ List<InvocationProvider.ArgumentProvider> argumentProviders = new ArrayList<InvocationProvider.ArgumentProvider>(typeDescription.length);
+ for (TypeDescription aTypeDescription : typeDescription) {
+ argumentProviders.add(new InvocationProvider.ArgumentProvider.ForClassConstant(aTypeDescription));
+ }
+ return new InvokeDynamic(bootstrapMethod,
+ handleArguments,
+ invocationProvider.appendArguments(argumentProviders),
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ /**
+ * Hands the provided enumerations to the dynamically bound method. The enumeration values are read from
+ * the enumeration class on demand. For this to be possible, the created class's class loader must be
+ * able to see the enumeration type.
+ *
+ * @param enumerationDescription The enumeration values to provide to the bound method as an argument.
+ * @return This invoke dynamic implementation where the bootstrapped method is passed the specified enumerations.
+ */
+ public InvokeDynamic withEnumeration(EnumerationDescription... enumerationDescription) {
+ List<InvocationProvider.ArgumentProvider> argumentProviders = new ArrayList<InvocationProvider.ArgumentProvider>(enumerationDescription.length);
+ for (EnumerationDescription anEnumerationDescription : enumerationDescription) {
+ argumentProviders.add(new InvocationProvider.ArgumentProvider.ForEnumerationValue(anEnumerationDescription));
+ }
+ return new InvokeDynamic(bootstrapMethod,
+ handleArguments,
+ invocationProvider.appendArguments(argumentProviders),
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ /**
+ * Hands the provided Java instance to the dynamically bound method. The instance is stored in the generated class's
+ * constant pool and is loaded at invocation time. For this to be possible, the created class's class loader must
+ * be able to create the provided Java instance.
+ *
+ * @param javaConstant The Java instance to provide to the bound method as an argument.
+ * @return This invoke dynamic implementation where the bootstrapped method is passed the specified Java instance.
+ */
+ public InvokeDynamic withInstance(JavaConstant... javaConstant) {
+ List<InvocationProvider.ArgumentProvider> argumentProviders = new ArrayList<InvocationProvider.ArgumentProvider>(javaConstant.length);
+ for (JavaConstant aJavaConstant : javaConstant) {
+ argumentProviders.add(new InvocationProvider.ArgumentProvider.ForJavaConstant(aJavaConstant));
+ }
+ return new InvokeDynamic(bootstrapMethod,
+ handleArguments,
+ invocationProvider.appendArguments(argumentProviders),
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ /**
+ * Passes {@code null} values of the given types to the bootstrapped method.
+ *
+ * @param type The type that the {@code null} values should represent.
+ * @return This invoke dynamic implementation where the bootstrapped method is passed the specified arguments.
+ */
+ public InvokeDynamic withNullValue(Class<?>... type) {
+ return withNullValue(new TypeList.ForLoadedTypes(type).toArray(new TypeDescription[type.length]));
+ }
+
+ /**
+ * Passes {@code null} values of the given types to the bootstrapped method.
+ *
+ * @param typeDescription The type that the {@code null} values should represent.
+ * @return This invoke dynamic implementation where the bootstrapped method is passed the specified arguments.
+ */
+ public InvokeDynamic withNullValue(TypeDescription... typeDescription) {
+ List<InvocationProvider.ArgumentProvider> argumentProviders = new ArrayList<InvocationProvider.ArgumentProvider>(typeDescription.length);
+ for (TypeDescription aTypeDescription : typeDescription) {
+ if (aTypeDescription.isPrimitive()) {
+ throw new IllegalArgumentException("Cannot assign null to primitive type: " + aTypeDescription);
+ }
+ argumentProviders.add(new InvocationProvider.ArgumentProvider.ForNullValue(aTypeDescription));
+ }
+ return new InvokeDynamic(bootstrapMethod,
+ handleArguments,
+ invocationProvider.appendArguments(argumentProviders),
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ /**
+ * Passes parameters of the instrumented method to the bootstrapped method.
+ *
+ * @param index The indices of the parameters that should be passed to the bootstrapped method.
+ * @return This invoke dynamic implementation where the bootstrapped method is passed the specified arguments.
+ */
+ public InvokeDynamic withArgument(int... index) {
+ List<InvocationProvider.ArgumentProvider> argumentProviders = new ArrayList<InvocationProvider.ArgumentProvider>(index.length);
+ for (int anIndex : index) {
+ if (anIndex < 0) {
+ throw new IllegalArgumentException("Method parameter indices cannot be negative: " + anIndex);
+ }
+ argumentProviders.add(new InvocationProvider.ArgumentProvider.ForMethodParameter(anIndex));
+ }
+ return new InvokeDynamic(bootstrapMethod,
+ handleArguments,
+ invocationProvider.appendArguments(argumentProviders),
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ /**
+ * Passes a parameter of the instrumented method to the bootstrapped method.
+ *
+ * @param index The index of the parameter that should be passed to the bootstrapped method.
+ * @return This invoke dynamic implementation where the bootstrapped method is passed the specified argument
+ * with its implicit type.
+ */
+ public WithImplicitType withArgument(int index) {
+ if (index < 0) {
+ throw new IllegalArgumentException("Method parameter indices cannot be negative: " + index);
+ }
+ return new WithImplicitType.OfArgument(bootstrapMethod,
+ handleArguments,
+ invocationProvider,
+ terminationHandler,
+ assigner,
+ typing,
+ index);
+ }
+
+ /**
+ * Passes references to {@code this} onto the operand stack where the instance is represented as
+ * the given types.
+ *
+ * @param type The types as which the {@code this} reference of the intercepted method should be masked.
+ * @return This implementation where {@code this} references are passed as the next arguments.
+ */
+ public InvokeDynamic withThis(Class<?>... type) {
+ return withThis(new TypeList.ForLoadedTypes(type).toArray(new TypeDescription[type.length]));
+ }
+
+ /**
+ * Passes references to {@code this} onto the operand stack where the instance is represented as
+ * the given types.
+ *
+ * @param typeDescription The types as which the {@code this} reference of the intercepted method should be masked.
+ * @return This implementation where {@code this} references are passed as the next arguments.
+ */
+ public InvokeDynamic withThis(TypeDescription... typeDescription) {
+ List<InvocationProvider.ArgumentProvider> argumentProviders = new ArrayList<InvocationProvider.ArgumentProvider>(typeDescription.length);
+ for (TypeDescription aTypeDescription : typeDescription) {
+ argumentProviders.add(new InvocationProvider.ArgumentProvider.ForThisInstance(aTypeDescription));
+ }
+ return new InvokeDynamic(bootstrapMethod,
+ handleArguments,
+ invocationProvider.appendArguments(argumentProviders),
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ /**
+ * Adds all method arguments to the the bootstrapped method.
+ *
+ * @return This invoke dynamic implementation with all parameters of the instrumented method added.
+ */
+ public InvokeDynamic withMethodArguments() {
+ return new InvokeDynamic(bootstrapMethod,
+ handleArguments,
+ invocationProvider.appendArgument(InvocationProvider.ArgumentProvider.ForInterceptedMethodParameters.INSTANCE),
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ /**
+ * Adds a potential {@code this} reference and all method arguments to the the bootstrapped method.
+ *
+ * @return This invoke dynamic implementation with a potential {@code this} reference and all
+ * parameters of the instrumented method added.
+ */
+ public InvokeDynamic withImplicitAndMethodArguments() {
+ return new InvokeDynamic(bootstrapMethod,
+ handleArguments,
+ invocationProvider.appendArgument(InvocationProvider.ArgumentProvider.ForInterceptedMethodInstanceAndParameters.INSTANCE),
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ /**
+ * Passes the values of the specified fields to the bootstrap method. Any of the specified fields must already
+ * exist for the instrumented type.
+ *
+ * @param name The names of the fields to be passed to the bootstrapped method.
+ * @return This invoke dynamic implementation where the bootstrapped method is passed the specified arguments.
+ */
+ public InvokeDynamic withField(String... name) {
+ return withField(FieldLocator.ForClassHierarchy.Factory.INSTANCE, name);
+ }
+
+ /**
+ * Passes the values of the specified fields to the bootstrap method. Any of the specified fields must already
+ * exist for the instrumented type.
+ *
+ * @param fieldLocatorFactory The field locator factory to use.
+ * @param name The names of the fields to be passed to the bootstrapped method.
+ * @return This invoke dynamic implementation where the bootstrapped method is passed the specified arguments.
+ */
+ public InvokeDynamic withField(FieldLocator.Factory fieldLocatorFactory, String... name) {
+ List<InvocationProvider.ArgumentProvider> argumentProviders = new ArrayList<InvocationProvider.ArgumentProvider>(name.length);
+ for (String aName : name) {
+ argumentProviders.add(new InvocationProvider.ArgumentProvider.ForField(aName, fieldLocatorFactory));
+ }
+ return new InvokeDynamic(bootstrapMethod,
+ handleArguments,
+ invocationProvider.appendArguments(argumentProviders),
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ /**
+ * Passes the values of the specified fields to the bootstrap method. Any of the specified fields must already
+ * exist for the instrumented type.
+ *
+ * @param name The names of the fields to be passed to the bootstrapped method.
+ * @return This invoke dynamic implementation where the bootstrapped method is passed the specified arguments.
+ */
+ public WithImplicitType withField(String name) {
+ return withField(name, FieldLocator.ForClassHierarchy.Factory.INSTANCE);
+ }
+
+ /**
+ * Passes the values of the specified fields to the bootstrap method. Any of the specified fields must already
+ * exist for the instrumented type.
+ *
+ * @param fieldLocatorFactory The field locator factory to use.
+ * @param name The names of the fields to be passed to the bootstrapped method.
+ * @return This invoke dynamic implementation where the bootstrapped method is passed the specified arguments.
+ */
+ public WithImplicitType withField(String name, FieldLocator.Factory fieldLocatorFactory) {
+ return new WithImplicitType.OfField(bootstrapMethod,
+ handleArguments,
+ invocationProvider,
+ terminationHandler,
+ assigner,
+ typing,
+ name,
+ fieldLocatorFactory);
+ }
+
+ /**
+ * Instructs this implementation to use the provided assigner and decides if the assigner should apply
+ * dynamic typing.
+ *
+ * @param assigner The assigner to use.
+ * @param typing {@code true} if the assigner should attempt dynamic typing.
+ * @return The invoke dynamic instruction where the given assigner and dynamic-typing directive are applied.
+ */
+ public Implementation.Composable withAssigner(Assigner assigner, Assigner.Typing typing) {
+ return new InvokeDynamic(bootstrapMethod,
+ handleArguments,
+ invocationProvider,
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ @Override
+ public Implementation andThen(Implementation implementation) {
+ return new Implementation.Compound(new InvokeDynamic(bootstrapMethod,
+ handleArguments,
+ invocationProvider,
+ TerminationHandler.DROPPING,
+ assigner,
+ typing),
+ implementation);
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return invocationProvider.prepare(instrumentedType);
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return new Appender(implementationTarget.getInstrumentedType());
+ }
+
+ /**
+ * Returns the invocation provider to be used for equals and hash code calculations.
+ *
+ * @return The invocation provider that represents this instance.
+ */
+ protected InvocationProvider getInvocationProvider() {
+ return invocationProvider;
+ }
+
+ /**
+ * An invocation provider is responsible for loading the arguments of the invoked method onto the operand
+ * stack and for creating the actual <i>invoke dynamic</i> instruction.
+ */
+ protected interface InvocationProvider {
+
+ /**
+ * Creates a target for the invocation.
+ *
+ * @param methodDescription The method that is being intercepted.
+ * @return The target for the invocation.
+ */
+ Target make(MethodDescription methodDescription);
+
+ /**
+ * Appends the given arguments to the invocation to be loaded onto the operand stack.
+ *
+ * @param argumentProviders The next arguments to be loaded onto the operand stack.
+ * @return An invocation provider for this target that loads the given arguments onto the operand stack.
+ */
+ InvocationProvider appendArguments(List<ArgumentProvider> argumentProviders);
+
+ /**
+ * Appends the given argument to the invocation to be loaded onto the operand stack.
+ *
+ * @param argumentProvider The next argument to be loaded onto the operand stack.
+ * @return An invocation provider for this target that loads the given arguments onto the operand stack.
+ */
+ InvocationProvider appendArgument(ArgumentProvider argumentProvider);
+
+ /**
+ * Returns a copy of this invocation provider that does not add any arguments.
+ *
+ * @return A copy of this invocation provider that does not add any arguments.
+ */
+ InvocationProvider withoutArguments();
+
+ /**
+ * Returns a copy of this invocation provider that applies the given name provider.
+ *
+ * @param nameProvider The name provider to be used.
+ * @return A copy of this invocation provider that applies the given name provider.
+ */
+ InvocationProvider withNameProvider(NameProvider nameProvider);
+
+ /**
+ * Returns a copy of this invocation provider that applies the given return type provider.
+ *
+ * @param returnTypeProvider The return type provider to be used.
+ * @return A copy of this invocation provider that applies the given return type provider.
+ */
+ InvocationProvider withReturnTypeProvider(ReturnTypeProvider returnTypeProvider);
+
+ /**
+ * Prepares the instrumented type.
+ *
+ * @param instrumentedType The instrumented type to prepare.
+ * @return The prepared instrumented type.
+ */
+ InstrumentedType prepare(InstrumentedType instrumentedType);
+
+ /**
+ * A target for a dynamic method invocation.
+ */
+ interface Target {
+
+ /**
+ * Resolves the target.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param assigner The assigner to be used.
+ * @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
+ * @return The resolved target.
+ */
+ Resolved resolve(TypeDescription instrumentedType, Assigner assigner, Assigner.Typing typing);
+
+ /**
+ * Represents a resolved {@link net.bytebuddy.implementation.InvokeDynamic.InvocationProvider.Target}.
+ */
+ interface Resolved {
+
+ /**
+ * Returns the stack manipulation that loads the arguments onto the operand stack.
+ *
+ * @return The stack manipulation that loads the arguments onto the operand stack.
+ */
+ StackManipulation getStackManipulation();
+
+ /**
+ * Returns the requested return type.
+ *
+ * @return The requested return type.
+ */
+ TypeDescription getReturnType();
+
+ /**
+ * Returns the internal name of the requested method.
+ *
+ * @return The internal name of the requested method.
+ */
+ String getInternalName();
+
+ /**
+ * Returns the types of the values on the operand stack.
+ *
+ * @return The types of the values on the operand stack.
+ */
+ List<TypeDescription> getParameterTypes();
+
+ /**
+ * A simple implementation of
+ * {@link net.bytebuddy.implementation.InvokeDynamic.InvocationProvider.Target.Resolved}.
+ */
+ @EqualsAndHashCode
+ class Simple implements Resolved {
+
+ /**
+ * The stack manipulation that loads the arguments onto the operand stack.
+ */
+ private final StackManipulation stackManipulation;
+
+ /**
+ * The internal name of the requested method.
+ */
+ private final String internalName;
+
+ /**
+ * The requested return type.
+ */
+ private final TypeDescription returnType;
+
+ /**
+ * The types of the values on the operand stack.
+ */
+ private final List<TypeDescription> parameterTypes;
+
+ /**
+ * Creates a new simple instance.
+ *
+ * @param stackManipulation The stack manipulation that loads the arguments onto the operand stack.
+ * @param internalName The internal name of the requested method.
+ * @param returnType The requested return type.
+ * @param parameterTypes The types of the values on the operand stack.
+ */
+ public Simple(StackManipulation stackManipulation,
+ String internalName,
+ TypeDescription returnType,
+ List<TypeDescription> parameterTypes) {
+ this.stackManipulation = stackManipulation;
+ this.internalName = internalName;
+ this.returnType = returnType;
+ this.parameterTypes = parameterTypes;
+ }
+
+ @Override
+ public StackManipulation getStackManipulation() {
+ return stackManipulation;
+ }
+
+ @Override
+ public TypeDescription getReturnType() {
+ return returnType;
+ }
+
+ @Override
+ public String getInternalName() {
+ return internalName;
+ }
+
+ @Override
+ public List<TypeDescription> getParameterTypes() {
+ return parameterTypes;
+ }
+ }
+ }
+
+ /**
+ * A target that requests to dynamically invoke a method to substitute for a given method.
+ */
+ @EqualsAndHashCode
+ class ForMethodDescription implements Target, Target.Resolved {
+
+ /**
+ * The method that is being substituted.
+ */
+ private final MethodDescription.InDefinedShape methodDescription;
+
+ /**
+ * Creates a new target for substituting a given method.
+ *
+ * @param methodDescription The method that is being substituted.
+ */
+ protected ForMethodDescription(MethodDescription.InDefinedShape methodDescription) {
+ this.methodDescription = methodDescription;
+ }
+
+ @Override
+ public Resolved resolve(TypeDescription instrumentedType, Assigner assigner, Assigner.Typing typing) {
+ return this;
+ }
+
+ @Override
+ public String getInternalName() {
+ return methodDescription.getInternalName();
+ }
+
+ @Override
+ public TypeDescription getReturnType() {
+ return methodDescription.getReturnType().asErasure();
+ }
+
+ @Override
+ public StackManipulation getStackManipulation() {
+ return MethodVariableAccess.allArgumentsOf(methodDescription).prependThisReference();
+ }
+
+ @Override
+ public List<TypeDescription> getParameterTypes() {
+ return methodDescription.isStatic()
+ ? methodDescription.getParameters().asTypeList().asErasures()
+ : CompoundList.of(methodDescription.getDeclaringType().asErasure(), methodDescription.getParameters().asTypeList().asErasures());
+ }
+ }
+ }
+
+ /**
+ * An argument provider is responsible for loading arguments to a bootstrapped method onto the operand
+ * stack and providing the types of these arguments.
+ */
+ interface ArgumentProvider {
+
+ /**
+ * Resolves an argument provider.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param instrumentedMethod The instrumented method.
+ * @param assigner The assigner to be used.
+ * @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
+ * @return A resolved version of this argument provider.
+ */
+ Resolved resolve(TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ Assigner assigner,
+ Assigner.Typing typing);
+
+ /**
+ * Prepares the instrumented type.
+ *
+ * @param instrumentedType The instrumented type.
+ * @return The prepared instrumented type.
+ */
+ InstrumentedType prepare(InstrumentedType instrumentedType);
+
+ /**
+ * An argument provider that loads a reference to the intercepted instance and all arguments of
+ * the intercepted method.
+ */
+ enum ForInterceptedMethodInstanceAndParameters implements ArgumentProvider {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Resolved resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Assigner.Typing typing) {
+ return new Resolved.Simple(MethodVariableAccess.allArgumentsOf(instrumentedMethod).prependThisReference(),
+ instrumentedMethod.isStatic()
+ ? instrumentedMethod.getParameters().asTypeList().asErasures()
+ : CompoundList.of(instrumentedMethod.getDeclaringType().asErasure(), instrumentedMethod.getParameters().asTypeList().asErasures()));
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+ }
+
+ /**
+ * An argument provider that loads all arguments of the intercepted method.
+ */
+ enum ForInterceptedMethodParameters implements ArgumentProvider {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Resolved resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Assigner.Typing typing) {
+ return new Resolved.Simple(MethodVariableAccess.allArgumentsOf(instrumentedMethod),
+ instrumentedMethod.getParameters().asTypeList().asErasures());
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+ }
+
+ /**
+ * Represents wrapper types and types that could be stored in a class's constant pool as such
+ * constant pool values.
+ */
+ enum ConstantPoolWrapper {
+
+ /**
+ * Stores a {@link java.lang.Boolean} as a {@code boolean} and wraps it on load.
+ */
+ BOOLEAN(boolean.class, Boolean.class) {
+ @Override
+ protected ArgumentProvider make(Object value) {
+ return new WrappingArgumentProvider(IntegerConstant.forValue((Boolean) value));
+ }
+ },
+
+ /**
+ * Stores a {@link java.lang.Byte} as a {@code byte} and wraps it on load.
+ */
+ BYTE(byte.class, Byte.class) {
+ @Override
+ protected ArgumentProvider make(Object value) {
+ return new WrappingArgumentProvider(IntegerConstant.forValue((Byte) value));
+ }
+ },
+
+ /**
+ * Stores a {@link java.lang.Short} as a {@code short} and wraps it on load.
+ */
+ SHORT(short.class, Short.class) {
+ @Override
+ protected ArgumentProvider make(Object value) {
+ return new WrappingArgumentProvider(IntegerConstant.forValue((Short) value));
+ }
+ },
+
+ /**
+ * Stores a {@link java.lang.Character} as a {@code char} and wraps it on load.
+ */
+ CHARACTER(char.class, Character.class) {
+ @Override
+ protected ArgumentProvider make(Object value) {
+ return new WrappingArgumentProvider(IntegerConstant.forValue((Character) value));
+ }
+ },
+
+ /**
+ * Stores a {@link java.lang.Integer} as a {@code int} and wraps it on load.
+ */
+ INTEGER(int.class, Integer.class) {
+ @Override
+ protected ArgumentProvider make(Object value) {
+ return new WrappingArgumentProvider(IntegerConstant.forValue((Integer) value));
+ }
+ },
+
+ /**
+ * Stores a {@link java.lang.Long} as a {@code long} and wraps it on load.
+ */
+ LONG(long.class, Long.class) {
+ @Override
+ protected ArgumentProvider make(Object value) {
+ return new WrappingArgumentProvider(LongConstant.forValue((Long) value));
+ }
+ },
+
+ /**
+ * Stores a {@link java.lang.Float} as a {@code float} and wraps it on load.
+ */
+ FLOAT(float.class, Float.class) {
+ @Override
+ protected ArgumentProvider make(Object value) {
+ return new WrappingArgumentProvider(FloatConstant.forValue((Float) value));
+ }
+ },
+
+ /**
+ * Stores a {@link java.lang.Double} as a {@code double} and wraps it on load.
+ */
+ DOUBLE(double.class, Double.class) {
+ @Override
+ protected ArgumentProvider make(Object value) {
+ return new WrappingArgumentProvider(DoubleConstant.forValue((Double) value));
+ }
+ };
+
+ /**
+ * The primitive type that can be stored on the constant pool.
+ */
+ private final TypeDescription primitiveType;
+
+ /**
+ * The wrapper type that is to be represented.
+ */
+ private final TypeDescription wrapperType;
+
+ /**
+ * Creates a new wrapper delegate for a primitive type.
+ *
+ * @param primitiveType The primitive type that can be stored on the constant pool.
+ * @param wrapperType The wrapper type that is to be represented.
+ */
+ ConstantPoolWrapper(Class<?> primitiveType, Class<?> wrapperType) {
+ this.primitiveType = new TypeDescription.ForLoadedType(primitiveType);
+ this.wrapperType = new TypeDescription.ForLoadedType(wrapperType);
+ }
+
+ /**
+ * Represents the given value by a constant pool value or as a field if this is not possible.
+ *
+ * @param value The value to provide to the bootstrapped method.
+ * @return An argument provider for this value.
+ */
+ public static ArgumentProvider of(Object value) {
+ if (value instanceof Boolean) {
+ return BOOLEAN.make(value);
+ } else if (value instanceof Byte) {
+ return BYTE.make(value);
+ } else if (value instanceof Short) {
+ return SHORT.make(value);
+ } else if (value instanceof Character) {
+ return CHARACTER.make(value);
+ } else if (value instanceof Integer) {
+ return INTEGER.make(value);
+ } else if (value instanceof Long) {
+ return LONG.make(value);
+ } else if (value instanceof Float) {
+ return FLOAT.make(value);
+ } else if (value instanceof Double) {
+ return DOUBLE.make(value);
+ } else if (value instanceof String) {
+ return new ForStringConstant((String) value);
+ } else if (value instanceof Class<?>) {
+ return new ForClassConstant(new TypeDescription.ForLoadedType((Class<?>) value));
+ } else if (value instanceof Enum<?>) {
+ return new ForEnumerationValue(new EnumerationDescription.ForLoadedEnumeration((Enum<?>) value));
+ } else if (JavaType.METHOD_HANDLE.getTypeStub().isInstance(value)) {
+ return new ForJavaConstant(JavaConstant.MethodHandle.ofLoaded(value));
+ } else if (JavaType.METHOD_TYPE.getTypeStub().isInstance(value)) {
+ return new ForJavaConstant(JavaConstant.MethodType.ofLoaded(value));
+ } else {
+ return ForInstance.of(value);
+ }
+ }
+
+ /**
+ * Creates an argument provider for a given primitive value.
+ *
+ * @param value The wrapper-type value to provide to the bootstrapped method.
+ * @return An argument provider for this value.
+ */
+ protected abstract ArgumentProvider make(Object value);
+
+ /**
+ * An argument provider that loads a primitive value from the constant pool and wraps it.
+ */
+ protected class WrappingArgumentProvider implements ArgumentProvider {
+
+ /**
+ * The stack manipulation that represents the loading of the primitive value.
+ */
+ private final StackManipulation stackManipulation;
+
+ /**
+ * Creates a new wrapping argument provider.
+ *
+ * @param stackManipulation The stack manipulation that represents the loading of the
+ * primitive value.
+ */
+ protected WrappingArgumentProvider(StackManipulation stackManipulation) {
+ this.stackManipulation = stackManipulation;
+ }
+
+ @Override
+ public Resolved resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Assigner.Typing typing) {
+ return new Resolved.Simple(new StackManipulation.Compound(stackManipulation,
+ assigner.assign(primitiveType.asGenericType(), wrapperType.asGenericType(), typing)), wrapperType);
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && ConstantPoolWrapper.this.equals(((WrappingArgumentProvider) other).getOuter())
+ && stackManipulation.equals(((WrappingArgumentProvider) other).stackManipulation);
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private ConstantPoolWrapper getOuter() {
+ return ConstantPoolWrapper.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return stackManipulation.hashCode() + 31 * ConstantPoolWrapper.this.hashCode();
+ }
+ }
+ }
+
+ /**
+ * A resolved {@link net.bytebuddy.implementation.InvokeDynamic.InvocationProvider.ArgumentProvider}.
+ */
+ interface Resolved {
+
+ /**
+ * Returns a stack manipulation that loads the arguments onto the operand stack.
+ *
+ * @return A stack manipulation that loads the arguments onto the operand stack.
+ */
+ StackManipulation getLoadInstruction();
+
+ /**
+ * Returns a list of all types of the arguments that were loaded onto the operand stack.
+ *
+ * @return A list of all types of the arguments that were loaded onto the operand stack.
+ */
+ List<TypeDescription> getLoadedTypes();
+
+ /**
+ * A simple implementation of a resolved argument provider.
+ */
+ @EqualsAndHashCode
+ class Simple implements Resolved {
+
+ /**
+ * A stack manipulation that loads the arguments onto the operand stack.
+ */
+ private final StackManipulation stackManipulation;
+
+ /**
+ * A list of all types of the arguments that were loaded onto the operand stack.
+ */
+ private final List<TypeDescription> loadedTypes;
+
+ /**
+ * Creates a simple resolved argument provider.
+ *
+ * @param stackManipulation A stack manipulation that loads the argument onto the operand stack.
+ * @param loadedType The type of the arguments that is loaded onto the operand stack.
+ */
+ public Simple(StackManipulation stackManipulation, TypeDescription loadedType) {
+ this(stackManipulation, Collections.singletonList(loadedType));
+ }
+
+ /**
+ * Creates a simple resolved argument provider.
+ *
+ * @param stackManipulation A stack manipulation that loads the arguments onto the operand stack.
+ * @param loadedTypes A list of all types of the arguments that were loaded onto the
+ * operand stack.
+ */
+ public Simple(StackManipulation stackManipulation, List<TypeDescription> loadedTypes) {
+ this.stackManipulation = stackManipulation;
+ this.loadedTypes = loadedTypes;
+ }
+
+ @Override
+ public StackManipulation getLoadInstruction() {
+ return stackManipulation;
+ }
+
+ @Override
+ public List<TypeDescription> getLoadedTypes() {
+ return loadedTypes;
+ }
+ }
+ }
+
+ /**
+ * An argument provider that loads the intercepted instance.
+ */
+ @EqualsAndHashCode
+ class ForThisInstance implements ArgumentProvider {
+
+ /**
+ * The type as which the intercepted instance should be loaded onto the operand stack.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new argument provider for the instance of the instrumented type.
+ *
+ * @param typeDescription The type as which the instrumented type should be loaded onto the operand stack.
+ */
+ protected ForThisInstance(TypeDescription typeDescription) {
+ this.typeDescription = typeDescription;
+ }
+
+ @Override
+ public Resolved resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Assigner.Typing typing) {
+ if (instrumentedMethod.isStatic()) {
+ throw new IllegalStateException("Cannot get this instance from static method: " + instrumentedMethod);
+ } else if (!instrumentedType.isAssignableTo(typeDescription)) {
+ throw new IllegalStateException(instrumentedType + " is not assignable to " + instrumentedType);
+ }
+ return new Resolved.Simple(MethodVariableAccess.loadThis(), typeDescription);
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+ }
+
+ /**
+ * An argument provider for a value that is stored in a randomly named static field.
+ */
+ @EqualsAndHashCode(exclude = "name")
+ class ForInstance implements ArgumentProvider {
+
+ /**
+ * The prefix of any field generated by this argument provider.
+ */
+ private static final String FIELD_PREFIX = "invokeDynamic";
+
+ /**
+ * The value that is stored in the static field.
+ */
+ private final Object value;
+
+ /**
+ * The type of the static field.
+ */
+ private final TypeDescription fieldType;
+
+ /**
+ * The name of the field.
+ */
+ private final String name;
+
+ /**
+ * Creates a new argument provider that stores the given value in a static field.
+ *
+ * @param value The value that is to be provided to the bootstrapped method.
+ * @param fieldType The type of the field which is also provided to the bootstrap method.
+ */
+ protected ForInstance(Object value, TypeDescription fieldType) {
+ this.value = value;
+ this.fieldType = fieldType;
+ name = String.format("%s$%s", FIELD_PREFIX, RandomString.make());
+ }
+
+ /**
+ * Creates a new argument provider that stores the given value in a static field of the instance type.
+ *
+ * @param value The value that is to be provided to the bootstrapped method.
+ * @return A corresponding argument provider.
+ */
+ protected static ArgumentProvider of(Object value) {
+ return new ForInstance(value, new TypeDescription.ForLoadedType(value.getClass()));
+ }
+
+ @Override
+ public Resolved resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Assigner.Typing typing) {
+ FieldDescription fieldDescription = instrumentedType.getDeclaredFields().filter(named(name)).getOnly();
+ StackManipulation stackManipulation = assigner.assign(fieldDescription.getType(), fieldType.asGenericType(), typing);
+ if (!stackManipulation.isValid()) {
+ throw new IllegalStateException("Cannot assign " + fieldDescription + " to " + fieldType);
+ }
+ return new Resolved.Simple(new StackManipulation.Compound(FieldAccess.forField(fieldDescription).read(),
+ stackManipulation), fieldDescription.getType().asErasure());
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType
+ .withField(new FieldDescription.Token(name, Opcodes.ACC_SYNTHETIC | Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, fieldType.asGenericType()))
+ .withInitializer(new LoadedTypeInitializer.ForStaticField(name, value));
+ }
+ }
+
+ /**
+ * Provides an argument from an existing field.
+ */
+ @EqualsAndHashCode
+ class ForField implements ArgumentProvider {
+
+ /**
+ * The name of the field.
+ */
+ protected final String fieldName;
+
+ /**
+ * The field locator factory to use.
+ */
+ protected final FieldLocator.Factory fieldLocatorFactory;
+
+ /**
+ * Creates a new argument provider that loads the value of an existing field.
+ *
+ * @param fieldName The name of the field.
+ * @param fieldLocatorFactory The field locator factory to use.
+ */
+ protected ForField(String fieldName, FieldLocator.Factory fieldLocatorFactory) {
+ this.fieldName = fieldName;
+ this.fieldLocatorFactory = fieldLocatorFactory;
+ }
+
+ @Override
+ public Resolved resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Assigner.Typing typing) {
+ FieldLocator.Resolution resolution = fieldLocatorFactory.make(instrumentedType).locate(fieldName);
+ if (!resolution.isResolved()) {
+ throw new IllegalStateException("Cannot find a field " + fieldName + " for " + instrumentedType);
+ } else if (!resolution.getField().isStatic() && instrumentedMethod.isStatic()) {
+ throw new IllegalStateException("Cannot access non-static " + resolution.getField() + " from " + instrumentedMethod);
+ }
+ return doResolve(new StackManipulation.Compound(resolution.getField().isStatic()
+ ? StackManipulation.Trivial.INSTANCE
+ : MethodVariableAccess.loadThis(), FieldAccess.forField(resolution.getField()).read()),
+ resolution.getField().getType(),
+ assigner,
+ typing);
+ }
+
+ /**
+ * Resolves this argument provider.
+ *
+ * @param access The stack manipulation for accessing the argument value.
+ * @param type The type of the loaded value.
+ * @param assigner The assigner to use.
+ * @param typing The typing required.
+ * @return A resolved version of this arguments provider.
+ */
+ protected Resolved doResolve(StackManipulation access, TypeDescription.Generic type, Assigner assigner, Assigner.Typing typing) {
+ return new Resolved.Simple(access, type.asErasure());
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ /**
+ * An argument provider for a field value with an explicit type.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ protected static class WithExplicitType extends ForField {
+
+ /**
+ * The explicit type.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates an argument provider for a field value with an explicit type.
+ *
+ * @param fieldName The name of the field.
+ * @param fieldLocatorFactory The field locator factory to use.
+ * @param typeDescription The explicit type.
+ */
+ protected WithExplicitType(String fieldName, FieldLocator.Factory fieldLocatorFactory, TypeDescription typeDescription) {
+ super(fieldName, fieldLocatorFactory);
+ this.typeDescription = typeDescription;
+ }
+
+ @Override
+ protected Resolved doResolve(StackManipulation access, TypeDescription.Generic typeDescription, Assigner assigner, Assigner.Typing typing) {
+ StackManipulation stackManipulation = assigner.assign(typeDescription, this.typeDescription.asGenericType(), typing);
+ if (!stackManipulation.isValid()) {
+ throw new IllegalStateException("Cannot assign " + typeDescription + " to " + this.typeDescription);
+ }
+ return new Resolved.Simple(new StackManipulation.Compound(access, stackManipulation), this.typeDescription);
+ }
+ }
+ }
+
+ /**
+ * An argument provider that loads an argument of the intercepted method.
+ */
+ @EqualsAndHashCode
+ class ForMethodParameter implements ArgumentProvider {
+
+ /**
+ * The index of the parameter.
+ */
+ protected final int index;
+
+ /**
+ * Creates an argument provider for an argument of the intercepted method.
+ *
+ * @param index The index of the parameter.
+ */
+ protected ForMethodParameter(int index) {
+ this.index = index;
+ }
+
+ @Override
+ public Resolved resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Assigner.Typing typing) {
+ ParameterList<?> parameters = instrumentedMethod.getParameters();
+ if (index >= parameters.size()) {
+ throw new IllegalStateException("No parameter " + index + " for " + instrumentedMethod);
+ }
+ return doResolve(MethodVariableAccess.load(parameters.get(index)), parameters.get(index).getType(), assigner, typing);
+ }
+
+ /**
+ * Resolves this argument provider.
+ *
+ * @param access The stack manipulation for accessing the argument value.
+ * @param type The type of the loaded value.
+ * @param assigner The assigner to use.
+ * @param typing The typing required.
+ * @return A resolved version of this arguments provider.
+ */
+ protected Resolved doResolve(StackManipulation access, TypeDescription.Generic type, Assigner assigner, Assigner.Typing typing) {
+ return new Resolved.Simple(access, type.asErasure());
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ /**
+ * An argument provider for a method parameter with an explicit type.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ protected static class WithExplicitType extends ForMethodParameter {
+
+ /**
+ * The explicit type.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new argument provider for a method parameter with an explicit type.
+ *
+ * @param index The index of the parameter.
+ * @param typeDescription The explicit type.
+ */
+ protected WithExplicitType(int index, TypeDescription typeDescription) {
+ super(index);
+ this.typeDescription = typeDescription;
+ }
+
+ @Override
+ protected Resolved doResolve(StackManipulation access, TypeDescription.Generic type, Assigner assigner, Assigner.Typing typing) {
+ StackManipulation stackManipulation = assigner.assign(type, typeDescription.asGenericType(), typing);
+ if (!stackManipulation.isValid()) {
+ throw new IllegalStateException("Cannot assign " + type + " to " + typeDescription);
+ }
+ return new Resolved.Simple(new StackManipulation.Compound(access, stackManipulation), typeDescription);
+ }
+ }
+ }
+
+ /**
+ * An argument provider for a {@code boolean} value.
+ */
+ @EqualsAndHashCode
+ class ForBooleanConstant implements ArgumentProvider {
+
+ /**
+ * The represented {@code boolean} value.
+ */
+ private final boolean value;
+
+ /**
+ * Creates a new argument provider for a {@code boolean} value.
+ *
+ * @param value The represented {@code boolean} value.
+ */
+ protected ForBooleanConstant(boolean value) {
+ this.value = value;
+ }
+
+ @Override
+ public Resolved resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Assigner.Typing typing) {
+ return new Resolved.Simple(IntegerConstant.forValue(value), new TypeDescription.ForLoadedType(boolean.class));
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+ }
+
+ /**
+ * An argument provider for a {@code byte} value.
+ */
+ @EqualsAndHashCode
+ class ForByteConstant implements ArgumentProvider {
+
+ /**
+ * The represented {@code byte} value.
+ */
+ private final byte value;
+
+ /**
+ * Creates a new argument provider for a {@code byte} value.
+ *
+ * @param value The represented {@code byte} value.
+ */
+ protected ForByteConstant(byte value) {
+ this.value = value;
+ }
+
+ @Override
+ public Resolved resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Assigner.Typing typing) {
+ return new Resolved.Simple(IntegerConstant.forValue(value), new TypeDescription.ForLoadedType(byte.class));
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+ }
+
+ /**
+ * An argument provider for a {@code short} value.
+ */
+ @EqualsAndHashCode
+ class ForShortConstant implements ArgumentProvider {
+
+ /**
+ * The represented {@code short} value.
+ */
+ private final short value;
+
+ /**
+ * Creates a new argument provider for a {@code short} value.
+ *
+ * @param value The represented {@code short} value.
+ */
+ protected ForShortConstant(short value) {
+ this.value = value;
+ }
+
+ @Override
+ public Resolved resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Assigner.Typing typing) {
+ return new Resolved.Simple(IntegerConstant.forValue(value), new TypeDescription.ForLoadedType(short.class));
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+ }
+
+ /**
+ * An argument provider for a {@code char} value.
+ */
+ @EqualsAndHashCode
+ class ForCharacterConstant implements ArgumentProvider {
+
+ /**
+ * The represented {@code char} value.
+ */
+ private final char value;
+
+ /**
+ * Creates a new argument provider for a {@code char} value.
+ *
+ * @param value The represented {@code char} value.
+ */
+ protected ForCharacterConstant(char value) {
+ this.value = value;
+ }
+
+ @Override
+ public Resolved resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Assigner.Typing typing) {
+ return new Resolved.Simple(IntegerConstant.forValue(value), new TypeDescription.ForLoadedType(char.class));
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+ }
+
+ /**
+ * An argument provider for a {@code int} value.
+ */
+ @EqualsAndHashCode
+ class ForIntegerConstant implements ArgumentProvider {
+
+ /**
+ * The represented {@code int} value.
+ */
+ private final int value;
+
+ /**
+ * Creates a new argument provider for a {@code int} value.
+ *
+ * @param value The represented {@code int} value.
+ */
+ protected ForIntegerConstant(int value) {
+ this.value = value;
+ }
+
+ @Override
+ public Resolved resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Assigner.Typing typing) {
+ return new Resolved.Simple(IntegerConstant.forValue(value), new TypeDescription.ForLoadedType(int.class));
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+ }
+
+ /**
+ * An argument provider for a {@code long} value.
+ */
+ @EqualsAndHashCode
+ class ForLongConstant implements ArgumentProvider {
+
+ /**
+ * The represented {@code long} value.
+ */
+ private final long value;
+
+ /**
+ * Creates a new argument provider for a {@code long} value.
+ *
+ * @param value The represented {@code long} value.
+ */
+ protected ForLongConstant(long value) {
+ this.value = value;
+ }
+
+ @Override
+ public Resolved resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Assigner.Typing typing) {
+ return new Resolved.Simple(LongConstant.forValue(value), new TypeDescription.ForLoadedType(long.class));
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+ }
+
+ /**
+ * An argument provider for a {@code float} value.
+ */
+ @EqualsAndHashCode
+ class ForFloatConstant implements ArgumentProvider {
+
+ /**
+ * The represented {@code float} value.
+ */
+ private final float value;
+
+ /**
+ * Creates a new argument provider for a {@code float} value.
+ *
+ * @param value The represented {@code float} value.
+ */
+ protected ForFloatConstant(float value) {
+ this.value = value;
+ }
+
+ @Override
+ public Resolved resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Assigner.Typing typing) {
+ return new Resolved.Simple(FloatConstant.forValue(value), new TypeDescription.ForLoadedType(float.class));
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+ }
+
+ /**
+ * An argument provider for a {@code double} value.
+ */
+ @EqualsAndHashCode
+ class ForDoubleConstant implements ArgumentProvider {
+
+ /**
+ * The represented {@code double} value.
+ */
+ private final double value;
+
+ /**
+ * Creates a new argument provider for a {@code double} value.
+ *
+ * @param value The represented {@code double} value.
+ */
+ protected ForDoubleConstant(double value) {
+ this.value = value;
+ }
+
+ @Override
+ public Resolved resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Assigner.Typing typing) {
+ return new Resolved.Simple(DoubleConstant.forValue(value), new TypeDescription.ForLoadedType(double.class));
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+ }
+
+ /**
+ * An argument provider for a {@link java.lang.String} value.
+ */
+ @EqualsAndHashCode
+ class ForStringConstant implements ArgumentProvider {
+
+ /**
+ * The represented {@link java.lang.String} value.
+ */
+ private final String value;
+
+ /**
+ * Creates a new argument provider for a {@link java.lang.String} value.
+ *
+ * @param value The represented {@link java.lang.String} value.
+ */
+ protected ForStringConstant(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public Resolved resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Assigner.Typing typing) {
+ return new Resolved.Simple(new TextConstant(value), TypeDescription.STRING);
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+ }
+
+ /**
+ * An argument provider for a {@link java.lang.Class} constant.
+ */
+ @EqualsAndHashCode
+ class ForClassConstant implements ArgumentProvider {
+
+ /**
+ * The type that is represented by this constant.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new argument provider for the given type description.
+ *
+ * @param typeDescription The type to represent.
+ */
+ protected ForClassConstant(TypeDescription typeDescription) {
+ this.typeDescription = typeDescription;
+ }
+
+ @Override
+ public Resolved resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Assigner.Typing typing) {
+ return new Resolved.Simple(ClassConstant.of(typeDescription), TypeDescription.CLASS);
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+ }
+
+ /**
+ * An argument provider for an {@link java.lang.Enum} constant.
+ */
+ @EqualsAndHashCode
+ class ForEnumerationValue implements ArgumentProvider {
+
+ /**
+ * A description of the enumeration to represent.
+ */
+ private final EnumerationDescription enumerationDescription;
+
+ /**
+ * Creates a new argument provider for an enumeration value.
+ *
+ * @param enumerationDescription A description of the enumeration to represent.
+ */
+ protected ForEnumerationValue(EnumerationDescription enumerationDescription) {
+ this.enumerationDescription = enumerationDescription;
+ }
+
+ @Override
+ public Resolved resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Assigner.Typing typing) {
+ return new Resolved.Simple(FieldAccess.forEnumeration(enumerationDescription), enumerationDescription.getEnumerationType());
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+ }
+
+ /**
+ * An argument provider for the {@code null} value.
+ */
+ @EqualsAndHashCode
+ class ForNullValue implements ArgumentProvider {
+
+ /**
+ * The type to be represented by the {@code null} value.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new argument provider for the {@code null} value.
+ *
+ * @param typeDescription The type to be represented by the {@code null} value.
+ */
+ protected ForNullValue(TypeDescription typeDescription) {
+ this.typeDescription = typeDescription;
+ }
+
+ @Override
+ public Resolved resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Assigner.Typing typing) {
+ return new Resolved.Simple(NullConstant.INSTANCE, typeDescription);
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+ }
+
+ /**
+ * An argument provider for a Java instance.
+ */
+ @EqualsAndHashCode
+ class ForJavaConstant implements ArgumentProvider {
+
+ /**
+ * The Java instance to provide to the bootstrapped method.
+ */
+ private final JavaConstant javaConstant;
+
+ /**
+ * Creates a new argument provider for the given Java instance.
+ *
+ * @param javaConstant The Java instance to provide to the bootstrapped method.
+ */
+ protected ForJavaConstant(JavaConstant javaConstant) {
+ this.javaConstant = javaConstant;
+ }
+
+ @Override
+ public Resolved resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Assigner.Typing typing) {
+ return new Resolved.Simple(javaConstant.asStackManipulation(), javaConstant.getType());
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+ }
+ }
+
+ /**
+ * Provides the name of the method that is to be bound by a dynamic method call.
+ */
+ interface NameProvider {
+
+ /**
+ * Resolves the name given the intercepted method.
+ *
+ * @param methodDescription The intercepted method.
+ * @return The name of the method to be bound by the bootstrap method.
+ */
+ String resolve(MethodDescription methodDescription);
+
+ /**
+ * A name provider that provides the name of the intercepted method.
+ */
+ enum ForInterceptedMethod implements NameProvider {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public String resolve(MethodDescription methodDescription) {
+ return methodDescription.getInternalName();
+ }
+ }
+
+ /**
+ * A name provider that provides an explicit name.
+ */
+ @EqualsAndHashCode
+ class ForExplicitName implements NameProvider {
+
+ /**
+ * The name to be provided.
+ */
+ private final String internalName;
+
+ /**
+ * Creates a new name provider for an explicit name.
+ *
+ * @param internalName The name to be provided.
+ */
+ protected ForExplicitName(String internalName) {
+ this.internalName = internalName;
+ }
+
+ @Override
+ public String resolve(MethodDescription methodDescription) {
+ return internalName;
+ }
+ }
+ }
+
+ /**
+ * Provides the return type that is requested from the bootstrap method.
+ */
+ interface ReturnTypeProvider {
+
+ /**
+ * Resolves the return type that is requested from the bootstrap method.
+ *
+ * @param methodDescription The intercepted method.
+ * @return The return type that is requested from the bootstrap method.
+ */
+ TypeDescription resolve(MethodDescription methodDescription);
+
+ /**
+ * Requests the return type of the intercepted method.
+ */
+ enum ForInterceptedMethod implements ReturnTypeProvider {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public TypeDescription resolve(MethodDescription methodDescription) {
+ return methodDescription.getReturnType().asErasure();
+ }
+ }
+
+ /**
+ * Requests an explicit return type.
+ */
+ @EqualsAndHashCode
+ class ForExplicitType implements ReturnTypeProvider {
+
+ /**
+ * The requested return type.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new return type provider for an explicit return type.
+ *
+ * @param typeDescription The requested return type.
+ */
+ protected ForExplicitType(TypeDescription typeDescription) {
+ this.typeDescription = typeDescription;
+ }
+
+ @Override
+ public TypeDescription resolve(MethodDescription methodDescription) {
+ return typeDescription;
+ }
+ }
+ }
+
+ /**
+ * An invocation provider that requests a synthetic dynamic invocation where all arguments are explicitly
+ * provided by the user.
+ */
+ @EqualsAndHashCode
+ class Default implements InvocationProvider {
+
+ /**
+ * The provider for the name of the intercepted method.
+ */
+ private final NameProvider nameProvider;
+
+ /**
+ * The provider for the required return type.
+ */
+ private final ReturnTypeProvider returnTypeProvider;
+
+ /**
+ * The providers for the method arguments in their order.
+ */
+ private final List<ArgumentProvider> argumentProviders;
+
+ /**
+ * Creates a new default invocation provider that provides information and arguments of the
+ * intercepted method.
+ */
+ protected Default() {
+ this(NameProvider.ForInterceptedMethod.INSTANCE,
+ ReturnTypeProvider.ForInterceptedMethod.INSTANCE,
+ Collections.<ArgumentProvider>singletonList(ArgumentProvider.ForInterceptedMethodInstanceAndParameters.INSTANCE));
+ }
+
+ /**
+ * Creates a new default invocation provider.
+ *
+ * @param nameProvider The provider for the name of the intercepted method.
+ * @param returnTypeProvider The provider for the required return type.
+ * @param argumentProviders The providers for the method arguments in their order.
+ */
+ protected Default(NameProvider nameProvider,
+ ReturnTypeProvider returnTypeProvider,
+ List<ArgumentProvider> argumentProviders) {
+ this.nameProvider = nameProvider;
+ this.returnTypeProvider = returnTypeProvider;
+ this.argumentProviders = argumentProviders;
+ }
+
+ @Override
+ public Target make(MethodDescription methodDescription) {
+ return new Target(nameProvider.resolve(methodDescription),
+ returnTypeProvider.resolve(methodDescription),
+ argumentProviders,
+ methodDescription);
+ }
+
+ @Override
+ public InvocationProvider appendArguments(List<ArgumentProvider> argumentProviders) {
+ return new Default(nameProvider,
+ returnTypeProvider,
+ CompoundList.of(this.argumentProviders, argumentProviders));
+ }
+
+ @Override
+ public InvocationProvider appendArgument(ArgumentProvider argumentProvider) {
+ return new Default(nameProvider,
+ returnTypeProvider,
+ CompoundList.of(this.argumentProviders, argumentProvider));
+ }
+
+ @Override
+ public InvocationProvider withoutArguments() {
+ return new Default(nameProvider,
+ returnTypeProvider,
+ Collections.<ArgumentProvider>emptyList());
+ }
+
+ @Override
+ public InvocationProvider withNameProvider(NameProvider nameProvider) {
+ return new Default(nameProvider,
+ returnTypeProvider,
+ argumentProviders);
+ }
+
+ @Override
+ public InvocationProvider withReturnTypeProvider(ReturnTypeProvider returnTypeProvider) {
+ return new Default(nameProvider,
+ returnTypeProvider,
+ argumentProviders);
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ for (ArgumentProvider argumentProvider : argumentProviders) {
+ instrumentedType = argumentProvider.prepare(instrumentedType);
+ }
+ return instrumentedType;
+ }
+
+ /**
+ * A target for a synthetically bound method call.
+ */
+ @EqualsAndHashCode
+ protected static class Target implements InvocationProvider.Target {
+
+ /**
+ * The name to be passed to the bootstrap method.
+ */
+ private final String internalName;
+
+ /**
+ * The return type to be requested from the bootstrapping method.
+ */
+ private final TypeDescription returnType;
+
+ /**
+ * The arguments to be passed to the bootstrap method.
+ */
+ private final List<ArgumentProvider> argumentProviders;
+
+ /**
+ * The intercepted method.
+ */
+ private final MethodDescription instrumentedMethod;
+
+ /**
+ * Creates a new target.
+ *
+ * @param internalName The name to be passed to the bootstrap method.
+ * @param returnType The return type to be requested from the bootstrapping method.
+ * @param argumentProviders The arguments to be passed to the bootstrap method.
+ * @param instrumentedMethod The intercepted method.
+ */
+ protected Target(String internalName,
+ TypeDescription returnType,
+ List<ArgumentProvider> argumentProviders,
+ MethodDescription instrumentedMethod) {
+ this.internalName = internalName;
+ this.returnType = returnType;
+ this.argumentProviders = argumentProviders;
+ this.instrumentedMethod = instrumentedMethod;
+ }
+
+ @Override
+ public InvocationProvider.Target.Resolved resolve(TypeDescription instrumentedType, Assigner assigner, Assigner.Typing typing) {
+ StackManipulation[] stackManipulation = new StackManipulation[argumentProviders.size()];
+ List<TypeDescription> parameterTypes = new ArrayList<TypeDescription>();
+ int index = 0;
+ for (ArgumentProvider argumentProvider : argumentProviders) {
+ ArgumentProvider.Resolved resolved = argumentProvider.resolve(instrumentedType, instrumentedMethod, assigner, typing);
+ parameterTypes.addAll(resolved.getLoadedTypes());
+ stackManipulation[index++] = resolved.getLoadInstruction();
+ }
+ return new Resolved.Simple(new StackManipulation.Compound(stackManipulation),
+ internalName,
+ returnType,
+ parameterTypes);
+ }
+ }
+ }
+ }
+
+ /**
+ * A termination handler is responsible to handle the return value of a method that is invoked via a
+ * {@link net.bytebuddy.implementation.InvokeDynamic}.
+ */
+ protected enum TerminationHandler {
+
+ /**
+ * A termination handler that returns the bound method's return value.
+ */
+ RETURNING {
+ @Override
+ protected StackManipulation resolve(MethodDescription interceptedMethod, TypeDescription returnType, Assigner assigner, Assigner.Typing typing) {
+ StackManipulation stackManipulation = assigner.assign(returnType.asGenericType(), interceptedMethod.getReturnType(), typing);
+ if (!stackManipulation.isValid()) {
+ throw new IllegalStateException("Cannot return " + returnType + " from " + interceptedMethod);
+ }
+ return new StackManipulation.Compound(stackManipulation, MethodReturn.of(interceptedMethod.getReturnType()));
+ }
+ },
+
+ /**
+ * A termination handler that drops the bound method's return value.
+ */
+ DROPPING {
+ @Override
+ protected StackManipulation resolve(MethodDescription interceptedMethod, TypeDescription returnType, Assigner assigner, Assigner.Typing typing) {
+ return Removal.of(interceptedMethod.isConstructor()
+ ? interceptedMethod.getDeclaringType()
+ : interceptedMethod.getReturnType());
+ }
+ };
+
+ /**
+ * Returns a stack manipulation that handles the method return.
+ *
+ * @param interceptedMethod The method being intercepted.
+ * @param returnType The return type of the instrumented method.
+ * @param assigner The assigner to use.
+ * @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
+ * @return A stack manipulation that handles the method return.
+ */
+ protected abstract StackManipulation resolve(MethodDescription interceptedMethod,
+ TypeDescription returnType,
+ Assigner assigner,
+ Assigner.Typing typing);
+ }
+
+ /**
+ * An abstract delegator that allows to specify a configuration for any specification of an argument.
+ */
+ protected abstract static class AbstractDelegator extends InvokeDynamic {
+
+ /**
+ * Creates a new abstract delegator for a dynamic method invocation.
+ *
+ * @param bootstrapMethod The bootstrap method.
+ * @param handleArguments The arguments that are provided to the bootstrap method.
+ * @param invocationProvider The target provided that identifies the method to be bootstrapped.
+ * @param terminationHandler A handler that handles the method return.
+ * @param assigner The assigner to be used.
+ * @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
+ */
+ protected AbstractDelegator(MethodDescription.InDefinedShape bootstrapMethod,
+ List<?> handleArguments,
+ InvocationProvider invocationProvider,
+ TerminationHandler terminationHandler,
+ Assigner assigner,
+ Assigner.Typing typing) {
+ super(bootstrapMethod, handleArguments, invocationProvider, terminationHandler, assigner, typing);
+ }
+
+ /**
+ * Resolves the current configuration into a fully initialized invoke dynamic instance.
+ *
+ * @return The fully resolved invoke dynamic instance.
+ */
+ protected abstract InvokeDynamic materialize();
+
+ @Override
+ public InvokeDynamic withBooleanValue(boolean... value) {
+ return materialize().withBooleanValue(value);
+ }
+
+ @Override
+ public InvokeDynamic withByteValue(byte... value) {
+ return materialize().withByteValue(value);
+ }
+
+ @Override
+ public InvokeDynamic withShortValue(short... value) {
+ return materialize().withShortValue(value);
+ }
+
+ @Override
+ public InvokeDynamic withCharacterValue(char... value) {
+ return materialize().withCharacterValue(value);
+ }
+
+ @Override
+ public InvokeDynamic withIntegerValue(int... value) {
+ return materialize().withIntegerValue(value);
+ }
+
+ @Override
+ public InvokeDynamic withLongValue(long... value) {
+ return materialize().withLongValue(value);
+ }
+
+ @Override
+ public InvokeDynamic withFloatValue(float... value) {
+ return materialize().withFloatValue(value);
+ }
+
+ @Override
+ public InvokeDynamic withDoubleValue(double... value) {
+ return materialize().withDoubleValue(value);
+ }
+
+ @Override
+ public InvokeDynamic withValue(Object... value) {
+ return materialize().withValue(value);
+ }
+
+ @Override
+ public WithImplicitType withReference(Object value) {
+ return materialize().withReference(value);
+ }
+
+ @Override
+ public InvokeDynamic withReference(Object... value) {
+ return materialize().withReference(value);
+ }
+
+ @Override
+ public InvokeDynamic withType(TypeDescription... typeDescription) {
+ return materialize().withType(typeDescription);
+ }
+
+ @Override
+ public InvokeDynamic withInstance(JavaConstant... javaConstant) {
+ return materialize().withInstance(javaConstant);
+ }
+
+ @Override
+ public InvokeDynamic withNullValue(Class<?>... type) {
+ return materialize().withNullValue(type);
+ }
+
+ @Override
+ public InvokeDynamic withNullValue(TypeDescription... typeDescription) {
+ return materialize().withNullValue(typeDescription);
+ }
+
+ @Override
+ public InvokeDynamic withArgument(int... index) {
+ return materialize().withArgument(index);
+ }
+
+ @Override
+ public WithImplicitType withArgument(int index) {
+ return materialize().withArgument(index);
+ }
+
+ @Override
+ public InvokeDynamic withThis(Class<?>... type) {
+ return materialize().withThis(type);
+ }
+
+ @Override
+ public InvokeDynamic withThis(TypeDescription... typeDescription) {
+ return materialize().withThis(typeDescription);
+ }
+
+ @Override
+ public InvokeDynamic withMethodArguments() {
+ return materialize().withMethodArguments();
+ }
+
+ @Override
+ public InvokeDynamic withImplicitAndMethodArguments() {
+ return materialize().withImplicitAndMethodArguments();
+ }
+
+ @Override
+ public InvokeDynamic withField(String... fieldName) {
+ return materialize().withField(fieldName);
+ }
+
+ @Override
+ public InvokeDynamic withEnumeration(EnumerationDescription... enumerationDescription) {
+ return materialize().withEnumeration(enumerationDescription);
+ }
+
+ @Override
+ public InvokeDynamic withField(FieldLocator.Factory fieldLocatorFactory, String... name) {
+ return materialize().withField(fieldLocatorFactory, name);
+ }
+
+ @Override
+ public WithImplicitType withField(String name) {
+ return materialize().withField(name);
+ }
+
+ @Override
+ public WithImplicitType withField(String name, FieldLocator.Factory fieldLocatorFactory) {
+ return materialize().withField(name, fieldLocatorFactory);
+ }
+
+ @Override
+ public Composable withAssigner(Assigner assigner, Assigner.Typing typing) {
+ return materialize().withAssigner(assigner, typing);
+ }
+
+ @Override
+ public Implementation andThen(Implementation implementation) {
+ return materialize().andThen(implementation);
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return materialize().prepare(instrumentedType);
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return materialize().appender(implementationTarget);
+ }
+ }
+
+ /**
+ * Representation of an {@link net.bytebuddy.implementation.InvokeDynamic} implementation where the bootstrapped
+ * method is passed a {@code this} reference, if available, and any arguments of the instrumented method.
+ */
+ public static class WithImplicitArguments extends AbstractDelegator {
+
+ /**
+ * Creates a new dynamic method invocation with implicit arguments.
+ *
+ * @param bootstrapMethod The bootstrap method.
+ * @param handleArguments The arguments that are provided to the bootstrap method.
+ * @param invocationProvider The target provided that identifies the method to be bootstrapped.
+ * @param terminationHandler A handler that handles the method return.
+ * @param assigner The assigner to be used.
+ * @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
+ */
+ protected WithImplicitArguments(MethodDescription.InDefinedShape bootstrapMethod,
+ List<?> handleArguments,
+ InvocationProvider invocationProvider,
+ TerminationHandler terminationHandler,
+ Assigner assigner,
+ Assigner.Typing typing) {
+ super(bootstrapMethod,
+ handleArguments,
+ invocationProvider,
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ /**
+ * Returns an instance of this instrumentation where the bootstrapped method is not passed any arguments.
+ *
+ * @return This implementation where the bootstrapped method is not passed any arguments.
+ */
+ public InvokeDynamic withoutArguments() {
+ return new InvokeDynamic(bootstrapMethod,
+ handleArguments,
+ invocationProvider.withoutArguments(),
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ @Override
+ protected InvokeDynamic materialize() {
+ return withoutArguments();
+ }
+
+ @Override
+ public WithImplicitArguments withAssigner(Assigner assigner, Assigner.Typing typing) {
+ return new WithImplicitArguments(bootstrapMethod,
+ handleArguments,
+ invocationProvider,
+ terminationHandler,
+ assigner,
+ typing);
+ }
+ }
+
+ /**
+ * Representation of an {@link net.bytebuddy.implementation.InvokeDynamic} implementation where the bootstrapped
+ * method is passed a {@code this} reference, if available, and any arguments of the instrumented method and
+ * where the invocation target is implicit.
+ */
+ public static class WithImplicitTarget extends WithImplicitArguments {
+
+ /**
+ * Creates a new dynamic method invocation with implicit arguments and an implicit invocation target.
+ *
+ * @param bootstrapMethod The bootstrap method.
+ * @param handleArguments The arguments that are provided to the bootstrap method.
+ * @param invocationProvider The target provided that identifies the method to be bootstrapped.
+ * @param terminationHandler A handler that handles the method return.
+ * @param assigner The assigner to be used.
+ * @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
+ */
+ protected WithImplicitTarget(MethodDescription.InDefinedShape bootstrapMethod,
+ List<?> handleArguments,
+ InvocationProvider invocationProvider,
+ TerminationHandler terminationHandler,
+ Assigner assigner,
+ Assigner.Typing typing) {
+ super(bootstrapMethod,
+ handleArguments,
+ invocationProvider,
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ /**
+ * Requests the bootstrap method to bind a method with the given return type. The return type
+ * is he assigned to the intercepted method's return type.
+ *
+ * @param returnType The return type to request from the bootstrapping method.
+ * @return This implementation where the bootstrap method is requested to bind a method with the given
+ * return type.
+ */
+ public InvokeDynamic.WithImplicitArguments invoke(Class<?> returnType) {
+ return invoke(new TypeDescription.ForLoadedType(returnType));
+ }
+
+ /**
+ * Requests the bootstrap method to bind a method with the given return type. The return type
+ * is he assigned to the intercepted method's return type.
+ *
+ * @param returnType The return type to request from the bootstrapping method.
+ * @return This implementation where the bootstrap method is requested to bind a method with the given
+ * return type.
+ */
+ public InvokeDynamic.WithImplicitArguments invoke(TypeDescription returnType) {
+ return new WithImplicitArguments(bootstrapMethod,
+ handleArguments,
+ invocationProvider.withReturnTypeProvider(new InvocationProvider.ReturnTypeProvider.ForExplicitType(returnType)),
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ /**
+ * Requests the bootstrap method is passed the given method name.
+ *
+ * @param methodName The method name to pass to the bootstrapping method.
+ * @return This implementation where the bootstrap method is passed the given method name.
+ */
+ public InvokeDynamic.WithImplicitArguments invoke(String methodName) {
+ return new WithImplicitArguments(bootstrapMethod,
+ handleArguments,
+ invocationProvider.withNameProvider(new InvocationProvider.NameProvider.ForExplicitName(methodName)),
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ /**
+ * Requests the bootstrap method to bind a method with the given return type. The return type
+ * is he assigned to the intercepted method's return type. Also, the bootstrap method is passed the
+ * given method name,
+ *
+ * @param methodName The method name to pass to the bootstrapping method.
+ * @param returnType The return type to request from the bootstrapping method.
+ * @return This implementation where the bootstrap method is requested to bind a method with the given
+ * return type while being passed the given method name.
+ */
+ public InvokeDynamic.WithImplicitArguments invoke(String methodName, Class<?> returnType) {
+ return invoke(methodName, new TypeDescription.ForLoadedType(returnType));
+ }
+
+ /**
+ * Requests the bootstrap method to bind a method with the given return type. The return type
+ * is he assigned to the intercepted method's return type. Also, the bootstrap method is passed the
+ * given method name,
+ *
+ * @param methodName The method name to pass to the bootstrapping method.
+ * @param returnType The return type to request from the bootstrapping method.
+ * @return This implementation where the bootstrap method is requested to bind a method with the given
+ * return type while being passed the given method name.
+ */
+ public InvokeDynamic.WithImplicitArguments invoke(String methodName, TypeDescription returnType) {
+ return new WithImplicitArguments(bootstrapMethod,
+ handleArguments,
+ invocationProvider
+ .withNameProvider(new InvocationProvider.NameProvider.ForExplicitName(methodName))
+ .withReturnTypeProvider(new InvocationProvider.ReturnTypeProvider.ForExplicitType(returnType)),
+ terminationHandler,
+ assigner,
+ typing);
+ }
+ }
+
+ /**
+ * An {@link InvokeDynamic} invocation where the last argument is assigned its implicit type.
+ */
+ public abstract static class WithImplicitType extends AbstractDelegator {
+
+ /**
+ * Creates a new abstract delegator for a dynamic method invocation where the last argument is assigned an implicit type.
+ *
+ * @param bootstrapMethod The bootstrap method.
+ * @param handleArguments The arguments that are provided to the bootstrap method.
+ * @param invocationProvider The target provided that identifies the method to be bootstrapped.
+ * @param terminationHandler A handler that handles the method return.
+ * @param assigner The assigner to be used.
+ * @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
+ */
+ protected WithImplicitType(MethodDescription.InDefinedShape bootstrapMethod,
+ List<?> handleArguments,
+ InvocationProvider invocationProvider,
+ TerminationHandler terminationHandler,
+ Assigner assigner,
+ Assigner.Typing typing) {
+ super(bootstrapMethod, handleArguments, invocationProvider, terminationHandler, assigner, typing);
+ }
+
+ /**
+ * Represents the last value as an instance of the given type.
+ *
+ * @param type The type to represent to the dynamic method invocation.
+ * @return A new dynamic method invocation where the last argument is represented by the given type.
+ */
+ public InvokeDynamic as(Class<?> type) {
+ return as(new TypeDescription.ForLoadedType(type));
+ }
+
+ /**
+ * Represents the last value as an instance of the given type.
+ *
+ * @param typeDescription The type to represent to the dynamic method invocation.
+ * @return A new dynamic method invocation where the last argument is represented by the given type.
+ */
+ public abstract InvokeDynamic as(TypeDescription typeDescription);
+
+ /**
+ * A step in the invoke dynamic domain specific language that allows to explicitly specify a field type for a reference value.
+ */
+ @SuppressFBWarnings(value = "EQ_DOESNT_OVERRIDE_EQUALS", justification = "Super type implementation convers use case")
+ protected static class OfInstance extends WithImplicitType {
+
+ /**
+ * The value that is supplied as the next argument to the bootstrapped method.
+ */
+ private final Object value;
+
+ /**
+ * An argument provider that represents the argument with an implicit type.
+ */
+ private final InvocationProvider.ArgumentProvider argumentProvider;
+
+ /**
+ * Creates a new invoke dynamic instance with an implicit field type for the provided value.
+ *
+ * @param bootstrapMethod The bootstrap method.
+ * @param handleArguments The arguments that are provided to the bootstrap method.
+ * @param invocationProvider The target provided that identifies the method to be bootstrapped.
+ * @param terminationHandler A handler that handles the method return.
+ * @param assigner The assigner to be used.
+ * @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
+ * @param value The value that is supplied as the next argument to the bootstrapped method.
+ */
+ protected OfInstance(MethodDescription.InDefinedShape bootstrapMethod,
+ List<?> handleArguments,
+ InvocationProvider invocationProvider,
+ TerminationHandler terminationHandler,
+ Assigner assigner,
+ Assigner.Typing typing,
+ Object value) {
+ super(bootstrapMethod, handleArguments, invocationProvider, terminationHandler, assigner, typing);
+ this.value = value;
+ this.argumentProvider = InvocationProvider.ArgumentProvider.ForInstance.of(value);
+ }
+
+ @Override
+ public InvokeDynamic as(TypeDescription typeDescription) {
+ if (!typeDescription.asBoxed().isInstance(value)) {
+ throw new IllegalArgumentException(value + " is not of type " + typeDescription);
+ }
+ return new InvokeDynamic(bootstrapMethod,
+ handleArguments,
+ invocationProvider.appendArgument(new InvocationProvider.ArgumentProvider.ForInstance(value, typeDescription)),
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ @Override
+ protected InvokeDynamic materialize() {
+ return new InvokeDynamic(bootstrapMethod,
+ handleArguments,
+ invocationProvider.appendArgument(argumentProvider),
+ terminationHandler,
+ assigner,
+ typing);
+ }
+ }
+
+ /**
+ * An invoke dynamic implementation where the last argument is an implicitly typed method argument.
+ */
+ @SuppressFBWarnings(value = "EQ_DOESNT_OVERRIDE_EQUALS", justification = "Super type implementation convers use case")
+ protected static class OfArgument extends WithImplicitType {
+
+ /**
+ * The index of the method argument.
+ */
+ private final int index;
+
+ /**
+ * Creates a new invoke dynamic instance with an implicit field type for the provided value.
+ *
+ * @param bootstrapMethod The bootstrap method.
+ * @param handleArguments The arguments that are provided to the bootstrap method.
+ * @param invocationProvider The target provided that identifies the method to be bootstrapped.
+ * @param terminationHandler A handler that handles the method return.
+ * @param assigner The assigner to be used.
+ * @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
+ * @param index The index of of the argument to supply to the bootstapped method.
+ */
+ protected OfArgument(MethodDescription.InDefinedShape bootstrapMethod,
+ List<?> handleArguments,
+ InvocationProvider invocationProvider,
+ TerminationHandler terminationHandler,
+ Assigner assigner,
+ Assigner.Typing typing,
+ int index) {
+ super(bootstrapMethod, handleArguments, invocationProvider, terminationHandler, assigner, typing);
+ this.index = index;
+ }
+
+ @Override
+ public InvokeDynamic as(TypeDescription typeDescription) {
+ return new InvokeDynamic(bootstrapMethod,
+ handleArguments,
+ invocationProvider.appendArgument(new InvocationProvider.ArgumentProvider.ForMethodParameter.WithExplicitType(index, typeDescription)),
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ @Override
+ protected InvokeDynamic materialize() {
+ return new InvokeDynamic(bootstrapMethod,
+ handleArguments,
+ invocationProvider.appendArgument(new InvocationProvider.ArgumentProvider.ForMethodParameter(index)),
+ terminationHandler,
+ assigner,
+ typing);
+ }
+ }
+
+ /**
+ * An invoke dynamic implementation where the last argument is an implicitly typed field value.
+ */
+ @SuppressFBWarnings(value = "EQ_DOESNT_OVERRIDE_EQUALS", justification = "Super type implementation convers use case")
+ protected static class OfField extends WithImplicitType {
+
+ /**
+ * The field name.
+ */
+ private final String fieldName;
+
+ /**
+ * The field locator factory to use.
+ */
+ private final FieldLocator.Factory fieldLocatorFactory;
+
+ /**
+ * Creates a new abstract delegator for a dynamic method invocation where the last argument is assigned an implicit type.
+ *
+ * @param bootstrapMethod The bootstrap method.
+ * @param handleArguments The arguments that are provided to the bootstrap method.
+ * @param invocationProvider The target provided that identifies the method to be bootstrapped.
+ * @param terminationHandler A handler that handles the method return.
+ * @param assigner The assigner to be used.
+ * @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
+ * @param fieldName The field name.
+ * @param fieldLocatorFactory The field locator factory to use.
+ */
+ protected OfField(MethodDescription.InDefinedShape bootstrapMethod,
+ List<?> handleArguments,
+ InvocationProvider invocationProvider,
+ TerminationHandler terminationHandler,
+ Assigner assigner,
+ Assigner.Typing typing,
+ String fieldName,
+ FieldLocator.Factory fieldLocatorFactory) {
+ super(bootstrapMethod, handleArguments, invocationProvider, terminationHandler, assigner, typing);
+ this.fieldName = fieldName;
+ this.fieldLocatorFactory = fieldLocatorFactory;
+ }
+
+ @Override
+ public InvokeDynamic as(TypeDescription typeDescription) {
+ return new InvokeDynamic(bootstrapMethod,
+ handleArguments,
+ invocationProvider.appendArgument(new InvocationProvider.ArgumentProvider.ForField.WithExplicitType(fieldName, fieldLocatorFactory, typeDescription)),
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ @Override
+ protected InvokeDynamic materialize() {
+ return new InvokeDynamic(bootstrapMethod,
+ handleArguments,
+ invocationProvider.appendArgument(new InvocationProvider.ArgumentProvider.ForField(fieldName, fieldLocatorFactory)),
+ terminationHandler,
+ assigner,
+ typing);
+ }
+ }
+ }
+
+ /**
+ * The byte code appender to be used by the {@link net.bytebuddy.implementation.InvokeDynamic} implementation.
+ */
+ protected class Appender implements ByteCodeAppender {
+
+ /**
+ * The instrumented type of the current implementation.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * Creates a new byte code appender for an invoke dynamic implementation.
+ *
+ * @param instrumentedType The instrumented type of the current implementation.
+ */
+ public Appender(TypeDescription instrumentedType) {
+ this.instrumentedType = instrumentedType;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ InvocationProvider.Target.Resolved target = invocationProvider.make(instrumentedMethod).resolve(instrumentedType, assigner, typing);
+ StackManipulation.Size size = new StackManipulation.Compound(
+ target.getStackManipulation(),
+ MethodInvocation.invoke(bootstrapMethod)
+ .dynamic(target.getInternalName(),
+ target.getReturnType(),
+ target.getParameterTypes(),
+ handleArguments),
+ terminationHandler.resolve(instrumentedMethod, target.getReturnType(), assigner, typing)
+ ).apply(methodVisitor, implementationContext);
+ return new Size(size.getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (other == null || getClass() != other.getClass()) return false;
+ Appender appender = (Appender) other;
+ return instrumentedType.equals(appender.instrumentedType)
+ && InvokeDynamic.this.equals(appender.getOuter());
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private InvokeDynamic getOuter() {
+ return InvokeDynamic.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return instrumentedType.hashCode() + 31 * InvokeDynamic.this.hashCode();
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/LoadedTypeInitializer.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/LoadedTypeInitializer.java
new file mode 100644
index 0000000..1af43db
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/LoadedTypeInitializer.java
@@ -0,0 +1,176 @@
+package net.bytebuddy.implementation;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.utility.privilege.SetAccessibleAction;
+
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.security.AccessController;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Implementations of this interface explicitly initialize a loaded type. Usually, such implementations inject runtime
+ * context into an instrumented type which cannot be defined by the means of the Java class file format.
+ */
+public interface LoadedTypeInitializer {
+
+ /**
+ * Callback that is invoked on the creation of an instrumented type. If the loaded type initializer is alive, this
+ * method should be implemented empty instead of throwing an exception.
+ *
+ * @param type The manifestation of the instrumented type.
+ */
+ void onLoad(Class<?> type);
+
+ /**
+ * Indicates if this initializer is alive and needs to be invoked. This is only meant as a mark. A loaded type
+ * initializer that is not alive might still be called and must therefore not throw an exception but rather
+ * provide an empty implementation.
+ *
+ * @return {@code true} if this initializer is alive.
+ */
+ boolean isAlive();
+
+ /**
+ * A loaded type initializer that does not do anything.
+ */
+ enum NoOp implements LoadedTypeInitializer {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public void onLoad(Class<?> type) {
+ /* do nothing */
+ }
+
+ @Override
+ public boolean isAlive() {
+ return false;
+ }
+ }
+
+ /**
+ * A type initializer for setting a value for a static field.
+ */
+ @EqualsAndHashCode
+ class ForStaticField implements LoadedTypeInitializer, Serializable {
+
+ /**
+ * This class's serial version UID.
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * A value for accessing a static field.
+ */
+ private static final Object STATIC_FIELD = null;
+
+ /**
+ * The name of the field.
+ */
+ private final String fieldName;
+
+ /**
+ * The value of the field.
+ */
+ private final Object value;
+
+ /**
+ * Creates a new {@link LoadedTypeInitializer} for setting a static field.
+ *
+ * @param fieldName the name of the field.
+ * @param value The value to be set.
+ */
+ protected ForStaticField(String fieldName, Object value) {
+ this.fieldName = fieldName;
+ this.value = value;
+ }
+
+ @Override
+ public void onLoad(Class<?> type) {
+ try {
+ Field field = type.getDeclaredField(fieldName);
+ if (!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers())) {
+ AccessController.doPrivileged(new SetAccessibleAction<Field>(field));
+ }
+ field.set(STATIC_FIELD, value);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalArgumentException("Cannot access " + fieldName + " from " + type, exception);
+ } catch (NoSuchFieldException exception) {
+ throw new IllegalStateException("There is no field " + fieldName + " defined on " + type, exception);
+ }
+ }
+
+ @Override
+ public boolean isAlive() {
+ return true;
+ }
+ }
+
+ /**
+ * A compound loaded type initializer that combines several type initializers.
+ */
+ @SuppressFBWarnings(value = "SE_BAD_FIELD", justification = "Serialization is considered opt-in for a rare use case")
+ @EqualsAndHashCode
+ class Compound implements LoadedTypeInitializer, Serializable {
+
+ /**
+ * This class's serial version UID.
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * The loaded type initializers that are represented by this compound type initializer.
+ */
+ private final List<LoadedTypeInitializer> loadedTypeInitializers;
+
+ /**
+ * Creates a new compound loaded type initializer.
+ *
+ * @param loadedTypeInitializer A number of loaded type initializers in their invocation order.
+ */
+ public Compound(LoadedTypeInitializer... loadedTypeInitializer) {
+ this(Arrays.asList(loadedTypeInitializer));
+ }
+
+ /**
+ * Creates a new compound loaded type initializer.
+ *
+ * @param loadedTypeInitializers A number of loaded type initializers in their invocation order.
+ */
+ public Compound(List<? extends LoadedTypeInitializer> loadedTypeInitializers) {
+ this.loadedTypeInitializers = new ArrayList<LoadedTypeInitializer>();
+ for (LoadedTypeInitializer loadedTypeInitializer : loadedTypeInitializers) {
+ if (loadedTypeInitializer instanceof Compound) {
+ this.loadedTypeInitializers.addAll(((Compound) loadedTypeInitializer).loadedTypeInitializers);
+ } else if (!(loadedTypeInitializer instanceof NoOp)) {
+ this.loadedTypeInitializers.add(loadedTypeInitializer);
+ }
+ }
+ }
+
+ @Override
+ public void onLoad(Class<?> type) {
+ for (LoadedTypeInitializer loadedTypeInitializer : loadedTypeInitializers) {
+ loadedTypeInitializer.onLoad(type);
+ }
+ }
+
+ @Override
+ public boolean isAlive() {
+ for (LoadedTypeInitializer loadedTypeInitializer : loadedTypeInitializers) {
+ if (loadedTypeInitializer.isAlive()) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/MethodAccessorFactory.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/MethodAccessorFactory.java
new file mode 100644
index 0000000..6781594
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/MethodAccessorFactory.java
@@ -0,0 +1,114 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.modifier.Visibility;
+
+/**
+ * A factory for creating method proxies for an auxiliary type. Such proxies are required to allow a type to
+ * call methods of a second type that are usually not accessible for the first type. This strategy is also adapted
+ * by the Java compiler that creates accessor methods for example to implement inner classes.
+ */
+public interface MethodAccessorFactory {
+
+ /**
+ * Registers an accessor method for a
+ * {@link Implementation.SpecialMethodInvocation} which cannot itself be
+ * triggered invoked directly from outside a type. The method is registered on the instrumented type
+ * with package-private visibility, similarly to a Java compiler's accessor methods.
+ *
+ * @param specialMethodInvocation The special method invocation.
+ * @param accessType The required access type.
+ * @return The accessor method for invoking the special method invocation.
+ */
+ MethodDescription.InDefinedShape registerAccessorFor(Implementation.SpecialMethodInvocation specialMethodInvocation, AccessType accessType);
+
+ /**
+ * Registers a getter for the given {@link net.bytebuddy.description.field.FieldDescription} which might
+ * itself not be accessible from outside the class. The returned getter method defines the field type as
+ * its return type, does not take any arguments and is of package-private visibility, similarly to the Java
+ * compiler's accessor methods. If the field is {@code static}, this accessor method is also {@code static}.
+ *
+ * @param fieldDescription The field which is to be accessed.
+ * @param accessType The required access type.
+ * @return A getter method for the given field.
+ */
+ MethodDescription.InDefinedShape registerGetterFor(FieldDescription fieldDescription, AccessType accessType);
+
+ /**
+ * Registers a setter for the given {@link FieldDescription} which might
+ * itself not be accessible from outside the class. The returned setter method defines the field type as
+ * its only argument type, returns {@code void} and is of package-private visibility, similarly to the Java
+ * compiler's accessor methods. If the field is {@code static}, this accessor method is also {@code static}.
+ *
+ * @param fieldDescription The field which is to be accessed.
+ * @param accessType The required access type.
+ * @return A setter method for the given field.
+ */
+ MethodDescription.InDefinedShape registerSetterFor(FieldDescription fieldDescription, AccessType accessType);
+
+ /**
+ * Indicates the type of access to an accessor method.
+ */
+ enum AccessType {
+
+ /**
+ * An access with {@code public visibility}.
+ */
+ PUBLIC(Visibility.PUBLIC),
+
+ /**
+ * An access with default visibility.
+ */
+ DEFAULT(Visibility.PACKAGE_PRIVATE);
+
+ /**
+ * The implied visibility.
+ */
+ private final Visibility visibility;
+
+ /**
+ * Creates a new access type.
+ *
+ * @param visibility The implied visibility.
+ */
+ AccessType(Visibility visibility) {
+ this.visibility = visibility;
+ }
+
+ /**
+ * Returns the implied visibility.
+ *
+ * @return The implied visibility.
+ */
+ public Visibility getVisibility() {
+ return visibility;
+ }
+ }
+
+ /**
+ * A method accessor factory that forbids any accessor registration.
+ */
+ enum Illegal implements MethodAccessorFactory {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public MethodDescription.InDefinedShape registerAccessorFor(Implementation.SpecialMethodInvocation specialMethodInvocation, AccessType accessType) {
+ throw new IllegalStateException("It is illegal to register an accessor for this type");
+ }
+
+ @Override
+ public MethodDescription.InDefinedShape registerGetterFor(FieldDescription fieldDescription, AccessType accessType) {
+ throw new IllegalStateException("It is illegal to register a field getter for this type");
+ }
+
+ @Override
+ public MethodDescription.InDefinedShape registerSetterFor(FieldDescription fieldDescription, AccessType accessType) {
+ throw new IllegalStateException("It is illegal to register a field setter for this type");
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/MethodCall.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/MethodCall.java
new file mode 100644
index 0000000..fd9ee39
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/MethodCall.java
@@ -0,0 +1,2037 @@
+package net.bytebuddy.implementation;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.enumeration.EnumerationDescription;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.scaffold.FieldLocator;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.dynamic.scaffold.MethodGraph;
+import net.bytebuddy.implementation.bytecode.*;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.collection.ArrayAccess;
+import net.bytebuddy.implementation.bytecode.collection.ArrayFactory;
+import net.bytebuddy.implementation.bytecode.constant.*;
+import net.bytebuddy.implementation.bytecode.member.FieldAccess;
+import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
+import net.bytebuddy.implementation.bytecode.member.MethodReturn;
+import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.utility.CompoundList;
+import net.bytebuddy.utility.JavaConstant;
+import net.bytebuddy.utility.JavaType;
+import net.bytebuddy.utility.RandomString;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.*;
+import java.util.concurrent.Callable;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+
+/**
+ * This {@link Implementation} allows the invocation of a specified method while
+ * providing explicit arguments to this method.
+ */
+ at EqualsAndHashCode
+public class MethodCall implements Implementation.Composable {
+
+ /**
+ * The method locator to use.
+ */
+ protected final MethodLocator methodLocator;
+
+ /**
+ * The target handler to use.
+ */
+ protected final TargetHandler targetHandler;
+
+ /**
+ * The argument loader to load arguments onto the operand stack in their application order.
+ */
+ protected final List<ArgumentLoader.Factory> argumentLoaders;
+
+ /**
+ * The method invoker to use.
+ */
+ protected final MethodInvoker methodInvoker;
+
+ /**
+ * The termination handler to use.
+ */
+ protected final TerminationHandler terminationHandler;
+
+ /**
+ * The assigner to use.
+ */
+ protected final Assigner assigner;
+
+ /**
+ * Indicates if dynamic type castings should be attempted for incompatible assignments.
+ */
+ protected final Assigner.Typing typing;
+
+ /**
+ * Creates a new method call implementation.
+ *
+ * @param methodLocator The method locator to use.
+ * @param targetHandler The target handler to use.
+ * @param argumentLoaders The argument loader to load arguments onto the operand stack in
+ * their application order.
+ * @param methodInvoker The method invoker to use.
+ * @param terminationHandler The termination handler to use.
+ * @param assigner The assigner to use.
+ * @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
+ */
+ protected MethodCall(MethodLocator methodLocator,
+ TargetHandler targetHandler,
+ List<ArgumentLoader.Factory> argumentLoaders,
+ MethodInvoker methodInvoker,
+ TerminationHandler terminationHandler,
+ Assigner assigner,
+ Assigner.Typing typing) {
+ this.methodLocator = methodLocator;
+ this.targetHandler = targetHandler;
+ this.argumentLoaders = argumentLoaders;
+ this.methodInvoker = methodInvoker;
+ this.terminationHandler = terminationHandler;
+ this.assigner = assigner;
+ this.typing = typing;
+ }
+
+ /**
+ * Invokes the given method. Without further specification, the method is invoked without any arguments on
+ * the instance of the instrumented class or statically, if the given method is {@code static}.
+ *
+ * @param method The method to invoke.
+ * @return A method call implementation that invokes the given method without providing any arguments.
+ */
+ public static WithoutSpecifiedTarget invoke(Method method) {
+ return invoke(new MethodDescription.ForLoadedMethod(method));
+ }
+
+ /**
+ * Invokes the given constructor on the instance of the instrumented type.
+ *
+ * @param constructor The constructor to invoke.
+ * @return A method call implementation that invokes the given constructor without providing any arguments.
+ */
+ public static WithoutSpecifiedTarget invoke(Constructor<?> constructor) {
+ return invoke(new MethodDescription.ForLoadedConstructor(constructor));
+ }
+
+ /**
+ * Invokes the given method. If the method description describes a constructor, it is automatically invoked as
+ * a special method invocation on the instance of the instrumented type. The same is true for {@code private}
+ * methods. Finally, {@code static} methods are invoked statically.
+ *
+ * @param methodDescription The method to invoke.
+ * @return A method call implementation that invokes the given method without providing any arguments.
+ */
+ public static WithoutSpecifiedTarget invoke(MethodDescription methodDescription) {
+ return invoke(new MethodLocator.ForExplicitMethod(methodDescription));
+ }
+
+ /**
+ * Invokes a unique virtual method of the instrumented type that is matched by the specified matcher.
+ *
+ * @param matcher The matcher to identify the method to invoke.
+ * @return A method call for the uniquely identified method.
+ */
+ public static WithoutSpecifiedTarget invoke(ElementMatcher<? super MethodDescription> matcher) {
+ return invoke(matcher, MethodGraph.Compiler.DEFAULT);
+ }
+
+ /**
+ * Invokes a unique virtual method of the instrumented type that is matched by the specified matcher.
+ *
+ * @param matcher The matcher to identify the method to invoke.
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @return A method call for the uniquely identified method.
+ */
+ public static WithoutSpecifiedTarget invoke(ElementMatcher<? super MethodDescription> matcher, MethodGraph.Compiler methodGraphCompiler) {
+ return invoke(new MethodLocator.ForElementMatcher(matcher, methodGraphCompiler));
+ }
+
+ /**
+ * Invokes a method using the provided method locator.
+ *
+ * @param methodLocator The method locator to apply for locating the method to invoke given the instrumented
+ * method.
+ * @return A method call implementation that uses the provided method locator for resolving the method
+ * to be invoked.
+ */
+ public static WithoutSpecifiedTarget invoke(MethodLocator methodLocator) {
+ return new WithoutSpecifiedTarget(methodLocator);
+ }
+
+ /**
+ * Invokes the instrumented method recursively. Invoking this method on the same instance causes a {@link StackOverflowError} due to
+ * infinite recursion.
+ *
+ * @return A method call that invokes the method being instrumented.
+ */
+ public static WithoutSpecifiedTarget invokeSelf() {
+ return new WithoutSpecifiedTarget(MethodLocator.ForInstrumentedMethod.INSTANCE);
+ }
+
+ /**
+ * Invokes the instrumented method as a super method call on the instance itself. This is a shortcut for {@code invokeSelf().onSuper()}.
+ *
+ * @return A method call that invokes the method being instrumented as a super method call.
+ */
+ public static MethodCall invokeSuper() {
+ return invokeSelf().onSuper();
+ }
+
+ /**
+ * Implements a method by invoking the provided {@link Callable}. The return value of the provided object is casted to the implemented method's
+ * return type, if necessary.
+ *
+ * @param callable The callable to invoke when a method is intercepted.
+ * @return A composable method implementation that invokes the given callable.
+ */
+ public static Composable call(Callable<?> callable) {
+ try {
+ return invoke(Callable.class.getMethod("call")).on(callable, Callable.class).withAssigner(Assigner.DEFAULT, Assigner.Typing.DYNAMIC);
+ } catch (NoSuchMethodException exception) {
+ throw new IllegalStateException("Could not locate Callable::call method", exception);
+ }
+ }
+
+ /**
+ * Implements a method by invoking the provided {@link Runnable}. If the instrumented method returns a value, {@code null} is returned.
+ *
+ * @param runnable The runnable to invoke when a method is intercepted.
+ * @return A composable method implementation that invokes the given runnable.
+ */
+ public static Composable run(Runnable runnable) {
+ try {
+ return invoke(Runnable.class.getMethod("run")).on(runnable, Runnable.class).withAssigner(Assigner.DEFAULT, Assigner.Typing.DYNAMIC);
+ } catch (NoSuchMethodException exception) {
+ throw new IllegalStateException("Could not locate Runnable::run method", exception);
+ }
+ }
+
+ /**
+ * Invokes the given constructor in order to create an instance.
+ *
+ * @param constructor The constructor to invoke.
+ * @return A method call that invokes the given constructor without providing any arguments.
+ */
+ public static MethodCall construct(Constructor<?> constructor) {
+ return construct(new MethodDescription.ForLoadedConstructor(constructor));
+ }
+
+ /**
+ * Invokes the given constructor in order to create an instance.
+ *
+ * @param methodDescription A description of the constructor to invoke.
+ * @return A method call that invokes the given constructor without providing any arguments.
+ */
+ public static MethodCall construct(MethodDescription methodDescription) {
+ if (!methodDescription.isConstructor()) {
+ throw new IllegalArgumentException("Not a constructor: " + methodDescription);
+ }
+ return new MethodCall(new MethodLocator.ForExplicitMethod(methodDescription),
+ TargetHandler.ForConstructingInvocation.INSTANCE,
+ Collections.<ArgumentLoader.Factory>emptyList(),
+ MethodInvoker.ForContextualInvocation.INSTANCE,
+ TerminationHandler.RETURNING,
+ Assigner.DEFAULT,
+ Assigner.Typing.STATIC);
+ }
+
+ /**
+ * Defines a number of arguments to be handed to the method that is being invoked by this implementation. Any
+ * wrapper type instances for primitive values, instances of {@link java.lang.String} or {@code null} are loaded
+ * directly onto the operand stack. This might corrupt referential identity for these values. Any other values
+ * are stored within a {@code static} field that is added to the instrumented type.
+ *
+ * @param argument The arguments to provide to the method that is being called in their order.
+ * @return A method call that hands the provided arguments to the invoked method.
+ */
+ public MethodCall with(Object... argument) {
+ List<ArgumentLoader.Factory> argumentLoaders = new ArrayList<ArgumentLoader.Factory>(argument.length);
+ for (Object anArgument : argument) {
+ argumentLoaders.add(ArgumentLoader.ForStackManipulation.of(anArgument));
+ }
+ return with(argumentLoaders);
+ }
+
+ /**
+ * Defines the given types to be provided as arguments to the invoked method where the represented types
+ * are stored in the generated class's constant pool.
+ *
+ * @param typeDescription The type descriptions to provide as arguments.
+ * @return A method call that hands the provided arguments to the invoked method.
+ */
+ public MethodCall with(TypeDescription... typeDescription) {
+ List<ArgumentLoader.Factory> argumentLoaders = new ArrayList<ArgumentLoader.Factory>(typeDescription.length);
+ for (TypeDescription aTypeDescription : typeDescription) {
+ argumentLoaders.add(new ArgumentLoader.ForStackManipulation(ClassConstant.of(aTypeDescription), Class.class));
+ }
+ return with(argumentLoaders);
+ }
+
+ /**
+ * Defines the given enumeration values to be provided as arguments to the invoked method where the values
+ * are read from the enumeration class on demand.
+ *
+ * @param enumerationDescription The enumeration descriptions to provide as arguments.
+ * @return A method call that hands the provided arguments to the invoked method.
+ */
+ public MethodCall with(EnumerationDescription... enumerationDescription) {
+ List<ArgumentLoader.Factory> argumentLoaders = new ArrayList<ArgumentLoader.Factory>(enumerationDescription.length);
+ for (EnumerationDescription anEnumerationDescription : enumerationDescription) {
+ argumentLoaders.add(new ArgumentLoader.ForStackManipulation(FieldAccess.forEnumeration(anEnumerationDescription), anEnumerationDescription.getEnumerationType()));
+ }
+ return with(argumentLoaders);
+ }
+
+ /**
+ * Defines the given Java instances to be provided as arguments to the invoked method where the given
+ * instances are stored in the generated class's constant pool.
+ *
+ * @param javaConstant The Java instances to provide as arguments.
+ * @return A method call that hands the provided arguments to the invoked method.
+ */
+ public MethodCall with(JavaConstant... javaConstant) {
+ List<ArgumentLoader.Factory> argumentLoaders = new ArrayList<ArgumentLoader.Factory>(javaConstant.length);
+ for (JavaConstant aJavaConstant : javaConstant) {
+ argumentLoaders.add(new ArgumentLoader.ForStackManipulation(aJavaConstant.asStackManipulation(), aJavaConstant.getType()));
+ }
+ return with(argumentLoaders);
+ }
+
+ /**
+ * Defines a number of arguments to be handed to the method that is being invoked by this implementation. Any
+ * value is stored within a field in order to preserve referential identity. As an exception, the {@code null}
+ * value is not stored within a field.
+ *
+ * @param argument The arguments to provide to the method that is being called in their order.
+ * @return A method call that hands the provided arguments to the invoked method.
+ */
+ public MethodCall withReference(Object... argument) {
+ List<ArgumentLoader.Factory> argumentLoaders = new ArrayList<ArgumentLoader.Factory>(argument.length);
+ for (Object anArgument : argument) {
+ argumentLoaders.add(anArgument == null
+ ? ArgumentLoader.ForNullConstant.INSTANCE
+ : new ArgumentLoader.ForInstance.Factory(anArgument));
+ }
+ return with(argumentLoaders);
+ }
+
+ /**
+ * Defines a number of arguments of the instrumented method by their parameter indices to be handed
+ * to the invoked method as an argument.
+ *
+ * @param index The parameter indices of the instrumented method to be handed to the invoked method as an
+ * argument in their order. The indices are zero-based.
+ * @return A method call that hands the provided arguments to the invoked method.
+ */
+ public MethodCall withArgument(int... index) {
+ List<ArgumentLoader.Factory> argumentLoaders = new ArrayList<ArgumentLoader.Factory>(index.length);
+ for (int anIndex : index) {
+ if (anIndex < 0) {
+ throw new IllegalArgumentException("Negative index: " + anIndex);
+ }
+ argumentLoaders.add(new ArgumentLoader.ForMethodParameter.Factory(anIndex));
+ }
+ return with(argumentLoaders);
+ }
+
+ /**
+ * Adds all arguments of the instrumented method as arguments to the invoked method to this method call.
+ *
+ * @return A method call that hands all arguments of the instrumented method to the invoked method.
+ */
+ public MethodCall withAllArguments() {
+ return with(ArgumentLoader.ForMethodParameter.OfInstrumentedMethod.INSTANCE);
+ }
+
+ /**
+ * Adds an array containing all arguments of the instrumented method to this method call.
+ *
+ * @return A method call that adds an array containing all arguments of the instrumented method to the invoked method.
+ */
+ public MethodCall withArgumentArray() {
+ return with(ArgumentLoader.ForMethodParameterArray.ForInstrumentedMethod.INSTANCE);
+ }
+
+ /**
+ * <p>
+ * Creates a method call where the parameter with {@code index} is expected to be an array and where each element of the array
+ * is expected to represent an argument for the method being invoked.
+ * </p>
+ * <p>
+ * <b>Note</b>: This is typically used in combination with dynamic type assignments which is activated via
+ * {@link MethodCall#withAssigner(Assigner, Assigner.Typing)} using a {@link Assigner.Typing#DYNAMIC}.
+ * </p>
+ *
+ * @param index The index of the parameter.
+ * @return A method call that loads {@code size} elements from the array handed to the instrumented method as argument {@code index}.
+ */
+ public MethodCall withArgumentArrayElements(int index) {
+ if (index < 0) {
+ throw new IllegalArgumentException("A parameter index cannot be negative: " + index);
+ }
+ return with(new ArgumentLoader.ForMethodParameterArrayElement.OfInvokedMethod(index));
+ }
+
+ /**
+ * <p>
+ * Creates a method call where the parameter with {@code index} is expected to be an array and where {@code size} elements are loaded
+ * from the array as arguments for the invoked method.
+ * </p>
+ * <p>
+ * <b>Note</b>: This is typically used in combination with dynamic type assignments which is activated via
+ * {@link MethodCall#withAssigner(Assigner, Assigner.Typing)} using a {@link Assigner.Typing#DYNAMIC}.
+ * </p>
+ *
+ * @param index The index of the parameter.
+ * @param size The amount of elements to load from the array.
+ * @return A method call that loads {@code size} elements from the array handed to the instrumented method as argument {@code index}.
+ */
+ public MethodCall withArgumentArrayElements(int index, int size) {
+ return withArgumentArrayElements(index, 0, size);
+ }
+
+ /**
+ * <p>
+ * Creates a method call where the parameter with {@code index} is expected to be an array and where {@code size} elements are loaded
+ * from the array as arguments for the invoked method. The first element is loaded from index {@code start}.
+ * </p>
+ * <p>
+ * <b>Note</b>: This is typically used in combination with dynamic type assignments which is activated via
+ * {@link MethodCall#withAssigner(Assigner, Assigner.Typing)} using a {@link Assigner.Typing#DYNAMIC}.
+ * </p>
+ *
+ * @param index The index of the parameter.
+ * @param start The first array index to consider.
+ * @param size The amount of elements to load from the array with increasing index from {@code start}.
+ * @return A method call that loads {@code size} elements from the array handed to the instrumented method as argument {@code index}.
+ */
+ public MethodCall withArgumentArrayElements(int index, int start, int size) {
+ if (index < 0) {
+ throw new IllegalArgumentException("A parameter index cannot be negative: " + index);
+ } else if (start < 0) {
+ throw new IllegalArgumentException("An array index cannot be negative: " + start);
+ } else if (size == 0) {
+ return this;
+ } else if (size < 0) {
+ throw new IllegalArgumentException("Size cannot be negative: " + size);
+ }
+ List<ArgumentLoader.Factory> argumentLoaders = new ArrayList<ArgumentLoader.Factory>(size);
+ for (int position = 0; position < size; position++) {
+ argumentLoaders.add(new ArgumentLoader.ForMethodParameterArrayElement.OfParameter(index, start + position));
+ }
+ return with(argumentLoaders);
+ }
+
+ /**
+ * Assigns the {@code this} reference to the next parameter.
+ *
+ * @return This method call where the next parameter is a assigned a reference to the {@code this} reference
+ * of the instance of the intercepted method.
+ */
+ public MethodCall withThis() {
+ return with(ArgumentLoader.ForThisReference.Factory.INSTANCE);
+ }
+
+ /**
+ * Assigns the {@link java.lang.Class} value of the instrumented type.
+ *
+ * @return This method call where the next parameter is a assigned a reference to the {@link java.lang.Class}
+ * value of the instrumented type.
+ */
+ public MethodCall withOwnType() {
+ return with(ArgumentLoader.ForInstrumentedType.Factory.INSTANCE);
+ }
+
+ /**
+ * Defines a method call which fetches a value from a list of existing fields.
+ *
+ * @param name The names of the fields.
+ * @return A method call which assigns the next parameters to the values of the given fields.
+ */
+ public MethodCall withField(String... name) {
+ return withField(FieldLocator.ForClassHierarchy.Factory.INSTANCE, name);
+ }
+
+ /**
+ * Defines a method call which fetches a value from a list of existing fields.
+ *
+ * @param fieldLocatorFactory The field locator factory to use.
+ * @param name The names of the fields.
+ * @return A method call which assigns the next parameters to the values of the given fields.
+ */
+ public MethodCall withField(FieldLocator.Factory fieldLocatorFactory, String... name) {
+ List<ArgumentLoader.Factory> argumentLoaders = new ArrayList<ArgumentLoader.Factory>(name.length);
+ for (String aFieldName : name) {
+ argumentLoaders.add(new ArgumentLoader.ForField.Factory(aFieldName, fieldLocatorFactory));
+ }
+ return with(argumentLoaders);
+ }
+
+ /**
+ * Adds a stack manipulation as an assignment to the next parameter.
+ *
+ * @param stackManipulation The stack manipulation loading the value.
+ * @param type The type of the argument being loaded.
+ * @return A method call that adds the stack manipulation as the next argument to the invoked method.
+ */
+ public MethodCall with(StackManipulation stackManipulation, Type type) {
+ return with(stackManipulation, TypeDefinition.Sort.describe(type));
+ }
+
+ /**
+ * Adds a stack manipulation as an assignment to the next parameter.
+ *
+ * @param stackManipulation The stack manipulation loading the value.
+ * @param typeDefinition The type of the argument being loaded.
+ * @return A method call that adds the stack manipulation as the next argument to the invoked method.
+ */
+ public MethodCall with(StackManipulation stackManipulation, TypeDefinition typeDefinition) {
+ return with(new ArgumentLoader.ForStackManipulation(stackManipulation, typeDefinition));
+ }
+
+ /**
+ * Defines a method call that resolves arguments by the supplied argument loader factories.
+ *
+ * @param argumentLoader The argument loaders to apply to the subsequent arguments of the
+ * @return A method call that adds the arguments of the supplied argument loaders to the invoked method.
+ */
+ public MethodCall with(ArgumentLoader.Factory... argumentLoader) {
+ return with(Arrays.asList(argumentLoader));
+ }
+
+ /**
+ * Defines a method call that resolves arguments by the supplied argument loader factories.
+ *
+ * @param argumentLoaders The argument loaders to apply to the subsequent arguments of the
+ * @return A method call that adds the arguments of the supplied argument loaders to the invoked method.
+ */
+ public MethodCall with(List<? extends ArgumentLoader.Factory> argumentLoaders) {
+ return new MethodCall(methodLocator,
+ targetHandler,
+ CompoundList.of(this.argumentLoaders, argumentLoaders),
+ methodInvoker,
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ /**
+ * Defines an assigner to be used for assigning values to the parameters of the invoked method. This assigner
+ * is also used for assigning the invoked method's return value to the return type of the instrumented method,
+ * if this method is not chained with
+ * {@link net.bytebuddy.implementation.MethodCall#andThen(Implementation)} such
+ * that a return value of this method call is discarded.
+ *
+ * @param assigner The assigner to use.
+ * @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
+ * @return This method call using the provided assigner.
+ */
+ public Implementation.Composable withAssigner(Assigner assigner, Assigner.Typing typing) {
+ return new MethodCall(methodLocator,
+ targetHandler,
+ argumentLoaders,
+ methodInvoker,
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ @Override
+ public Implementation andThen(Implementation implementation) {
+ return new Implementation.Compound(new MethodCall(methodLocator,
+ targetHandler,
+ argumentLoaders,
+ methodInvoker,
+ TerminationHandler.DROPPING,
+ assigner,
+ typing), implementation);
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ for (ArgumentLoader.Factory argumentLoader : argumentLoaders) {
+ instrumentedType = argumentLoader.prepare(instrumentedType);
+ }
+ return targetHandler.prepare(instrumentedType);
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return new Appender(implementationTarget);
+ }
+
+ /**
+ * A method locator is responsible for identifying the method that is to be invoked
+ * by a {@link net.bytebuddy.implementation.MethodCall}.
+ */
+ public interface MethodLocator {
+
+ /**
+ * Resolves the method to be invoked.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param instrumentedMethod The method being instrumented.
+ * @return The method to invoke.
+ */
+ MethodDescription resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod);
+
+ /**
+ * A method locator that simply returns the intercepted method.
+ */
+ enum ForInstrumentedMethod implements MethodLocator {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public MethodDescription resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
+ return instrumentedMethod;
+ }
+ }
+
+ /**
+ * Invokes a given method.
+ */
+ @EqualsAndHashCode
+ class ForExplicitMethod implements MethodLocator {
+
+ /**
+ * The method to be invoked.
+ */
+ private final MethodDescription methodDescription;
+
+ /**
+ * Creates a new method locator for a given method.
+ *
+ * @param methodDescription The method to be invoked.
+ */
+ protected ForExplicitMethod(MethodDescription methodDescription) {
+ this.methodDescription = methodDescription;
+ }
+
+ @Override
+ public MethodDescription resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
+ return methodDescription;
+ }
+ }
+
+ /**
+ * A method locator that identifies a unique virtual method.
+ */
+ @EqualsAndHashCode
+ class ForElementMatcher implements MethodLocator {
+
+ /**
+ * The matcher to use.
+ */
+ private final ElementMatcher<? super MethodDescription> matcher;
+
+ /**
+ * The method graph compiler to use.
+ */
+ private final MethodGraph.Compiler methodGraphCompiler;
+
+ /**
+ * Creates a new method locator for an element matcher.
+ *
+ * @param matcher The matcher to use.
+ * @param methodGraphCompiler The method graph compiler to use.
+ */
+ protected ForElementMatcher(ElementMatcher<? super MethodDescription> matcher, MethodGraph.Compiler methodGraphCompiler) {
+ this.matcher = matcher;
+ this.methodGraphCompiler = methodGraphCompiler;
+ }
+
+ @Override
+ public MethodDescription resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
+ MethodList<?> candidates = methodGraphCompiler.compile(instrumentedType).listNodes().asMethodList().filter(matcher);
+ if (candidates.size() == 1) {
+ return candidates.getOnly();
+ } else {
+ throw new IllegalStateException(instrumentedType + " does not define exactly one virtual method for " + matcher);
+ }
+ }
+ }
+ }
+
+ /**
+ * An argument loader is responsible for loading an argument for an invoked method
+ * onto the operand stack.
+ */
+ public interface ArgumentLoader {
+
+ /**
+ * Loads the argument that is represented by this instance onto the operand stack.
+ *
+ * @param target The target parameter.
+ * @param assigner The assigner to be used.
+ * @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
+ * @return The stack manipulation that loads the represented argument onto the stack.
+ */
+ StackManipulation resolve(ParameterDescription target, Assigner assigner, Assigner.Typing typing);
+
+ /**
+ * A factory that produces {@link ArgumentLoader}s for a given instrumented method.
+ */
+ interface Factory {
+
+ /**
+ * Prepares the instrumented type in order to allow the loading of the represented argument.
+ *
+ * @param instrumentedType The instrumented type.
+ * @return The prepared instrumented type.
+ */
+ InstrumentedType prepare(InstrumentedType instrumentedType);
+
+ /**
+ * Creates any number of argument loaders for an instrumentation.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param instrumentedMethod The instrumented method.
+ * @param invokedMethod The invoked method.
+ * @return Any number of argument loaders to supply for the method call.
+ */
+ List<ArgumentLoader> make(TypeDescription instrumentedType, MethodDescription instrumentedMethod, MethodDescription invokedMethod);
+ }
+
+ /**
+ * An argument loader that loads the {@code null} value onto the operand stack.
+ */
+ enum ForNullConstant implements ArgumentLoader, Factory {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public List<ArgumentLoader> make(TypeDescription instrumentedType, MethodDescription instrumentedMethod, MethodDescription invokedMethod) {
+ return Collections.<ArgumentLoader>singletonList(this);
+ }
+
+ @Override
+ public StackManipulation resolve(ParameterDescription target, Assigner assigner, Assigner.Typing typing) {
+ if (target.getType().isPrimitive()) {
+ throw new IllegalStateException("Cannot assign null to " + target);
+ }
+ return NullConstant.INSTANCE;
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+ }
+
+ /**
+ * An argument loader that assigns the {@code this} reference to a parameter.
+ */
+ @EqualsAndHashCode
+ class ForThisReference implements ArgumentLoader {
+
+ /**
+ * The instrumented type.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * Creates an argument loader that supplies the {@code this} instance as an argument.
+ *
+ * @param instrumentedType The instrumented type.
+ */
+ public ForThisReference(TypeDescription instrumentedType) {
+ this.instrumentedType = instrumentedType;
+ }
+
+ @Override
+ public StackManipulation resolve(ParameterDescription target, Assigner assigner, Assigner.Typing typing) {
+ StackManipulation stackManipulation = new StackManipulation.Compound(
+ MethodVariableAccess.loadThis(),
+ assigner.assign(instrumentedType.asGenericType(), target.getType(), typing));
+ if (!stackManipulation.isValid()) {
+ throw new IllegalStateException("Cannot assign " + instrumentedType + " to " + target);
+ }
+ return stackManipulation;
+ }
+
+ /**
+ * A factory for an argument loader that supplies the {@code this} value as an argument.
+ */
+ public enum Factory implements ArgumentLoader.Factory {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public List<ArgumentLoader> make(TypeDescription instrumentedType, MethodDescription instrumentedMethod, MethodDescription invokedMethod) {
+ if (instrumentedMethod.isStatic()) {
+ throw new IllegalStateException(instrumentedMethod + " is static and cannot supply an invoker instance");
+ }
+ return Collections.<ArgumentLoader>singletonList(new ForThisReference(instrumentedType));
+ }
+ }
+ }
+
+ /**
+ * Loads the instrumented type onto the operand stack.
+ */
+ @EqualsAndHashCode
+ class ForInstrumentedType implements ArgumentLoader {
+
+ /**
+ * The instrumented type.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * Creates an argument loader for supporting the instrumented type as a type constant as an argument.
+ *
+ * @param instrumentedType The instrumented type.
+ */
+ public ForInstrumentedType(TypeDescription instrumentedType) {
+ this.instrumentedType = instrumentedType;
+ }
+
+ @Override
+ public StackManipulation resolve(ParameterDescription target, Assigner assigner, Assigner.Typing typing) {
+ StackManipulation stackManipulation = new StackManipulation.Compound(
+ ClassConstant.of(instrumentedType),
+ assigner.assign(new TypeDescription.Generic.OfNonGenericType.ForLoadedType(Class.class), target.getType(), typing));
+ if (!stackManipulation.isValid()) {
+ throw new IllegalStateException("Cannot assign Class value to " + target);
+ }
+ return stackManipulation;
+ }
+
+ /**
+ * A factory for an argument loader that supplies the instrumented type as an argument.
+ */
+ public enum Factory implements ArgumentLoader.Factory {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public List<ArgumentLoader> make(TypeDescription instrumentedType, MethodDescription instrumentedMethod, MethodDescription invokedMethod) {
+ return Collections.<ArgumentLoader>singletonList(new ForInstrumentedType(instrumentedType));
+ }
+ }
+ }
+
+ /**
+ * Loads a parameter of the instrumented method onto the operand stack.
+ */
+ @EqualsAndHashCode
+ class ForMethodParameter implements ArgumentLoader {
+
+ /**
+ * The index of the parameter to be loaded onto the operand stack.
+ */
+ private final int index;
+
+ /**
+ * The instrumented method.
+ */
+ private final MethodDescription instrumentedMethod;
+
+ /**
+ * Creates an argument loader for a parameter of the instrumented method.
+ *
+ * @param index The index of the parameter to be loaded onto the operand stack.
+ * @param instrumentedMethod The instrumented method.
+ */
+ public ForMethodParameter(int index, MethodDescription instrumentedMethod) {
+ this.index = index;
+ this.instrumentedMethod = instrumentedMethod;
+ }
+
+ @Override
+ public StackManipulation resolve(ParameterDescription target, Assigner assigner, Assigner.Typing typing) {
+ ParameterDescription parameterDescription = instrumentedMethod.getParameters().get(index);
+ StackManipulation stackManipulation = new StackManipulation.Compound(
+ MethodVariableAccess.load(parameterDescription),
+ assigner.assign(parameterDescription.getType(), target.getType(), typing));
+ if (!stackManipulation.isValid()) {
+ throw new IllegalStateException("Cannot assign " + parameterDescription + " to " + target + " for " + instrumentedMethod);
+ }
+ return stackManipulation;
+ }
+
+ /**
+ * A factory for argument loaders that supplies all arguments of the instrumented method as arguments.
+ */
+ protected enum OfInstrumentedMethod implements ArgumentLoader.Factory {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public List<ArgumentLoader> make(TypeDescription instrumentedType, MethodDescription instrumentedMethod, MethodDescription invokedMethod) {
+ List<ArgumentLoader> argumentLoaders = new ArrayList<ArgumentLoader>(instrumentedMethod.getParameters().size());
+ for (ParameterDescription parameterDescription : instrumentedMethod.getParameters()) {
+ argumentLoaders.add(new ForMethodParameter(parameterDescription.getIndex(), instrumentedMethod));
+ }
+ return argumentLoaders;
+ }
+ }
+
+ /**
+ * A factory for an argument loader that supplies a method parameter as an argument.
+ */
+ @EqualsAndHashCode
+ protected static class Factory implements ArgumentLoader.Factory {
+
+ /**
+ * The index of the parameter to be loaded onto the operand stack.
+ */
+ private final int index;
+
+ /**
+ * Creates a factory for an argument loader that supplies a method parameter as an argument.
+ *
+ * @param index The index of the parameter to supply.
+ */
+ public Factory(int index) {
+ this.index = index;
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public List<ArgumentLoader> make(TypeDescription instrumentedType, MethodDescription instrumentedMethod, MethodDescription invokedMethod) {
+ if (index >= instrumentedMethod.getParameters().size()) {
+ throw new IllegalStateException(instrumentedMethod + " does not have a parameter with index " + index);
+ }
+ return Collections.<ArgumentLoader>singletonList(new ForMethodParameter(index, instrumentedMethod));
+ }
+ }
+ }
+
+ /**
+ * Loads an array containing all arguments of a method.
+ */
+ @EqualsAndHashCode
+ class ForMethodParameterArray implements ArgumentLoader {
+
+ /**
+ * The parameters to load.
+ */
+ private final ParameterList<?> parameters;
+
+ /**
+ * Creates an argument loader that loads the supplied parameters onto the operand stack.
+ *
+ * @param parameters The parameters to load.
+ */
+ public ForMethodParameterArray(ParameterList<?> parameters) {
+ this.parameters = parameters;
+ }
+
+ @Override
+ public StackManipulation resolve(ParameterDescription target, Assigner assigner, Assigner.Typing typing) {
+ TypeDescription.Generic componentType;
+ if (target.getType().represents(Object.class)) {
+ componentType = TypeDescription.Generic.OBJECT;
+ } else if (target.getType().isArray()) {
+ componentType = target.getType().getComponentType();
+ } else {
+ throw new IllegalStateException();
+ }
+ List<StackManipulation> stackManipulations = new ArrayList<StackManipulation>(parameters.size());
+ for (ParameterDescription parameter : parameters) {
+ StackManipulation stackManipulation = new StackManipulation.Compound(
+ MethodVariableAccess.load(parameter),
+ assigner.assign(parameter.getType(), componentType, typing)
+ );
+ if (stackManipulation.isValid()) {
+ stackManipulations.add(stackManipulation);
+ } else {
+ throw new IllegalStateException("Cannot assign " + parameter + " to " + componentType);
+ }
+ }
+ return new StackManipulation.Compound(ArrayFactory.forType(componentType).withValues(stackManipulations));
+ }
+
+ /**
+ * A factory that creates an arguments loader that loads all parameters of the instrumented method contained in an array.
+ */
+ public enum ForInstrumentedMethod implements ArgumentLoader.Factory {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public List<ArgumentLoader> make(TypeDescription instrumentedType, MethodDescription instrumentedMethod, MethodDescription invokedMethod) {
+ return Collections.<ArgumentLoader>singletonList(new ForMethodParameterArray(instrumentedMethod.getParameters()));
+ }
+ }
+ }
+
+ /**
+ * An argument loader that loads an element of a parameter of an array type.
+ */
+ @EqualsAndHashCode
+ class ForMethodParameterArrayElement implements ArgumentLoader {
+
+ /**
+ * The parameter to load the array from.
+ */
+ private final ParameterDescription parameterDescription;
+
+ /**
+ * The array index to load.
+ */
+ private final int index;
+
+ /**
+ * Creates an argument loader for a parameter of the instrumented method where an array element is assigned to the invoked method.
+ *
+ * @param parameterDescription The parameter from which to load an array element.
+ * @param index The array index to load.
+ */
+ public ForMethodParameterArrayElement(ParameterDescription parameterDescription, int index) {
+ this.parameterDescription = parameterDescription;
+ this.index = index;
+ }
+
+ @Override
+ public StackManipulation resolve(ParameterDescription target, Assigner assigner, Assigner.Typing typing) {
+ StackManipulation stackManipulation = new StackManipulation.Compound(
+ MethodVariableAccess.load(parameterDescription),
+ IntegerConstant.forValue(index),
+ ArrayAccess.of(parameterDescription.getType().getComponentType()).load(),
+ assigner.assign(parameterDescription.getType().getComponentType(), target.getType(), typing)
+ );
+ if (!stackManipulation.isValid()) {
+ throw new IllegalStateException("Cannot assign " + parameterDescription.getType().getComponentType() + " to " + target);
+ }
+ return stackManipulation;
+ }
+
+ /**
+ * Creates an argument loader for an array element that of a specific parameter.
+ */
+ @EqualsAndHashCode
+ protected static class OfParameter implements ArgumentLoader.Factory {
+
+ /**
+ * The parameter index.
+ */
+ private final int index;
+
+ /**
+ * The array index to load.
+ */
+ private final int arrayIndex;
+
+ /**
+ * Creates a factory for an argument loader that loads a given parameter's array value.
+ *
+ * @param index The index of the parameter.
+ * @param arrayIndex The array index to load.
+ */
+ public OfParameter(int index, int arrayIndex) {
+ this.index = index;
+ this.arrayIndex = arrayIndex;
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public List<ArgumentLoader> make(TypeDescription instrumentedType, MethodDescription instrumentedMethod, MethodDescription invokedMethod) {
+ if (instrumentedMethod.getParameters().size() <= index) {
+ throw new IllegalStateException(instrumentedMethod + " does not declare a parameter with index " + index);
+ } else if (!instrumentedMethod.getParameters().get(index).getType().isArray()) {
+ throw new IllegalStateException("Cannot access an item from non-array parameter " + instrumentedMethod.getParameters().get(index));
+ }
+ return Collections.<ArgumentLoader>singletonList(new ForMethodParameterArrayElement(instrumentedMethod.getParameters().get(index), arrayIndex));
+ }
+ }
+
+ /**
+ * An argument loader factory that loads an array element from a parameter for each argument of the invoked method.
+ */
+ @EqualsAndHashCode
+ public static class OfInvokedMethod implements ArgumentLoader.Factory {
+
+ /**
+ * The parameter index.
+ */
+ private final int index;
+
+ /**
+ * Creates an argument loader factory for an invoked method.
+ *
+ * @param index The parameter index.
+ */
+ public OfInvokedMethod(int index) {
+ this.index = index;
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public List<ArgumentLoader> make(TypeDescription instrumentedType, MethodDescription instrumentedMethod, MethodDescription invokedMethod) {
+ if (instrumentedMethod.getParameters().size() <= index) {
+ throw new IllegalStateException(instrumentedMethod + " does not declare a parameter with index " + index);
+ } else if (!instrumentedMethod.getParameters().get(index).getType().isArray()) {
+ throw new IllegalStateException("Cannot access an item from non-array parameter " + instrumentedMethod.getParameters().get(index));
+ }
+ List<ArgumentLoader> argumentLoaders = new ArrayList<ArgumentLoader>(instrumentedMethod.getParameters().size());
+ for (int index = 0; index < invokedMethod.getParameters().size(); index++) {
+ argumentLoaders.add(new ForMethodParameterArrayElement(instrumentedMethod.getParameters().get(this.index), index++));
+ }
+ return argumentLoaders;
+ }
+ }
+ }
+
+ /**
+ * Loads a value onto the operand stack that is stored in a static field.
+ */
+ @EqualsAndHashCode
+ class ForInstance implements ArgumentLoader {
+
+ /**
+ * The description of the field.
+ */
+ private final FieldDescription fieldDescription;
+
+ /**
+ * Creates an argument loader that supplies the value of a static field as an argument.
+ *
+ * @param fieldDescription The description of the field.
+ */
+ public ForInstance(FieldDescription fieldDescription) {
+ this.fieldDescription = fieldDescription;
+ }
+
+ @Override
+ public StackManipulation resolve(ParameterDescription target, Assigner assigner, Assigner.Typing typing) {
+ StackManipulation stackManipulation = new StackManipulation.Compound(
+ FieldAccess.forField(fieldDescription).read(),
+ assigner.assign(fieldDescription.getType(), target.getType(), typing));
+ if (!stackManipulation.isValid()) {
+ throw new IllegalStateException("Cannot assign " + fieldDescription.getType() + " to " + target);
+ }
+ return stackManipulation;
+ }
+
+ /**
+ * A factory that supplies the value of a static field as an argument.
+ */
+ @EqualsAndHashCode(exclude = "name")
+ protected static class Factory implements ArgumentLoader.Factory {
+
+ /**
+ * The name prefix of the field to store the argument.
+ */
+ private static final String FIELD_PREFIX = "methodCall";
+
+ /**
+ * The value to be stored in the field.
+ */
+ private final Object value;
+
+ /**
+ * The name of the field.
+ */
+ private final String name;
+
+ /**
+ * Creates a factory that loads the value of a static field as an argument.
+ *
+ * @param value The value to supply as an argument.
+ */
+ public Factory(Object value) {
+ this.value = value;
+ name = String.format("%s$%s", FIELD_PREFIX, RandomString.make());
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType
+ .withField(new FieldDescription.Token(name,
+ Opcodes.ACC_SYNTHETIC | Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC,
+ new TypeDescription.Generic.OfNonGenericType.ForLoadedType(value.getClass())))
+ .withInitializer(new LoadedTypeInitializer.ForStaticField(name, value));
+ }
+
+ @Override
+ public List<ArgumentLoader> make(TypeDescription instrumentedType, MethodDescription instrumentedMethod, MethodDescription invokedMethod) {
+ return Collections.<ArgumentLoader>singletonList(new ForInstance(instrumentedType.getDeclaredFields().filter(named(name)).getOnly()));
+ }
+ }
+ }
+
+ /**
+ * Loads the value of an existing field onto the operand stack.
+ */
+ @EqualsAndHashCode
+ class ForField implements ArgumentLoader {
+
+ /**
+ * The field containing the loaded value.
+ */
+ private final FieldDescription fieldDescription;
+
+ /**
+ * The instrumented method.
+ */
+ private final MethodDescription instrumentedMethod;
+
+ /**
+ * Creates a new argument loader for loading an existing field.
+ *
+ * @param fieldDescription The field containing the loaded value.
+ * @param instrumentedMethod The instrumented method.
+ */
+ public ForField(FieldDescription fieldDescription, MethodDescription instrumentedMethod) {
+ this.fieldDescription = fieldDescription;
+ this.instrumentedMethod = instrumentedMethod;
+ }
+
+ @Override
+ public StackManipulation resolve(ParameterDescription target, Assigner assigner, Assigner.Typing typing) {
+ if (!fieldDescription.isStatic() && instrumentedMethod.isStatic()) {
+ throw new IllegalStateException("Cannot access non-static " + fieldDescription + " from " + instrumentedMethod);
+ }
+ StackManipulation stackManipulation = new StackManipulation.Compound(
+ fieldDescription.isStatic()
+ ? StackManipulation.Trivial.INSTANCE
+ : MethodVariableAccess.loadThis(),
+ FieldAccess.forField(fieldDescription).read(),
+ assigner.assign(fieldDescription.getType(), target.getType(), typing)
+ );
+ if (!stackManipulation.isValid()) {
+ throw new IllegalStateException("Cannot assign " + fieldDescription + " to " + target);
+ }
+ return stackManipulation;
+ }
+
+ /**
+ * A factory for an argument loaded that loads the value of an existing field as an argument.
+ */
+ @EqualsAndHashCode
+ protected static class Factory implements ArgumentLoader.Factory {
+
+ /**
+ * The name of the field.
+ */
+ private final String name;
+
+ /**
+ * The field locator to use.
+ */
+ private final FieldLocator.Factory fieldLocatorFactory;
+
+ /**
+ * Creates a new argument loader for an existing field.
+ *
+ * @param name The name of the field.
+ * @param fieldLocatorFactory The field locator to use.
+ */
+ public Factory(String name, FieldLocator.Factory fieldLocatorFactory) {
+ this.name = name;
+ this.fieldLocatorFactory = fieldLocatorFactory;
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public List<ArgumentLoader> make(TypeDescription instrumentedType, MethodDescription instrumentedMethod, MethodDescription invokedMethod) {
+ FieldLocator.Resolution resolution = fieldLocatorFactory.make(instrumentedType).locate(name);
+ if (!resolution.isResolved()) {
+ throw new IllegalStateException("Could not locate field '" + name + "' on " + instrumentedType);
+ }
+ return Collections.<ArgumentLoader>singletonList(new ForField(resolution.getField(), instrumentedMethod));
+ }
+ }
+ }
+
+ /**
+ * Loads a stack manipulation resulting in a specific type as an argument.
+ */
+ @EqualsAndHashCode
+ class ForStackManipulation implements ArgumentLoader, Factory {
+
+ /**
+ * The stack manipulation to load.
+ */
+ private final StackManipulation stackManipulation;
+
+ /**
+ * The type of the resulting value.
+ */
+ private final TypeDefinition typeDefinition;
+
+ /**
+ * Creates an argument loader that loads a stack manipulation as an argument.
+ *
+ * @param stackManipulation The stack manipulation to load.
+ * @param type The type of the resulting value.
+ */
+ public ForStackManipulation(StackManipulation stackManipulation, Type type) {
+ this(stackManipulation, TypeDescription.Generic.Sort.describe(type));
+ }
+
+ /**
+ * Creates an argument loader that loads a stack manipulation as an argument.
+ *
+ * @param stackManipulation The stack manipulation to load.
+ * @param typeDefinition The type of the resulting value.
+ */
+ public ForStackManipulation(StackManipulation stackManipulation, TypeDefinition typeDefinition) {
+ this.stackManipulation = stackManipulation;
+ this.typeDefinition = typeDefinition;
+ }
+
+ /**
+ * Creates an argument loader that loads the supplied value as a constant. If the value cannot be represented
+ * in the constant pool, a field is created to store the value.
+ *
+ * @param value The value to load as an argument or {@code null}.
+ * @return An appropriate argument loader.
+ */
+ public static ArgumentLoader.Factory of(Object value) {
+ if (value == null) {
+ return ForNullConstant.INSTANCE;
+ } else if (value instanceof String) {
+ return new ForStackManipulation(new TextConstant((String) value), String.class);
+ } else if (value instanceof Boolean) {
+ return new ForStackManipulation(IntegerConstant.forValue((Boolean) value), boolean.class);
+ } else if (value instanceof Byte) {
+ return new ForStackManipulation(IntegerConstant.forValue((Byte) value), byte.class);
+ } else if (value instanceof Short) {
+ return new ForStackManipulation(IntegerConstant.forValue((Short) value), short.class);
+ } else if (value instanceof Character) {
+ return new ForStackManipulation(IntegerConstant.forValue((Character) value), char.class);
+ } else if (value instanceof Integer) {
+ return new ForStackManipulation(IntegerConstant.forValue((Integer) value), int.class);
+ } else if (value instanceof Long) {
+ return new ForStackManipulation(LongConstant.forValue((Long) value), long.class);
+ } else if (value instanceof Float) {
+ return new ForStackManipulation(FloatConstant.forValue((Float) value), float.class);
+ } else if (value instanceof Double) {
+ return new ForStackManipulation(DoubleConstant.forValue((Double) value), double.class);
+ } else if (value instanceof Class) {
+ return new ForStackManipulation(ClassConstant.of(new TypeDescription.ForLoadedType((Class<?>) value)), Class.class);
+ } else if (JavaType.METHOD_HANDLE.getTypeStub().isInstance(value)) {
+ return new ForStackManipulation(JavaConstant.MethodHandle.ofLoaded(value).asStackManipulation(), JavaType.METHOD_HANDLE.getTypeStub());
+ } else if (JavaType.METHOD_TYPE.getTypeStub().isInstance(value)) {
+ return new ForStackManipulation(JavaConstant.MethodType.ofLoaded(value).asStackManipulation(), JavaType.METHOD_TYPE.getTypeStub());
+ } else if (value instanceof Enum<?>) {
+ EnumerationDescription enumerationDescription = new EnumerationDescription.ForLoadedEnumeration((Enum<?>) value);
+ return new ForStackManipulation(FieldAccess.forEnumeration(enumerationDescription), enumerationDescription.getEnumerationType());
+ } else {
+ return new ForInstance.Factory(value);
+ }
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public List<ArgumentLoader> make(TypeDescription instrumentedType, MethodDescription instrumentedMethod, MethodDescription invokedMethod) {
+ return Collections.<ArgumentLoader>singletonList(this);
+ }
+
+ @Override
+ public StackManipulation resolve(ParameterDescription target, Assigner assigner, Assigner.Typing typing) {
+ StackManipulation assigment = assigner.assign(typeDefinition.asGenericType(), target.getType(), typing);
+ if (!assigment.isValid()) {
+ throw new IllegalStateException("Cannot assign " + target + " to " + typeDefinition);
+ }
+ return new StackManipulation.Compound(stackManipulation, assigment);
+ }
+ }
+ }
+
+ /**
+ * A target handler is responsible for invoking a method for a
+ * {@link net.bytebuddy.implementation.MethodCall}.
+ */
+ protected interface TargetHandler extends InstrumentedType.Prepareable {
+
+ /**
+ * Creates a stack manipulation that represents the method's invocation.
+ *
+ * @param invokedMethod The method to be invoked.
+ * @param instrumentedMethod The instrumented method.
+ * @param instrumentedType The instrumented type. @return A stack manipulation that invokes the method.
+ * @param assigner The assigner to use.
+ * @param typing The typing to apply.
+ * @return A stack manipulation that loads the method target onto the operand stack.
+ */
+ StackManipulation resolve(MethodDescription invokedMethod,
+ MethodDescription instrumentedMethod,
+ TypeDescription instrumentedType,
+ Assigner assigner,
+ Assigner.Typing typing);
+
+ /**
+ * A target handler that invokes a method either on the instance of the instrumented
+ * type or as a static method.
+ */
+ enum ForSelfOrStaticInvocation implements TargetHandler {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public StackManipulation resolve(MethodDescription invokedMethod, MethodDescription instrumentedMethod, TypeDescription instrumentedType, Assigner assigner, Assigner.Typing typing) {
+ return new StackManipulation.Compound(
+ invokedMethod.isStatic()
+ ? StackManipulation.Trivial.INSTANCE
+ : MethodVariableAccess.loadThis(),
+ invokedMethod.isConstructor()
+ ? Duplication.SINGLE
+ : StackManipulation.Trivial.INSTANCE
+ );
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+ }
+
+ /**
+ * Invokes a method in order to construct a new instance.
+ */
+ enum ForConstructingInvocation implements TargetHandler {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public StackManipulation resolve(MethodDescription invokedMethod, MethodDescription instrumentedMethod, TypeDescription instrumentedType, Assigner assigner, Assigner.Typing typing) {
+ return new StackManipulation.Compound(TypeCreation.of(invokedMethod.getDeclaringType().asErasure()), Duplication.SINGLE);
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+ }
+
+ /**
+ * A target handler that invokes a method on an instance that is stored in a static field.
+ */
+ @EqualsAndHashCode(exclude = "name")
+ class ForValue implements TargetHandler {
+
+ /**
+ * The name prefix of the field to store the instance.
+ */
+ private static final String FIELD_PREFIX = "invocationTarget";
+
+ /**
+ * The target on which the method is to be invoked.
+ */
+ private final Object target;
+
+ /**
+ * The type of the field.
+ */
+ private final TypeDescription.Generic fieldType;
+
+ /**
+ * The name of the field to store the target.
+ */
+ private final String name;
+
+ /**
+ * Creates a new target handler for a static field.
+ *
+ * @param target The target on which the method is to be invoked.
+ * @param fieldType The type of the field.
+ */
+ protected ForValue(Object target, TypeDescription.Generic fieldType) {
+ this.target = target;
+ this.fieldType = fieldType;
+ name = String.format("%s$%s", FIELD_PREFIX, RandomString.make());
+ }
+
+ @Override
+ public StackManipulation resolve(MethodDescription invokedMethod,
+ MethodDescription instrumentedMethod,
+ TypeDescription instrumentedType,
+ Assigner assigner,
+ Assigner.Typing typing) {
+ StackManipulation stackManipulation = assigner.assign(fieldType, invokedMethod.getDeclaringType().asGenericType(), typing);
+ if (!stackManipulation.isValid()) {
+ throw new IllegalStateException("Cannot invoke " + invokedMethod + " on " + fieldType);
+ }
+ return new StackManipulation.Compound(
+ FieldAccess.forField(instrumentedType.getDeclaredFields().filter(named(name)).getOnly()).read(),
+ stackManipulation
+ );
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType
+ .withField(new FieldDescription.Token(name, Opcodes.ACC_SYNTHETIC | Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, fieldType))
+ .withInitializer(new LoadedTypeInitializer.ForStaticField(name, target));
+ }
+ }
+
+ /**
+ * Creates a target handler that stores the instance to invoke a method on in an instance field.
+ */
+ @EqualsAndHashCode
+ class ForField implements TargetHandler {
+
+ /**
+ * The name of the field.
+ */
+ private final String name;
+
+ /**
+ * The field locator factory to use.
+ */
+ private final FieldLocator.Factory fieldLocatorFactory;
+
+ /**
+ * Creates a new target handler for storing a method invocation target in an
+ * instance field.
+ *
+ * @param name The name of the field.
+ * @param fieldLocatorFactory The field locator factory to use.
+ */
+ protected ForField(String name, FieldLocator.Factory fieldLocatorFactory) {
+ this.name = name;
+ this.fieldLocatorFactory = fieldLocatorFactory;
+ }
+
+ @Override
+ public StackManipulation resolve(MethodDescription invokedMethod, MethodDescription instrumentedMethod, TypeDescription instrumentedType, Assigner assigner, Assigner.Typing typing) {
+ FieldLocator.Resolution resolution = fieldLocatorFactory.make(instrumentedType).locate(name);
+ if (!resolution.isResolved()) {
+ throw new IllegalStateException("Could not locate field name " + name + " on " + instrumentedType);
+ } else if (!resolution.getField().isStatic() && !instrumentedType.isAssignableTo(resolution.getField().getDeclaringType().asErasure())) {
+ throw new IllegalStateException("Cannot access " + resolution.getField() + " from " + instrumentedType);
+ }
+ StackManipulation stackManipulation = assigner.assign(resolution.getField().getType(), invokedMethod.getDeclaringType().asGenericType(), typing);
+ if (!stackManipulation.isValid()) {
+ throw new IllegalStateException("Cannot invoke " + invokedMethod + " on " + resolution.getField());
+ }
+ return new StackManipulation.Compound(invokedMethod.isStatic()
+ ? StackManipulation.Trivial.INSTANCE
+ : MethodVariableAccess.loadThis(), FieldAccess.forField(resolution.getField()).read(), stackManipulation);
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+ }
+
+ /**
+ * A target handler that loads the parameter of the given index as the target object.
+ */
+ @EqualsAndHashCode
+ class ForMethodParameter implements TargetHandler {
+
+ /**
+ * The index of the instrumented method's parameter that is the target of the method invocation.
+ */
+ private final int index;
+
+ /**
+ * Creates a new target handler for the instrumented method's argument.
+ *
+ * @param index The index of the instrumented method's parameter that is the target of the method invocation.
+ */
+ protected ForMethodParameter(int index) {
+ this.index = index;
+ }
+
+ @Override
+ public StackManipulation resolve(MethodDescription invokedMethod,
+ MethodDescription instrumentedMethod,
+ TypeDescription instrumentedType,
+ Assigner assigner,
+ Assigner.Typing typing) {
+ if (instrumentedMethod.getParameters().size() < index) {
+ throw new IllegalArgumentException(instrumentedMethod + " does not have a parameter with index " + index);
+ }
+ ParameterDescription parameterDescription = instrumentedMethod.getParameters().get(index);
+ StackManipulation stackManipulation = assigner.assign(parameterDescription.getType(), invokedMethod.getDeclaringType().asGenericType(), typing);
+ if (!stackManipulation.isValid()) {
+ throw new IllegalStateException("Cannot invoke " + invokedMethod + " on " + parameterDescription.getType());
+ }
+ return new StackManipulation.Compound(MethodVariableAccess.load(parameterDescription), stackManipulation);
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+ }
+ }
+
+ /**
+ * A method invoker is responsible for creating a method invocation that is to be applied by a
+ * {@link net.bytebuddy.implementation.MethodCall}.
+ */
+ protected interface MethodInvoker {
+
+ /**
+ * Invokes the method.
+ *
+ * @param invokedMethod The method to be invoked.
+ * @param implementationTarget The implementation target of the instrumented instance.
+ * @return A stack manipulation that represents the method invocation.
+ */
+ StackManipulation invoke(MethodDescription invokedMethod, Target implementationTarget);
+
+ /**
+ * Applies a contextual invocation of the provided method, i.e. a static invocation for static methods,
+ * a special invocation for constructors and private methods and a virtual invocation for any other method.
+ */
+ enum ForContextualInvocation implements MethodInvoker {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public StackManipulation invoke(MethodDescription invokedMethod, Target implementationTarget) {
+ if (invokedMethod.isVirtual() && !invokedMethod.isInvokableOn(implementationTarget.getInstrumentedType())) {
+ throw new IllegalStateException("Cannot invoke " + invokedMethod + " on " + implementationTarget.getInstrumentedType());
+ } else if (!invokedMethod.isVisibleTo(implementationTarget.getInstrumentedType())) {
+ throw new IllegalStateException(invokedMethod + " is not visible to " + implementationTarget.getInstrumentedType());
+ }
+ return invokedMethod.isVirtual()
+ ? MethodInvocation.invoke(invokedMethod).virtual(implementationTarget.getInstrumentedType())
+ : MethodInvocation.invoke(invokedMethod);
+ }
+ }
+
+ /**
+ * Applies a virtual invocation on a given type.
+ */
+ @EqualsAndHashCode
+ class ForVirtualInvocation implements MethodInvoker {
+
+ /**
+ * The type description to virtually invoke the method upon.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new method invoking for a virtual method invocation.
+ *
+ * @param typeDescription The type description to virtually invoke the method upon.
+ */
+ protected ForVirtualInvocation(TypeDescription typeDescription) {
+ this.typeDescription = typeDescription;
+ }
+
+ /**
+ * Creates a new method invoking for a virtual method invocation.
+ *
+ * @param type The type to virtually invoke the method upon.
+ */
+ protected ForVirtualInvocation(Class<?> type) {
+ this(new TypeDescription.ForLoadedType(type));
+ }
+
+ @Override
+ public StackManipulation invoke(MethodDescription invokedMethod, Target implementationTarget) {
+ if (!invokedMethod.isVirtual()) {
+ throw new IllegalStateException("Cannot invoke " + invokedMethod + " virtually");
+ } else if (!invokedMethod.isInvokableOn(typeDescription.asErasure())) {
+ throw new IllegalStateException("Cannot invoke " + invokedMethod + " on " + typeDescription);
+ } else if (!typeDescription.asErasure().isAccessibleTo(implementationTarget.getInstrumentedType())) {
+ throw new IllegalStateException(typeDescription + " is not accessible to " + implementationTarget.getInstrumentedType());
+ }
+ return MethodInvocation.invoke(invokedMethod).virtual(typeDescription.asErasure());
+ }
+
+ /**
+ * A method invoker for a virtual method that uses an implicit target type.
+ */
+ public enum WithImplicitType implements MethodInvoker {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public StackManipulation invoke(MethodDescription invokedMethod, Target implementationTarget) {
+ if (!invokedMethod.isVirtual()) {
+ throw new IllegalStateException("Cannot invoke " + invokedMethod + " virtually");
+ }
+ return MethodInvocation.invoke(invokedMethod);
+ }
+ }
+ }
+
+ /**
+ * Applies a super method invocation of the provided method.
+ */
+ enum ForSuperMethodInvocation implements MethodInvoker {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public StackManipulation invoke(MethodDescription invokedMethod, Target implementationTarget) {
+ if (implementationTarget.getInstrumentedType().getSuperClass() == null) {
+ throw new IllegalStateException("Cannot invoke super method for " + implementationTarget.getInstrumentedType());
+ } else if (!invokedMethod.isInvokableOn(implementationTarget.getOriginType().asErasure())) {
+ throw new IllegalStateException("Cannot invoke " + invokedMethod + " as super method of " + implementationTarget.getInstrumentedType());
+ }
+ StackManipulation stackManipulation = implementationTarget.invokeDominant(invokedMethod.asSignatureToken());
+ if (!stackManipulation.isValid()) {
+ throw new IllegalStateException("Cannot invoke " + invokedMethod + " as a super method");
+ }
+ return stackManipulation;
+ }
+ }
+
+ /**
+ * Invokes a method as a Java 8 default method.
+ */
+ enum ForDefaultMethodInvocation implements MethodInvoker {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public StackManipulation invoke(MethodDescription invokedMethod, Target implementationTarget) {
+ if (!invokedMethod.isInvokableOn(implementationTarget.getInstrumentedType())) {
+ throw new IllegalStateException("Cannot invoke " + invokedMethod + " as default method of " + implementationTarget.getInstrumentedType());
+ }
+ StackManipulation stackManipulation = implementationTarget.invokeDefault(invokedMethod.asSignatureToken(), invokedMethod.getDeclaringType().asErasure());
+ if (!stackManipulation.isValid()) {
+ throw new IllegalStateException("Cannot invoke " + invokedMethod + " on " + implementationTarget.getInstrumentedType());
+ }
+ return stackManipulation;
+ }
+ }
+ }
+
+ /**
+ * A termination handler is responsible to handle the return value of a method that is invoked via a
+ * {@link net.bytebuddy.implementation.MethodCall}.
+ */
+ protected enum TerminationHandler {
+
+ /**
+ * A termination handler that returns the invoked method's return value.
+ */
+ RETURNING {
+ @Override
+ public StackManipulation resolve(MethodDescription invokedMethod, MethodDescription instrumentedMethod, Assigner assigner, Assigner.Typing typing) {
+ StackManipulation stackManipulation = assigner.assign(invokedMethod.isConstructor()
+ ? invokedMethod.getDeclaringType().asGenericType()
+ : invokedMethod.getReturnType(), instrumentedMethod.getReturnType(), typing);
+ if (!stackManipulation.isValid()) {
+ throw new IllegalStateException("Cannot return " + invokedMethod.getReturnType() + " from " + instrumentedMethod);
+ }
+ return new StackManipulation.Compound(stackManipulation, MethodReturn.of(instrumentedMethod.getReturnType()));
+ }
+ },
+
+ /**
+ * A termination handler that drops the invoked method's return value.
+ */
+ DROPPING {
+ @Override
+ protected StackManipulation resolve(MethodDescription invokedMethod, MethodDescription instrumentedMethod, Assigner assigner, Assigner.Typing typing) {
+ return Removal.of(invokedMethod.isConstructor()
+ ? invokedMethod.getDeclaringType()
+ : invokedMethod.getReturnType());
+ }
+ };
+
+ /**
+ * Returns a stack manipulation that handles the method return.
+ *
+ * @param invokedMethod The method that was invoked by the method call.
+ * @param instrumentedMethod The method being intercepted.
+ * @param assigner The assigner to be used.
+ * @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
+ * @return A stack manipulation that handles the method return.
+ */
+ protected abstract StackManipulation resolve(MethodDescription invokedMethod,
+ MethodDescription instrumentedMethod,
+ Assigner assigner,
+ Assigner.Typing typing);
+ }
+
+ /**
+ * Represents a {@link net.bytebuddy.implementation.MethodCall} that invokes a method without specifying
+ * an invocation method. Some methods can for example be invoked both virtually or as a super method invocation.
+ * Similarly, interface methods can be invoked virtually or as an explicit invocation of a default method. If
+ * no explicit invocation type is set, a method is always invoked virtually unless the method
+ * represents a static methods or a constructor.
+ */
+ public static class WithoutSpecifiedTarget extends MethodCall {
+
+ /**
+ * Creates a new method call without a specified target.
+ *
+ * @param methodLocator The method locator to use.
+ */
+ protected WithoutSpecifiedTarget(MethodLocator methodLocator) {
+ super(methodLocator,
+ TargetHandler.ForSelfOrStaticInvocation.INSTANCE,
+ Collections.<ArgumentLoader.Factory>emptyList(),
+ MethodInvoker.ForContextualInvocation.INSTANCE,
+ TerminationHandler.RETURNING,
+ Assigner.DEFAULT,
+ Assigner.Typing.STATIC);
+ }
+
+ /**
+ * Invokes the specified method on the given instance.
+ *
+ * @param target The object on which the method is to be invoked upon.
+ * @return A method call that invokes the provided method on the given object.
+ */
+ @SuppressWarnings("unchecked")
+ public MethodCall on(Object target) {
+ return on(target, (Class) target.getClass());
+ }
+
+ /**
+ * Invokes the specified method on the given instance.
+ *
+ * @param target The object on which the method is to be invoked upon.
+ * @param type The object's type.
+ * @param <T> The type of the object.
+ * @return A method call that invokes the provided method on the given object.
+ */
+ public <T> MethodCall on(T target, Class<? super T> type) {
+ return new MethodCall(methodLocator,
+ new TargetHandler.ForValue(target, new TypeDescription.Generic.OfNonGenericType.ForLoadedType(type)),
+ argumentLoaders,
+ new MethodInvoker.ForVirtualInvocation(type),
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ /**
+ * Invokes the specified method on the instrumented method's argument of the given index.
+ *
+ * @param index The index of the method's argument on which the specified method should be invoked.
+ * @return Amethod call that invokes the provided method on the given method argument.
+ */
+ public MethodCall onArgument(int index) {
+ if (index < 0) {
+ throw new IllegalArgumentException("An argument index cannot be negative: " + index);
+ }
+ return new MethodCall(methodLocator,
+ new TargetHandler.ForMethodParameter(index),
+ argumentLoaders,
+ MethodInvoker.ForVirtualInvocation.WithImplicitType.INSTANCE,
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ /**
+ * Invokes a method on the object stored in the specified field.
+ *
+ * @param name The name of the field.
+ * @return A method call that invokes the given method on an instance that is read from a field.
+ */
+ public MethodCall onField(String name) {
+ return onField(name, FieldLocator.ForClassHierarchy.Factory.INSTANCE);
+ }
+
+ /**
+ * Invokes a method on the object stored in the specified field.
+ *
+ * @param name The name of the field.
+ * @param fieldLocatorFactory The field locator factory to use for locating the field.
+ * @return A method call that invokes the given method on an instance that is read from a field.
+ */
+ public MethodCall onField(String name, FieldLocator.Factory fieldLocatorFactory) {
+ return new MethodCall(methodLocator,
+ new TargetHandler.ForField(name, fieldLocatorFactory),
+ argumentLoaders,
+ MethodInvoker.ForVirtualInvocation.WithImplicitType.INSTANCE,
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ /**
+ * Invokes the given method by a super method invocation on the instance of the instrumented type.
+ * Note that the super method is resolved depending on the type of implementation when this method is called.
+ * In case that a subclass is created, the super type is invoked. If a type is rebased, the rebased method
+ * is invoked if such a method exists.
+ *
+ * @return A method call where the given method is invoked as a super method invocation.
+ */
+ public MethodCall onSuper() {
+ return new MethodCall(methodLocator,
+ TargetHandler.ForSelfOrStaticInvocation.INSTANCE,
+ argumentLoaders,
+ MethodInvoker.ForSuperMethodInvocation.INSTANCE,
+ terminationHandler,
+ assigner,
+ typing);
+ }
+
+ /**
+ * Invokes the given method by a Java 8default method invocation on the instance of the instrumented type.
+ *
+ * @return A method call where the given method is invoked as a super method invocation.
+ */
+ public MethodCall onDefault() {
+ return new MethodCall(methodLocator,
+ TargetHandler.ForSelfOrStaticInvocation.INSTANCE,
+ argumentLoaders,
+ MethodInvoker.ForDefaultMethodInvocation.INSTANCE,
+ terminationHandler,
+ assigner,
+ typing);
+ }
+ }
+
+ /**
+ * The appender being used to implement a {@link net.bytebuddy.implementation.MethodCall}.
+ */
+ protected class Appender implements ByteCodeAppender {
+
+ /**
+ * The implementation target of the current implementation.
+ */
+ private final Target implementationTarget;
+
+ /**
+ * Creates a new appender.
+ *
+ * @param implementationTarget The implementation target of the current implementation.
+ */
+ protected Appender(Target implementationTarget) {
+ this.implementationTarget = implementationTarget;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ MethodDescription invokedMethod = methodLocator.resolve(implementationTarget.getInstrumentedType(), instrumentedMethod);
+ List<ArgumentLoader> argumentLoaders = new ArrayList<ArgumentLoader>(MethodCall.this.argumentLoaders.size());
+ for (ArgumentLoader.Factory argumentLoader : MethodCall.this.argumentLoaders) {
+ argumentLoaders.addAll(argumentLoader.make(implementationTarget.getInstrumentedType(), instrumentedMethod, invokedMethod));
+ }
+ ParameterList<?> parameters = invokedMethod.getParameters();
+ Iterator<? extends ParameterDescription> parameterIterator = parameters.iterator();
+ if (parameters.size() != argumentLoaders.size()) {
+ throw new IllegalStateException(invokedMethod + " does not take " + argumentLoaders.size() + " arguments");
+ }
+ List<StackManipulation> argumentInstructions = new ArrayList<StackManipulation>(argumentLoaders.size());
+ for (ArgumentLoader argumentLoader : argumentLoaders) {
+ argumentInstructions.add(argumentLoader.resolve(parameterIterator.next(), assigner, typing));
+ }
+ StackManipulation.Size size = new StackManipulation.Compound(
+ targetHandler.resolve(invokedMethod, instrumentedMethod, implementationTarget.getInstrumentedType(), assigner, typing),
+ new StackManipulation.Compound(argumentInstructions),
+ methodInvoker.invoke(invokedMethod, implementationTarget),
+ terminationHandler.resolve(invokedMethod, instrumentedMethod, assigner, typing)
+ ).apply(methodVisitor, implementationContext);
+ return new Size(size.getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private MethodCall getOuter() {
+ return MethodCall.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (other == null || getClass() != other.getClass()) return false;
+ Appender appender = (Appender) other;
+ return implementationTarget.equals(appender.implementationTarget)
+ && MethodCall.this.equals(appender.getOuter());
+
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return implementationTarget.hashCode() + 31 * MethodCall.this.hashCode();
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/MethodDelegation.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/MethodDelegation.java
new file mode 100644
index 0000000..8d80cdd
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/MethodDelegation.java
@@ -0,0 +1,1408 @@
+package net.bytebuddy.implementation;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.scaffold.FieldLocator;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.dynamic.scaffold.MethodGraph;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.implementation.bytecode.Duplication;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.TypeCreation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.member.FieldAccess;
+import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.utility.CompoundList;
+import net.bytebuddy.utility.RandomString;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static net.bytebuddy.matcher.ElementMatchers.*;
+
+/**
+ * This implementation delegates an method call to another method which can either be {@code static} by providing
+ * a reference to a {@link java.lang.Class} or an instance method when another object is provided. The potential
+ * targets of the method delegation can further be filtered by applying a filter. The method delegation can be
+ * customized by invoking the {@code MethodDelegation}'s several builder methods.
+ * <h3>Without any customization, the method delegation will work as follows:</h3>
+ * <span style="text-decoration: underline">Binding an instrumented method to a given delegate method</span>
+ * <p> </p>
+ * A method will be bound parameter by parameter. Considering a method {@code Foo#bar} being bound to a method
+ * {@code Qux#baz}, the method delegation will be decided on basis of the following annotations:
+ * <ul>
+ * <li>{@link net.bytebuddy.implementation.bind.annotation.Argument}:
+ * This annotation will bind the {@code n}-th parameter of {@code Foo#bar} to that parameter of {@code Qux#baz}that
+ * is annotated with this annotation where {@code n} is the obligatory argument of the {@code @Argument} annotation.</li>
+ * <li>{@link net.bytebuddy.implementation.bind.annotation.AllArguments}:
+ * This annotation will assign a collection of all parameters of {@code Foo#bar} to that parameter of {@code Qux#baz}
+ * that is annotated with {@code AllArguments}.</li>
+ * <li>{@link net.bytebuddy.implementation.bind.annotation.This}: A parameter
+ * of {@code Qux#baz} that is annotated with {@code This} will be assigned the instance that is instrumented for
+ * a non-static method.</li>
+ * <li>{@link net.bytebuddy.implementation.bind.annotation.Super}: A parameter that is annotated with
+ * this annotation is assigned a proxy that allows calling an instrumented type's super methods.</li>
+ * <li>{@link net.bytebuddy.implementation.bind.annotation.Default}: A parameter that is annotated with
+ * this annotation is assigned a proxy that allows calling an instrumented type's directly implemented interfaces'
+ * default methods.</li>
+ * <li>{@link net.bytebuddy.implementation.bind.annotation.SuperCall}: A parameter
+ * of {@code Qux#baz} that is annotated with {@code SuperCall} will be assigned an instance of a type implementing both
+ * {@link java.lang.Runnable} and {@link java.util.concurrent.Callable} which will invoke the instrumented method on the
+ * invocation of either interface's method. The call is made using the original arguments of the method invocation.
+ * The return value is only emitted for the {@link java.util.concurrent.Callable#call()} method which additionally
+ * requires to catch any unchecked exceptions that might be thrown by the original method's implementation. If a
+ * source method is abstract, using this annotation excludes the method with this parameter annotation from being bound
+ * to this source method.
+ * </li>
+ * <li>{@link net.bytebuddy.implementation.bind.annotation.DefaultCall}:
+ * This annotation is similar to the {@link net.bytebuddy.implementation.bind.annotation.SuperCall}
+ * annotation but it invokes a default method that is compatible to this method. If a source method does not represent
+ * a default method, using this annotation excludes the method with this parameter annotation from being bound to this
+ * source method.</li>
+ * <li>The {@link net.bytebuddy.implementation.bind.annotation.SuperMethod} or
+ * {@link net.bytebuddy.implementation.bind.annotation.DefaultMethod} annotations can be used on any parameter type
+ * that is assignable from the {@link java.lang.reflect.Method} type. the parameter is bound a method instance that
+ * allows for the reflective invocation of a super or default method. Note that this method is not equal to the intercepted
+ * method but represents a synthetic accessor method. Using this annotation also causes this accessor to be {@code public}
+ * which allows its outside invocation without any access checks by a security manager.</li>
+ * <li>{@link net.bytebuddy.implementation.bind.annotation.Origin}: A parameter of
+ * {@code Qux#baz} that is annotated with {@code Origin} is assigned a reference to either a {@link java.lang.reflect.Method},
+ * a {@link java.lang.reflect.Constructor}, a {@code java.lang.reflect.Executable} or a {@link java.lang.Class} instance.
+ * A {@code Method}-typed, {@code Constructor} or {@code Executable} parameter is assigned a reference to the original
+ * method that is instrumented. A {@code Class}-typed parameter is assigned the type of the caller. Furthermore, {@code MethodType}
+ * and {@code MethodHandle} parameters are also supported. When using the annotation on a {@link java.lang.String} type,
+ * the intercepted method's {@code toString} value is injected. The same holds for a parameter of type {@code int} that receives
+ * the modifiers of the instrumented method.</li>
+ * <li>{@link net.bytebuddy.implementation.bind.annotation.StubValue}: Assigns the (boxed) default value of the
+ * intercepted method's return type to the parameter. If the return type is {@code void}, {@code null} is assigned.</li>
+ * <li>{@link net.bytebuddy.implementation.bind.annotation.Empty}: Assigns the parameter type's
+ * default value, i.e. {@code null} for a reference type or zero for primitive types. This is an opportunity to
+ * ignore a parameter.</li>
+ * <li>{@link net.bytebuddy.implementation.bind.annotation.Pipe}: A parameter that is annotated
+ * with this annotation is assigned a proxy for forwarding the source method invocation to another instance of the
+ * same type as the declaring type of the intercepted method. <b>This annotation needs to be installed and explicitly
+ * registered before it can be used.</b> See the {@link net.bytebuddy.implementation.bind.annotation.Pipe}
+ * annotation's documentation for further information on how this can be done.</li>
+ * <li>{@link net.bytebuddy.implementation.bind.annotation.Morph}: The morph annotation is similar to
+ * the {@link net.bytebuddy.implementation.bind.annotation.SuperCall} annotation but allows to
+ * explicitly define and therewith alter the arguments that are handed to the super method. <b>This annotation needs
+ * to be installed and explicitly registered before it can be used.</b> See the documentation to the annotation for
+ * further information.</li>
+ * <li>{@link net.bytebuddy.implementation.bind.annotation.FieldValue}: Allows to access a field's value at the time
+ * of the method invocation. The field's value is directly assigned to the annotated parameter.</li>
+ * <li>{@link net.bytebuddy.implementation.bind.annotation.FieldProxy}: Allows to access fields via getter
+ * and setter proxies. <b>This annotation needs to be installed and explicitly registered before it can be used.</b>
+ * Note that any field access requires boxing such that a use of {@link net.bytebuddy.implementation.FieldAccessor} in
+ * combination with {@link net.bytebuddy.implementation.MethodDelegation#andThen(Implementation)} might be a more
+ * performant alternative for implementing field getters and setters.</li>
+ * </ul>
+ * If a method is not annotated with any of the above methods, it will be treated as if it was annotated
+ * {@link net.bytebuddy.implementation.bind.annotation.Argument} using the next
+ * unbound parameter index of the source method as its parameter. This means that a method
+ * {@code Qux#baz(@Argument(2) Object p1, Object p2, @Argument(0) Object p3} would be treated as if {@code p2} was annotated
+ * with {@code @Argument(1)}.
+ * <p> </p>
+ * In addition, the {@link net.bytebuddy.implementation.bind.annotation.RuntimeType}
+ * annotation can instruct a parameter to be bound by a
+ * {@link net.bytebuddy.implementation.bytecode.assign.Assigner} with considering the
+ * runtime type of the parameter.
+ * <p> </p>
+ * <span style="text-decoration: underline">Selecting among different methods that can be used for binding a method
+ * of the instrumented type</span>
+ * <p> </p>
+ * When deciding between two methods {@code Foo#bar} and {@code Foo#qux} that could both be used to delegating a
+ * method call, the following consideration is applied in the given order:
+ * <ol>
+ * <li>{@link net.bytebuddy.implementation.bind.annotation.BindingPriority}:
+ * A method that is annotated with this annotation is given a specific priority where the default priority is set
+ * to {@link net.bytebuddy.implementation.bind.annotation.BindingPriority#DEFAULT}
+ * for non-annotated method. A method with a higher priority is considered a better target for delegation.</li>
+ * <li>{@link net.bytebuddy.implementation.bind.DeclaringTypeResolver}:
+ * If a target method is declared by a more specific type than another method, the method with the most specific
+ * type is bound.</li>
+ * <li>{@link net.bytebuddy.implementation.bind.MethodNameEqualityResolver}:
+ * If a source method {@code Baz#qux} is the source method, it will rather be assigned to {@code Foo#qux} because
+ * of their equal names. Similar names and case-insensitive equality are not considered.</li>
+ * <li>{@link net.bytebuddy.implementation.bind.ArgumentTypeResolver}:
+ * The most specific type resolver will consider all bindings that are using the
+ * {@link net.bytebuddy.implementation.bind.annotation.Argument}
+ * annotation for resolving a binding conflict. In this context, the resolution will equal the most-specific
+ * type resolution that is performed by the Java compiler. This means that a source method {@code Bar#baz(String)}
+ * will rather be bound to a method {@code Foo#bar(String)} than {@code Foo#qux(Object)} because the {@code String}
+ * type is more specific than the {@code Object} type. If two methods are equally adequate by their parameter types,
+ * then the method with the higher numbers of {@code @Argument} annotated parameters is considered as the better
+ * delegation target.</li>
+ * <li>{@link net.bytebuddy.implementation.bind.ParameterLengthResolver}:
+ * If a target methods has a higher number of total parameters that were successfully bound, the method with
+ * the higher number will be considered as the better delegation target.</li>
+ * </ol>
+ * <p>
+ * Additionally, if a method is annotated by
+ * {@link net.bytebuddy.implementation.bind.annotation.IgnoreForBinding},
+ * it is never considered as a target for a method delegation.
+ * </p>
+ * <p>
+ * <b>Important</b>: For invoking a method on another instance, use the {@link MethodCall} implementation. A method delegation
+ * intends to bind a interceptor class and its resolution algorithm will not necessarily yield a delegation to the intercepted
+ * method.
+ * </p>
+ *
+ * @see MethodCall
+ * @see net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder.ParameterBinder.ForFixedValue
+ */
+ at EqualsAndHashCode
+public class MethodDelegation implements Implementation.Composable {
+
+ /**
+ * The implementation delegate for this method delegation.
+ */
+ private final ImplementationDelegate implementationDelegate;
+
+ /**
+ * A list of {@link net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder.ParameterBinder}s
+ * to be used by this method delegation.
+ */
+ private final List<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>> parameterBinders;
+
+ /**
+ * The {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver}
+ * to be used by this method delegation.
+ */
+ private final MethodDelegationBinder.AmbiguityResolver ambiguityResolver;
+
+ /**
+ * The termination handler to apply.
+ */
+ private final TargetMethodAnnotationDrivenBinder.TerminationHandler terminationHandler;
+
+ /**
+ * The {@link net.bytebuddy.implementation.bytecode.assign.Assigner} to be used by this method delegation.
+ */
+ private final Assigner assigner;
+
+ /**
+ * Creates a new method delegation.
+ *
+ * @param implementationDelegate The implementation delegate to use by this method delegator.
+ * @param parameterBinders The parameter binders to use by this method delegator.
+ * @param ambiguityResolver The ambiguity resolver to use by this method delegator.
+ */
+ protected MethodDelegation(ImplementationDelegate implementationDelegate,
+ List<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>> parameterBinders,
+ MethodDelegationBinder.AmbiguityResolver ambiguityResolver) {
+ this(implementationDelegate, parameterBinders, ambiguityResolver, MethodDelegationBinder.TerminationHandler.Default.RETURNING, Assigner.DEFAULT);
+ }
+
+ /**
+ * Creates a new method delegation.
+ *
+ * @param implementationDelegate The implementation delegate to use by this method delegator.
+ * @param parameterBinders The parameter binders to use by this method delegator.
+ * @param ambiguityResolver The ambiguity resolver to use by this method delegator.
+ * @param terminationHandler The termination handler to apply.
+ * @param assigner The assigner to be supplied by this method delegator.
+ */
+ private MethodDelegation(ImplementationDelegate implementationDelegate,
+ List<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>> parameterBinders,
+ MethodDelegationBinder.AmbiguityResolver ambiguityResolver,
+ TargetMethodAnnotationDrivenBinder.TerminationHandler terminationHandler,
+ Assigner assigner) {
+ this.implementationDelegate = implementationDelegate;
+ this.parameterBinders = parameterBinders;
+ this.terminationHandler = terminationHandler;
+ this.ambiguityResolver = ambiguityResolver;
+ this.assigner = assigner;
+ }
+
+ /**
+ * Delegates any intercepted method to invoke a {@code static} method that is declared by the supplied type. To be considered
+ * a valid delegation target, the target method must be visible and accessible to the instrumented type. This is the case if
+ * the target type is either public or in the same package as the instrumented type and if the target method is either public
+ * or non-private and in the same package as the instrumented type. Private methods can only be used as a delegation target if
+ * the interception is targeting the instrumented type.
+ *
+ * @param type The target type for the delegation.
+ * @return A method delegation that redirects method calls to a static method of the supplied type.
+ */
+ public static MethodDelegation to(Class<?> type) {
+ return withDefaultConfiguration().to(type);
+ }
+
+ /**
+ * Delegates any intercepted method to invoke a {@code static} method that is declared by the supplied type. To be considered
+ * a valid delegation target, the target method must be visible and accessible to the instrumented type. This is the case if
+ * the target type is either public or in the same package as the instrumented type and if the target method is either public
+ * or non-private and in the same package as the instrumented type. Private methods can only be used as a delegation target if
+ * the interception is targeting the instrumented type.
+ *
+ * @param typeDescription The target type for the delegation.
+ * @return A method delegation that redirects method calls to a static method of the supplied type.
+ */
+ public static MethodDelegation to(TypeDescription typeDescription) {
+ return withDefaultConfiguration().to(typeDescription);
+ }
+
+ /**
+ * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
+ * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
+ * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
+ * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
+ * a delegation target if the delegation is targeting the instrumented type.
+ *
+ * @param target The target instance for the delegation.
+ * @return A method delegation that redirects method calls to a static method of the supplied type.
+ */
+ public static MethodDelegation to(Object target) {
+ return withDefaultConfiguration().to(target);
+ }
+
+ /**
+ * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
+ * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
+ * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
+ * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
+ * a delegation target if the delegation is targeting the instrumented type.
+ *
+ * @param target The target instance for the delegation.
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @return A method delegation that redirects method calls to a static method of the supplied type.
+ */
+ public static MethodDelegation to(Object target, MethodGraph.Compiler methodGraphCompiler) {
+ return withDefaultConfiguration().to(target, methodGraphCompiler);
+ }
+
+ /**
+ * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
+ * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
+ * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
+ * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
+ * a delegation target if the delegation is targeting the instrumented type.
+ *
+ * @param target The target instance for the delegation.
+ * @param fieldName The name of the field that is holding the {@code target} instance.
+ * @return A method delegation that redirects method calls to a static method of the supplied type.
+ */
+ public static MethodDelegation to(Object target, String fieldName) {
+ return withDefaultConfiguration().to(target, fieldName);
+ }
+
+ /**
+ * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
+ * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
+ * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
+ * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
+ * a delegation target if the delegation is targeting the instrumented type.
+ *
+ * @param target The target instance for the delegation.
+ * @param fieldName The name of the field that is holding the {@code target} instance.
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @return A method delegation that redirects method calls to a static method of the supplied type.
+ */
+ public static MethodDelegation to(Object target, String fieldName, MethodGraph.Compiler methodGraphCompiler) {
+ return withDefaultConfiguration().to(target, fieldName, methodGraphCompiler);
+ }
+
+ /**
+ * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
+ * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
+ * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
+ * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
+ * a delegation target if the delegation is targeting the instrumented type.
+ *
+ * @param target The target instance for the delegation.
+ * @param type The most specific type of which {@code target} should be cosnidered. Must be a super type of the target's actual type.
+ * @return A method delegation that redirects method calls to a static method of the supplied type.
+ */
+ public static MethodDelegation to(Object target, Type type) {
+ return withDefaultConfiguration().to(target, type);
+ }
+
+ /**
+ * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
+ * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
+ * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
+ * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
+ * a delegation target if the delegation is targeting the instrumented type.
+ *
+ * @param target The target instance for the delegation.
+ * @param type The most specific type of which {@code target} should be cosnidered. Must be a super type of the target's actual type.
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @return A method delegation that redirects method calls to a static method of the supplied type.
+ */
+ public static MethodDelegation to(Object target, Type type, MethodGraph.Compiler methodGraphCompiler) {
+ return withDefaultConfiguration().to(target, type, methodGraphCompiler);
+ }
+
+ /**
+ * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
+ * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
+ * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
+ * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
+ * a delegation target if the delegation is targeting the instrumented type.
+ *
+ * @param target The target instance for the delegation.
+ * @param type The most specific type of which {@code target} should be cosnidered. Must be a super type of the target's actual type.
+ * @param fieldName The name of the field that is holding the {@code target} instance.
+ * @return A method delegation that redirects method calls to a static method of the supplied type.
+ */
+ public static MethodDelegation to(Object target, Type type, String fieldName) {
+ return withDefaultConfiguration().to(target, type, fieldName);
+ }
+
+ /**
+ * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
+ * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
+ * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
+ * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
+ * a delegation target if the delegation is targeting the instrumented type.
+ *
+ * @param target The target instance for the delegation.
+ * @param type The most specific type of which {@code target} should be cosnidered. Must be a super type of the target's actual type.
+ * @param fieldName The name of the field that is holding the {@code target} instance.
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @return A method delegation that redirects method calls to a static method of the supplied type.
+ */
+ public static MethodDelegation to(Object target, Type type, String fieldName, MethodGraph.Compiler methodGraphCompiler) {
+ return withDefaultConfiguration().to(target, type, fieldName, methodGraphCompiler);
+ }
+
+ /**
+ * Delegates any intercepted method to invoke a constructor of the supplied type. To be considered a valid delegation target,
+ * a constructor must be visible and accessible to the instrumented type. This is the case if the constructor's declaring type is
+ * either public or in the same package as the instrumented type and if the constructor is either public or non-private and in
+ * the same package as the instrumented type. Private constructors can only be used as a delegation target if the delegation is
+ * targeting the instrumented type.
+ *
+ * @param type The type to construct.
+ * @return A delegation that redirects method calls to a constructor of the supplied type.
+ */
+ public static MethodDelegation toConstructor(Class<?> type) {
+ return withDefaultConfiguration().toConstructor(type);
+ }
+
+ /**
+ * Delegates any intercepted method to invoke a constructor of the supplied type. To be considered a valid delegation target,
+ * a constructor must be visible and accessible to the instrumented type. This is the case if the constructor's declaring type is
+ * either public or in the same package as the instrumented type and if the constructor is either public or non-private and in
+ * the same package as the instrumented type. Private constructors can only be used as a delegation target if the delegation is
+ * targeting the instrumented type.
+ *
+ * @param typeDescription The type to construct.
+ * @return A delegation that redirects method calls to a constructor of the supplied type.
+ */
+ public static MethodDelegation toConstructor(TypeDescription typeDescription) {
+ return withDefaultConfiguration().toConstructor(typeDescription);
+ }
+
+ /**
+ * Delegates any intercepted method to invoke a non-{@code static} method on the instance of the supplied field. To be
+ * considered a valid delegation target, a method must be visible and accessible to the instrumented type. This is the
+ * case if the method's declaring type is either public or in the same package as the instrumented type and if the method
+ * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
+ * a delegation target if the ddelegation is targeting the instrumented type.
+ *
+ * @param name The field's name.
+ * @return A delegation that redirects invocations to a method of the specified field's instance.
+ */
+ public static MethodDelegation toField(String name) {
+ return withDefaultConfiguration().toField(name);
+ }
+
+ /**
+ * Delegates any intercepted method to invoke a non-{@code static} method on the instance of the supplied field. To be
+ * considered a valid delegation target, a method must be visible and accessible to the instrumented type. This is the
+ * case if the method's declaring type is either public or in the same package as the instrumented type and if the method
+ * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
+ * a delegation target if the ddelegation is targeting the instrumented type.
+ *
+ * @param name The field's name.
+ * @param fieldLocatorFactory The field locator factory to use.
+ * @return A delegation that redirects invocations to a method of the specified field's instance.
+ */
+ public static MethodDelegation toField(String name, FieldLocator.Factory fieldLocatorFactory) {
+ return withDefaultConfiguration().toField(name, fieldLocatorFactory);
+ }
+
+ /**
+ * Delegates any intercepted method to invoke a non-{@code static} method on the instance of the supplied field. To be
+ * considered a valid delegation target, a method must be visible and accessible to the instrumented type. This is the
+ * case if the method's declaring type is either public or in the same package as the instrumented type and if the method
+ * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
+ * a delegation target if the ddelegation is targeting the instrumented type.
+ *
+ * @param name The field's name.
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @return A delegation that redirects invocations to a method of the specified field's instance.
+ */
+ public static MethodDelegation toField(String name, MethodGraph.Compiler methodGraphCompiler) {
+ return withDefaultConfiguration().toField(name, methodGraphCompiler);
+ }
+
+ /**
+ * Delegates any intercepted method to invoke a non-{@code static} method on the instance of the supplied field. To be
+ * considered a valid delegation target, a method must be visible and accessible to the instrumented type. This is the
+ * case if the method's declaring type is either public or in the same package as the instrumented type and if the method
+ * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
+ * a delegation target if the ddelegation is targeting the instrumented type.
+ *
+ * @param name The field's name.
+ * @param fieldLocatorFactory The field locator factory to use.
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @return A delegation that redirects invocations to a method of the specified field's instance.
+ */
+ public static MethodDelegation toField(String name, FieldLocator.Factory fieldLocatorFactory, MethodGraph.Compiler methodGraphCompiler) {
+ return withDefaultConfiguration().toField(name, fieldLocatorFactory, methodGraphCompiler);
+ }
+
+ /**
+ * Creates a configuration builder for a method delegation that is pre-configured with the ambiguity resolvers defined by
+ * {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver#DEFAULT} and the parameter binders
+ * defined by {@link net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder.ParameterBinder#DEFAULTS}.
+ *
+ * @return A method delegation configuration with pre-configuration.
+ */
+ public static WithCustomProperties withDefaultConfiguration() {
+ return new WithCustomProperties(MethodDelegationBinder.AmbiguityResolver.DEFAULT, TargetMethodAnnotationDrivenBinder.ParameterBinder.DEFAULTS);
+ }
+
+ /**
+ * Creates a configuration builder for a method delegation that does not apply any pre-configured
+ * {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver}s or
+ * {@link net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder.ParameterBinder}s.
+ *
+ * @return A method delegation configuration without any pre-configuration.
+ */
+ public static WithCustomProperties withEmptyConfiguration() {
+ return new WithCustomProperties(MethodDelegationBinder.AmbiguityResolver.NoOp.INSTANCE, Collections.<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>>emptyList());
+ }
+
+ /**
+ * Applies an assigner to the method delegation that is used for assigning method return and parameter types.
+ *
+ * @param assigner The assigner to apply.
+ * @return A method delegation implementation that makes use of the given designer.
+ */
+ public Implementation.Composable withAssigner(Assigner assigner) {
+ return new MethodDelegation(implementationDelegate,
+ parameterBinders,
+ ambiguityResolver,
+ terminationHandler,
+ assigner);
+ }
+
+ @Override
+ public Implementation andThen(Implementation implementation) {
+ return new Compound(new MethodDelegation(implementationDelegate,
+ parameterBinders,
+ ambiguityResolver,
+ MethodDelegationBinder.TerminationHandler.Default.DROPPING,
+ assigner), implementation);
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return implementationDelegate.prepare(instrumentedType);
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ ImplementationDelegate.Compiled compiled = implementationDelegate.compile(implementationTarget.getInstrumentedType());
+ return new Appender(implementationTarget,
+ new MethodDelegationBinder.Processor(compiled.getRecords(), ambiguityResolver),
+ terminationHandler,
+ assigner,
+ compiled);
+ }
+
+ /**
+ * An implementation delegate is responsible for executing the actual method delegation and for resolving the target methods.
+ */
+ protected interface ImplementationDelegate extends InstrumentedType.Prepareable {
+
+ /**
+ * A name prefix for fields.
+ */
+ String FIELD_NAME_PREFIX = "delegate";
+
+ /**
+ * Compiles this implementation delegate.
+ *
+ * @param instrumentedType The instrumented type.
+ * @return A compiled implementation delegate.
+ */
+ Compiled compile(TypeDescription instrumentedType);
+
+ /**
+ * A compiled implementation delegate.
+ */
+ interface Compiled {
+
+ /**
+ * Resolves a stack manipulation that prepares the delegation invocation.
+ *
+ * @param instrumentedMethod The instrumented method.
+ * @return A stack manipulation that is applied prior to loading arguments and executing the method call.
+ */
+ StackManipulation prepare(MethodDescription instrumentedMethod);
+
+ /**
+ * Resolves an invoker to use for invoking the delegation target.
+ *
+ * @return The method invoker to use.
+ */
+ MethodDelegationBinder.MethodInvoker invoke();
+
+ /**
+ * Returns a list of binding records to consider for delegation.
+ *
+ * @return A list of delegation binder records to consider.
+ */
+ List<MethodDelegationBinder.Record> getRecords();
+
+ /**
+ * A compiled implementation delegate for invoking a static method.
+ */
+ @EqualsAndHashCode
+ class ForStaticCall implements Compiled {
+
+ /**
+ * The list of records to consider.
+ */
+ private final List<MethodDelegationBinder.Record> records;
+
+ /**
+ * Creates a new compiled implementation delegate for a static method call.
+ *
+ * @param records The list of records to consider.
+ */
+ protected ForStaticCall(List<MethodDelegationBinder.Record> records) {
+ this.records = records;
+ }
+
+ @Override
+ public StackManipulation prepare(MethodDescription instrumentedMethod) {
+ return StackManipulation.Trivial.INSTANCE;
+ }
+
+ @Override
+ public MethodDelegationBinder.MethodInvoker invoke() {
+ return MethodDelegationBinder.MethodInvoker.Simple.INSTANCE;
+ }
+
+ @Override
+ public List<MethodDelegationBinder.Record> getRecords() {
+ return records;
+ }
+ }
+
+ /**
+ * A compiled implementation delegate that invokes methods on a field.
+ */
+ @EqualsAndHashCode
+ class ForField implements Compiled {
+
+ /**
+ * The field to delegate to.
+ */
+ private final FieldDescription fieldDescription;
+
+ /**
+ * The records to consider for delegation.
+ */
+ private final List<MethodDelegationBinder.Record> records;
+
+ /**
+ * Creates a new compiled implementation delegate for a field delegation.
+ *
+ * @param fieldDescription The field to delegate to.
+ * @param records The records to consider for delegation.
+ */
+ protected ForField(FieldDescription fieldDescription, List<MethodDelegationBinder.Record> records) {
+ this.fieldDescription = fieldDescription;
+ this.records = records;
+ }
+
+ @Override
+ public StackManipulation prepare(MethodDescription instrumentedMethod) {
+ if (instrumentedMethod.isStatic() && !fieldDescription.isStatic()) {
+ throw new IllegalStateException("Cannot read " + fieldDescription + " from " + instrumentedMethod);
+ }
+ return new StackManipulation.Compound(fieldDescription.isStatic()
+ ? StackManipulation.Trivial.INSTANCE
+ : MethodVariableAccess.loadThis(), FieldAccess.forField(fieldDescription).read());
+ }
+
+ @Override
+ public MethodDelegationBinder.MethodInvoker invoke() {
+ return new MethodDelegationBinder.MethodInvoker.Virtual(fieldDescription.getType().asErasure());
+ }
+
+ @Override
+ public List<MethodDelegationBinder.Record> getRecords() {
+ return records;
+ }
+ }
+
+ /**
+ * A compiled implementation delegate for a constructor delegation.
+ */
+ @EqualsAndHashCode
+ class ForConstruction implements Compiled {
+
+ /**
+ * The type to be constructed.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * The records to consider for delegation.
+ */
+ private final List<MethodDelegationBinder.Record> records;
+
+ /**
+ * Creates a new compiled implementation delegate for a constructor delegation.
+ *
+ * @param typeDescription The type to be constructed.
+ * @param records The records to consider for delegation.
+ */
+ protected ForConstruction(TypeDescription typeDescription, List<MethodDelegationBinder.Record> records) {
+ this.typeDescription = typeDescription;
+ this.records = records;
+ }
+
+ @Override
+ public StackManipulation prepare(MethodDescription instrumentedMethod) {
+ return new StackManipulation.Compound(TypeCreation.of(typeDescription), Duplication.SINGLE);
+ }
+
+ @Override
+ public MethodDelegationBinder.MethodInvoker invoke() {
+ return MethodDelegationBinder.MethodInvoker.Simple.INSTANCE;
+ }
+
+ @Override
+ public List<MethodDelegationBinder.Record> getRecords() {
+ return records;
+ }
+ }
+ }
+
+ /**
+ * An implementation delegate for a static method delegation.
+ */
+ @EqualsAndHashCode
+ class ForStaticMethod implements ImplementationDelegate {
+
+ /**
+ * The precompiled records.
+ */
+ private final List<MethodDelegationBinder.Record> records;
+
+ /**
+ * Creates a new implementation delegate for a static method delegation.
+ *
+ * @param records The precompiled record.
+ */
+ protected ForStaticMethod(List<MethodDelegationBinder.Record> records) {
+ this.records = records;
+ }
+
+ /**
+ * Precompiles a static method delegation for a given list of methods.
+ *
+ * @param methods The methods to consider.
+ * @param methodDelegationBinder The method delegation binder to use.
+ * @return An appropriate implementation delegate.
+ */
+ protected static ImplementationDelegate of(MethodList<?> methods, MethodDelegationBinder methodDelegationBinder) {
+ List<MethodDelegationBinder.Record> records = new ArrayList<MethodDelegationBinder.Record>(methods.size());
+ for (MethodDescription methodDescription : methods) {
+ records.add(methodDelegationBinder.compile(methodDescription));
+ }
+ return new ForStaticMethod(records);
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public ImplementationDelegate.Compiled compile(TypeDescription instrumentedType) {
+ return new Compiled.ForStaticCall(records);
+ }
+ }
+
+ /**
+ * An implementation delegate for invoking methods on a field that is declared by the instrumented type or a super type.
+ */
+ @EqualsAndHashCode
+ abstract class ForField implements ImplementationDelegate {
+
+ /**
+ * The name of the field that is target of the delegation.
+ */
+ protected final String fieldName;
+
+ /**
+ * The method graph compiler to use.
+ */
+ protected final MethodGraph.Compiler methodGraphCompiler;
+
+ /**
+ * The parameter binders to use.
+ */
+ protected final List<? extends TargetMethodAnnotationDrivenBinder.ParameterBinder<?>> parameterBinders;
+
+ /**
+ * The matcher to use for filtering methods.
+ */
+ protected final ElementMatcher<? super MethodDescription> matcher;
+
+ /**
+ * Creates a new implementation delegate for a field delegation.
+ *
+ * @param fieldName The name of the field that is target of the delegation.
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @param parameterBinders The parameter binders to use.
+ * @param matcher The matcher to use for filtering methods.
+ */
+ protected ForField(String fieldName,
+ MethodGraph.Compiler methodGraphCompiler,
+ List<? extends TargetMethodAnnotationDrivenBinder.ParameterBinder<?>> parameterBinders,
+ ElementMatcher<? super MethodDescription> matcher) {
+ this.fieldName = fieldName;
+ this.methodGraphCompiler = methodGraphCompiler;
+ this.parameterBinders = parameterBinders;
+ this.matcher = matcher;
+ }
+
+ @Override
+ public Compiled compile(TypeDescription instrumentedType) {
+ FieldDescription fieldDescription = resolve(instrumentedType);
+ if (!fieldDescription.getType().asErasure().isVisibleTo(instrumentedType)) {
+ throw new IllegalStateException(fieldDescription + " is not visible to " + instrumentedType);
+ } else {
+ MethodList<?> candidates = methodGraphCompiler.compile(fieldDescription.getType(), instrumentedType)
+ .listNodes()
+ .asMethodList()
+ .filter(matcher);
+ List<MethodDelegationBinder.Record> records = new ArrayList<MethodDelegationBinder.Record>(candidates.size());
+ MethodDelegationBinder methodDelegationBinder = TargetMethodAnnotationDrivenBinder.of(parameterBinders);
+ for (MethodDescription candidate : candidates) {
+ records.add(methodDelegationBinder.compile(candidate));
+ }
+ return new Compiled.ForField(fieldDescription, records);
+ }
+ }
+
+ /**
+ * Resolves the field to which is delegated.
+ *
+ * @param instrumentedType The instrumented type.
+ * @return The field that is the delegation target.
+ */
+ protected abstract FieldDescription resolve(TypeDescription instrumentedType);
+
+ /**
+ * An implementation target for a static field that is declared by the instrumented type and that is assigned an instance.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ protected static class WithInstance extends ForField {
+
+ /**
+ * The target instance.
+ */
+ private final Object target;
+
+ /**
+ * The field's type.
+ */
+ private final TypeDescription.Generic fieldType;
+
+ /**
+ * Creates a new implementation delegate for invoking methods on a supplied instance.
+ *
+ * @param fieldName The name of the field that is target of the delegation.
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @param parameterBinders The parameter binders to use.
+ * @param matcher The matcher to use for filtering methods.
+ * @param target The target instance.
+ * @param fieldType The field's type.
+ */
+ protected WithInstance(String fieldName,
+ MethodGraph.Compiler methodGraphCompiler,
+ List<? extends TargetMethodAnnotationDrivenBinder.ParameterBinder<?>> parameterBinders,
+ ElementMatcher<? super MethodDescription> matcher,
+ Object target,
+ TypeDescription.Generic fieldType) {
+ super(fieldName, methodGraphCompiler, parameterBinders, matcher);
+ this.target = target;
+ this.fieldType = fieldType;
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType
+ .withField(new FieldDescription.Token(fieldName, Opcodes.ACC_STATIC | Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, fieldType))
+ .withInitializer(new LoadedTypeInitializer.ForStaticField(fieldName, target));
+ }
+
+ @Override
+ protected FieldDescription resolve(TypeDescription instrumentedType) {
+ if (!fieldType.asErasure().isVisibleTo(instrumentedType)) {
+ throw new IllegalStateException(fieldType + " is not visible to " + instrumentedType);
+ } else {
+ return instrumentedType.getDeclaredFields()
+ .filter(named(fieldName).and(fieldType(fieldType.asErasure())))
+ .getOnly();
+ }
+ }
+ }
+
+ /**
+ * An implementation target for a field that is declared by the instrumented type or a super type.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ protected static class WithLookup extends ForField {
+
+ /**
+ * The field locator factory to use for locating the field to delegate to.
+ */
+ private final FieldLocator.Factory fieldLocatorFactory;
+
+ /**
+ * Creates a new implementation delegate for a field that is declared by the instrumented type or any super type.
+ *
+ * @param fieldName The name of the field that is target of the delegation.
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @param parameterBinders The parameter binders to use.
+ * @param matcher The matcher to use for filtering methods.
+ * @param fieldLocatorFactory The field locator factory to use for locating the field to delegate to.
+ */
+ protected WithLookup(String fieldName,
+ MethodGraph.Compiler methodGraphCompiler,
+ List<? extends TargetMethodAnnotationDrivenBinder.ParameterBinder<?>> parameterBinders,
+ ElementMatcher<? super MethodDescription> matcher,
+ FieldLocator.Factory fieldLocatorFactory) {
+ super(fieldName, methodGraphCompiler, parameterBinders, matcher);
+ this.fieldLocatorFactory = fieldLocatorFactory;
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ protected FieldDescription resolve(TypeDescription instrumentedType) {
+ FieldLocator.Resolution resolution = fieldLocatorFactory.make(instrumentedType).locate(fieldName);
+ if (!resolution.isResolved()) {
+ throw new IllegalStateException("Could not locate " + fieldName + " on " + instrumentedType);
+ } else {
+ return resolution.getField();
+ }
+ }
+ }
+ }
+
+ /**
+ * An implementation delegate for constructing an instance.
+ */
+ @EqualsAndHashCode
+ class ForConstruction implements ImplementationDelegate {
+
+ /**
+ * The type being constructed.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * The precompiled delegation records.
+ */
+ private final List<MethodDelegationBinder.Record> records;
+
+ /**
+ * Creates an implementation delegate for constructing a new instance.
+ *
+ * @param typeDescription The type being constructed.
+ * @param records The precompiled delegation records.
+ */
+ protected ForConstruction(TypeDescription typeDescription, List<MethodDelegationBinder.Record> records) {
+ this.typeDescription = typeDescription;
+ this.records = records;
+ }
+
+ /**
+ * Creates an implementation delegate for constructing a new instance.
+ *
+ * @param typeDescription The type being constructed.
+ * @param methods The constructors to consider.
+ * @param methodDelegationBinder The method delegation binder to use.
+ * @return An appropriate implementation delegate.
+ */
+ protected static ImplementationDelegate of(TypeDescription typeDescription,
+ MethodList<?> methods,
+ MethodDelegationBinder methodDelegationBinder) {
+ List<MethodDelegationBinder.Record> records = new ArrayList<MethodDelegationBinder.Record>(methods.size());
+ for (MethodDescription methodDescription : methods) {
+ records.add(methodDelegationBinder.compile(methodDescription));
+ }
+ return new ForConstruction(typeDescription, records);
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public Compiled compile(TypeDescription instrumentedType) {
+ return new Compiled.ForConstruction(typeDescription, records);
+ }
+ }
+ }
+
+ /**
+ * The appender for implementing a {@link net.bytebuddy.implementation.MethodDelegation}.
+ */
+ @EqualsAndHashCode
+ protected static class Appender implements ByteCodeAppender {
+
+ /**
+ * The implementation target of this implementation.
+ */
+ private final Target implementationTarget;
+
+ /**
+ * The method delegation binder processor which is responsible for implementing the method delegation.
+ */
+ private final MethodDelegationBinder.Processor processor;
+
+ /**
+ * A termination handler for a method delegation binder.
+ */
+ private final MethodDelegationBinder.TerminationHandler terminationHandler;
+
+ /**
+ * The assigner to use.
+ */
+ private final Assigner assigner;
+
+ /**
+ * The compiled implementation delegate.
+ */
+ private final ImplementationDelegate.Compiled compiled;
+
+ /**
+ * Creates a new appender for a method delegation.
+ *
+ * @param implementationTarget The implementation target of this implementation.
+ * @param processor The method delegation binder processor which is responsible for implementing the method delegation.
+ * @param terminationHandler A termination handler for a method delegation binder.
+ * @param assigner The assigner to use.
+ * @param compiled The compiled implementation delegate.
+ */
+ protected Appender(Target implementationTarget,
+ MethodDelegationBinder.Processor processor,
+ MethodDelegationBinder.TerminationHandler terminationHandler,
+ Assigner assigner,
+ ImplementationDelegate.Compiled compiled) {
+ this.implementationTarget = implementationTarget;
+ this.processor = processor;
+ this.terminationHandler = terminationHandler;
+ this.assigner = assigner;
+ this.compiled = compiled;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ StackManipulation.Size stackSize = new StackManipulation.Compound(
+ compiled.prepare(instrumentedMethod),
+ processor.bind(implementationTarget, instrumentedMethod, terminationHandler, compiled.invoke(), assigner)
+ ).apply(methodVisitor, implementationContext);
+ return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+ }
+
+ /**
+ * A {@link MethodDelegation} with custom configuration.
+ */
+ @EqualsAndHashCode
+ public static class WithCustomProperties {
+
+ /**
+ * The ambiguity resolver to use.
+ */
+ private final MethodDelegationBinder.AmbiguityResolver ambiguityResolver;
+
+ /**
+ * The parameter binders to use.
+ */
+ private final List<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>> parameterBinders;
+
+ /**
+ * The matcher to use for filtering relevant methods.
+ */
+ private final ElementMatcher<? super MethodDescription> matcher;
+
+ /**
+ * Creates a new method delegation with custom properties that does not filter any methods.
+ *
+ * @param ambiguityResolver The ambiguity resolver to use.
+ * @param parameterBinders The parameter binders to use.
+ */
+ protected WithCustomProperties(MethodDelegationBinder.AmbiguityResolver ambiguityResolver,
+ List<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>> parameterBinders) {
+ this(ambiguityResolver, parameterBinders, any());
+ }
+
+ /**
+ * Creates a new method delegation with custom properties.
+ *
+ * @param ambiguityResolver The ambiguity resolver to use.
+ * @param parameterBinders The parameter binders to use.
+ * @param matcher The matcher to use for filtering relevant methods.
+ */
+ private WithCustomProperties(MethodDelegationBinder.AmbiguityResolver ambiguityResolver,
+ List<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>> parameterBinders,
+ ElementMatcher<? super MethodDescription> matcher) {
+ this.ambiguityResolver = ambiguityResolver;
+ this.parameterBinders = parameterBinders;
+ this.matcher = matcher;
+ }
+
+ /**
+ * Configures this method delegation to use the supplied ambiguity resolvers when deciding which out of two ore
+ * more legal delegation targets should be considered.
+ *
+ * @param ambiguityResolver The ambiguity resolvers to use in their application order.
+ * @return A new delegation configuration which also applies the supplied ambiguity resolvers.
+ */
+ public WithCustomProperties withResolvers(MethodDelegationBinder.AmbiguityResolver... ambiguityResolver) {
+ return withResolvers(Arrays.asList(ambiguityResolver));
+ }
+
+ /**
+ * Configures this method delegation to use the supplied ambiguity resolvers when deciding which out of two ore
+ * more legal delegation targets should be considered.
+ *
+ * @param ambiguityResolvers The ambiguity resolvers to use in their application order.
+ * @return A new delegation configuration which also applies the supplied ambiguity resolvers.
+ */
+ public WithCustomProperties withResolvers(List<? extends MethodDelegationBinder.AmbiguityResolver> ambiguityResolvers) {
+ return new WithCustomProperties(new MethodDelegationBinder.AmbiguityResolver.Compound(CompoundList.of(this.ambiguityResolver,
+ ambiguityResolvers)), parameterBinders, matcher);
+ }
+
+ /**
+ * Configures this method delegation to use the supplied parameter binders when deciding what value to assign to
+ * a parameter of a delegation target.
+ *
+ * @param parameterBinder The parameter binders to use.
+ * @return A new delegation configuration which also applies the supplied parameter binders.
+ */
+ public WithCustomProperties withBinders(TargetMethodAnnotationDrivenBinder.ParameterBinder<?>... parameterBinder) {
+ return withBinders(Arrays.asList(parameterBinder));
+ }
+
+ /**
+ * Configures this method delegation to use the supplied parameter binders when deciding what value to assign to
+ * a parameter of a delegation target.
+ *
+ * @param parameterBinders The parameter binders to use.
+ * @return A new delegation configuration which also applies the supplied parameter binders.
+ */
+ public WithCustomProperties withBinders(List<? extends TargetMethodAnnotationDrivenBinder.ParameterBinder<?>> parameterBinders) {
+ return new WithCustomProperties(ambiguityResolver, CompoundList.of(this.parameterBinders, parameterBinders), matcher);
+ }
+
+ /**
+ * Configures this method delegation to only consider methods or constructors as a delegation target if they match the supplied matcher.
+ *
+ * @param matcher The matcher any delegation target needs to match in order to be considered a for delegation.
+ * @return A new delegation configuration which only considers methods for delegation if they match the supplied matcher.
+ */
+ public WithCustomProperties filter(ElementMatcher<? super MethodDescription> matcher) {
+ return new WithCustomProperties(ambiguityResolver,
+ parameterBinders,
+ new ElementMatcher.Junction.Conjunction<MethodDescription>(this.matcher, matcher));
+ }
+
+ /**
+ * Delegates any intercepted method to invoke a {@code static} method that is declared by the supplied type. To be considered
+ * a valid delegation target, the target method must be visible and accessible to the instrumented type. This is the case if
+ * the target type is either public or in the same package as the instrumented type and if the target method is either public
+ * or non-private and in the same package as the instrumented type. Private methods can only be used as a delegation target if
+ * the interception is targeting the instrumented type.
+ *
+ * @param type The target type for the delegation.
+ * @return A method delegation that redirects method calls to a static method of the supplied type.
+ */
+ public MethodDelegation to(Class<?> type) {
+ return to(new TypeDescription.ForLoadedType(type));
+ }
+
+ /**
+ * Delegates any intercepted method to invoke a {@code static} method that is declared by the supplied type. To be considered
+ * a valid delegation target, the target method must be visible and accessible to the instrumented type. This is the case if
+ * the target type is either public or in the same package as the instrumented type and if the target method is either public
+ * or non-private and in the same package as the instrumented type. Private methods can only be used as a delegation target if
+ * the delegation is targeting the instrumented type.
+ *
+ * @param typeDescription The target type for the delegation.
+ * @return A method delegation that redirects method calls to a static method of the supplied type.
+ */
+ public MethodDelegation to(TypeDescription typeDescription) {
+ if (typeDescription.isArray()) {
+ throw new IllegalArgumentException("Cannot delegate to array " + typeDescription);
+ } else if (typeDescription.isPrimitive()) {
+ throw new IllegalArgumentException("Cannot delegate to primitive " + typeDescription);
+ }
+ return new MethodDelegation(ImplementationDelegate.ForStaticMethod.of(typeDescription.getDeclaredMethods().filter(isStatic().and(matcher)),
+ TargetMethodAnnotationDrivenBinder.of(parameterBinders)), parameterBinders, ambiguityResolver);
+ }
+
+ /**
+ * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
+ * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
+ * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
+ * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
+ * a delegation target if the delegation is targeting the instrumented type.
+ *
+ * @param target The target instance for the delegation.
+ * @return A method delegation that redirects method calls to a static method of the supplied type.
+ */
+ public MethodDelegation to(Object target) {
+ return to(target, MethodGraph.Compiler.DEFAULT);
+ }
+
+ /**
+ * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
+ * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
+ * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
+ * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
+ * a delegation target if the delegation is targeting the instrumented type.
+ *
+ * @param target The target instance for the delegation.
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @return A method delegation that redirects method calls to a static method of the supplied type.
+ */
+ public MethodDelegation to(Object target, MethodGraph.Compiler methodGraphCompiler) {
+ return to(target, target.getClass(), methodGraphCompiler);
+ }
+
+ /**
+ * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
+ * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
+ * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
+ * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
+ * a delegation target if the delegation is targeting the instrumented type.
+ *
+ * @param target The target instance for the delegation.
+ * @param fieldName The name of the field that is holding the {@code target} instance.
+ * @return A method delegation that redirects method calls to a static method of the supplied type.
+ */
+ public MethodDelegation to(Object target, String fieldName) {
+ return to(target, fieldName, MethodGraph.Compiler.DEFAULT);
+ }
+
+ /**
+ * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
+ * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
+ * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
+ * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
+ * a delegation target if the delegation is targeting the instrumented type.
+ *
+ * @param target The target instance for the delegation.
+ * @param fieldName The name of the field that is holding the {@code target} instance.
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @return A method delegation that redirects method calls to a static method of the supplied type.
+ */
+ public MethodDelegation to(Object target, String fieldName, MethodGraph.Compiler methodGraphCompiler) {
+ return to(target, target.getClass(), fieldName, methodGraphCompiler);
+ }
+
+ /**
+ * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
+ * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
+ * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
+ * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
+ * a delegation target if the delegation is targeting the instrumented type.
+ *
+ * @param target The target instance for the delegation.
+ * @param type The most specific type of which {@code target} should be cosnidered. Must be a super type of the target's actual type.
+ * @return A method delegation that redirects method calls to a static method of the supplied type.
+ */
+ public MethodDelegation to(Object target, Type type) {
+ return to(target, type, MethodGraph.Compiler.DEFAULT);
+ }
+
+ /**
+ * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
+ * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
+ * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
+ * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
+ * a delegation target if the delegation is targeting the instrumented type.
+ *
+ * @param target The target instance for the delegation.
+ * @param type The most specific type of which {@code target} should be cosnidered. Must be a super type of the target's actual type.
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @return A method delegation that redirects method calls to a static method of the supplied type.
+ */
+ public MethodDelegation to(Object target, Type type, MethodGraph.Compiler methodGraphCompiler) {
+ return to(target,
+ type,
+ String.format("%s$%s", ImplementationDelegate.FIELD_NAME_PREFIX, RandomString.hashOf(target.hashCode())),
+ methodGraphCompiler);
+ }
+
+ /**
+ * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
+ * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
+ * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
+ * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
+ * a delegation target if the delegation is targeting the instrumented type.
+ *
+ * @param target The target instance for the delegation.
+ * @param type The most specific type of which {@code target} should be cosnidered. Must be a super type of the target's actual type.
+ * @param fieldName The name of the field that is holding the {@code target} instance.
+ * @return A method delegation that redirects method calls to a static method of the supplied type.
+ */
+ public MethodDelegation to(Object target, Type type, String fieldName) {
+ return to(target, type, fieldName, MethodGraph.Compiler.DEFAULT);
+ }
+
+ /**
+ * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
+ * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
+ * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
+ * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
+ * a delegation target if the delegation is targeting the instrumented type.
+ *
+ * @param target The target instance for the delegation.
+ * @param type The most specific type of which {@code target} should be cosnidered. Must be a super type of the target's actual type.
+ * @param fieldName The name of the field that is holding the {@code target} instance.
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @return A method delegation that redirects method calls to a static method of the supplied type.
+ */
+ public MethodDelegation to(Object target, Type type, String fieldName, MethodGraph.Compiler methodGraphCompiler) {
+ TypeDescription.Generic typeDescription = TypeDefinition.Sort.describe(type);
+ if (!typeDescription.asErasure().isInstance(target)) {
+ throw new IllegalArgumentException(target + " is not an instance of " + type);
+ }
+ return new MethodDelegation(new ImplementationDelegate.ForField.WithInstance(fieldName,
+ methodGraphCompiler,
+ parameterBinders,
+ matcher,
+ target,
+ typeDescription), parameterBinders, ambiguityResolver);
+ }
+
+ /**
+ * Delegates any intercepted method to invoke a constructor of the supplied type. To be considered a valid delegation target,
+ * a constructor must be visible and accessible to the instrumented type. This is the case if the constructor's declaring type is
+ * either public or in the same package as the instrumented type and if the constructor is either public or non-private and in
+ * the same package as the instrumented type. Private constructors can only be used as a delegation target if the delegation is
+ * targeting the instrumented type.
+ *
+ * @param type The type to construct.
+ * @return A delegation that redirects method calls to a constructor of the supplied type.
+ */
+ public MethodDelegation toConstructor(Class<?> type) {
+ return toConstructor(new TypeDescription.ForLoadedType(type));
+ }
+
+ /**
+ * Delegates any intercepted method to invoke a constructor of the supplied type. To be considered a valid delegation target,
+ * a constructor must be visible and accessible to the instrumented type. This is the case if the constructor's declaring type is
+ * either public or in the same package as the instrumented type and if the constructor is either public or non-private and in
+ * the same package as the instrumented type. Private constructors can only be used as a delegation target if the delegation is
+ * targeting the instrumented type.
+ *
+ * @param typeDescription The type to construct.
+ * @return A delegation that redirects method calls to a constructor of the supplied type.
+ */
+ public MethodDelegation toConstructor(TypeDescription typeDescription) {
+ return new MethodDelegation(ImplementationDelegate.ForConstruction.of(typeDescription,
+ typeDescription.getDeclaredMethods().filter(isConstructor().and(matcher)),
+ TargetMethodAnnotationDrivenBinder.of(parameterBinders)), parameterBinders, ambiguityResolver);
+ }
+
+ /**
+ * Delegates any intercepted method to invoke a non-{@code static} method on the instance of the supplied field. To be
+ * considered a valid delegation target, a method must be visible and accessible to the instrumented type. This is the
+ * case if the method's declaring type is either public or in the same package as the instrumented type and if the method
+ * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
+ * a delegation target if the delegation is targeting the instrumented type.
+ *
+ * @param name The field's name.
+ * @return A delegation that redirects invocations to a method of the specified field's instance.
+ */
+ public MethodDelegation toField(String name) {
+ return toField(name, FieldLocator.ForClassHierarchy.Factory.INSTANCE);
+ }
+
+ /**
+ * Delegates any intercepted method to invoke a non-{@code static} method on the instance of the supplied field. To be
+ * considered a valid delegation target, a method must be visible and accessible to the instrumented type. This is the
+ * case if the method's declaring type is either public or in the same package as the instrumented type and if the method
+ * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
+ * a delegation target if the delegation is targeting the instrumented type.
+ *
+ * @param name The field's name.
+ * @param fieldLocatorFactory The field locator factory to use.
+ * @return A delegation that redirects invocations to a method of the specified field's instance.
+ */
+ public MethodDelegation toField(String name, FieldLocator.Factory fieldLocatorFactory) {
+ return toField(name, fieldLocatorFactory, MethodGraph.Compiler.DEFAULT);
+ }
+
+ /**
+ * Delegates any intercepted method to invoke a non-{@code static} method on the instance of the supplied field. To be
+ * considered a valid delegation target, a method must be visible and accessible to the instrumented type. This is the
+ * case if the method's declaring type is either public or in the same package as the instrumented type and if the method
+ * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
+ * a delegation target if the ddelegation is targeting the instrumented type.
+ *
+ * @param name The field's name.
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @return A delegation that redirects invocations to a method of the specified field's instance.
+ */
+ public MethodDelegation toField(String name, MethodGraph.Compiler methodGraphCompiler) {
+ return toField(name, FieldLocator.ForClassHierarchy.Factory.INSTANCE, methodGraphCompiler);
+ }
+
+ /**
+ * Delegates any intercepted method to invoke a non-{@code static} method on the instance of the supplied field. To be
+ * considered a valid delegation target, a method must be visible and accessible to the instrumented type. This is the
+ * case if the method's declaring type is either public or in the same package as the instrumented type and if the method
+ * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
+ * a delegation target if the ddelegation is targeting the instrumented type.
+ *
+ * @param name The field's name.
+ * @param fieldLocatorFactory The field locator factory to use.
+ * @param methodGraphCompiler The method graph compiler to use.
+ * @return A delegation that redirects invocations to a method of the specified field's instance.
+ */
+ public MethodDelegation toField(String name, FieldLocator.Factory fieldLocatorFactory, MethodGraph.Compiler methodGraphCompiler) {
+ return new MethodDelegation(new ImplementationDelegate.ForField.WithLookup(name,
+ methodGraphCompiler,
+ parameterBinders,
+ matcher,
+ fieldLocatorFactory), parameterBinders, ambiguityResolver);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/StubMethod.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/StubMethod.java
new file mode 100644
index 0000000..e1a9310
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/StubMethod.java
@@ -0,0 +1,49 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.constant.DefaultValue;
+import net.bytebuddy.implementation.bytecode.member.MethodReturn;
+import org.objectweb.asm.MethodVisitor;
+
+/**
+ * This implementation creates a method stub which does nothing but returning the default value of the return
+ * type of the method. These default values are:
+ * <ol>
+ * <li>The value {@code 0} for all numeric type.</li>
+ * <li>The null character for the {@code char} type.</li>
+ * <li>{@code false} for the {@code boolean} type.</li>
+ * <li>Nothing for {@code void} types.</li>
+ * <li>A {@code null} reference for any reference types. Note that this includes primitive wrapper types.</li>
+ * </ol>
+ */
+public enum StubMethod implements Implementation, ByteCodeAppender {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return this;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor,
+ Context implementationContext,
+ MethodDescription instrumentedMethod) {
+ StackManipulation.Size stackSize = new StackManipulation.Compound(
+ DefaultValue.of(instrumentedMethod.getReturnType()),
+ MethodReturn.of(instrumentedMethod.getReturnType())
+ ).apply(methodVisitor, implementationContext);
+ return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/SuperMethodCall.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/SuperMethodCall.java
new file mode 100644
index 0000000..5012a08
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/SuperMethodCall.java
@@ -0,0 +1,142 @@
+package net.bytebuddy.implementation;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.implementation.bytecode.Removal;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.member.MethodReturn;
+import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+import org.objectweb.asm.MethodVisitor;
+
+/**
+ * This implementation will create a new method which simply calls its super method. If no such method is defined,
+ * an exception will be thrown. Constructors are considered to have a super method if the direct super class defines
+ * a constructor with an identical signature. Default methods are invoked as such if they are non-ambiguous. Static
+ * methods can have a (pseudo) super method if a type that defines such a method is rebased. Rebased types can also
+ * shadow constructors or methods of an actual super class. Besides implementing constructors, this implementation
+ * is useful when a method of a super type is not supposed to be altered but should be equipped with additional
+ * annotations. Furthermore, this implementation allows to hard code a super method call to be performed after
+ * performing another {@link Implementation}.
+ */
+public enum SuperMethodCall implements Implementation.Composable {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return new Appender(implementationTarget, Appender.TerminationHandler.RETURNING);
+ }
+
+ @Override
+ public Implementation andThen(Implementation implementation) {
+ return new Compound(WithoutReturn.INSTANCE, implementation);
+ }
+
+ /**
+ * A super method invocation where the return value is dropped instead of returning from the method.
+ */
+ protected enum WithoutReturn implements Implementation {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return new Appender(implementationTarget, Appender.TerminationHandler.DROPPING);
+ }
+ }
+
+ /**
+ * An appender for implementing a {@link net.bytebuddy.implementation.SuperMethodCall}.
+ */
+ @EqualsAndHashCode
+ protected static class Appender implements ByteCodeAppender {
+
+ /**
+ * The target of the current implementation.
+ */
+ private final Target implementationTarget;
+
+ /**
+ * The termination handler to apply after invoking the super method.
+ */
+ private final TerminationHandler terminationHandler;
+
+ /**
+ * Creates a new appender.
+ *
+ * @param implementationTarget The implementation target of the current type creation.
+ * @param terminationHandler The termination handler to apply after invoking the super method.
+ */
+ protected Appender(Target implementationTarget, TerminationHandler terminationHandler) {
+ this.implementationTarget = implementationTarget;
+ this.terminationHandler = terminationHandler;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodDescription instrumentedMethod) {
+ StackManipulation superMethodCall = implementationTarget.invokeDominant(instrumentedMethod.asSignatureToken());
+ if (!superMethodCall.isValid()) {
+ throw new IllegalStateException("Cannot call super (or default) method for " + instrumentedMethod);
+ }
+ StackManipulation.Size stackSize = new StackManipulation.Compound(
+ MethodVariableAccess.allArgumentsOf(instrumentedMethod).prependThisReference(),
+ superMethodCall,
+ terminationHandler.of(instrumentedMethod)
+ ).apply(methodVisitor, implementationContext);
+ return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+
+ /**
+ * A handler that determines how to handle the method return value.
+ */
+ protected enum TerminationHandler {
+
+ /**
+ * A termination handler that returns the value of the super method invocation.
+ */
+ RETURNING {
+ @Override
+ protected StackManipulation of(MethodDescription methodDescription) {
+ return MethodReturn.of(methodDescription.getReturnType());
+ }
+ },
+
+ /**
+ * A termination handler that simply pops the value of the super method invocation off the stack.
+ */
+ DROPPING {
+ @Override
+ protected StackManipulation of(MethodDescription methodDescription) {
+ return Removal.of(methodDescription.getReturnType());
+ }
+ };
+
+ /**
+ * Creates a stack manipulation that represents this handler's behavior.
+ *
+ * @param methodDescription The method for which this handler is supposed to create a stack
+ * manipulation for.
+ * @return The stack manipulation that implements this handler.
+ */
+ protected abstract StackManipulation of(MethodDescription methodDescription);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/attribute/AnnotationAppender.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/attribute/AnnotationAppender.java
new file mode 100644
index 0000000..a30e099
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/attribute/AnnotationAppender.java
@@ -0,0 +1,645 @@
+package net.bytebuddy.implementation.attribute;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.enumeration.EnumerationDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import org.objectweb.asm.*;
+
+import java.lang.reflect.Array;
+import java.util.List;
+
+/**
+ * Annotation appenders are capable of writing annotations to a specified target.
+ */
+public interface AnnotationAppender {
+
+ /**
+ * A constant for informing ASM over ignoring a given name.
+ */
+ String NO_NAME = null;
+
+ /**
+ * Writes the given annotation to the target that this appender represents.
+ *
+ * @param annotationDescription The annotation to be written.
+ * @param annotationValueFilter The annotation value filter to use.
+ * @return Usually {@code this} or any other annotation appender capable of writing another annotation to the specified target.
+ */
+ AnnotationAppender append(AnnotationDescription annotationDescription, AnnotationValueFilter annotationValueFilter);
+
+ /**
+ * Writes the given type annotation to the target that this appender represents.
+ *
+ * @param annotationDescription The annotation to be written.
+ * @param annotationValueFilter The annotation value filter to use.
+ * @param typeReference The type variable's type reference.
+ * @param typePath The type variable's type path.
+ * @return Usually {@code this} or any other annotation appender capable of writing another annotation to the specified target.
+ */
+ AnnotationAppender append(AnnotationDescription annotationDescription, AnnotationValueFilter annotationValueFilter, int typeReference, String typePath);
+
+ /**
+ * Represents a target for an annotation writing process.
+ */
+ interface Target {
+
+ /**
+ * Creates an annotation visitor for writing the specified annotation.
+ *
+ * @param annotationTypeDescriptor The type descriptor for the annotation to be written.
+ * @param visible {@code true} if the annotation is to be visible at runtime.
+ * @return An annotation visitor for consuming the specified annotation.
+ */
+ AnnotationVisitor visit(String annotationTypeDescriptor, boolean visible);
+
+ /**
+ * Creates an annotation visitor for writing the specified type annotation.
+ *
+ * @param annotationTypeDescriptor The type descriptor for the annotation to be written.
+ * @param visible {@code true} if the annotation is to be visible at runtime.
+ * @param typeReference The type annotation's type reference.
+ * @param typePath The type annotation's type path.
+ * @return An annotation visitor for consuming the specified annotation.
+ */
+ AnnotationVisitor visit(String annotationTypeDescriptor, boolean visible, int typeReference, String typePath);
+
+ /**
+ * Target for an annotation that is written to a Java type.
+ */
+ @EqualsAndHashCode
+ class OnType implements Target {
+
+ /**
+ * The class visitor to write the annotation to.
+ */
+ private final ClassVisitor classVisitor;
+
+ /**
+ * Creates a new wrapper for a Java type.
+ *
+ * @param classVisitor The ASM class visitor to which the annotations are appended to.
+ */
+ public OnType(ClassVisitor classVisitor) {
+ this.classVisitor = classVisitor;
+ }
+
+ @Override
+ public AnnotationVisitor visit(String annotationTypeDescriptor, boolean visible) {
+ return classVisitor.visitAnnotation(annotationTypeDescriptor, visible);
+ }
+
+ @Override
+ public AnnotationVisitor visit(String annotationTypeDescriptor, boolean visible, int typeReference, String typePath) {
+ return classVisitor.visitTypeAnnotation(typeReference, TypePath.fromString(typePath), annotationTypeDescriptor, visible);
+ }
+ }
+
+ /**
+ * Target for an annotation that is written to a Java method or constructor.
+ */
+ @EqualsAndHashCode
+ class OnMethod implements Target {
+
+ /**
+ * The method visitor to write the annotation to.
+ */
+ private final MethodVisitor methodVisitor;
+
+ /**
+ * Creates a new wrapper for a Java method or constructor.
+ *
+ * @param methodVisitor The ASM method visitor to which the annotations are appended to.
+ */
+ public OnMethod(MethodVisitor methodVisitor) {
+ this.methodVisitor = methodVisitor;
+ }
+
+ @Override
+ public AnnotationVisitor visit(String annotationTypeDescriptor, boolean visible) {
+ return methodVisitor.visitAnnotation(annotationTypeDescriptor, visible);
+ }
+
+ @Override
+ public AnnotationVisitor visit(String annotationTypeDescriptor, boolean visible, int typeReference, String typePath) {
+ return methodVisitor.visitTypeAnnotation(typeReference, TypePath.fromString(typePath), annotationTypeDescriptor, visible);
+ }
+ }
+
+ /**
+ * Target for an annotation that is written to a Java method or constructor parameter.
+ */
+ @EqualsAndHashCode
+ class OnMethodParameter implements Target {
+
+ /**
+ * The method visitor to write the annotation to.
+ */
+ private final MethodVisitor methodVisitor;
+
+ /**
+ * The method parameter index to write the annotation to.
+ */
+ private final int parameterIndex;
+
+ /**
+ * Creates a new wrapper for a Java method or constructor.
+ *
+ * @param methodVisitor The ASM method visitor to which the annotations are appended to.
+ * @param parameterIndex The index of the method parameter.
+ */
+ public OnMethodParameter(MethodVisitor methodVisitor, int parameterIndex) {
+ this.methodVisitor = methodVisitor;
+ this.parameterIndex = parameterIndex;
+ }
+
+ @Override
+ public AnnotationVisitor visit(String annotationTypeDescriptor, boolean visible) {
+ return methodVisitor.visitParameterAnnotation(parameterIndex, annotationTypeDescriptor, visible);
+ }
+
+ @Override
+ public AnnotationVisitor visit(String annotationTypeDescriptor, boolean visible, int typeReference, String typePath) {
+ return methodVisitor.visitTypeAnnotation(typeReference, TypePath.fromString(typePath), annotationTypeDescriptor, visible);
+ }
+ }
+
+ /**
+ * Target for an annotation that is written to a Java field.
+ */
+ @EqualsAndHashCode
+ class OnField implements Target {
+
+ /**
+ * The field visitor to write the annotation to.
+ */
+ private final FieldVisitor fieldVisitor;
+
+ /**
+ * Creates a new wrapper for a Java field.
+ *
+ * @param fieldVisitor The ASM field visitor to which the annotations are appended to.
+ */
+ public OnField(FieldVisitor fieldVisitor) {
+ this.fieldVisitor = fieldVisitor;
+ }
+
+ @Override
+ public AnnotationVisitor visit(String annotationTypeDescriptor, boolean visible) {
+ return fieldVisitor.visitAnnotation(annotationTypeDescriptor, visible);
+ }
+
+ @Override
+ public AnnotationVisitor visit(String annotationTypeDescriptor, boolean visible, int typeReference, String typePath) {
+ return fieldVisitor.visitTypeAnnotation(typeReference, TypePath.fromString(typePath), annotationTypeDescriptor, visible);
+ }
+ }
+ }
+
+ /**
+ * A default implementation for an annotation appender that writes annotations to a given byte consumer
+ * represented by an ASM {@link org.objectweb.asm.AnnotationVisitor}.
+ */
+ @EqualsAndHashCode
+ class Default implements AnnotationAppender {
+
+ /**
+ * The target onto which an annotation write process is to be applied.
+ */
+ private final Target target;
+
+ /**
+ * Creates a default annotation appender.
+ *
+ * @param target The target to which annotations are written to.
+ */
+ public Default(Target target) {
+ this.target = target;
+ }
+
+ /**
+ * Handles the writing of a single annotation to an annotation visitor.
+ *
+ * @param annotationVisitor The annotation visitor the write process is to be applied on.
+ * @param annotation The annotation to be written.
+ * @param annotationValueFilter The value filter to apply for discovering which values of an annotation should be written.
+ */
+ private static void handle(AnnotationVisitor annotationVisitor, AnnotationDescription annotation, AnnotationValueFilter annotationValueFilter) {
+ for (MethodDescription.InDefinedShape methodDescription : annotation.getAnnotationType().getDeclaredMethods()) {
+ if (annotationValueFilter.isRelevant(annotation, methodDescription)) {
+ apply(annotationVisitor, methodDescription.getReturnType().asErasure(), methodDescription.getName(), annotation.getValue(methodDescription).resolve());
+ }
+ }
+ annotationVisitor.visitEnd();
+ }
+
+ /**
+ * Performs the writing of a given annotation value to an annotation visitor.
+ *
+ * @param annotationVisitor The annotation visitor the write process is to be applied on.
+ * @param valueType The type of the annotation value.
+ * @param name The name of the annotation type.
+ * @param value The annotation's value.
+ */
+ public static void apply(AnnotationVisitor annotationVisitor, TypeDescription valueType, String name, Object value) {
+ if (valueType.isArray()) { // The Android emulator reads annotation arrays as annotation types. Therefore, this check needs to come first.
+ AnnotationVisitor arrayVisitor = annotationVisitor.visitArray(name);
+ int length = Array.getLength(value);
+ TypeDescription componentType = valueType.getComponentType();
+ for (int index = 0; index < length; index++) {
+ apply(arrayVisitor, componentType, NO_NAME, Array.get(value, index));
+ }
+ arrayVisitor.visitEnd();
+ } else if (valueType.isAnnotation()) {
+ handle(annotationVisitor.visitAnnotation(name, valueType.getDescriptor()), (AnnotationDescription) value, AnnotationValueFilter.Default.APPEND_DEFAULTS);
+ } else if (valueType.isEnum()) {
+ annotationVisitor.visitEnum(name, valueType.getDescriptor(), ((EnumerationDescription) value).getValue());
+ } else if (valueType.represents(Class.class)) {
+ annotationVisitor.visit(name, Type.getType(((TypeDescription) value).getDescriptor()));
+ } else {
+ annotationVisitor.visit(name, value);
+ }
+ }
+
+ @Override
+ public AnnotationAppender append(AnnotationDescription annotationDescription, AnnotationValueFilter annotationValueFilter) {
+ switch (annotationDescription.getRetention()) {
+ case RUNTIME:
+ doAppend(annotationDescription, true, annotationValueFilter);
+ break;
+ case CLASS:
+ doAppend(annotationDescription, false, annotationValueFilter);
+ break;
+ case SOURCE:
+ break;
+ default:
+ throw new IllegalStateException("Unexpected retention policy: " + annotationDescription.getRetention());
+ }
+ return this;
+ }
+
+ /**
+ * Tries to append a given annotation by reflectively reading an annotation.
+ *
+ * @param annotation The annotation to be written.
+ * @param visible {@code true} if this annotation should be treated as visible at runtime.
+ * @param annotationValueFilter The annotation value filter to apply.
+ */
+ private void doAppend(AnnotationDescription annotation, boolean visible, AnnotationValueFilter annotationValueFilter) {
+ handle(target.visit(annotation.getAnnotationType().getDescriptor(), visible), annotation, annotationValueFilter);
+ }
+
+ @Override
+ public AnnotationAppender append(AnnotationDescription annotationDescription, AnnotationValueFilter annotationValueFilter, int typeReference, String typePath) {
+ switch (annotationDescription.getRetention()) {
+ case RUNTIME:
+ doAppend(annotationDescription, true, annotationValueFilter, typeReference, typePath);
+ break;
+ case CLASS:
+ doAppend(annotationDescription, false, annotationValueFilter, typeReference, typePath);
+ break;
+ case SOURCE:
+ break;
+ default:
+ throw new IllegalStateException("Unexpected retention policy: " + annotationDescription.getRetention());
+ }
+ return this;
+ }
+
+ /**
+ * Tries to append a given annotation by reflectively reading an annotation.
+ *
+ * @param annotation The annotation to be written.
+ * @param visible {@code true} if this annotation should be treated as visible at runtime.
+ * @param annotationValueFilter The annotation value filter to apply.
+ * @param typeReference The type annotation's type reference.
+ * @param typePath The type annotation's type path.
+ */
+ private void doAppend(AnnotationDescription annotation,
+ boolean visible,
+ AnnotationValueFilter annotationValueFilter,
+ int typeReference,
+ String typePath) {
+ handle(target.visit(annotation.getAnnotationType().getDescriptor(), visible, typeReference, typePath), annotation, annotationValueFilter);
+ }
+ }
+
+ /**
+ * A type visitor that visits all type annotations of a generic type and writes any discovered annotation to a
+ * supplied {@link AnnotationAppender}.
+ */
+ @EqualsAndHashCode
+ class ForTypeAnnotations implements TypeDescription.Generic.Visitor<AnnotationAppender> {
+
+ /**
+ * Indicates that type variables type annotations are written on a Java type.
+ */
+ public static final boolean VARIABLE_ON_TYPE = true;
+
+ /**
+ * Indicates that type variables type annotations are written on a Java method or constructor.
+ */
+ public static final boolean VARIABLE_ON_INVOKEABLE = false;
+
+ /**
+ * Represents an empty type path.
+ */
+ private static final String EMPTY_TYPE_PATH = "";
+
+ /**
+ * Represents a step to a component type within a type path.
+ */
+ private static final char COMPONENT_TYPE_PATH = '[';
+
+ /**
+ * Represents a wildcard type step within a type path.
+ */
+ private static final char WILDCARD_TYPE_PATH = '*';
+
+ /**
+ * Represents a (reversed) type step to an inner class within a type path.
+ */
+ private static final char INNER_CLASS_PATH = '.';
+
+ /**
+ * Represents an index tzpe delimiter within a type path.
+ */
+ private static final char INDEXED_TYPE_DELIMITER = ';';
+
+ /**
+ * The index that indicates that super type type annotations are written onto a super class.
+ */
+ private static final int SUPER_CLASS_INDEX = -1;
+
+ /**
+ * The annotation appender to use.
+ */
+ private final AnnotationAppender annotationAppender;
+
+ /**
+ * The annotation value filter to use.
+ */
+ private final AnnotationValueFilter annotationValueFilter;
+
+ /**
+ * The type reference to use.
+ */
+ private final int typeReference;
+
+ /**
+ * The type path to use.
+ */
+ private final String typePath;
+
+ /**
+ * Creates a new type annotation appending visitor for an empty type path.
+ *
+ * @param annotationAppender The annotation appender to use.
+ * @param annotationValueFilter The annotation value filter to use.
+ * @param typeReference The type reference to use.
+ */
+ protected ForTypeAnnotations(AnnotationAppender annotationAppender, AnnotationValueFilter annotationValueFilter, TypeReference typeReference) {
+ this(annotationAppender, annotationValueFilter, typeReference.getValue(), EMPTY_TYPE_PATH);
+ }
+
+ /**
+ * Creates a new type annotation appending visitor.
+ *
+ * @param annotationAppender The annotation appender to use.
+ * @param annotationValueFilter The annotation value filter to use.
+ * @param typeReference The type reference to use.
+ * @param typePath The type path to use.
+ */
+ protected ForTypeAnnotations(AnnotationAppender annotationAppender, AnnotationValueFilter annotationValueFilter, int typeReference, String typePath) {
+ this.annotationAppender = annotationAppender;
+ this.annotationValueFilter = annotationValueFilter;
+ this.typeReference = typeReference;
+ this.typePath = typePath;
+ }
+
+ /**
+ * Creates a type annotation appender for a type annotations of a super class type.
+ *
+ * @param annotationAppender The annotation appender to write any type annotation to.
+ * @param annotationValueFilter The annotation value filter to apply.
+ * @return A visitor for appending type annotations of a super class.
+ */
+ public static TypeDescription.Generic.Visitor<AnnotationAppender> ofSuperClass(AnnotationAppender annotationAppender,
+ AnnotationValueFilter annotationValueFilter) {
+ return new ForTypeAnnotations(annotationAppender, annotationValueFilter, TypeReference.newSuperTypeReference(SUPER_CLASS_INDEX));
+ }
+
+ /**
+ * Creates a type annotation appender for type annotations of an interface type.
+ *
+ * @param annotationAppender The annotation appender to write any type annotation to.
+ * @param annotationValueFilter The annotation value filter to apply.
+ * @param index The index of the interface type.
+ * @return A visitor for appending type annotations of an interface type.
+ */
+ public static TypeDescription.Generic.Visitor<AnnotationAppender> ofInterfaceType(AnnotationAppender annotationAppender,
+ AnnotationValueFilter annotationValueFilter,
+ int index) {
+ return new ForTypeAnnotations(annotationAppender, annotationValueFilter, TypeReference.newSuperTypeReference(index));
+ }
+
+ /**
+ * Creates a type annotation appender for type annotations of a field's type.
+ *
+ * @param annotationAppender The annotation appender to write any type annotation to.
+ * @param annotationValueFilter The annotation value filter to apply.
+ * @return A visitor for appending type annotations of a field's type.
+ */
+ public static TypeDescription.Generic.Visitor<AnnotationAppender> ofFieldType(AnnotationAppender annotationAppender,
+ AnnotationValueFilter annotationValueFilter) {
+ return new ForTypeAnnotations(annotationAppender, annotationValueFilter, TypeReference.newTypeReference(TypeReference.FIELD));
+ }
+
+ /**
+ * Creates a type annotation appender for type annotations of a method's return type.
+ *
+ * @param annotationAppender The annotation appender to write any type annotation to.
+ * @param annotationValueFilter The annotation value filter to apply.
+ * @return A visitor for appending type annotations of a method's return type.
+ */
+ public static TypeDescription.Generic.Visitor<AnnotationAppender> ofMethodReturnType(AnnotationAppender annotationAppender,
+ AnnotationValueFilter annotationValueFilter) {
+ return new ForTypeAnnotations(annotationAppender, annotationValueFilter, TypeReference.newTypeReference(TypeReference.METHOD_RETURN));
+ }
+
+ /**
+ * Creates a type annotation appender for type annotations of a method's parameter type.
+ *
+ * @param annotationAppender The annotation appender to write any type annotation to.
+ * @param annotationValueFilter The annotation value filter to apply.
+ * @param index The parameter index.
+ * @return A visitor for appending type annotations of a method's parameter type.
+ */
+ public static TypeDescription.Generic.Visitor<AnnotationAppender> ofMethodParameterType(AnnotationAppender annotationAppender,
+ AnnotationValueFilter annotationValueFilter,
+ int index) {
+ return new ForTypeAnnotations(annotationAppender, annotationValueFilter, TypeReference.newFormalParameterReference(index));
+ }
+
+ /**
+ * Creates a type annotation appender for type annotations of a method's exception type.
+ *
+ * @param annotationAppender The annotation appender to write any type annotation to.
+ * @param annotationValueFilter The annotation value filter to apply.
+ * @param index The exception type's index.
+ * @return A visitor for appending type annotations of a method's exception type.
+ */
+ public static TypeDescription.Generic.Visitor<AnnotationAppender> ofExceptionType(AnnotationAppender annotationAppender,
+ AnnotationValueFilter annotationValueFilter,
+ int index) {
+ return new ForTypeAnnotations(annotationAppender, annotationValueFilter, TypeReference.newExceptionReference(index));
+ }
+
+ /**
+ * Creates a type annotation appender for type annotations of a method's receiver type.
+ *
+ * @param annotationAppender The annotation appender to write any type annotation to.
+ * @param annotationValueFilter The annotation value filter to apply.
+ * @return A visitor for appending type annotations of a method's receiver type.
+ */
+ public static TypeDescription.Generic.Visitor<AnnotationAppender> ofReceiverType(AnnotationAppender annotationAppender,
+ AnnotationValueFilter annotationValueFilter) {
+ return new ForTypeAnnotations(annotationAppender, annotationValueFilter, TypeReference.newTypeReference(TypeReference.METHOD_RECEIVER));
+ }
+
+ /**
+ * Appends all supplied type variables to the supplied method appender.
+ *
+ * @param annotationAppender The annotation appender to write any type annotation to.
+ * @param annotationValueFilter The annotation value filter to apply.
+ * @param variableOnType {@code true} if the type variables are declared by a type, {@code false} if they are declared by a method.
+ * @param typeVariables The type variables to append.
+ * @return The resulting annotation appender.
+ */
+ public static AnnotationAppender ofTypeVariable(AnnotationAppender annotationAppender,
+ AnnotationValueFilter annotationValueFilter,
+ boolean variableOnType,
+ List<? extends TypeDescription.Generic> typeVariables) {
+ return ofTypeVariable(annotationAppender, annotationValueFilter, variableOnType, 0, typeVariables);
+ }
+
+ /**
+ * Appends all supplied type variables to the supplied method appender.
+ *
+ * @param annotationAppender The annotation appender to write any type annotation to.
+ * @param annotationValueFilter The annotation value filter to apply.
+ * @param variableOnType {@code true} if the type variables are declared by a type, {@code false} if they are declared by a method.
+ * @param subListIndex The index of the first type variable to append. All previous type variables are ignored.
+ * @param typeVariables The type variables to append.
+ * @return The resulting annotation appender.
+ */
+ public static AnnotationAppender ofTypeVariable(AnnotationAppender annotationAppender,
+ AnnotationValueFilter annotationValueFilter,
+ boolean variableOnType,
+ int subListIndex,
+ List<? extends TypeDescription.Generic> typeVariables) {
+ int typeVariableIndex = subListIndex, variableBaseReference, variableBoundBaseBase;
+ if (variableOnType) {
+ variableBaseReference = TypeReference.CLASS_TYPE_PARAMETER;
+ variableBoundBaseBase = TypeReference.CLASS_TYPE_PARAMETER_BOUND;
+ } else {
+ variableBaseReference = TypeReference.METHOD_TYPE_PARAMETER;
+ variableBoundBaseBase = TypeReference.METHOD_TYPE_PARAMETER_BOUND;
+ }
+ for (TypeDescription.Generic typeVariable : typeVariables.subList(subListIndex, typeVariables.size())) {
+ int typeReference = TypeReference.newTypeParameterReference(variableBaseReference, typeVariableIndex).getValue();
+ for (AnnotationDescription annotationDescription : typeVariable.getDeclaredAnnotations()) {
+ annotationAppender = annotationAppender.append(annotationDescription, annotationValueFilter, typeReference, EMPTY_TYPE_PATH);
+ }
+ int boundIndex = !typeVariable.getUpperBounds().get(0).getSort().isTypeVariable() && typeVariable.getUpperBounds().get(0).isInterface()
+ ? 1
+ : 0;
+ for (TypeDescription.Generic typeBound : typeVariable.getUpperBounds()) {
+ annotationAppender = typeBound.accept(new ForTypeAnnotations(annotationAppender,
+ annotationValueFilter,
+ TypeReference.newTypeParameterBoundReference(variableBoundBaseBase, typeVariableIndex, boundIndex++)));
+ }
+ typeVariableIndex++;
+ }
+ return annotationAppender;
+ }
+
+ @Override
+ public AnnotationAppender onGenericArray(TypeDescription.Generic genericArray) {
+ return genericArray.getComponentType().accept(new ForTypeAnnotations(apply(genericArray, typePath),
+ annotationValueFilter,
+ typeReference,
+ typePath + COMPONENT_TYPE_PATH));
+ }
+
+ @Override
+ public AnnotationAppender onWildcard(TypeDescription.Generic wildcard) {
+ TypeList.Generic lowerBounds = wildcard.getLowerBounds();
+ return (lowerBounds.isEmpty()
+ ? wildcard.getUpperBounds().getOnly()
+ : lowerBounds.getOnly()).accept(new ForTypeAnnotations(apply(wildcard, typePath), annotationValueFilter, typeReference, typePath + WILDCARD_TYPE_PATH));
+ }
+
+ @Override
+ public AnnotationAppender onParameterizedType(TypeDescription.Generic parameterizedType) {
+ StringBuilder typePath = new StringBuilder(this.typePath);
+ for (int index = 0; index < parameterizedType.asErasure().getSegmentCount(); index++) {
+ typePath = typePath.append(INNER_CLASS_PATH);
+ }
+ AnnotationAppender annotationAppender = apply(parameterizedType, typePath.toString());
+ TypeDescription.Generic ownerType = parameterizedType.getOwnerType();
+ if (ownerType != null) {
+ annotationAppender = ownerType.accept(new ForTypeAnnotations(annotationAppender,
+ annotationValueFilter,
+ typeReference,
+ this.typePath));
+ }
+ int index = 0;
+ for (TypeDescription.Generic typeArgument : parameterizedType.getTypeArguments()) {
+ annotationAppender = typeArgument.accept(new ForTypeAnnotations(annotationAppender,
+ annotationValueFilter,
+ typeReference,
+ typePath.toString() + index++ + INDEXED_TYPE_DELIMITER));
+ }
+ return annotationAppender;
+ }
+
+ @Override
+ public AnnotationAppender onTypeVariable(TypeDescription.Generic typeVariable) {
+ return apply(typeVariable, typePath);
+ }
+
+ @Override
+ public AnnotationAppender onNonGenericType(TypeDescription.Generic typeDescription) {
+ StringBuilder typePath = new StringBuilder(this.typePath);
+ for (int index = 0; index < typeDescription.asErasure().getSegmentCount(); index++) {
+ typePath = typePath.append(INNER_CLASS_PATH);
+ }
+ AnnotationAppender annotationAppender = apply(typeDescription, typePath.toString());
+ if (typeDescription.isArray()) {
+ annotationAppender = typeDescription.getComponentType().accept(new ForTypeAnnotations(annotationAppender,
+ annotationValueFilter,
+ typeReference,
+ this.typePath + COMPONENT_TYPE_PATH)); // Impossible to be inner class
+ }
+ return annotationAppender;
+ }
+
+ /**
+ * Writes all annotations of the supplied type to this instance's annotation appender.
+ *
+ * @param typeDescription The type of what all annotations should be written of.
+ * @param typePath The type path to use.
+ * @return The resulting annotation appender.
+ */
+ private AnnotationAppender apply(TypeDescription.Generic typeDescription, String typePath) {
+ AnnotationAppender annotationAppender = this.annotationAppender;
+ for (AnnotationDescription annotationDescription : typeDescription.getDeclaredAnnotations()) {
+ annotationAppender = annotationAppender.append(annotationDescription, annotationValueFilter, typeReference, typePath);
+ }
+ return annotationAppender;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/attribute/AnnotationRetention.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/attribute/AnnotationRetention.java
new file mode 100644
index 0000000..f160138
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/attribute/AnnotationRetention.java
@@ -0,0 +1,55 @@
+package net.bytebuddy.implementation.attribute;
+
+/**
+ * An annotation retention strategy decides if annotations that are contained within a class file are preserved upon redefining
+ * or rebasing a method. When annotations are retained, it is important not to define annotations explicitly that are already
+ * defined. When annotations are retained, they are retained in their original format, i.e. default values that were not included
+ * in the class file are not added or skipped as determined by a {@link AnnotationValueFilter}.
+ */
+public enum AnnotationRetention {
+
+ /**
+ * Enables annotation retention, i.e. annotations within an existing class files are preserved as they are.
+ */
+ ENABLED(true),
+
+ /**
+ * Disables annotation retention, i.e. annotations within an existing class files are discarded.
+ */
+ DISABLED(false);
+
+ /**
+ * {@code true} if annotation retention is enabled.
+ */
+ private final boolean enabled;
+
+ /**
+ * Creates an annotation retention strategy.
+ *
+ * @param enabled {@code true} if annotation retention is enabled.
+ */
+ AnnotationRetention(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ /**
+ * Resolves an annotation retention from a boolean value.
+ *
+ * @param enabled {@code true} if annotation retention is enabled.
+ * @return An enabled annotation retention if the value is {@code true}.
+ */
+ public static AnnotationRetention of(boolean enabled) {
+ return enabled
+ ? ENABLED
+ : DISABLED;
+ }
+
+ /**
+ * Returns {@code true} if annotation retention is enabled.
+ *
+ * @return {@code true} if annotation retention is enabled.
+ */
+ public boolean isEnabled() {
+ return enabled;
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/attribute/AnnotationValueFilter.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/attribute/AnnotationValueFilter.java
new file mode 100644
index 0000000..3ac8157
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/attribute/AnnotationValueFilter.java
@@ -0,0 +1,94 @@
+package net.bytebuddy.implementation.attribute;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+
+/**
+ * An annotation value filter is responsible for determining which values should be skipped and rather be represented as an
+ * annotation type's default values when writing an annotation to a class file.
+ */
+public interface AnnotationValueFilter {
+
+ /**
+ * Checks if the given annotation value should be written as the value of the provided annotation property.
+ *
+ * @param annotationDescription The annotation value that is being written.
+ * @param methodDescription The annotation method of which a value is being written.
+ * @return {@code true} if the value should be written to the annotation.
+ */
+ boolean isRelevant(AnnotationDescription annotationDescription, MethodDescription.InDefinedShape methodDescription);
+
+ /**
+ * A factory for creating an annotation value filter for an annotation's target.
+ */
+ interface Factory {
+
+ /**
+ * Creates an annotation value filter for writing annotations on an instrumented type.
+ *
+ * @param instrumentedType The instrumented type onto which the annotations are written.
+ * @return An annotation value filter to be used when writing annotations onto the given type.
+ */
+ AnnotationValueFilter on(TypeDescription instrumentedType);
+
+ /**
+ * Creates an annotation value filter for writing annotations on a field.
+ *
+ * @param fieldDescription The field onto which the annotations are written.
+ * @return An annotation value filter to be used when writing annotations onto the given field.
+ */
+ AnnotationValueFilter on(FieldDescription fieldDescription);
+
+ /**
+ * Creates an annotation value filter for writing annotations on a method.
+ *
+ * @param methodDescription The method onto which the annotations are written.
+ * @return An annotation value filter to be used when writing annotations onto the given method.
+ */
+ AnnotationValueFilter on(MethodDescription methodDescription);
+ }
+
+ /**
+ * A default implementation of an annotation value filter that applies the same strategy for any type, field or method.
+ */
+ enum Default implements AnnotationValueFilter, Factory {
+
+ /**
+ * An annotation value filter where default values are skipped and not included in the class file.
+ */
+ SKIP_DEFAULTS {
+ @Override
+ public boolean isRelevant(AnnotationDescription annotationDescription, MethodDescription.InDefinedShape methodDescription) {
+ Object defaultValue = methodDescription.getDefaultValue();
+ return defaultValue == null || !defaultValue.equals(annotationDescription.getValue(methodDescription));
+ }
+ },
+
+ /**
+ * An annotation value filter where default values are included in the class file.
+ */
+ APPEND_DEFAULTS {
+ @Override
+ public boolean isRelevant(AnnotationDescription annotationDescription, MethodDescription.InDefinedShape methodDescription) {
+ return true;
+ }
+ };
+
+ @Override
+ public AnnotationValueFilter on(TypeDescription instrumentedType) {
+ return this;
+ }
+
+ @Override
+ public AnnotationValueFilter on(FieldDescription fieldDescription) {
+ return this;
+ }
+
+ @Override
+ public AnnotationValueFilter on(MethodDescription methodDescription) {
+ return this;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/attribute/FieldAttributeAppender.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/attribute/FieldAttributeAppender.java
new file mode 100644
index 0000000..ba18a8f
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/attribute/FieldAttributeAppender.java
@@ -0,0 +1,215 @@
+package net.bytebuddy.implementation.attribute;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import org.objectweb.asm.FieldVisitor;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * An appender that writes attributes or annotations to a given ASM {@link org.objectweb.asm.FieldVisitor}.
+ */
+public interface FieldAttributeAppender {
+
+ /**
+ * Applies this attribute appender to a given field visitor.
+ *
+ * @param fieldVisitor The field visitor to which the attributes that are represented by this attribute appender are written to.
+ * @param fieldDescription The description of the field to which the field visitor belongs to.
+ * @param annotationValueFilter The annotation value filter to apply when writing annotations.
+ */
+ void apply(FieldVisitor fieldVisitor, FieldDescription fieldDescription, AnnotationValueFilter annotationValueFilter);
+
+ /**
+ * A field attribute appender that does not append any attributes.
+ */
+ enum NoOp implements FieldAttributeAppender, Factory {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public FieldAttributeAppender make(TypeDescription typeDescription) {
+ return this;
+ }
+
+ @Override
+ public void apply(FieldVisitor fieldVisitor, FieldDescription fieldDescription, AnnotationValueFilter annotationValueFilter) {
+ /* do nothing */
+ }
+ }
+
+ /**
+ * A factory that creates field attribute appenders for a given type.
+ */
+ interface Factory {
+
+ /**
+ * Returns a field attribute appender that is applicable for a given type description.
+ *
+ * @param typeDescription The type for which a field attribute appender is to be applied for.
+ * @return The field attribute appender which should be applied for the given type.
+ */
+ FieldAttributeAppender make(TypeDescription typeDescription);
+
+ /**
+ * A field attribute appender factory that combines several field attribute appender factories to be
+ * represented as a single factory.
+ */
+ @EqualsAndHashCode
+ class Compound implements Factory {
+
+ /**
+ * The factories that this compound factory represents in their application order.
+ */
+ private final List<Factory> factories;
+
+ /**
+ * Creates a new compound field attribute appender factory.
+ *
+ * @param factory The factories to represent in the order of their application.
+ */
+ public Compound(Factory... factory) {
+ this(Arrays.asList(factory));
+ }
+
+ /**
+ * Creates a new compound field attribute appender factory.
+ *
+ * @param factories The factories to represent in the order of their application.
+ */
+ public Compound(List<? extends Factory> factories) {
+ this.factories = new ArrayList<Factory>();
+ for (Factory factory : factories) {
+ if (factory instanceof Compound) {
+ this.factories.addAll(((Compound) factory).factories);
+ } else if (!(factory instanceof NoOp)) {
+ this.factories.add(factory);
+ }
+ }
+ }
+
+ @Override
+ public FieldAttributeAppender make(TypeDescription typeDescription) {
+ List<FieldAttributeAppender> fieldAttributeAppenders = new ArrayList<FieldAttributeAppender>(factories.size());
+ for (Factory factory : factories) {
+ fieldAttributeAppenders.add(factory.make(typeDescription));
+ }
+ return new FieldAttributeAppender.Compound(fieldAttributeAppenders);
+ }
+ }
+ }
+
+ /**
+ * An attribute appender that writes all annotations that are declared on a field.
+ */
+ enum ForInstrumentedField implements FieldAttributeAppender, Factory {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public void apply(FieldVisitor fieldVisitor, FieldDescription fieldDescription, AnnotationValueFilter annotationValueFilter) {
+ AnnotationAppender annotationAppender = new AnnotationAppender.Default(new AnnotationAppender.Target.OnField(fieldVisitor));
+ annotationAppender = fieldDescription.getType().accept(AnnotationAppender.ForTypeAnnotations.ofFieldType(annotationAppender, annotationValueFilter));
+ for (AnnotationDescription annotation : fieldDescription.getDeclaredAnnotations()) {
+ annotationAppender = annotationAppender.append(annotation, annotationValueFilter);
+ }
+ }
+
+ @Override
+ public FieldAttributeAppender make(TypeDescription typeDescription) {
+ return this;
+ }
+ }
+
+ /**
+ * Appends an annotation to a field. The visibility of the annotation is determined by the annotation type's
+ * {@link java.lang.annotation.RetentionPolicy} annotation.
+ */
+ @EqualsAndHashCode
+ class Explicit implements FieldAttributeAppender, Factory {
+
+ /**
+ * The annotations that this appender appends.
+ */
+ private final List<? extends AnnotationDescription> annotations;
+
+ /**
+ * Creates a new annotation attribute appender for explicit annotation values. All values, including default values, are copied.
+ *
+ * @param annotations The annotations to be appended to the field.
+ */
+ public Explicit(List<? extends AnnotationDescription> annotations) {
+ this.annotations = annotations;
+ }
+
+ @Override
+ public void apply(FieldVisitor fieldVisitor, FieldDescription fieldDescription, AnnotationValueFilter annotationValueFilter) {
+ AnnotationAppender appender = new AnnotationAppender.Default(new AnnotationAppender.Target.OnField(fieldVisitor));
+ for (AnnotationDescription annotation : annotations) {
+ appender = appender.append(annotation, annotationValueFilter);
+ }
+ }
+
+ @Override
+ public FieldAttributeAppender make(TypeDescription typeDescription) {
+ return this;
+ }
+ }
+
+ /**
+ * A field attribute appender that combines several method attribute appenders to be represented as a single
+ * field attribute appender.
+ */
+ @EqualsAndHashCode
+ class Compound implements FieldAttributeAppender {
+
+ /**
+ * The field attribute appenders this appender represents in their application order.
+ */
+ private final List<FieldAttributeAppender> fieldAttributeAppenders;
+
+ /**
+ * Creates a new compound field attribute appender.
+ *
+ * @param fieldAttributeAppender The field attribute appenders that are to be combined by this compound appender
+ * in the order of their application.
+ */
+ public Compound(FieldAttributeAppender... fieldAttributeAppender) {
+ this(Arrays.asList(fieldAttributeAppender));
+ }
+
+ /**
+ * Creates a new compound field attribute appender.
+ *
+ * @param fieldAttributeAppenders The field attribute appenders that are to be combined by this compound appender
+ * in the order of their application.
+ */
+ public Compound(List<? extends FieldAttributeAppender> fieldAttributeAppenders) {
+ this.fieldAttributeAppenders = new ArrayList<FieldAttributeAppender>();
+ for (FieldAttributeAppender fieldAttributeAppender : fieldAttributeAppenders) {
+ if (fieldAttributeAppender instanceof Compound) {
+ this.fieldAttributeAppenders.addAll(((Compound) fieldAttributeAppender).fieldAttributeAppenders);
+ } else if (!(fieldAttributeAppender instanceof NoOp)) {
+ this.fieldAttributeAppenders.add(fieldAttributeAppender);
+ }
+ }
+ }
+
+ @Override
+ public void apply(FieldVisitor fieldVisitor, FieldDescription fieldDescription, AnnotationValueFilter annotationValueFilter) {
+ for (FieldAttributeAppender fieldAttributeAppender : fieldAttributeAppenders) {
+ fieldAttributeAppender.apply(fieldVisitor, fieldDescription, annotationValueFilter);
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/attribute/MethodAttributeAppender.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/attribute/MethodAttributeAppender.java
new file mode 100644
index 0000000..8d78cbb
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/attribute/MethodAttributeAppender.java
@@ -0,0 +1,429 @@
+package net.bytebuddy.implementation.attribute;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.TypeDescription;
+import org.objectweb.asm.MethodVisitor;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static net.bytebuddy.matcher.ElementMatchers.*;
+
+/**
+ * An appender that writes attributes or annotations to a given ASM {@link org.objectweb.asm.MethodVisitor}.
+ */
+public interface MethodAttributeAppender {
+
+ /**
+ * Applies this attribute appender to a given method visitor.
+ *
+ * @param methodVisitor The method visitor to which the attributes that are represented by this attribute
+ * appender are written to.
+ * @param methodDescription The description of the method for which the given method visitor creates an
+ * instrumentation for.
+ * @param annotationValueFilter The annotation value filter to apply when the annotations are written.
+ */
+ void apply(MethodVisitor methodVisitor, MethodDescription methodDescription, AnnotationValueFilter annotationValueFilter);
+
+ /**
+ * A method attribute appender that does not append any attributes.
+ */
+ enum NoOp implements MethodAttributeAppender, Factory {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public MethodAttributeAppender make(TypeDescription typeDescription) {
+ return this;
+ }
+
+ @Override
+ public void apply(MethodVisitor methodVisitor, MethodDescription methodDescription, AnnotationValueFilter annotationValueFilter) {
+ /* do nothing */
+ }
+ }
+
+ /**
+ * A factory that creates method attribute appenders for a given type.
+ */
+ interface Factory {
+
+ /**
+ * Returns a method attribute appender that is applicable for a given type description.
+ *
+ * @param typeDescription The type for which a method attribute appender is to be applied for.
+ * @return The method attribute appender which should be applied for the given type.
+ */
+ MethodAttributeAppender make(TypeDescription typeDescription);
+
+ /**
+ * A method attribute appender factory that combines several method attribute appender factories to be
+ * represented as a single factory.
+ */
+ @EqualsAndHashCode
+ class Compound implements Factory {
+
+ /**
+ * The factories this compound factory represents in their application order.
+ */
+ private final List<Factory> factories;
+
+ /**
+ * Creates a new compound method attribute appender factory.
+ *
+ * @param factory The factories that are to be combined by this compound factory in the order of their application.
+ */
+ public Compound(Factory... factory) {
+ this(Arrays.asList(factory));
+ }
+
+ /**
+ * Creates a new compound method attribute appender factory.
+ *
+ * @param factories The factories that are to be combined by this compound factory in the order of their application.
+ */
+ public Compound(List<? extends Factory> factories) {
+ this.factories = new ArrayList<Factory>();
+ for (Factory factory : factories) {
+ if (factory instanceof Compound) {
+ this.factories.addAll(((Compound) factory).factories);
+ } else if (!(factory instanceof NoOp)) {
+ this.factories.add(factory);
+ }
+ }
+ }
+
+ @Override
+ public MethodAttributeAppender make(TypeDescription typeDescription) {
+ List<MethodAttributeAppender> methodAttributeAppenders = new ArrayList<MethodAttributeAppender>(factories.size());
+ for (Factory factory : factories) {
+ methodAttributeAppenders.add(factory.make(typeDescription));
+ }
+ return new MethodAttributeAppender.Compound(methodAttributeAppenders);
+ }
+ }
+ }
+
+ /**
+ * <p>
+ * Implementation of a method attribute appender that writes all annotations of the instrumented method to the
+ * method that is being created. This includes method and parameter annotations.
+ * </p>
+ * <p>
+ * <b>Important</b>: This attribute appender does not apply for annotation types within the {@code jdk.internal.} namespace
+ * which are silently ignored. If such annotations should be inherited, they need to be added explicitly.
+ * </p>
+ */
+ enum ForInstrumentedMethod implements MethodAttributeAppender, Factory {
+
+ /**
+ * Appends all annotations of the instrumented method but not the annotations of the method's receiver type if such a type exists.
+ */
+ EXCLUDING_RECEIVER {
+ @Override
+ protected AnnotationAppender appendReceiver(AnnotationAppender annotationAppender,
+ AnnotationValueFilter annotationValueFilter,
+ MethodDescription methodDescription) {
+ return annotationAppender;
+ }
+ },
+
+ /**
+ * <p>
+ * Appends all annotations of the instrumented method including the annotations of the method's receiver type if such a type exists.
+ * </p>
+ * <p>
+ * If a method is overridden, the annotations can be misplaced if the overriding class does not expose a similar structure to
+ * the method that declared the method, i.e. the same amount of type variables and similar owner types. If this is not the case,
+ * type annotations are appended as if the overridden method was declared by the original type. This does not corrupt the resulting
+ * class file but it might result in type annotations not being visible via core reflection. This might however confuse other tools
+ * that parse the resulting class file manually.
+ * </p>
+ */
+ INCLUDING_RECEIVER {
+ @Override
+ protected AnnotationAppender appendReceiver(AnnotationAppender annotationAppender,
+ AnnotationValueFilter annotationValueFilter,
+ MethodDescription methodDescription) {
+ TypeDescription.Generic receiverType = methodDescription.getReceiverType();
+ return receiverType == null
+ ? annotationAppender
+ : receiverType.accept(AnnotationAppender.ForTypeAnnotations.ofReceiverType(annotationAppender, annotationValueFilter));
+ }
+ };
+
+ @Override
+ public MethodAttributeAppender make(TypeDescription typeDescription) {
+ return this;
+ }
+
+ @Override
+ public void apply(MethodVisitor methodVisitor, MethodDescription methodDescription, AnnotationValueFilter annotationValueFilter) {
+ AnnotationAppender annotationAppender = new AnnotationAppender.Default(new AnnotationAppender.Target.OnMethod(methodVisitor));
+ annotationAppender = methodDescription.getReturnType().accept(AnnotationAppender.ForTypeAnnotations.ofMethodReturnType(annotationAppender,
+ annotationValueFilter));
+ annotationAppender = AnnotationAppender.ForTypeAnnotations.ofTypeVariable(annotationAppender,
+ annotationValueFilter,
+ AnnotationAppender.ForTypeAnnotations.VARIABLE_ON_INVOKEABLE,
+ methodDescription.getTypeVariables());
+ for (AnnotationDescription annotation : methodDescription.getDeclaredAnnotations().filter(not(annotationType(nameStartsWith("jdk.internal."))))) {
+ annotationAppender = annotationAppender.append(annotation, annotationValueFilter);
+ }
+ for (ParameterDescription parameterDescription : methodDescription.getParameters()) {
+ AnnotationAppender parameterAppender = new AnnotationAppender.Default(new AnnotationAppender.Target.OnMethodParameter(methodVisitor,
+ parameterDescription.getIndex()));
+ parameterAppender = parameterDescription.getType().accept(AnnotationAppender.ForTypeAnnotations.ofMethodParameterType(parameterAppender,
+ annotationValueFilter,
+ parameterDescription.getIndex()));
+ for (AnnotationDescription annotation : parameterDescription.getDeclaredAnnotations()) {
+ parameterAppender = parameterAppender.append(annotation, annotationValueFilter);
+ }
+ }
+ annotationAppender = appendReceiver(annotationAppender, annotationValueFilter, methodDescription);
+ int exceptionTypeIndex = 0;
+ for (TypeDescription.Generic exceptionType : methodDescription.getExceptionTypes()) {
+ annotationAppender = exceptionType.accept(AnnotationAppender.ForTypeAnnotations.ofExceptionType(annotationAppender,
+ annotationValueFilter,
+ exceptionTypeIndex++));
+ }
+ }
+
+ /**
+ * Appends the annotations of the instrumented method's receiver type if this is enabled and such a type exists.
+ *
+ * @param annotationAppender The annotation appender to use.
+ * @param annotationValueFilter The annotation value filter to apply when the annotations are written.
+ * @param methodDescription The instrumented method.
+ * @return The resulting annotation appender.
+ */
+ protected abstract AnnotationAppender appendReceiver(AnnotationAppender annotationAppender,
+ AnnotationValueFilter annotationValueFilter,
+ MethodDescription methodDescription);
+ }
+
+ /**
+ * Appends an annotation to a method or method parameter. The visibility of the annotation is determined by the
+ * annotation type's {@link java.lang.annotation.RetentionPolicy} annotation.
+ */
+ @EqualsAndHashCode
+ class Explicit implements MethodAttributeAppender, Factory {
+
+ /**
+ * The target to which the annotations are written to.
+ */
+ private final Target target;
+
+ /**
+ * the annotations this method attribute appender is writing to its target.
+ */
+ private final List<? extends AnnotationDescription> annotations;
+
+ /**
+ * Creates a new appender for appending an annotation to a method.
+ *
+ * @param parameterIndex The index of the parameter to which the annotations should be written.
+ * @param annotations The annotations that should be written.
+ */
+ public Explicit(int parameterIndex, List<? extends AnnotationDescription> annotations) {
+ this(new Target.OnMethodParameter(parameterIndex), annotations);
+ }
+
+ /**
+ * Creates a new appender for appending an annotation to a method.
+ *
+ * @param annotations The annotations that should be written.
+ */
+ public Explicit(List<? extends AnnotationDescription> annotations) {
+ this(Target.OnMethod.INSTANCE, annotations);
+ }
+
+ /**
+ * Creates an explicit annotation appender for a either a method or one of its parameters..
+ *
+ * @param target The target to which the annotation should be written to.
+ * @param annotations The annotations to write.
+ */
+ protected Explicit(Target target, List<? extends AnnotationDescription> annotations) {
+ this.target = target;
+ this.annotations = annotations;
+ }
+
+ /**
+ * Creates a method attribute appender factory that writes all annotations of a given method, both the method
+ * annotations themselves and all annotations that are defined for every parameter.
+ *
+ * @param methodDescription The method from which to extract the annotations.
+ * @return A method attribute appender factory for an appender that writes all annotations of the supplied method.
+ */
+ public static Factory of(MethodDescription methodDescription) {
+ ParameterList<?> parameters = methodDescription.getParameters();
+ List<MethodAttributeAppender.Factory> methodAttributeAppenders = new ArrayList<MethodAttributeAppender.Factory>(parameters.size() + 1);
+ methodAttributeAppenders.add(new Explicit(methodDescription.getDeclaredAnnotations()));
+ for (ParameterDescription parameter : parameters) {
+ methodAttributeAppenders.add(new Explicit(parameter.getIndex(), parameter.getDeclaredAnnotations()));
+ }
+ return new Factory.Compound(methodAttributeAppenders);
+ }
+
+ @Override
+ public MethodAttributeAppender make(TypeDescription typeDescription) {
+ return this;
+ }
+
+ @Override
+ public void apply(MethodVisitor methodVisitor, MethodDescription methodDescription, AnnotationValueFilter annotationValueFilter) {
+ AnnotationAppender appender = new AnnotationAppender.Default(target.make(methodVisitor, methodDescription));
+ for (AnnotationDescription annotation : annotations) {
+ appender = appender.append(annotation, annotationValueFilter);
+ }
+ }
+
+ /**
+ * Represents the target on which this method attribute appender should write its annotations to.
+ */
+ protected interface Target {
+
+ /**
+ * Materializes the target for a given creation process.
+ *
+ * @param methodVisitor The method visitor to which the attributes that are represented by this
+ * attribute appender are written to.
+ * @param methodDescription The description of the method for which the given method visitor creates an
+ * instrumentation for.
+ * @return The target of the annotation appender this target represents.
+ */
+ AnnotationAppender.Target make(MethodVisitor methodVisitor, MethodDescription methodDescription);
+
+ /**
+ * A method attribute appender target for writing annotations directly onto the method.
+ */
+ enum OnMethod implements Target {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public AnnotationAppender.Target make(MethodVisitor methodVisitor, MethodDescription methodDescription) {
+ return new AnnotationAppender.Target.OnMethod(methodVisitor);
+ }
+ }
+
+ /**
+ * A method attribute appender target for writing annotations onto a given method parameter.
+ */
+ @EqualsAndHashCode
+ class OnMethodParameter implements Target {
+
+ /**
+ * The index of the parameter to write the annotation to.
+ */
+ private final int parameterIndex;
+
+ /**
+ * Creates a target for a method attribute appender for a method parameter of the given index.
+ *
+ * @param parameterIndex The index of the target parameter.
+ */
+ protected OnMethodParameter(int parameterIndex) {
+ this.parameterIndex = parameterIndex;
+ }
+
+ @Override
+ public AnnotationAppender.Target make(MethodVisitor methodVisitor, MethodDescription methodDescription) {
+ if (parameterIndex >= methodDescription.getParameters().size()) {
+ throw new IllegalArgumentException("Method " + methodDescription + " has less then " + parameterIndex + " parameters");
+ }
+ return new AnnotationAppender.Target.OnMethodParameter(methodVisitor, parameterIndex);
+ }
+ }
+ }
+ }
+
+ /**
+ * A method attribute appender that writes a receiver type.
+ */
+ @EqualsAndHashCode
+ class ForReceiverType implements MethodAttributeAppender, Factory {
+
+ /**
+ * The receiver type for which annotations are appended to the instrumented method.
+ */
+ private final TypeDescription.Generic receiverType;
+
+ /**
+ * Creates a new attribute appender that writes a receiver type.
+ *
+ * @param receiverType The receiver type for which annotations are appended to the instrumented method.
+ */
+ public ForReceiverType(TypeDescription.Generic receiverType) {
+ this.receiverType = receiverType;
+ }
+
+ @Override
+ public MethodAttributeAppender make(TypeDescription typeDescription) {
+ return this;
+ }
+
+ @Override
+ public void apply(MethodVisitor methodVisitor, MethodDescription methodDescription, AnnotationValueFilter annotationValueFilter) {
+ receiverType.accept(AnnotationAppender.ForTypeAnnotations.ofReceiverType(new AnnotationAppender.Default(new AnnotationAppender.Target.OnMethod(methodVisitor)), annotationValueFilter));
+ }
+ }
+
+ /**
+ * A method attribute appender that combines several method attribute appenders to be represented as a single
+ * method attribute appender.
+ */
+ @EqualsAndHashCode
+ class Compound implements MethodAttributeAppender {
+
+ /**
+ * The method attribute appenders this compound appender represents in their application order.
+ */
+ private final List<MethodAttributeAppender> methodAttributeAppenders;
+
+ /**
+ * Creates a new compound method attribute appender.
+ *
+ * @param methodAttributeAppender The method attribute appenders that are to be combined by this compound appender
+ * in the order of their application.
+ */
+ public Compound(MethodAttributeAppender... methodAttributeAppender) {
+ this(Arrays.asList(methodAttributeAppender));
+ }
+
+ /**
+ * Creates a new compound method attribute appender.
+ *
+ * @param methodAttributeAppenders The method attribute appenders that are to be combined by this compound appender
+ * in the order of their application.
+ */
+ public Compound(List<? extends MethodAttributeAppender> methodAttributeAppenders) {
+ this.methodAttributeAppenders = new ArrayList<MethodAttributeAppender>();
+ for (MethodAttributeAppender methodAttributeAppender : methodAttributeAppenders) {
+ if (methodAttributeAppender instanceof Compound) {
+ this.methodAttributeAppenders.addAll(((Compound) methodAttributeAppender).methodAttributeAppenders);
+ } else if (!(methodAttributeAppender instanceof NoOp)) {
+ this.methodAttributeAppenders.add(methodAttributeAppender);
+ }
+ }
+ }
+
+ @Override
+ public void apply(MethodVisitor methodVisitor, MethodDescription methodDescription, AnnotationValueFilter annotationValueFilter) {
+ for (MethodAttributeAppender methodAttributeAppender : methodAttributeAppenders) {
+ methodAttributeAppender.apply(methodVisitor, methodDescription, annotationValueFilter);
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/attribute/TypeAttributeAppender.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/attribute/TypeAttributeAppender.java
new file mode 100644
index 0000000..8512227
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/attribute/TypeAttributeAppender.java
@@ -0,0 +1,218 @@
+package net.bytebuddy.implementation.attribute;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import org.objectweb.asm.ClassVisitor;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * An appender that writes attributes or annotations to a given ASM {@link org.objectweb.asm.ClassVisitor}.
+ */
+public interface TypeAttributeAppender {
+
+ /**
+ * Applies this type attribute appender.
+ *
+ * @param classVisitor The class visitor to which the annotations of this visitor should be written to.
+ * @param instrumentedType A description of the instrumented type that is target of the ongoing instrumentation.
+ * @param annotationValueFilter The annotation value filter to apply when writing annotations.
+ */
+ void apply(ClassVisitor classVisitor, TypeDescription instrumentedType, AnnotationValueFilter annotationValueFilter);
+
+ /**
+ * A type attribute appender that does not append any attributes.
+ */
+ enum NoOp implements TypeAttributeAppender {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public void apply(ClassVisitor classVisitor, TypeDescription instrumentedType, AnnotationValueFilter annotationValueFilter) {
+ /* do nothing */
+ }
+ }
+
+ /**
+ * An attribute appender that writes all annotations that are found on a given target type to the
+ * instrumented type this type attribute appender is applied onto. The visibility for the annotation
+ * will be inferred from the annotations' {@link java.lang.annotation.RetentionPolicy}.
+ */
+ enum ForInstrumentedType implements TypeAttributeAppender {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public void apply(ClassVisitor classVisitor, TypeDescription instrumentedType, AnnotationValueFilter annotationValueFilter) {
+ AnnotationAppender annotationAppender = new AnnotationAppender.Default(new AnnotationAppender.Target.OnType(classVisitor));
+ annotationAppender = AnnotationAppender.ForTypeAnnotations.ofTypeVariable(annotationAppender,
+ annotationValueFilter,
+ AnnotationAppender.ForTypeAnnotations.VARIABLE_ON_TYPE,
+ instrumentedType.getTypeVariables());
+ TypeDescription.Generic superClass = instrumentedType.getSuperClass();
+ if (superClass != null) {
+ annotationAppender = superClass.accept(AnnotationAppender.ForTypeAnnotations.ofSuperClass(annotationAppender, annotationValueFilter));
+ }
+ int interfaceIndex = 0;
+ for (TypeDescription.Generic interfaceType : instrumentedType.getInterfaces()) {
+ annotationAppender = interfaceType.accept(AnnotationAppender.ForTypeAnnotations.ofInterfaceType(annotationAppender,
+ annotationValueFilter,
+ interfaceIndex++));
+ }
+ for (AnnotationDescription annotation : instrumentedType.getDeclaredAnnotations()) {
+ annotationAppender = annotationAppender.append(annotation, annotationValueFilter);
+ }
+ }
+
+ /**
+ * A type attribute appender that writes all annotations of the instrumented but excludes annotations up to
+ * a given index.
+ */
+ @EqualsAndHashCode
+ public static class Differentiating implements TypeAttributeAppender {
+
+ /**
+ * The index of the first annotations that should be directly written onto the type.
+ */
+ private final int annotationIndex;
+
+ /**
+ * The index of the first type variable for which type annotations should be directly written onto the type.
+ */
+ private final int typeVariableIndex;
+
+ /**
+ * The index of the first interface type for which type annotations should be directly written onto the type.
+ */
+ private final int interfaceTypeIndex;
+
+ /**
+ * Creates a new differentiating type attribute appender.
+ *
+ * @param typeDescription The type for which to resolve all exclusion indices.
+ */
+ public Differentiating(TypeDescription typeDescription) {
+ this(typeDescription.getDeclaredAnnotations().size(), typeDescription.getTypeVariables().size(), typeDescription.getInterfaces().size());
+ }
+
+ /**
+ * Creates a new differentiating type attribute appender.
+ *
+ * @param annotationIndex The index of the first annotations that should be directly written onto the type.
+ * @param typeVariableIndex The index of the first interface type for which type annotations should be directly written onto the type.
+ * @param interfaceTypeIndex The index of the first interface type for which type annotations should be directly written onto the type.
+ */
+ protected Differentiating(int annotationIndex, int typeVariableIndex, int interfaceTypeIndex) {
+ this.annotationIndex = annotationIndex;
+ this.typeVariableIndex = typeVariableIndex;
+ this.interfaceTypeIndex = interfaceTypeIndex;
+ }
+
+ @Override
+ public void apply(ClassVisitor classVisitor, TypeDescription instrumentedType, AnnotationValueFilter annotationValueFilter) {
+ AnnotationAppender annotationAppender = new AnnotationAppender.Default(new AnnotationAppender.Target.OnType(classVisitor));
+ AnnotationAppender.ForTypeAnnotations.ofTypeVariable(annotationAppender,
+ annotationValueFilter,
+ AnnotationAppender.ForTypeAnnotations.VARIABLE_ON_TYPE,
+ typeVariableIndex,
+ instrumentedType.getTypeVariables());
+ TypeList.Generic interfaceTypes = instrumentedType.getInterfaces();
+ int interfaceTypeIndex = this.interfaceTypeIndex;
+ for (TypeDescription.Generic interfaceType : interfaceTypes.subList(this.interfaceTypeIndex, interfaceTypes.size())) {
+ annotationAppender = interfaceType.accept(AnnotationAppender.ForTypeAnnotations.ofInterfaceType(annotationAppender,
+ annotationValueFilter,
+ interfaceTypeIndex++));
+ }
+ AnnotationList declaredAnnotations = instrumentedType.getDeclaredAnnotations();
+ for (AnnotationDescription annotationDescription : declaredAnnotations.subList(annotationIndex, declaredAnnotations.size())) {
+ annotationAppender = annotationAppender.append(annotationDescription, annotationValueFilter);
+ }
+ }
+ }
+ }
+
+ /**
+ * An attribute appender that appends a single annotation to a given type. The visibility for the annotation
+ * will be inferred from the annotation's {@link java.lang.annotation.RetentionPolicy}.
+ */
+ @EqualsAndHashCode
+ class Explicit implements TypeAttributeAppender {
+
+ /**
+ * The annotations to write to the given type.
+ */
+ private final List<? extends AnnotationDescription> annotations;
+
+ /**
+ * Creates a new annotation attribute appender for explicit annotation values.
+ *
+ * @param annotations The annotations to write to the given type.
+ */
+ public Explicit(List<? extends AnnotationDescription> annotations) {
+ this.annotations = annotations;
+ }
+
+ @Override
+ public void apply(ClassVisitor classVisitor, TypeDescription instrumentedType, AnnotationValueFilter annotationValueFilter) {
+ AnnotationAppender appender = new AnnotationAppender.Default(new AnnotationAppender.Target.OnType(classVisitor));
+ for (AnnotationDescription annotation : annotations) {
+ appender = appender.append(annotation, annotationValueFilter);
+ }
+ }
+ }
+
+ /**
+ * A compound type attribute appender that concatenates a number of other attribute appenders.
+ */
+ @EqualsAndHashCode
+ class Compound implements TypeAttributeAppender {
+
+ /**
+ * The type attribute appenders this compound appender represents in their application order.
+ */
+ private final List<TypeAttributeAppender> typeAttributeAppenders;
+
+ /**
+ * Creates a new compound attribute appender.
+ *
+ * @param typeAttributeAppender The type attribute appenders to concatenate in the order of their application.
+ */
+ public Compound(TypeAttributeAppender... typeAttributeAppender) {
+ this(Arrays.asList(typeAttributeAppender));
+ }
+
+ /**
+ * Creates a new compound attribute appender.
+ *
+ * @param typeAttributeAppenders The type attribute appenders to concatenate in the order of their application.
+ */
+ public Compound(List<? extends TypeAttributeAppender> typeAttributeAppenders) {
+ this.typeAttributeAppenders = new ArrayList<TypeAttributeAppender>();
+ for (TypeAttributeAppender typeAttributeAppender : typeAttributeAppenders) {
+ if (typeAttributeAppender instanceof Compound) {
+ this.typeAttributeAppenders.addAll(((Compound) typeAttributeAppender).typeAttributeAppenders);
+ } else if (!(typeAttributeAppender instanceof NoOp)) {
+ this.typeAttributeAppenders.add(typeAttributeAppender);
+ }
+ }
+ }
+
+ @Override
+ public void apply(ClassVisitor classVisitor, TypeDescription instrumentedType, AnnotationValueFilter annotationValueFilter) {
+ for (TypeAttributeAppender typeAttributeAppender : typeAttributeAppenders) {
+ typeAttributeAppender.apply(classVisitor, instrumentedType, annotationValueFilter);
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/attribute/package-info.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/attribute/package-info.java
new file mode 100644
index 0000000..f08e7d8
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/attribute/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * All types and classes in this package are responsible for writing attributes for a given Java byte code element,
+ * i.e. fields, types and byte code methods. A common Java attribute is a Java annotation which are represented by
+ * the {@link java.lang.annotation.Annotation} interface.
+ */
+package net.bytebuddy.implementation.attribute;
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/auxiliary/AuxiliaryType.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/auxiliary/AuxiliaryType.java
new file mode 100644
index 0000000..5402997
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/auxiliary/AuxiliaryType.java
@@ -0,0 +1,99 @@
+package net.bytebuddy.implementation.auxiliary;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.description.modifier.ModifierContributor;
+import net.bytebuddy.description.modifier.SyntheticState;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.implementation.MethodAccessorFactory;
+import net.bytebuddy.utility.RandomString;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * An auxiliary type that provides services to the instrumentation of another type. Implementations should provide
+ * meaningful {@code equals(Object)} and {@code hashCode()} implementations in order to avoid multiple creations
+ * of this type.
+ */
+public interface AuxiliaryType {
+
+ /**
+ * The default type access of an auxiliary type. <b>This array must not be mutated</b>.
+ */
+ @SuppressFBWarnings(value = {"MS_MUTABLE_ARRAY", "MS_OOI_PKGPROTECT"}, justification = "The array is not to be modified by contract")
+ ModifierContributor.ForType[] DEFAULT_TYPE_MODIFIER = {SyntheticState.SYNTHETIC};
+
+ /**
+ * Creates a new auxiliary type.
+ *
+ * @param auxiliaryTypeName The fully qualified binary name for this auxiliary type. The type should be in
+ * the same package than the instrumented type this auxiliary type is providing services
+ * to in order to allow package-private access.
+ * @param classFileVersion The class file version the auxiliary class should be written in.
+ * @param methodAccessorFactory A factory for accessor methods.
+ * @return A dynamically created type representing this auxiliary type.
+ */
+ DynamicType make(String auxiliaryTypeName, ClassFileVersion classFileVersion, MethodAccessorFactory methodAccessorFactory);
+
+ /**
+ * Representation of a naming strategy for an auxiliary type.
+ */
+ interface NamingStrategy {
+
+ /**
+ * Names an auxiliary type.
+ *
+ * @param instrumentedType The instrumented type for which an auxiliary type is registered.
+ * @return The fully qualified name for the given auxiliary type.
+ */
+ String name(TypeDescription instrumentedType);
+
+ /**
+ * A naming strategy for an auxiliary type which returns the instrumented type's name with a fixed extension
+ * and a random number as a suffix. All generated names will be in the same package as the instrumented type.
+ */
+ @EqualsAndHashCode(of = "suffix")
+ class SuffixingRandom implements NamingStrategy {
+
+ /**
+ * The suffix to append to the instrumented type for creating names for the auxiliary types.
+ */
+ private final String suffix;
+
+ /**
+ * An instance for creating random values.
+ */
+ private final RandomString randomString;
+
+ /**
+ * Creates a new suffixing random naming strategy.
+ *
+ * @param suffix The suffix to extend to the instrumented type.
+ */
+ public SuffixingRandom(String suffix) {
+ this.suffix = suffix;
+ randomString = new RandomString();
+ }
+
+ @Override
+ public String name(TypeDescription instrumentedType) {
+ return String.format("%s$%s$%s", instrumentedType.getName(), suffix, randomString.nextString());
+ }
+ }
+ }
+
+ /**
+ * A marker to indicate that an auxiliary type is part of the instrumented types signature. This information can be used to load a type before
+ * the instrumented type such that reflection on the instrumented type does not cause a {@link NoClassDefFoundError}.
+ */
+ @Retention(RetentionPolicy.CLASS)
+ @Target(ElementType.TYPE)
+ @interface SignatureRelevant {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/auxiliary/MethodCallProxy.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/auxiliary/MethodCallProxy.java
new file mode 100644
index 0000000..a70eda1
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/auxiliary/MethodCallProxy.java
@@ -0,0 +1,435 @@
+package net.bytebuddy.implementation.auxiliary;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationValue;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeVariableToken;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.dynamic.scaffold.MethodGraph;
+import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.MethodAccessorFactory;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.implementation.bytecode.Duplication;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.TypeCreation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.member.FieldAccess;
+import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
+import net.bytebuddy.implementation.bytecode.member.MethodReturn;
+import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.io.Serializable;
+import java.util.*;
+import java.util.concurrent.Callable;
+
+import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
+
+/**
+ * A method call proxy represents a class that is compiled against a particular method which can then be called whenever
+ * either its {@link java.util.concurrent.Callable#call()} or {@link Runnable#run()} method is called where the method
+ * call proxy implements both interfaces.
+ * <p> </p>
+ * In order to do so, the method call proxy instances are constructed by providing all the necessary information for
+ * calling a particular method:
+ * <ol>
+ * <li>If the target method is not {@code static}, the first argument should be an instance on which the method is called.</li>
+ * <li>All arguments for the called method in the order in which they are required.</li>
+ * </ol>
+ */
+ at EqualsAndHashCode
+public class MethodCallProxy implements AuxiliaryType {
+
+ /**
+ * The prefix of the fields holding the original method invocation's arguments.
+ */
+ private static final String FIELD_NAME_PREFIX = "argument";
+
+ /**
+ * The special method invocation to invoke from the auxiliary type.
+ */
+ private final Implementation.SpecialMethodInvocation specialMethodInvocation;
+
+ /**
+ * Determines if the generated proxy should be serializableProxy.
+ */
+ private final boolean serializableProxy;
+
+ /**
+ * The assigner to use for invoking a bridge method target where the parameter and return types need to be
+ * assigned.
+ */
+ private final Assigner assigner;
+
+ /**
+ * Creates a new method call proxy for a given method and uses a default assigner for assigning the method's return
+ * value to either the {@link java.util.concurrent.Callable#call()} or {@link Runnable#run()} method returns.
+ *
+ * @param specialMethodInvocation The special method invocation which should be invoked by this method call proxy.
+ * @param serializableProxy Determines if the generated proxy should be serializableProxy.
+ */
+ public MethodCallProxy(Implementation.SpecialMethodInvocation specialMethodInvocation, boolean serializableProxy) {
+ this(specialMethodInvocation, serializableProxy, Assigner.DEFAULT);
+ }
+
+ /**
+ * Creates a new method call proxy for a given method.
+ *
+ * @param specialMethodInvocation The special method invocation which should be invoked by this method call proxy.
+ * @param serializableProxy Determines if the generated proxy should be serializableProxy.
+ * @param assigner An assigner for assigning the target method's return value to either the
+ * {@link java.util.concurrent.Callable#call()} or {@link Runnable#run()}} methods'
+ * return values.
+ */
+ public MethodCallProxy(Implementation.SpecialMethodInvocation specialMethodInvocation, boolean serializableProxy, Assigner assigner) {
+ this.specialMethodInvocation = specialMethodInvocation;
+ this.serializableProxy = serializableProxy;
+ this.assigner = assigner;
+ }
+
+ /**
+ * Creates a linked hash map of field names to their types where each field represents a parameter of the method.
+ *
+ * @param methodDescription The method to extract into fields.
+ * @return A map of fields in the order they need to be loaded onto the operand stack for invoking the original
+ * method, including a reference to the instance of the instrumented type that is invoked if applicable.
+ */
+ private static LinkedHashMap<String, TypeDescription> extractFields(MethodDescription methodDescription) {
+ LinkedHashMap<String, TypeDescription> typeDescriptions = new LinkedHashMap<String, TypeDescription>();
+ int currentIndex = 0;
+ if (!methodDescription.isStatic()) {
+ typeDescriptions.put(fieldName(currentIndex++), methodDescription.getDeclaringType().asErasure());
+ }
+ for (ParameterDescription parameterDescription : methodDescription.getParameters()) {
+ typeDescriptions.put(fieldName(currentIndex++), parameterDescription.getType().asErasure());
+ }
+ return typeDescriptions;
+ }
+
+ /**
+ * Creates a field name for a method parameter of a given index.
+ *
+ * @param index The index for which the field name is to be created.
+ * @return The name for the given parameter.
+ */
+ private static String fieldName(int index) {
+ return String.format("%s%d", FIELD_NAME_PREFIX, index);
+ }
+
+ @Override
+ public DynamicType make(String auxiliaryTypeName,
+ ClassFileVersion classFileVersion,
+ MethodAccessorFactory methodAccessorFactory) {
+ MethodDescription accessorMethod = methodAccessorFactory.registerAccessorFor(specialMethodInvocation, MethodAccessorFactory.AccessType.DEFAULT);
+ LinkedHashMap<String, TypeDescription> parameterFields = extractFields(accessorMethod);
+ DynamicType.Builder<?> builder = new ByteBuddy(classFileVersion)
+ .with(PrecomputedMethodGraph.INSTANCE)
+ .subclass(Object.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
+ .name(auxiliaryTypeName)
+ .modifiers(DEFAULT_TYPE_MODIFIER)
+ .implement(Runnable.class, Callable.class).intercept(new MethodCall(accessorMethod, assigner))
+ .implement(serializableProxy ? new Class<?>[]{Serializable.class} : new Class<?>[0])
+ .defineConstructor().withParameters(parameterFields.values())
+ .intercept(ConstructorCall.INSTANCE);
+ for (Map.Entry<String, TypeDescription> field : parameterFields.entrySet()) {
+ builder = builder.defineField(field.getKey(), field.getValue(), Visibility.PRIVATE);
+ }
+ return builder.make();
+ }
+
+ /**
+ * A precomputed method graph that only displays the methods that are relevant for creating a method call proxy.
+ */
+ protected enum PrecomputedMethodGraph implements MethodGraph.Compiler {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ /**
+ * The precomputed method graph.
+ */
+ private final MethodGraph.Linked methodGraph;
+
+ /**
+ * Creates the precomputed method graph.
+ */
+ @SuppressFBWarnings(value = "SE_BAD_FIELD_STORE", justification = "Precomputed method graph is not intended for serialization")
+ PrecomputedMethodGraph() {
+ LinkedHashMap<MethodDescription.SignatureToken, MethodGraph.Node> nodes = new LinkedHashMap<MethodDescription.SignatureToken, MethodGraph.Node>();
+ MethodDescription callMethod = new MethodDescription.Latent(new TypeDescription.ForLoadedType(Callable.class),
+ "call",
+ Opcodes.ACC_PUBLIC | Opcodes.ACC_ABSTRACT,
+ Collections.<TypeVariableToken>emptyList(),
+ TypeDescription.Generic.OBJECT,
+ Collections.<ParameterDescription.Token>emptyList(),
+ Collections.singletonList(new TypeDescription.Generic.OfNonGenericType.ForLoadedType(Exception.class)),
+ Collections.<AnnotationDescription>emptyList(),
+ AnnotationValue.UNDEFINED,
+ TypeDescription.Generic.UNDEFINED);
+ nodes.put(callMethod.asSignatureToken(), new MethodGraph.Node.Simple(callMethod));
+ MethodDescription runMethod = new MethodDescription.Latent(new TypeDescription.ForLoadedType(Runnable.class),
+ "run",
+ Opcodes.ACC_PUBLIC | Opcodes.ACC_ABSTRACT,
+ Collections.<TypeVariableToken>emptyList(),
+ TypeDescription.Generic.VOID,
+ Collections.<ParameterDescription.Token>emptyList(),
+ Collections.<TypeDescription.Generic>emptyList(),
+ Collections.<AnnotationDescription>emptyList(),
+ AnnotationValue.UNDEFINED,
+ TypeDescription.Generic.UNDEFINED);
+ nodes.put(runMethod.asSignatureToken(), new MethodGraph.Node.Simple(runMethod));
+ MethodGraph methodGraph = new MethodGraph.Simple(nodes);
+ this.methodGraph = new MethodGraph.Linked.Delegation(methodGraph, methodGraph, Collections.<TypeDescription, MethodGraph>emptyMap());
+ }
+
+ @Override
+ public MethodGraph.Linked compile(TypeDescription typeDescription) {
+ return compile(typeDescription, typeDescription);
+ }
+
+ @Override
+ public MethodGraph.Linked compile(TypeDefinition typeDefinition, TypeDescription viewPoint) {
+ return methodGraph;
+ }
+ }
+
+ /**
+ * An implementation for a constructor of a {@link net.bytebuddy.implementation.auxiliary.MethodCallProxy}.
+ */
+ protected enum ConstructorCall implements Implementation {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ /**
+ * A reference of the {@link Object} type default constructor.
+ */
+ private final MethodDescription objectTypeDefaultConstructor;
+
+ /**
+ * Creates the constructor call singleton.
+ */
+ ConstructorCall() {
+ objectTypeDefaultConstructor = TypeDescription.OBJECT.getDeclaredMethods().filter(isConstructor()).getOnly();
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return new Appender(implementationTarget.getInstrumentedType());
+ }
+
+ /**
+ * The appender for implementing the {@link net.bytebuddy.implementation.auxiliary.MethodCallProxy.ConstructorCall}.
+ */
+ @EqualsAndHashCode
+ protected static class Appender implements ByteCodeAppender {
+
+ /**
+ * The instrumented type being created.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * Creates a new appender.
+ *
+ * @param instrumentedType The instrumented type that is being created.
+ */
+ private Appender(TypeDescription instrumentedType) {
+ this.instrumentedType = instrumentedType;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ FieldList<?> fieldList = instrumentedType.getDeclaredFields();
+ StackManipulation[] fieldLoading = new StackManipulation[fieldList.size()];
+ int index = 0;
+ for (FieldDescription fieldDescription : fieldList) {
+ fieldLoading[index] = new StackManipulation.Compound(
+ MethodVariableAccess.loadThis(),
+ MethodVariableAccess.load(instrumentedMethod.getParameters().get(index)),
+ FieldAccess.forField(fieldDescription).write()
+ );
+ index++;
+ }
+ StackManipulation.Size stackSize = new StackManipulation.Compound(
+ MethodVariableAccess.loadThis(),
+ MethodInvocation.invoke(ConstructorCall.INSTANCE.objectTypeDefaultConstructor),
+ new StackManipulation.Compound(fieldLoading),
+ MethodReturn.VOID
+ ).apply(methodVisitor, implementationContext);
+ return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+ }
+ }
+
+ /**
+ * An implementation for a method of a {@link net.bytebuddy.implementation.auxiliary.MethodCallProxy}.
+ */
+ @EqualsAndHashCode
+ protected static class MethodCall implements Implementation {
+
+ /**
+ * The method that is accessed by the implemented method.
+ */
+ private final MethodDescription accessorMethod;
+
+ /**
+ * The assigner to be used for invoking the accessor method.
+ */
+ private final Assigner assigner;
+
+ /**
+ * Creates a new method call implementation.
+ *
+ * @param accessorMethod The method that is accessed by the implemented method.
+ * @param assigner The assigner to be used for invoking the accessor method.
+ */
+ protected MethodCall(MethodDescription accessorMethod, Assigner assigner) {
+ this.accessorMethod = accessorMethod;
+ this.assigner = assigner;
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return new Appender(implementationTarget.getInstrumentedType());
+ }
+
+ /**
+ * The appender for implementing the {@link net.bytebuddy.implementation.auxiliary.MethodCallProxy.MethodCall}.
+ */
+ protected class Appender implements ByteCodeAppender {
+
+ /**
+ * The instrumented type that is implemented.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * Creates a new appender.
+ *
+ * @param instrumentedType The instrumented type to be implemented.
+ */
+ private Appender(TypeDescription instrumentedType) {
+ this.instrumentedType = instrumentedType;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor,
+ Context implementationContext,
+ MethodDescription instrumentedMethod) {
+ FieldList<?> fieldList = instrumentedType.getDeclaredFields();
+ List<StackManipulation> fieldLoadings = new ArrayList<StackManipulation>(fieldList.size());
+ for (FieldDescription fieldDescription : fieldList) {
+ fieldLoadings.add(new StackManipulation.Compound(MethodVariableAccess.loadThis(), FieldAccess.forField(fieldDescription).read()));
+ }
+ StackManipulation.Size stackSize = new StackManipulation.Compound(
+ new StackManipulation.Compound(fieldLoadings),
+ MethodInvocation.invoke(accessorMethod),
+ assigner.assign(accessorMethod.getReturnType(), instrumentedMethod.getReturnType(), Assigner.Typing.DYNAMIC),
+ MethodReturn.of(instrumentedMethod.getReturnType())
+ ).apply(methodVisitor, implementationContext);
+ return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private MethodCall getMethodCall() {
+ return MethodCall.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && instrumentedType.equals(((Appender) other).instrumentedType)
+ && MethodCall.this.equals(((Appender) other).getMethodCall());
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return 31 * MethodCall.this.hashCode() + instrumentedType.hashCode();
+ }
+ }
+ }
+
+ /**
+ * A stack manipulation that creates a {@link net.bytebuddy.implementation.auxiliary.MethodCallProxy}
+ * for a given method an pushes such an object onto the call stack. For this purpose, all arguments of the proxied method
+ * are loaded onto the stack what is only possible if this instance is used from a method with an identical signature such
+ * as the target method itself.
+ */
+ @EqualsAndHashCode
+ public static class AssignableSignatureCall implements StackManipulation {
+
+ /**
+ * The special method invocation to be proxied by this stack manipulation.
+ */
+ private final Implementation.SpecialMethodInvocation specialMethodInvocation;
+
+ /**
+ * Determines if the generated proxy should be serializableProxy.
+ */
+ private final boolean serializable;
+
+ /**
+ * Creates an operand stack assignment that creates a
+ * {@link net.bytebuddy.implementation.auxiliary.MethodCallProxy} for the
+ * {@code targetMethod} and pushes this proxy object onto the stack.
+ *
+ * @param specialMethodInvocation The special method invocation which should be invoked by the created method
+ * call proxy.
+ * @param serializable Determines if the generated proxy should be serializableProxy.
+ */
+ public AssignableSignatureCall(Implementation.SpecialMethodInvocation specialMethodInvocation,
+ boolean serializable) {
+ this.specialMethodInvocation = specialMethodInvocation;
+ this.serializable = serializable;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ TypeDescription auxiliaryType = implementationContext
+ .register(new MethodCallProxy(specialMethodInvocation, serializable));
+ return new Compound(
+ TypeCreation.of(auxiliaryType),
+ Duplication.SINGLE,
+ MethodVariableAccess.allArgumentsOf(specialMethodInvocation.getMethodDescription()).prependThisReference(),
+ MethodInvocation.invoke(auxiliaryType.getDeclaredMethods().filter(isConstructor()).getOnly())
+ ).apply(methodVisitor, implementationContext);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/auxiliary/TrivialType.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/auxiliary/TrivialType.java
new file mode 100644
index 0000000..186acb9
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/auxiliary/TrivialType.java
@@ -0,0 +1,57 @@
+package net.bytebuddy.implementation.auxiliary;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.scaffold.MethodGraph;
+import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
+import net.bytebuddy.implementation.MethodAccessorFactory;
+
+import java.util.Collections;
+
+/**
+ * A trivial type that extends {@link java.lang.Object} without defining any fields, methods or constructors.
+ * This type is meant to be used as a marker type only.
+ */
+public enum TrivialType implements AuxiliaryType {
+
+ /**
+ * A trivial type that defines the {@link SignatureRelevant} annotation.
+ */
+ SIGNATURE_RELEVANT(true),
+
+ /**
+ * A non-annotated trivial type.
+ */
+ PLAIN(false);
+
+ /**
+ * Determines if this type determines the {@link SignatureRelevant} annotation.
+ */
+ private final boolean eager;
+
+ /**
+ * Creates a new trivial type.
+ *
+ * @param eager Determines if this type determines the {@link SignatureRelevant} annotation.
+ */
+ TrivialType(boolean eager) {
+ this.eager = eager;
+ }
+
+ @Override
+ public DynamicType make(String auxiliaryTypeName,
+ ClassFileVersion classFileVersion,
+ MethodAccessorFactory methodAccessorFactory) {
+ return new ByteBuddy(classFileVersion)
+ .with(MethodGraph.Empty.INSTANCE) // avoid parsing the graph
+ .subclass(Object.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
+ .annotateType(eager
+ ? Collections.singletonList(AnnotationDescription.Builder.ofType(SignatureRelevant.class).build())
+ : Collections.<AnnotationDescription>emptyList())
+ .name(auxiliaryTypeName)
+ .modifiers(DEFAULT_TYPE_MODIFIER)
+ .make();
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/auxiliary/TypeProxy.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/auxiliary/TypeProxy.java
new file mode 100644
index 0000000..f0a11d9
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/auxiliary/TypeProxy.java
@@ -0,0 +1,736 @@
+package net.bytebuddy.implementation.auxiliary;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.modifier.Ownership;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.TargetType;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.MethodAccessorFactory;
+import net.bytebuddy.implementation.bytecode.*;
+import net.bytebuddy.implementation.bytecode.constant.DefaultValue;
+import net.bytebuddy.implementation.bytecode.member.FieldAccess;
+import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
+import net.bytebuddy.implementation.bytecode.member.MethodReturn;
+import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+import net.bytebuddy.matcher.ElementMatchers;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.io.Serializable;
+import java.util.List;
+
+import static net.bytebuddy.matcher.ElementMatchers.*;
+
+/**
+ * A type proxy creates accessor methods for all overridable methods of a given type by subclassing the given type and
+ * delegating all method calls to accessor methods of the instrumented type it was created for.
+ */
+ at EqualsAndHashCode
+public class TypeProxy implements AuxiliaryType {
+
+ /**
+ * The name of the {@code static} method that is added to this auxiliary type for creating instances by using the
+ * Oracle JDK's {@link sun.reflect.ReflectionFactory}.
+ */
+ public static final String REFLECTION_METHOD = "make";
+
+ /**
+ * The name of the field that stores the delegation instance.
+ */
+ public static final String INSTANCE_FIELD = "target";
+
+ /**
+ * The type that is proxied, i.e. the original instrumented type this proxy is created for.
+ */
+ private final TypeDescription proxiedType;
+
+ /**
+ * The implementation target of the proxied type.
+ */
+ private final Implementation.Target implementationTarget;
+
+ /**
+ * The invocation factory for creating special method invocations.
+ */
+ private final InvocationFactory invocationFactory;
+
+ /**
+ * {@code true} if the finalizer method should not be instrumented.
+ */
+ private final boolean ignoreFinalizer;
+
+ /**
+ * Determines if the proxy should be serializable.
+ */
+ private final boolean serializableProxy;
+
+ /**
+ * Creates a new type proxy.
+ *
+ * @param proxiedType The type this proxy should implement which can either be a non-final class or an interface.
+ * @param implementationTarget The implementation target this type proxy is created for.
+ * @param invocationFactory The invocation factory for creating special method invocations.
+ * @param ignoreFinalizer {@code true} if any finalizer methods should be ignored for proxying.
+ * @param serializableProxy Determines if the proxy should be serializable.
+ */
+ public TypeProxy(TypeDescription proxiedType,
+ Implementation.Target implementationTarget,
+ InvocationFactory invocationFactory,
+ boolean ignoreFinalizer,
+ boolean serializableProxy) {
+ this.proxiedType = proxiedType;
+ this.implementationTarget = implementationTarget;
+ this.invocationFactory = invocationFactory;
+ this.ignoreFinalizer = ignoreFinalizer;
+ this.serializableProxy = serializableProxy;
+ }
+
+ @Override
+ public DynamicType make(String auxiliaryTypeName,
+ ClassFileVersion classFileVersion,
+ MethodAccessorFactory methodAccessorFactory) {
+ return new ByteBuddy(classFileVersion)
+ .ignore(ignoreFinalizer ? isFinalizer() : ElementMatchers.<MethodDescription>none())
+ .subclass(proxiedType)
+ .name(auxiliaryTypeName)
+ .modifiers(DEFAULT_TYPE_MODIFIER)
+ .implement(serializableProxy ? new Class<?>[]{Serializable.class} : new Class<?>[0])
+ .method(any()).intercept(new MethodCall(methodAccessorFactory))
+ .defineMethod(REFLECTION_METHOD, TargetType.class, Ownership.STATIC).intercept(SilentConstruction.INSTANCE)
+ .make();
+ }
+
+ /**
+ * A stack manipulation that throws an abstract method error in case that a given super method cannot be invoked.
+ */
+ protected enum AbstractMethodErrorThrow implements StackManipulation {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ /**
+ * The stack manipulation that throws the abstract method error.
+ */
+ private final StackManipulation implementation;
+
+ /**
+ * Creates the singleton instance.
+ */
+ @SuppressFBWarnings(value = "SE_BAD_FIELD_STORE", justification = "Fields of enumerations are never serialized")
+ AbstractMethodErrorThrow() {
+ TypeDescription abstractMethodError = new TypeDescription.ForLoadedType(AbstractMethodError.class);
+ MethodDescription constructor = abstractMethodError.getDeclaredMethods()
+ .filter(isConstructor().and(takesArguments(0))).getOnly();
+ implementation = new Compound(TypeCreation.of(abstractMethodError),
+ Duplication.SINGLE,
+ MethodInvocation.invoke(constructor),
+ Throw.INSTANCE);
+ }
+
+ @Override
+ public boolean isValid() {
+ return implementation.isValid();
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ return implementation.apply(methodVisitor, implementationContext);
+ }
+ }
+
+ /**
+ * An implementation of a <i>silent construction</i> of a given type by using the non-standardized
+ * {@link sun.reflect.ReflectionFactory}. This way, a constructor invocation can be avoided. However, this comes
+ * at the cost of potentially breaking compatibility as the reflection factory is not standardized.
+ */
+ protected enum SilentConstruction implements Implementation {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return new Appender(implementationTarget.getInstrumentedType());
+ }
+
+ /**
+ * The appender for implementing a {@link net.bytebuddy.implementation.auxiliary.TypeProxy.SilentConstruction}.
+ */
+ @EqualsAndHashCode
+ protected static class Appender implements ByteCodeAppender {
+
+ /**
+ * The internal name of the reflection factory class.
+ */
+ public static final String REFLECTION_FACTORY_INTERNAL_NAME = "sun/reflect/ReflectionFactory";
+
+ /**
+ * The name of the factory method for getting hold of an instance of the reflection factory class.
+ */
+ public static final String GET_REFLECTION_FACTORY_METHOD_NAME = "getReflectionFactory";
+
+ /**
+ * The descriptor of the factory method for getting hold of an instance of the reflection factory class.
+ */
+ public static final String GET_REFLECTION_FACTORY_METHOD_DESCRIPTOR = "()Lsun/reflect/ReflectionFactory;";
+
+ /**
+ * The name of the method for creating a new serialization constructor.
+ */
+ public static final String NEW_CONSTRUCTOR_FOR_SERIALIZATION_METHOD_NAME = "newConstructorForSerialization";
+
+ /**
+ * The descriptor of the method for creating a new serialization constructor.
+ */
+ public static final String NEW_CONSTRUCTOR_FOR_SERIALIZATION_METHOD_DESCRIPTOR =
+ "(Ljava/lang/Class;Ljava/lang/reflect/Constructor;)Ljava/lang/reflect/Constructor;";
+
+ /**
+ * The descriptor of the {@link java.lang.Object} class.
+ */
+ public static final String JAVA_LANG_OBJECT_DESCRIPTOR = "Ljava/lang/Object;";
+
+ /**
+ * The internal name of the {@link java.lang.Object} class.
+ */
+ public static final String JAVA_LANG_OBJECT_INTERNAL_NAME = "java/lang/Object";
+
+ /**
+ * The internal name of the {@link java.lang.reflect.Constructor} class.
+ */
+ public static final String JAVA_LANG_CONSTRUCTOR_INTERNAL_NAME = "java/lang/reflect/Constructor";
+
+ /**
+ * The internal name of the {@link java.lang.reflect.Constructor#newInstance(Object...)} method.
+ */
+ public static final String NEW_INSTANCE_METHOD_NAME = "newInstance";
+
+ /**
+ * The descriptor of the {@link java.lang.reflect.Constructor#newInstance(Object...)} method.
+ */
+ public static final String NEW_INSTANCE_METHOD_DESCRIPTOR = "([Ljava/lang/Object;)Ljava/lang/Object;";
+
+ /**
+ * The internal name of the {@link java.lang.Class} class.
+ */
+ public static final String JAVA_LANG_CLASS_INTERNAL_NAME = "java/lang/Class";
+
+ /**
+ * The internal name of the {@link Class#getDeclaredClasses()} method.
+ */
+ public static final String GET_DECLARED_CONSTRUCTOR_METHOD_NAME = "getDeclaredConstructor";
+
+ /**
+ * The descriptor of the {@link Class#getDeclaredClasses()} method.
+ */
+ public static final String GET_DECLARED_CONSTRUCTOR_METHOD_DESCRIPTOR =
+ "([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;";
+
+ /**
+ * The instrumented type that this factory method is created for.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * Creates a new appender.
+ *
+ * @param instrumentedType The instrumented type that the factory method is created for.
+ */
+ private Appender(TypeDescription instrumentedType) {
+ this.instrumentedType = instrumentedType;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
+ REFLECTION_FACTORY_INTERNAL_NAME,
+ GET_REFLECTION_FACTORY_METHOD_NAME,
+ GET_REFLECTION_FACTORY_METHOD_DESCRIPTOR,
+ false);
+ methodVisitor.visitLdcInsn(Type.getType(instrumentedType.getDescriptor()));
+ methodVisitor.visitLdcInsn(Type.getType(JAVA_LANG_OBJECT_DESCRIPTOR));
+ methodVisitor.visitInsn(Opcodes.ICONST_0);
+ methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, JAVA_LANG_CLASS_INTERNAL_NAME);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
+ JAVA_LANG_CLASS_INTERNAL_NAME,
+ GET_DECLARED_CONSTRUCTOR_METHOD_NAME,
+ GET_DECLARED_CONSTRUCTOR_METHOD_DESCRIPTOR,
+ false);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
+ REFLECTION_FACTORY_INTERNAL_NAME,
+ NEW_CONSTRUCTOR_FOR_SERIALIZATION_METHOD_NAME,
+ NEW_CONSTRUCTOR_FOR_SERIALIZATION_METHOD_DESCRIPTOR,
+ false);
+ methodVisitor.visitInsn(Opcodes.ICONST_0);
+ methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, JAVA_LANG_OBJECT_INTERNAL_NAME);
+ methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, JAVA_LANG_CONSTRUCTOR_INTERNAL_NAME,
+ NEW_INSTANCE_METHOD_NAME,
+ NEW_INSTANCE_METHOD_DESCRIPTOR,
+ false);
+ methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, instrumentedType.getInternalName());
+ methodVisitor.visitInsn(Opcodes.ARETURN);
+ return new Size(4, 0);
+ }
+ }
+ }
+
+ /**
+ * An invocation factory is responsible for creating a special method invocation for any method that is to be
+ * invoked. These special method invocations are then implemented by the
+ * {@link net.bytebuddy.implementation.auxiliary.TypeProxy}.
+ * Illegal {@link Implementation.SpecialMethodInvocation} are implemented by
+ * throwing an {@link java.lang.AbstractMethodError}.
+ */
+ public interface InvocationFactory {
+
+ /**
+ * Creates a special method invocation to implement for a given method.
+ *
+ * @param implementationTarget The implementation target the type proxy is created for.
+ * @param proxiedType The type for the type proxy to subclass or implement.
+ * @param instrumentedMethod The instrumented method that is to be invoked.
+ * @return A special method invocation of the given method or an illegal invocation if the proxy should
+ * throw an {@link java.lang.AbstractMethodError} when the instrumented method is invoked.
+ */
+ Implementation.SpecialMethodInvocation invoke(Implementation.Target implementationTarget,
+ TypeDescription proxiedType,
+ MethodDescription instrumentedMethod);
+
+ /**
+ * Default implementations of the
+ * {@link net.bytebuddy.implementation.auxiliary.TypeProxy.InvocationFactory}.
+ */
+ enum Default implements InvocationFactory {
+
+ /**
+ * Invokes the super method of the instrumented method.
+ */
+ SUPER_METHOD {
+ @Override
+ public Implementation.SpecialMethodInvocation invoke(Implementation.Target implementationTarget,
+ TypeDescription proxiedType,
+ MethodDescription instrumentedMethod) {
+ return implementationTarget.invokeDominant(instrumentedMethod.asSignatureToken());
+ }
+ },
+
+ /**
+ * Invokes the default method of the instrumented method if it exists and is not ambiguous.
+ */
+ DEFAULT_METHOD {
+ @Override
+ public Implementation.SpecialMethodInvocation invoke(Implementation.Target implementationTarget,
+ TypeDescription proxiedType,
+ MethodDescription instrumentedMethod) {
+ return implementationTarget.invokeDefault(instrumentedMethod.asSignatureToken(), proxiedType);
+ }
+ };
+ }
+ }
+
+ /**
+ * Loads a type proxy onto the operand stack which is created by calling one of its constructors. When this
+ * stack manipulation is applied, an instance of the instrumented type must lie on top of the operand stack.
+ * All constructor parameters will be assigned their default values when this stack operation is applied.
+ */
+ @EqualsAndHashCode
+ public static class ForSuperMethodByConstructor implements StackManipulation {
+
+ /**
+ * The type for the type proxy to subclass or implement.
+ */
+ private final TypeDescription proxiedType;
+
+ /**
+ * The implementation target this type proxy is created for.
+ */
+ private final Implementation.Target implementationTarget;
+
+ /**
+ * The parameter types of the constructor that should be called.
+ */
+ private final List<TypeDescription> constructorParameters;
+
+ /**
+ * {@code true} if any finalizers should be ignored for the delegation.
+ */
+ private final boolean ignoreFinalizer;
+
+ /**
+ * Determines if the proxy should be serializable.
+ */
+ private final boolean serializableProxy;
+
+ /**
+ * Creates a new stack operation for creating a type proxy by calling one of its constructors.
+ *
+ * @param proxiedType The type for the type proxy to subclass or implement.
+ * @param implementationTarget The implementation target this type proxy is created for.
+ * @param constructorParameters The parameter types of the constructor that should be called.
+ * @param ignoreFinalizer {@code true} if any finalizers should be ignored for the delegation.
+ * @param serializableProxy Determines if the proxy should be serializable.
+ */
+ public ForSuperMethodByConstructor(TypeDescription proxiedType,
+ Implementation.Target implementationTarget,
+ List<TypeDescription> constructorParameters,
+ boolean ignoreFinalizer,
+ boolean serializableProxy) {
+ this.proxiedType = proxiedType;
+ this.implementationTarget = implementationTarget;
+ this.constructorParameters = constructorParameters;
+ this.ignoreFinalizer = ignoreFinalizer;
+ this.serializableProxy = serializableProxy;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ TypeDescription proxyType = implementationContext
+ .register(new TypeProxy(proxiedType,
+ implementationTarget,
+ InvocationFactory.Default.SUPER_METHOD,
+ ignoreFinalizer,
+ serializableProxy));
+ StackManipulation[] constructorValue = new StackManipulation[constructorParameters.size()];
+ int index = 0;
+ for (TypeDescription parameterType : constructorParameters) {
+ constructorValue[index++] = DefaultValue.of(parameterType);
+ }
+ return new Compound(
+ TypeCreation.of(proxyType),
+ Duplication.SINGLE,
+ new Compound(constructorValue),
+ MethodInvocation.invoke(proxyType.getDeclaredMethods().filter(isConstructor().and(takesArguments(constructorParameters))).getOnly()),
+ Duplication.SINGLE,
+ MethodVariableAccess.loadThis(),
+ FieldAccess.forField(proxyType.getDeclaredFields().filter((named(INSTANCE_FIELD))).getOnly()).write()
+ ).apply(methodVisitor, implementationContext);
+ }
+ }
+
+ /**
+ * Loads a type proxy onto the operand stack which is created by constructing a serialization constructor using
+ * the Oracle JDK's {@link sun.reflect.ReflectionFactory#newConstructorForSerialization(Class, java.lang.reflect.Constructor)}
+ * method which might not be available in any Java runtime. When this stack manipulation is applied, an instance of
+ * the instrumented type must lie on top of the operand stack.
+ */
+ @EqualsAndHashCode
+ public static class ForSuperMethodByReflectionFactory implements StackManipulation {
+
+ /**
+ * The type for which a proxy type is created.
+ */
+ private final TypeDescription proxiedType;
+
+ /**
+ * The implementation target of the proxied type.
+ */
+ private final Implementation.Target implementationTarget;
+
+ /**
+ * {@code true} {@code true} if any finalizer methods should be ignored for proxying.
+ */
+ private final boolean ignoreFinalizer;
+
+ /**
+ * Determines if the proxy should be serializable.
+ */
+ private final boolean serializableProxy;
+
+ /**
+ * Creates a new stack operation for reflectively creating a type proxy for the given arguments.
+ *
+ * @param proxiedType The type for the type proxy to subclass or implement.
+ * @param implementationTarget The implementation target this type proxy is created for.
+ * @param ignoreFinalizer {@code true} if any finalizer methods should be ignored for proxying.
+ * @param serializableProxy Determines if the proxy should be serializable.
+ */
+ public ForSuperMethodByReflectionFactory(TypeDescription proxiedType,
+ Implementation.Target implementationTarget,
+ boolean ignoreFinalizer,
+ boolean serializableProxy) {
+ this.proxiedType = proxiedType;
+ this.implementationTarget = implementationTarget;
+ this.ignoreFinalizer = ignoreFinalizer;
+ this.serializableProxy = serializableProxy;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ TypeDescription proxyType = implementationContext.register(new TypeProxy(proxiedType,
+ implementationTarget,
+ InvocationFactory.Default.SUPER_METHOD,
+ ignoreFinalizer,
+ serializableProxy));
+ return new Compound(
+ MethodInvocation.invoke(proxyType.getDeclaredMethods().filter(named(REFLECTION_METHOD).and(takesArguments(0))).getOnly()),
+ Duplication.SINGLE,
+ MethodVariableAccess.loadThis(),
+ FieldAccess.forField(proxyType.getDeclaredFields().filter((named(INSTANCE_FIELD))).getOnly()).write()
+ ).apply(methodVisitor, implementationContext);
+ }
+ }
+
+ /**
+ * Creates a type proxy which delegates its super method calls to any invokable default method of
+ * a given interface and loads an instance of this proxy onto the operand stack.
+ */
+ @EqualsAndHashCode
+ public static class ForDefaultMethod implements StackManipulation {
+
+ /**
+ * The proxied interface type.
+ */
+ private final TypeDescription proxiedType;
+
+ /**
+ * The implementation target for the original instrumentation.
+ */
+ private final Implementation.Target implementationTarget;
+
+ /**
+ * {@code true} if the proxy should be {@link java.io.Serializable}.
+ */
+ private final boolean serializableProxy;
+
+ /**
+ * Creates a new proxy creation for a default interface type proxy.
+ *
+ * @param proxiedType The proxied interface type.
+ * @param implementationTarget The implementation target for the original implementation.
+ * @param serializableProxy {@code true} if the proxy should be {@link java.io.Serializable}.
+ */
+ public ForDefaultMethod(TypeDescription proxiedType,
+ Implementation.Target implementationTarget,
+ boolean serializableProxy) {
+ this.proxiedType = proxiedType;
+ this.implementationTarget = implementationTarget;
+ this.serializableProxy = serializableProxy;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ TypeDescription proxyType = implementationContext
+ .register(new TypeProxy(proxiedType,
+ implementationTarget,
+ InvocationFactory.Default.DEFAULT_METHOD,
+ true,
+ serializableProxy));
+ return new Compound(
+ TypeCreation.of(proxyType),
+ Duplication.SINGLE,
+ MethodInvocation.invoke(proxyType.getDeclaredMethods().filter(isConstructor()).getOnly()),
+ Duplication.SINGLE,
+ MethodVariableAccess.loadThis(),
+ FieldAccess.forField(proxyType.getDeclaredFields().filter((named(INSTANCE_FIELD))).getOnly()).write()
+ ).apply(methodVisitor, implementationContext);
+ }
+ }
+
+ /**
+ * An implementation for a method call of a {@link net.bytebuddy.implementation.auxiliary.TypeProxy}.
+ */
+ protected class MethodCall implements Implementation {
+
+ /**
+ * The method accessor factory to query for the super method invocation.
+ */
+ private final MethodAccessorFactory methodAccessorFactory;
+
+ /**
+ * Creates a new method call implementation.
+ *
+ * @param methodAccessorFactory The method accessor factory to query for the super method invocation.
+ */
+ protected MethodCall(MethodAccessorFactory methodAccessorFactory) {
+ this.methodAccessorFactory = methodAccessorFactory;
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType.withField(new FieldDescription.Token(INSTANCE_FIELD,
+ Opcodes.ACC_SYNTHETIC,
+ implementationTarget.getInstrumentedType().asGenericType()));
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return new Appender(implementationTarget.getInstrumentedType());
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private TypeProxy getTypeProxy() {
+ return TypeProxy.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && methodAccessorFactory.equals(((MethodCall) other).methodAccessorFactory)
+ && TypeProxy.this.equals(((MethodCall) other).getTypeProxy());
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return 31 * TypeProxy.this.hashCode() + methodAccessorFactory.hashCode();
+ }
+
+ /**
+ * Implementation of a byte code appender for a {@link net.bytebuddy.implementation.auxiliary.TypeProxy.MethodCall}.
+ */
+ protected class Appender implements ByteCodeAppender {
+
+ /**
+ * The stack manipulation for loading the proxied instance onto the stack.
+ */
+ private final StackManipulation fieldLoadingInstruction;
+
+ /**
+ * Creates a new appender.
+ *
+ * @param instrumentedType The instrumented type that is proxied by the enclosing instrumentation.
+ */
+ protected Appender(TypeDescription instrumentedType) {
+ fieldLoadingInstruction = FieldAccess.forField(instrumentedType.getDeclaredFields().filter((named(INSTANCE_FIELD))).getOnly()).read();
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ SpecialMethodInvocation specialMethodInvocation = invocationFactory.invoke(implementationTarget, proxiedType, instrumentedMethod);
+ StackManipulation.Size size = (specialMethodInvocation.isValid()
+ ? new AccessorMethodInvocation(instrumentedMethod, specialMethodInvocation)
+ : AbstractMethodErrorThrow.INSTANCE).apply(methodVisitor, implementationContext);
+ return new Size(size.getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private MethodCall getMethodCall() {
+ return MethodCall.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && fieldLoadingInstruction.equals(((Appender) other).fieldLoadingInstruction)
+ && MethodCall.this.equals(((Appender) other).getMethodCall());
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return 31 * MethodCall.this.hashCode() + fieldLoadingInstruction.hashCode();
+ }
+
+ /**
+ * Stack manipulation for invoking an accessor method.
+ */
+ protected class AccessorMethodInvocation implements StackManipulation {
+
+ /**
+ * The instrumented method that is implemented.
+ */
+ private final MethodDescription instrumentedMethod;
+
+ /**
+ * The special method invocation that is invoked by this accessor method invocation.
+ */
+ private final SpecialMethodInvocation specialMethodInvocation;
+
+ /**
+ * Creates a new accessor method invocation.
+ *
+ * @param instrumentedMethod The instrumented method that is implemented.
+ * @param specialMethodInvocation The special method invocation that is invoked by this accessor
+ * method invocation.
+ */
+ protected AccessorMethodInvocation(MethodDescription instrumentedMethod,
+ SpecialMethodInvocation specialMethodInvocation) {
+ this.instrumentedMethod = instrumentedMethod;
+ this.specialMethodInvocation = specialMethodInvocation;
+ }
+
+ @Override
+ public boolean isValid() {
+ return specialMethodInvocation.isValid();
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ MethodDescription.InDefinedShape proxyMethod = methodAccessorFactory.registerAccessorFor(specialMethodInvocation, MethodAccessorFactory.AccessType.DEFAULT);
+ return new StackManipulation.Compound(
+ MethodVariableAccess.loadThis(),
+ fieldLoadingInstruction,
+ MethodVariableAccess.allArgumentsOf(instrumentedMethod).asBridgeOf(proxyMethod),
+ MethodInvocation.invoke(proxyMethod),
+ MethodReturn.of(instrumentedMethod.getReturnType())
+ ).apply(methodVisitor, implementationContext);
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private Appender getAppender() {
+ return Appender.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (other == null || getClass() != other.getClass()) return false;
+ AccessorMethodInvocation that = (AccessorMethodInvocation) other;
+ return Appender.this.equals(that.getAppender())
+ && instrumentedMethod.equals(that.instrumentedMethod)
+ && specialMethodInvocation.equals(that.specialMethodInvocation);
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ int result = Appender.this.hashCode();
+ result = 31 * result + instrumentedMethod.hashCode();
+ result = 31 * result + specialMethodInvocation.hashCode();
+ return result;
+ }
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/auxiliary/package-info.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/auxiliary/package-info.java
new file mode 100644
index 0000000..a5c9fdd
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/auxiliary/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * Auxiliary types describe helper types that aid as a supplementary to a given
+ * {@link net.bytebuddy.dynamic.scaffold.InstrumentedType}. A typical use case for auxiliary types is granting
+ * access to {@code super} invocations of method calls.
+ */
+package net.bytebuddy.implementation.auxiliary;
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/ArgumentTypeResolver.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/ArgumentTypeResolver.java
new file mode 100644
index 0000000..292fe4e
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/ArgumentTypeResolver.java
@@ -0,0 +1,253 @@
+package net.bytebuddy.implementation.bind;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.TypeDescription;
+
+/**
+ * Implementation of an
+ * {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver}
+ * that resolves two conflicting bindings by considering most-specific types of target method parameters in the same manner
+ * as the Java compiler resolves bindings of overloaded method.
+ * <p> </p>
+ * This ambiguity resolver:
+ * <ol>
+ * <li>Checks for each parameter of the source method if a one-to-one parameter binding to both of the target methods exist.</li>
+ * <li>If any of the source method parameters were bound one-to-one to both target methods, the method with the most specific
+ * type is considered as dominant.</li>
+ * <li>If this result is dominant for both the left and the right target method, this resolver will consider the binding as
+ * ambiguous.</li>
+ * <li>If none of the methods is dominant and if the comparison did not result in an ambigous resolution, the method that
+ * consists of the most one-to-one parameter bindings is considered dominant.</li>
+ * </ol>
+ * Primitive types are considered dominant in the same manner as by the Java compiler.
+ * <p> </p>
+ * For example: If a source method only parameter was successfully bound one-to-one to the only parameters of the target
+ * methods {@code foo(Object)} and {@code bar(String)}, this ambiguity resolver will detect that the {@code String} type
+ * is more specific than the {@code Object} type and determine {@code bar(String)} as the dominant binding.
+ */
+public enum ArgumentTypeResolver implements MethodDelegationBinder.AmbiguityResolver {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ /**
+ * Resolves two bindings by comparing their binding of similar arguments and determining their most specific types.
+ *
+ * @param sourceParameterType The parameter type of the source method
+ * @param leftParameterIndex The index of the parameter of the left method.
+ * @param left The left method's parameter binding.
+ * @param rightParameterIndex The index of the parameter of the right method.
+ * @param right The right method's parameter binding.
+ * @return A resolution according to the given parameters.
+ */
+ private static Resolution resolveRivalBinding(TypeDescription sourceParameterType,
+ int leftParameterIndex,
+ MethodDelegationBinder.MethodBinding left,
+ int rightParameterIndex,
+ MethodDelegationBinder.MethodBinding right) {
+ TypeDescription leftParameterType = left.getTarget().getParameters().get(leftParameterIndex).getType().asErasure();
+ TypeDescription rightParameterType = right.getTarget().getParameters().get(rightParameterIndex).getType().asErasure();
+ if (!leftParameterType.equals(rightParameterType)) {
+ if (leftParameterType.isPrimitive() && rightParameterType.isPrimitive()) {
+ return PrimitiveTypePrecedence.forPrimitive(leftParameterType)
+ .resolve(PrimitiveTypePrecedence.forPrimitive(rightParameterType));
+ } else if (leftParameterType.isPrimitive() /* && !rightParameterType.isPrimitive() */) {
+ return sourceParameterType.isPrimitive() ? Resolution.LEFT : Resolution.RIGHT;
+ } else if (/* !leftParameterType.isPrimitive() && */ rightParameterType.isPrimitive()) {
+ return sourceParameterType.isPrimitive() ? Resolution.RIGHT : Resolution.LEFT;
+ } else {
+ // Note that leftParameterType != rightParameterType, thus both cannot be true.
+ if (leftParameterType.isAssignableFrom(rightParameterType)) {
+ return Resolution.RIGHT;
+ } else if (rightParameterType.isAssignableFrom(leftParameterType)) {
+ return Resolution.LEFT;
+ } else {
+ return Resolution.AMBIGUOUS;
+ }
+ }
+ } else {
+ return Resolution.UNKNOWN;
+ }
+ }
+
+ /**
+ * Resolves the most specific method by their score. A method's score is calculated by the absolute number of
+ * parameters that were bound by using an explicit {@link net.bytebuddy.implementation.bind.annotation.Argument}
+ * annotation.
+ *
+ * @param boundParameterScore The difference of the scores of the left and the right method.
+ * @return A resolution according to this score.
+ */
+ private static Resolution resolveByScore(int boundParameterScore) {
+ if (boundParameterScore == 0) {
+ return Resolution.AMBIGUOUS;
+ } else if (boundParameterScore > 0) {
+ return Resolution.LEFT;
+ } else /* difference < 0*/ {
+ return Resolution.RIGHT;
+ }
+ }
+
+ @Override
+ public Resolution resolve(MethodDescription source,
+ MethodDelegationBinder.MethodBinding left,
+ MethodDelegationBinder.MethodBinding right) {
+ Resolution resolution = Resolution.UNKNOWN;
+ ParameterList<?> sourceParameters = source.getParameters();
+ int leftExtra = 0, rightExtra = 0;
+ for (int sourceParameterIndex = 0; sourceParameterIndex < sourceParameters.size(); sourceParameterIndex++) {
+ ParameterIndexToken parameterIndexToken = new ParameterIndexToken(sourceParameterIndex);
+ Integer leftParameterIndex = left.getTargetParameterIndex(parameterIndexToken);
+ Integer rightParameterIndex = right.getTargetParameterIndex(parameterIndexToken);
+ if (leftParameterIndex != null && rightParameterIndex != null) {
+ resolution = resolution.merge(resolveRivalBinding(sourceParameters.get(sourceParameterIndex).getType().asErasure(),
+ leftParameterIndex,
+ left,
+ rightParameterIndex,
+ right));
+ } else if (leftParameterIndex != null /* && rightParameterIndex == null */) {
+ leftExtra++;
+ } else if (/*leftParameterIndex == null && */ rightParameterIndex != null) {
+ rightExtra++;
+ }
+ }
+ return resolution == Resolution.UNKNOWN
+ ? resolveByScore(leftExtra - rightExtra)
+ : resolution;
+ }
+
+ /**
+ * A representation of the precedence of a most specific primitive type in the Java programming language.
+ */
+ protected enum PrimitiveTypePrecedence {
+
+ /**
+ * The precedence of the {@code boolean} type.
+ */
+ BOOLEAN(0),
+
+ /**
+ * The precedence of the {@code byte} type.
+ */
+ BYTE(1),
+
+ /**
+ * The precedence of the {@code short} type.
+ */
+ SHORT(2),
+
+ /**
+ * The precedence of the {@code int} type.
+ */
+ INTEGER(3),
+
+ /**
+ * The precedence of the {@code char} type.
+ */
+ CHARACTER(4),
+
+ /**
+ * The precedence of the {@code long} type.
+ */
+ LONG(5),
+
+ /**
+ * The precedence of the {@code float} type.
+ */
+ FLOAT(6),
+
+ /**
+ * The precedence of the {@code double} type.
+ */
+ DOUBLE(7);
+
+ /**
+ * A score representing the precedence where a higher score represents a less specific type.
+ */
+ private final int score;
+
+ /**
+ * Creates a new primitive type precedence.
+ *
+ * @param score A score representing the precedence where a higher score represents a less specific type.
+ */
+ PrimitiveTypePrecedence(int score) {
+ this.score = score;
+ }
+
+ /**
+ * Locates the primitive type precedence for a given type.
+ *
+ * @param typeDescription The non-void, primitive type for which the precedence should be located.
+ * @return The corresponding primitive type precedence.
+ */
+ public static PrimitiveTypePrecedence forPrimitive(TypeDescription typeDescription) {
+ if (typeDescription.represents(boolean.class)) {
+ return BOOLEAN;
+ } else if (typeDescription.represents(byte.class)) {
+ return BYTE;
+ } else if (typeDescription.represents(short.class)) {
+ return SHORT;
+ } else if (typeDescription.represents(int.class)) {
+ return INTEGER;
+ } else if (typeDescription.represents(char.class)) {
+ return CHARACTER;
+ } else if (typeDescription.represents(long.class)) {
+ return LONG;
+ } else if (typeDescription.represents(float.class)) {
+ return FLOAT;
+ } else if (typeDescription.represents(double.class)) {
+ return DOUBLE;
+ } else {
+ throw new IllegalArgumentException("Not a non-void, primitive type " + typeDescription);
+ }
+ }
+
+ /**
+ * Resolves the least specific type of two primitive type precedence with this instance representing a
+ * {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver.Resolution#LEFT}
+ * resolution and the argument type representing the
+ * {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver.Resolution#RIGHT}
+ * resolution.
+ *
+ * @param right Another primitive type precedence against which this precedence should be resolved.
+ * @return The resolution of
+ */
+ public Resolution resolve(PrimitiveTypePrecedence right) {
+ if (score - right.score == 0) {
+ return Resolution.UNKNOWN;
+ } else if (score - right.score > 0) {
+ return Resolution.RIGHT;
+ } else /* score - right.score < 0 */ {
+ return Resolution.LEFT;
+ }
+ }
+ }
+
+ /**
+ * This token is used to mark a one-to-one binding of a source method parameter to a target method parameter.
+ *
+ * @see net.bytebuddy.implementation.bind.MethodDelegationBinder.MethodBinding#getTargetParameterIndex(Object)
+ */
+ @EqualsAndHashCode
+ public static class ParameterIndexToken {
+
+ /**
+ * The parameter index that is represented by this token.
+ */
+ private final int parameterIndex;
+
+ /**
+ * Create a parameter index token for a given parameter of the source method.
+ *
+ * @param parameterIndex The parameter index of the source method which is mapped to a target method parameter.
+ */
+ public ParameterIndexToken(int parameterIndex) {
+ this.parameterIndex = parameterIndex;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/DeclaringTypeResolver.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/DeclaringTypeResolver.java
new file mode 100644
index 0000000..ef0a8aa
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/DeclaringTypeResolver.java
@@ -0,0 +1,33 @@
+package net.bytebuddy.implementation.bind;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+
+/**
+ * This ambiguity resolver matches that method out of two methods that is declared by the more specific type. If two
+ * methods are declared by the same type or by two unrelated types, this resolver returns an ambiguous result.
+ */
+public enum DeclaringTypeResolver implements MethodDelegationBinder.AmbiguityResolver {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Resolution resolve(MethodDescription source,
+ MethodDelegationBinder.MethodBinding left,
+ MethodDelegationBinder.MethodBinding right) {
+ TypeDescription leftType = left.getTarget().getDeclaringType().asErasure();
+ TypeDescription rightType = right.getTarget().getDeclaringType().asErasure();
+ if (leftType.equals(rightType)) {
+ return Resolution.AMBIGUOUS;
+ } else if (leftType.isAssignableFrom(rightType)) {
+ return Resolution.RIGHT;
+ } else if (leftType.isAssignableTo(rightType)) {
+ return Resolution.LEFT;
+ } else {
+ return Resolution.AMBIGUOUS;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/MethodDelegationBinder.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/MethodDelegationBinder.java
new file mode 100644
index 0000000..4530b24
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/MethodDelegationBinder.java
@@ -0,0 +1,892 @@
+package net.bytebuddy.implementation.bind;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bind.annotation.BindingPriority;
+import net.bytebuddy.implementation.bytecode.Removal;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
+import net.bytebuddy.implementation.bytecode.member.MethodReturn;
+import net.bytebuddy.utility.CompoundList;
+import org.objectweb.asm.MethodVisitor;
+
+import java.util.*;
+
+/**
+ * A method delegation binder is responsible for creating a method binding for a <i>source method</i> to a
+ * <i>target method</i>. Such a binding allows to implement the source method by calling the target method.
+ * <p> </p>
+ * Usually, an implementation will attempt to bind a specific source method to a set of target method candidates
+ * where all legal bindings are considered for binding. To chose a specific candidate, an
+ * {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver}
+ * will be consulted for selecting a <i>best</i> binding.
+ */
+public interface MethodDelegationBinder {
+
+ /**
+ * Compiles this method delegation binder for a target method.
+ *
+ * @param candidate The target method to bind.
+ * @return A compiled target for binding.
+ */
+ Record compile(MethodDescription candidate);
+
+ /**
+ * A method delegation that was compiled to a target method.
+ */
+ interface Record {
+
+ /**
+ * Attempts a binding of a source method to this compiled target.
+ *
+ * @param implementationTarget The target of the current implementation onto which this binding is to be applied.
+ * @param source The method that is to be bound to the {@code target} method.
+ * @param terminationHandler Ther termination handler to apply.
+ * @param methodInvoker The method invoker to use.
+ * @param assigner The assigner to use.
+ * @return A binding representing this attempt to bind the {@code source} method to the {@code target} method.
+ */
+ MethodBinding bind(Implementation.Target implementationTarget,
+ MethodDescription source,
+ TerminationHandler terminationHandler,
+ MethodInvoker methodInvoker,
+ Assigner assigner);
+
+ /**
+ * A compiled method delegation binder that only yields illegal bindings.
+ */
+ enum Illegal implements Record {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public MethodBinding bind(Implementation.Target implementationTarget,
+ MethodDescription source,
+ TerminationHandler terminationHandler,
+ MethodInvoker methodInvoker,
+ Assigner assigner) {
+ return MethodBinding.Illegal.INSTANCE;
+ }
+ }
+ }
+
+ /**
+ * Implementations are used as delegates for invoking a method that was bound
+ * using a {@link net.bytebuddy.implementation.bind.MethodDelegationBinder}.
+ */
+ interface MethodInvoker {
+
+ /**
+ * Creates a method invocation for a given method.
+ *
+ * @param methodDescription The method to be invoked.
+ * @return A stack manipulation encapsulating this method invocation.
+ */
+ StackManipulation invoke(MethodDescription methodDescription);
+
+ /**
+ * A simple method invocation that merely uses the most general form of method invocation as provided by
+ * {@link net.bytebuddy.implementation.bytecode.member.MethodInvocation}.
+ */
+ enum Simple implements MethodInvoker {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public StackManipulation invoke(MethodDescription methodDescription) {
+ return MethodInvocation.invoke(methodDescription);
+ }
+ }
+
+ /**
+ * A method invocation that enforces a virtual invocation that is dispatched on a given type.
+ */
+ @EqualsAndHashCode
+ class Virtual implements MethodInvoker {
+
+ /**
+ * The type on which a method should be invoked virtually.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates an immutable method invoker that dispatches all methods on a given type.
+ *
+ * @param typeDescription The type on which the method is invoked by virtual invocation.
+ */
+ public Virtual(TypeDescription typeDescription) {
+ this.typeDescription = typeDescription;
+ }
+
+ @Override
+ public StackManipulation invoke(MethodDescription methodDescription) {
+ return MethodInvocation.invoke(methodDescription).virtual(typeDescription);
+ }
+ }
+ }
+
+ /**
+ * A binding attempt for a single parameter. Implementations of this type are a suggestion of composing a
+ * {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.MethodBinding}
+ * by using a
+ * {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.MethodBinding.Builder}.
+ * However, method bindings can also be composed without this type which is merely a suggestion.
+ *
+ * @param <T> The type of the identification token for this parameter binding.
+ */
+ interface ParameterBinding<T> extends StackManipulation {
+
+ /**
+ * Returns an identification token for this binding.
+ *
+ * @return An identification token unique to this binding.
+ */
+ T getIdentificationToken();
+
+ /**
+ * A singleton representation of an illegal binding for a method parameter. An illegal binding usually
+ * suggests that a source method cannot be bound to a specific target method.
+ */
+ enum Illegal implements ParameterBinding<Void> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Void getIdentificationToken() {
+ throw new IllegalStateException("An illegal binding does not define an identification token");
+ }
+
+ @Override
+ public boolean isValid() {
+ return false;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ throw new IllegalStateException("An illegal parameter binding must not be applied");
+ }
+ }
+
+ /**
+ * An anonymous binding of a target method parameter.
+ */
+ @EqualsAndHashCode(of = "delegate")
+ class Anonymous implements ParameterBinding<Object> {
+
+ /**
+ * A pseudo-token that is not exposed and therefore anonymous.
+ */
+ private final Object anonymousToken;
+
+ /**
+ * The stack manipulation that represents the loading of the parameter binding onto the stack.
+ */
+ private final StackManipulation delegate;
+
+ /**
+ * Creates a new, anonymous parameter binding.
+ *
+ * @param delegate The stack manipulation that is responsible for loading the parameter value for this
+ * target method parameter onto the stack.
+ */
+ public Anonymous(StackManipulation delegate) {
+ this.delegate = delegate;
+ anonymousToken = new Object();
+ }
+
+ @Override
+ public Object getIdentificationToken() {
+ return anonymousToken;
+ }
+
+ @Override
+ public boolean isValid() {
+ return delegate.isValid();
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ return delegate.apply(methodVisitor, implementationContext);
+ }
+ }
+
+ /**
+ * A uniquely identifiable parameter binding for a target method. Such bindings are usually later processed by
+ * a {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver}
+ * in order to resolve binding conflicts between several bindable target methods to the same source method.
+ *
+ * @param <T> The type of the identification token.
+ * @see net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver
+ */
+ @EqualsAndHashCode
+ class Unique<T> implements ParameterBinding<T> {
+
+ /**
+ * The token that identifies this parameter binding as unique.
+ */
+ private final T identificationToken;
+
+ /**
+ * The stack manipulation that represents the loading of the parameter binding onto the stack.
+ */
+ private final StackManipulation delegate;
+
+ /**
+ * Creates a new unique parameter binding representant.
+ *
+ * @param delegate The stack manipulation that loads the argument for this parameter onto the operand stack.
+ * @param identificationToken The token used for identifying this parameter binding.
+ */
+ public Unique(StackManipulation delegate, T identificationToken) {
+ this.delegate = delegate;
+ this.identificationToken = identificationToken;
+ }
+
+ /**
+ * A factory method for creating a unique binding that infers the tokens type.
+ *
+ * @param delegate The stack manipulation delegate.
+ * @param identificationToken The identification token.
+ * @param <S> The type of the identification token.
+ * @return A new instance representing this unique binding.
+ */
+ public static <S> Unique<S> of(StackManipulation delegate, S identificationToken) {
+ return new Unique<S>(delegate, identificationToken);
+ }
+
+ @Override
+ public T getIdentificationToken() {
+ return identificationToken;
+ }
+
+ @Override
+ public boolean isValid() {
+ return delegate.isValid();
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ return delegate.apply(methodVisitor, implementationContext);
+ }
+ }
+ }
+
+ /**
+ * A binding attempt created by a
+ * {@link net.bytebuddy.implementation.bind.MethodDelegationBinder}.
+ */
+ interface MethodBinding extends StackManipulation {
+
+ /**
+ * Returns the target method's parameter index for a given parameter binding token.
+ * <p> </p>
+ * A binding token can be any object
+ * that implements valid {@link Object#hashCode()} and {@link Object#equals(Object)} methods in order
+ * to look up a given binding. This way, two bindings can be evaluated of having performed a similar type of
+ * binding such that these bindings can be compared and a dominant binding can be identified by an
+ * {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver}.
+ * Furthermore, a binding is implicitly required to insure the uniqueness of such a parameter binding.
+ *
+ * @param parameterBindingToken A token which is used to identify a specific unique binding for a given parameter
+ * of the target method.
+ * @return The target method's parameter index of this binding or {@code null} if no such argument binding
+ * was applied for this binding.
+ */
+ Integer getTargetParameterIndex(Object parameterBindingToken);
+
+ /**
+ * Returns the target method of the method binding attempt.
+ *
+ * @return The target method to which the
+ */
+ MethodDescription getTarget();
+
+ /**
+ * Representation of an attempt to bind a source method to a target method that is not applicable.
+ *
+ * @see net.bytebuddy.implementation.bind.MethodDelegationBinder
+ */
+ enum Illegal implements MethodBinding {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Integer getTargetParameterIndex(Object parameterBindingToken) {
+ throw new IllegalStateException("Method is not bound");
+ }
+
+ @Override
+ public MethodDescription getTarget() {
+ throw new IllegalStateException("Method is not bound");
+ }
+
+ @Override
+ public boolean isValid() {
+ return false;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ throw new IllegalStateException("Cannot delegate to an unbound method");
+ }
+ }
+
+ /**
+ * A mutable builder that allows to compose a
+ * {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.MethodBinding}
+ * by adding parameter bindings incrementally.
+ */
+ class Builder {
+
+ /**
+ * The method invoker for invoking the actual method that is bound.
+ */
+ private final MethodInvoker methodInvoker;
+
+ /**
+ * The target method that for which a binding is to be constructed by this builder..
+ */
+ private final MethodDescription candidate;
+
+ /**
+ * The current list of stack manipulations for loading values for each parameter onto the operand stack.
+ */
+ private final List<StackManipulation> parameterStackManipulations;
+
+ /**
+ * A mapping of identification tokens to the parameter index they were bound for.
+ */
+ private final LinkedHashMap<Object, Integer> registeredTargetIndices;
+
+ /**
+ * The index of the next parameter that is to be bound.
+ */
+ private int nextParameterIndex;
+
+ /**
+ * Creates a new builder for the binding of a given method.
+ *
+ * @param methodInvoker The method invoker that is used to create the method invocation of the {@code target} method.
+ * @param candidate The target method that is target of the binding.
+ */
+ public Builder(MethodInvoker methodInvoker, MethodDescription candidate) {
+ this.methodInvoker = methodInvoker;
+ this.candidate = candidate;
+ parameterStackManipulations = new ArrayList<StackManipulation>(candidate.getParameters().size());
+ registeredTargetIndices = new LinkedHashMap<Object, Integer>();
+ nextParameterIndex = 0;
+ }
+
+ /**
+ * Appends a stack manipulation for the next parameter of the target method.
+ *
+ * @param parameterBinding A binding representing the next subsequent parameter of the method.
+ * @return {@code false} if the {@code parameterBindingToken} was already bound. A conflicting binding should
+ * usually abort the attempt of binding a method and this {@code Builder} should be discarded.
+ */
+ public boolean append(ParameterBinding<?> parameterBinding) {
+ parameterStackManipulations.add(parameterBinding);
+ return registeredTargetIndices.put(parameterBinding.getIdentificationToken(), nextParameterIndex++) == null;
+ }
+
+ /**
+ * Creates a binding that represents the bindings collected by this {@code Builder}.
+ *
+ * @param terminatingManipulation A stack manipulation that is applied after the method invocation.
+ * @return A binding representing the parameter bindings collected by this builder.
+ */
+ public MethodBinding build(StackManipulation terminatingManipulation) {
+ if (candidate.getParameters().size() != nextParameterIndex) {
+ throw new IllegalStateException("The number of parameters bound does not equal the target's number of parameters");
+ }
+ return new Build(candidate,
+ registeredTargetIndices,
+ methodInvoker.invoke(candidate),
+ parameterStackManipulations,
+ terminatingManipulation);
+ }
+
+ /**
+ * A method binding that was created by a
+ * {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.MethodBinding.Builder}.
+ */
+ @EqualsAndHashCode
+ protected static class Build implements MethodBinding {
+
+ /**
+ * The target method this binding represents.
+ */
+ private final MethodDescription target;
+
+ /**
+ * A map of identification tokens to the indices of their binding parameters.
+ */
+ private final Map<?, Integer> registeredTargetIndices;
+
+ /**
+ * A stack manipulation that represents the actual method invocation.
+ */
+ private final StackManipulation methodInvocation;
+
+ /**
+ * A list of manipulations that each represent the loading of a parameter value onto the operand stack.
+ */
+ private final List<StackManipulation> parameterStackManipulations;
+
+ /**
+ * The stack manipulation that is applied after the method invocation.
+ */
+ private final StackManipulation terminatingStackManipulation;
+
+ /**
+ * Creates a new method binding.
+ *
+ * @param target The target method this binding represents.
+ * @param registeredTargetIndices A map of identification tokens to the indices of their binding
+ * parameters.
+ * @param methodInvocation A stack manipulation that represents the actual method invocation.
+ * @param parameterStackManipulations A list of manipulations that each represent the loading of a
+ * parameter value onto the operand stack.
+ * @param terminatingStackManipulation The stack manipulation that is applied after the method invocation.
+ */
+ protected Build(MethodDescription target,
+ Map<?, Integer> registeredTargetIndices,
+ StackManipulation methodInvocation,
+ List<StackManipulation> parameterStackManipulations,
+ StackManipulation terminatingStackManipulation) {
+ this.target = target;
+ this.registeredTargetIndices = new HashMap<Object, Integer>(registeredTargetIndices);
+ this.methodInvocation = methodInvocation;
+ this.parameterStackManipulations = new ArrayList<StackManipulation>(parameterStackManipulations);
+ this.terminatingStackManipulation = terminatingStackManipulation;
+ }
+
+ @Override
+ public boolean isValid() {
+ boolean result = methodInvocation.isValid() && terminatingStackManipulation.isValid();
+ Iterator<StackManipulation> assignment = parameterStackManipulations.iterator();
+ while (result && assignment.hasNext()) {
+ result = assignment.next().isValid();
+ }
+ return result;
+ }
+
+ @Override
+ public Integer getTargetParameterIndex(Object parameterBindingToken) {
+ return registeredTargetIndices.get(parameterBindingToken);
+ }
+
+ @Override
+ public MethodDescription getTarget() {
+ return target;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ return new Compound(
+ CompoundList.of(parameterStackManipulations, Arrays.asList(methodInvocation, terminatingStackManipulation))
+ ).apply(methodVisitor, implementationContext);
+ }
+ }
+ }
+ }
+
+ /**
+ * Implementations of this interface are able to attempt the resolution of two successful bindings of a method
+ * to two different target methods in order to identify a dominating binding.
+ */
+ @SuppressFBWarnings(value = "IC_SUPERCLASS_USES_SUBCLASS_DURING_INITIALIZATION", justification = "Safe initialization is implied")
+ interface AmbiguityResolver {
+
+ /**
+ * The default ambiguity resolver to use.
+ */
+ AmbiguityResolver DEFAULT = new MethodDelegationBinder.AmbiguityResolver.Compound(BindingPriority.Resolver.INSTANCE,
+ DeclaringTypeResolver.INSTANCE,
+ ArgumentTypeResolver.INSTANCE,
+ MethodNameEqualityResolver.INSTANCE,
+ ParameterLengthResolver.INSTANCE);
+
+ /**
+ * Attempts to resolve to conflicting bindings.
+ *
+ * @param source The source method that was bound to both target methods.
+ * @param left The first successful binding of the {@code source} method.
+ * @param right The second successful binding of the {@code source} method.
+ * @return The resolution state when resolving a conflicting binding where
+ * {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver.Resolution#LEFT}
+ * indicates a successful binding to the {@code left} binding while
+ * {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver.Resolution#RIGHT}
+ * indicates a successful binding to the {@code right} binding.
+ */
+ Resolution resolve(MethodDescription source, MethodBinding left, MethodBinding right);
+
+ /**
+ * A resolution state of an attempt to resolve two conflicting bindings.
+ */
+ enum Resolution {
+
+ /**
+ * Describes a resolution state where no information about dominance could be gathered.
+ */
+ UNKNOWN(true),
+
+ /**
+ * Describes a resolution state where the left method dominates the right method.
+ */
+ LEFT(false),
+
+ /**
+ * Describes a resolution state where the right method dominates the left method.
+ */
+ RIGHT(false),
+
+ /**
+ * Describes a resolution state where both methods have inflicting dominance over each other.
+ */
+ AMBIGUOUS(true);
+
+ /**
+ * {@code true} if this resolution is unresolved.
+ */
+ private final boolean unresolved;
+
+ /**
+ * Creates a new resolution.
+ *
+ * @param unresolved {@code true} if this resolution is unresolved.
+ */
+ Resolution(boolean unresolved) {
+ this.unresolved = unresolved;
+ }
+
+ /**
+ * Checks if this binding is unresolved.
+ *
+ * @return {@code true} if this binding is unresolved.
+ */
+ public boolean isUnresolved() {
+ return unresolved;
+ }
+
+ /**
+ * Merges two resolutions in order to determine their compatibility.
+ *
+ * @param other The resolution this resolution is to be checked against.
+ * @return The merged resolution.
+ */
+ public Resolution merge(Resolution other) {
+ switch (this) {
+ case UNKNOWN:
+ return other;
+ case AMBIGUOUS:
+ return AMBIGUOUS;
+ case LEFT:
+ case RIGHT:
+ return other == UNKNOWN || other == this
+ ? this
+ : AMBIGUOUS;
+ default:
+ throw new AssertionError();
+ }
+ }
+ }
+
+ /**
+ * An ambiguity resolver that does not attempt to resolve a conflicting binding.
+ */
+ enum NoOp implements AmbiguityResolver {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Resolution resolve(MethodDescription source, MethodBinding left, MethodBinding right) {
+ return Resolution.UNKNOWN;
+ }
+ }
+
+ /**
+ * An ambiguity resolver that always resolves in the specified direction.
+ */
+ enum Directional implements AmbiguityResolver {
+
+ /**
+ * A resolver that always resolves to
+ * {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver.Resolution#LEFT}.
+ */
+ LEFT(true),
+
+ /**
+ * A resolver that always resolves to
+ * {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver.Resolution#RIGHT}.
+ */
+ RIGHT(false);
+
+ /**
+ * {@code true} if this instance should resolve to the left side.
+ */
+ private final boolean left;
+
+ /**
+ * Creates a new directional resolver.
+ *
+ * @param left {@code true} if this instance should resolve to the left side.
+ */
+ Directional(boolean left) {
+ this.left = left;
+ }
+
+ @Override
+ public Resolution resolve(MethodDescription source, MethodBinding left, MethodBinding right) {
+ return this.left
+ ? Resolution.LEFT
+ : Resolution.RIGHT;
+ }
+ }
+
+ /**
+ * A chain of {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver}s
+ * that are applied in the given order until two bindings can be resolved.
+ */
+ @EqualsAndHashCode
+ class Compound implements AmbiguityResolver {
+
+ /**
+ * A list of ambiguity resolvers that are applied by this chain in their order of application.
+ */
+ private final List<AmbiguityResolver> ambiguityResolvers;
+
+ /**
+ * Creates an immutable chain of ambiguity resolvers.
+ *
+ * @param ambiguityResolver The ambiguity resolvers to chain in the order of their application.
+ */
+ public Compound(AmbiguityResolver... ambiguityResolver) {
+ this(Arrays.asList(ambiguityResolver));
+ }
+
+ /**
+ * Creates an immutable chain of ambiguity resolvers.
+ *
+ * @param ambiguityResolvers The ambiguity resolvers to chain in the order of their application.
+ */
+ public Compound(List<? extends AmbiguityResolver> ambiguityResolvers) {
+ this.ambiguityResolvers = new ArrayList<AmbiguityResolver>();
+ for (AmbiguityResolver ambiguityResolver : ambiguityResolvers) {
+ if (ambiguityResolver instanceof Compound) {
+ this.ambiguityResolvers.addAll(((Compound) ambiguityResolver).ambiguityResolvers);
+ } else if (!(ambiguityResolver instanceof NoOp)) {
+ this.ambiguityResolvers.add(ambiguityResolver);
+ }
+ }
+ }
+
+ @Override
+ public Resolution resolve(MethodDescription source, MethodBinding left, MethodBinding right) {
+ Resolution resolution = Resolution.UNKNOWN;
+ Iterator<? extends AmbiguityResolver> iterator = ambiguityResolvers.iterator();
+ while (resolution.isUnresolved() && iterator.hasNext()) {
+ resolution = iterator.next().resolve(source, left, right);
+ }
+ return resolution;
+ }
+ }
+ }
+
+ /**
+ * A termination handler is responsible for terminating a method delegation.
+ */
+ interface TerminationHandler {
+
+ /**
+ * Creates a stack manipulation that is to be applied after the method return.
+ *
+ * @param assigner The supplied assigner.
+ * @param typing The typing to apply.
+ * @param source The source method that is bound to the {@code target} method.
+ * @param target The target method that is subject to be bound by the {@code source} method.
+ * @return A stack manipulation that is applied after the method return.
+ */
+ StackManipulation resolve(Assigner assigner, Assigner.Typing typing, MethodDescription source, MethodDescription target);
+
+ /**
+ * Responsible for creating a {@link StackManipulation}
+ * that is applied after the interception method is applied.
+ */
+ enum Default implements TerminationHandler {
+
+ /**
+ * A termination handler that returns the delegate method's return value.
+ */
+ RETURNING {
+ @Override
+ public StackManipulation resolve(Assigner assigner, Assigner.Typing typing, MethodDescription source, MethodDescription target) {
+ return new StackManipulation.Compound(assigner.assign(target.isConstructor()
+ ? target.getDeclaringType().asGenericType()
+ : target.getReturnType(),
+ source.getReturnType(),
+ typing), MethodReturn.of(source.getReturnType()));
+ }
+ },
+
+ /**
+ * A termination handler that drops the delegate method's return value.
+ */
+ DROPPING {
+ @Override
+ public StackManipulation resolve(Assigner assigner, Assigner.Typing typing, MethodDescription source, MethodDescription target) {
+ return Removal.of(target.isConstructor()
+ ? target.getDeclaringType()
+ : target.getReturnType());
+ }
+ };
+ }
+ }
+
+ /**
+ * A helper class that allows to identify a best binding for a given type and source method chosing from a list of given
+ * target methods by using a given {@link net.bytebuddy.implementation.bind.MethodDelegationBinder}
+ * and an {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver}.
+ * <p> </p>
+ * The {@code Processor} will:
+ * <ol>
+ * <li>Try to bind the {@code source} method using the {@code MethodDelegationBinder}.</li>
+ * <li>Find a best method among the successful bindings using the {@code AmbiguityResolver}.</li>
+ * </ol>
+ */
+ @EqualsAndHashCode
+ class Processor implements MethodDelegationBinder.Record {
+
+ /**
+ * Represents the index of the only value of two elements in a list.
+ */
+ private static final int ONLY = 0;
+
+ /**
+ * Represents the index of the left value of two elements in a list.
+ */
+ private static final int LEFT = 0;
+
+ /**
+ * Represents the index of the right value of two elements in a list.
+ */
+ private static final int RIGHT = 1;
+
+ /**
+ * The delegation records to consider.
+ */
+ private final List<? extends Record> records;
+
+ /**
+ * The processor's ambiguity resolver.
+ */
+ private final AmbiguityResolver ambiguityResolver;
+
+ /**
+ * Creates a new processor.
+ *
+ * @param records The delegation records to consider.
+ * @param ambiguityResolver The ambiguity resolver to apply.
+ */
+ public Processor(List<? extends Record> records, AmbiguityResolver ambiguityResolver) {
+ this.records = records;
+ this.ambiguityResolver = ambiguityResolver;
+ }
+
+ @Override
+ public MethodBinding bind(Implementation.Target implementationTarget,
+ MethodDescription source,
+ TerminationHandler terminationHandler,
+ MethodInvoker methodInvoker,
+ Assigner assigner) {
+ List<MethodBinding> targets = new ArrayList<MethodBinding>();
+ for (Record record : records) {
+ MethodBinding methodBinding = record.bind(implementationTarget, source, terminationHandler, methodInvoker, assigner);
+ if (methodBinding.isValid()) {
+ targets.add(methodBinding);
+ }
+ }
+ if (targets.isEmpty()) {
+ throw new IllegalArgumentException("None of " + records + " allows for delegation from " + source);
+ }
+ return resolve(source, targets);
+ }
+
+ /**
+ * Resolves the most specific target method of a list of legal method bindings.
+ *
+ * @param source The source method that is to be bound.
+ * @param targets A list of possible binding targets.
+ * @return The most specific method binding that was located from the given list of candidate targets.
+ */
+ private MethodBinding resolve(MethodDescription source, List<MethodBinding> targets) {
+ switch (targets.size()) {
+ case 1:
+ return targets.get(ONLY);
+ case 2: {
+ MethodBinding left = targets.get(LEFT);
+ MethodBinding right = targets.get(RIGHT);
+ switch (ambiguityResolver.resolve(source, left, right)) {
+ case LEFT:
+ return left;
+ case RIGHT:
+ return right;
+ case AMBIGUOUS:
+ case UNKNOWN:
+ throw new IllegalArgumentException("Cannot resolve ambiguous delegation of " + source + " to " + left + " or " + right);
+ default:
+ throw new AssertionError();
+ }
+ }
+ default: /* case 3+: */ {
+ MethodBinding left = targets.get(LEFT);
+ MethodBinding right = targets.get(RIGHT);
+ switch (ambiguityResolver.resolve(source, left, right)) {
+ case LEFT:
+ targets.remove(RIGHT);
+ return resolve(source, targets);
+ case RIGHT:
+ targets.remove(LEFT);
+ return resolve(source, targets);
+ case AMBIGUOUS:
+ case UNKNOWN:
+ targets.remove(RIGHT); // Remove right element first due to index alteration!
+ targets.remove(LEFT);
+ MethodBinding subResult = resolve(source, targets);
+ switch (ambiguityResolver.resolve(source, left, subResult).merge(ambiguityResolver.resolve(source, right, subResult))) {
+ case RIGHT:
+ return subResult;
+ case LEFT:
+ case AMBIGUOUS:
+ case UNKNOWN:
+ throw new IllegalArgumentException("Cannot resolve ambiguous delegation of " + source + " to " + left + " or " + right);
+ default:
+ throw new AssertionError();
+ }
+ default:
+ throw new IllegalStateException("Unexpected targets: " + targets);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/MethodNameEqualityResolver.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/MethodNameEqualityResolver.java
new file mode 100644
index 0000000..3492203
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/MethodNameEqualityResolver.java
@@ -0,0 +1,33 @@
+package net.bytebuddy.implementation.bind;
+
+import net.bytebuddy.description.method.MethodDescription;
+
+/**
+ * Implementation of an
+ * {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver}
+ * that resolves conflicting bindings by considering equality of a target method's internalName as an indicator for a dominant
+ * binding.
+ * <p> </p>
+ * For example, if method {@code source.foo} can be bound to methods {@code targetA.foo} and {@code targetB.bar},
+ * {@code targetA.foo} will be considered as dominant.
+ */
+public enum MethodNameEqualityResolver implements MethodDelegationBinder.AmbiguityResolver {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Resolution resolve(MethodDescription source,
+ MethodDelegationBinder.MethodBinding left,
+ MethodDelegationBinder.MethodBinding right) {
+ boolean leftEquals = left.getTarget().getName().equals(source.getName());
+ boolean rightEquals = right.getTarget().getName().equals(source.getName());
+ if (leftEquals ^ rightEquals) {
+ return leftEquals ? Resolution.LEFT : Resolution.RIGHT;
+ } else {
+ return Resolution.AMBIGUOUS;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/ParameterLengthResolver.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/ParameterLengthResolver.java
new file mode 100644
index 0000000..1541ebe
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/ParameterLengthResolver.java
@@ -0,0 +1,30 @@
+package net.bytebuddy.implementation.bind;
+
+import net.bytebuddy.description.method.MethodDescription;
+
+/**
+ * This {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver} selects
+ * the method with more arguments. If two methods have equally many arguments, the resolution is ambiguous.
+ */
+public enum ParameterLengthResolver implements MethodDelegationBinder.AmbiguityResolver {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Resolution resolve(MethodDescription source,
+ MethodDelegationBinder.MethodBinding left,
+ MethodDelegationBinder.MethodBinding right) {
+ int leftLength = left.getTarget().getParameters().size();
+ int rightLength = right.getTarget().getParameters().size();
+ if (leftLength == rightLength) {
+ return Resolution.AMBIGUOUS;
+ } else if (leftLength < rightLength) {
+ return Resolution.RIGHT;
+ } else {
+ return Resolution.LEFT;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/AllArguments.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/AllArguments.java
new file mode 100644
index 0000000..b633032
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/AllArguments.java
@@ -0,0 +1,158 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.collection.ArrayFactory;
+import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+import net.bytebuddy.utility.CompoundList;
+
+import java.lang.annotation.*;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Parameters that are annotated with this annotation will be assigned a collection (or an array) containing
+ * all arguments of the source method. Currently, this annotation supports the following collection types:
+ * <ul>
+ * <li>Array</li>
+ * </ul>
+ * <p> </p>
+ * By default, this annotation applies a
+ * {@link net.bytebuddy.implementation.bind.annotation.AllArguments.Assignment#STRICT}
+ * assignment of the source method's parameters to the array. This implies that parameters that are not assignable to
+ * the annotated array's component type make the method with this parameter unbindable. To avoid this, you can
+ * use a {@link net.bytebuddy.implementation.bind.annotation.AllArguments.Assignment#SLACK} assignment
+ * which simply skips non-assignable values instead.
+ *
+ * @see net.bytebuddy.implementation.MethodDelegation
+ * @see net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder
+ * @see net.bytebuddy.implementation.bind.annotation.RuntimeType
+ */
+ at Documented
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.PARAMETER)
+public @interface AllArguments {
+
+ /**
+ * Defines the type of {@link net.bytebuddy.implementation.bind.annotation.AllArguments.Assignment}
+ * type that is applied for filling the annotated array with values.
+ *
+ * @return The assignment handling to be applied for the annotated parameter.
+ */
+ Assignment value() default Assignment.STRICT;
+
+ /**
+ * Determines if the array should contain the instance that defines the intercepted value when intercepting
+ * a non-static method.
+ *
+ * @return {@code true} if the instance on which the intercepted method should be invoked should be
+ * included in the array containing the arguments.
+ */
+ boolean includeSelf() default false;
+
+ /**
+ * A directive for how an {@link net.bytebuddy.implementation.bind.annotation.AllArguments}
+ * annotation on an array is to be interpreted.
+ */
+ enum Assignment {
+
+ /**
+ * A strict assignment attempts to include <b>all</b> parameter values of the source method. If only one of these
+ * parameters is not assignable to the component type of the annotated array, the method is considered as
+ * non-bindable.
+ */
+ STRICT(true),
+
+ /**
+ * Other than a {@link net.bytebuddy.implementation.bind.annotation.AllArguments.Assignment#STRICT}
+ * assignment, a slack assignment simply ignores non-bindable parameters and does not include them in the target
+ * array. In the most extreme case where no source method parameter is assignable to the component type
+ * of the annotated array, the array that is assigned to the target parameter is empty.
+ */
+ SLACK(false);
+
+ /**
+ * Determines if this assignment is strict.
+ */
+ private final boolean strict;
+
+ /**
+ * Creates a new assignment type.
+ *
+ * @param strict {@code true} if this assignment is strict.
+ */
+ Assignment(boolean strict) {
+ this.strict = strict;
+ }
+
+ /**
+ * Returns {@code true} if this assignment is strict.
+ *
+ * @return {@code true} if this assignment is strict.
+ */
+ protected boolean isStrict() {
+ return strict;
+ }
+ }
+
+ /**
+ * A binder for handling the
+ * {@link net.bytebuddy.implementation.bind.annotation.AllArguments}
+ * annotation.
+ *
+ * @see TargetMethodAnnotationDrivenBinder
+ */
+ enum Binder implements TargetMethodAnnotationDrivenBinder.ParameterBinder<AllArguments> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Class<AllArguments> getHandledType() {
+ return AllArguments.class;
+ }
+
+ @Override
+ public MethodDelegationBinder.ParameterBinding<?> bind(AnnotationDescription.Loadable<AllArguments> annotation,
+ MethodDescription source,
+ ParameterDescription target,
+ Implementation.Target implementationTarget,
+ Assigner assigner,
+ Assigner.Typing typing) {
+ TypeDescription.Generic componentType;
+ if (target.getType().represents(Object.class)) {
+ componentType = TypeDescription.Generic.OBJECT;
+ } else if (target.getType().isArray()) {
+ componentType = target.getType().getComponentType();
+ } else {
+ throw new IllegalStateException("Expected an array type for all argument annotation on " + source);
+ }
+ boolean includeThis = !source.isStatic() && annotation.loadSilent().includeSelf();
+ List<StackManipulation> stackManipulations = new ArrayList<StackManipulation>(source.getParameters().size() + (includeThis ? 1 : 0));
+ int offset = source.isStatic() || includeThis ? 0 : 1;
+ for (TypeDescription.Generic sourceParameter : includeThis
+ ? CompoundList.of(implementationTarget.getInstrumentedType().asGenericType(), source.getParameters().asTypeList())
+ : source.getParameters().asTypeList()) {
+ StackManipulation stackManipulation = new StackManipulation.Compound(
+ MethodVariableAccess.of(sourceParameter).loadFrom(offset),
+ assigner.assign(sourceParameter, componentType, typing)
+ );
+ if (stackManipulation.isValid()) {
+ stackManipulations.add(stackManipulation);
+ } else if (annotation.loadSilent().value().isStrict()) {
+ return MethodDelegationBinder.ParameterBinding.Illegal.INSTANCE;
+ }
+ offset += sourceParameter.getStackSize().getSize();
+ }
+ return new MethodDelegationBinder.ParameterBinding.Anonymous(ArrayFactory.forType(componentType).withValues(stackManipulations));
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/Argument.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/Argument.java
new file mode 100644
index 0000000..8c0a870
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/Argument.java
@@ -0,0 +1,164 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bind.ArgumentTypeResolver;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+
+import java.lang.annotation.*;
+
+/**
+ * Parameters that are annotated with this annotation will be assigned the value of the parameter of the source method
+ * with the given parameter. For example, if source method {@code foo(String, Integer)} is bound to target method
+ * {@code bar(@Argument(1) Integer)}, the second parameter of {@code foo} will be bound to the first argument of
+ * {@code bar}.
+ * <p> </p>
+ * If a source method has less parameters than specified by {@link Argument#value()}, the method carrying this parameter
+ * annotation is excluded from the list of possible binding candidates to this particular source method. The same happens,
+ * if the source method parameter at the specified index is not assignable to the annotated parameter.
+ *
+ * @see net.bytebuddy.implementation.MethodDelegation
+ * @see net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder
+ * @see net.bytebuddy.implementation.bind.annotation.RuntimeType
+ */
+ at Documented
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.PARAMETER)
+public @interface Argument {
+
+ /**
+ * The index of the parameter of the source method that should be bound to this parameter.
+ *
+ * @return The required parameter index.
+ */
+ int value();
+
+ /**
+ * Determines if the argument binding is to be considered by a
+ * {@link net.bytebuddy.implementation.bind.ArgumentTypeResolver}
+ * for resolving ambiguous bindings of two methods. If
+ * {@link net.bytebuddy.implementation.bind.annotation.Argument.BindingMechanic#UNIQUE},
+ * of two bindable target methods such as for example {@code foo(String)} and {@code bar(Object)}, the {@code foo}
+ * method would be considered as dominant over the {@code bar} method because of its more specific argument type. As
+ * a side effect, only one parameter of any target method can be bound to a source method parameter with a given
+ * index unless the {@link net.bytebuddy.implementation.bind.annotation.Argument.BindingMechanic#ANONYMOUS}
+ * option is used for any other binding.
+ *
+ * @return The binding type that should be applied to this parameter binding.
+ * @see net.bytebuddy.implementation.bind.ArgumentTypeResolver
+ */
+ BindingMechanic bindingMechanic() default BindingMechanic.UNIQUE;
+
+ /**
+ * Determines if a parameter binding should be considered for resolving ambiguous method bindings.
+ *
+ * @see Argument#bindingMechanic()
+ * @see net.bytebuddy.implementation.bind.ArgumentTypeResolver
+ */
+ enum BindingMechanic {
+
+ /**
+ * The binding is unique, i.e. only one such binding must be present among all parameters of a method. As a
+ * consequence, the binding can be latter identified by an
+ * {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver}.
+ */
+ UNIQUE {
+ @Override
+ protected MethodDelegationBinder.ParameterBinding<?> makeBinding(TypeDescription.Generic source,
+ TypeDescription.Generic target,
+ int sourceParameterIndex,
+ Assigner assigner,
+ Assigner.Typing typing,
+ int parameterOffset) {
+ return MethodDelegationBinder.ParameterBinding.Unique.of(
+ new StackManipulation.Compound(
+ MethodVariableAccess.of(source).loadFrom(parameterOffset),
+ assigner.assign(source, target, typing)),
+ new ArgumentTypeResolver.ParameterIndexToken(sourceParameterIndex)
+ );
+ }
+ },
+
+ /**
+ * The binding is anonymous, i.e. it can be present on several parameters of the same method.
+ */
+ ANONYMOUS {
+ @Override
+ protected MethodDelegationBinder.ParameterBinding<?> makeBinding(TypeDescription.Generic source,
+ TypeDescription.Generic target,
+ int sourceParameterIndex,
+ Assigner assigner,
+ Assigner.Typing typing,
+ int parameterOffset) {
+ return new MethodDelegationBinder.ParameterBinding.Anonymous(
+ new StackManipulation.Compound(MethodVariableAccess.of(source).loadFrom(parameterOffset), assigner.assign(source, target, typing))
+ );
+ }
+ };
+
+ /**
+ * Creates a binding that corresponds to this binding mechanic.
+ *
+ * @param source The source type to be bound.
+ * @param target The target type the {@code sourceType} is to be bound to.
+ * @param sourceParameterIndex The index of the source parameter.
+ * @param assigner The assigner that is used to perform the assignment.
+ * @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
+ * @param parameterOffset The offset of the source method's parameter.
+ * @return A binding considering the chosen binding mechanic.
+ */
+ protected abstract MethodDelegationBinder.ParameterBinding<?> makeBinding(TypeDescription.Generic source,
+ TypeDescription.Generic target,
+ int sourceParameterIndex,
+ Assigner assigner,
+ Assigner.Typing typing,
+ int parameterOffset);
+ }
+
+ /**
+ * A binder for handling the
+ * {@link net.bytebuddy.implementation.bind.annotation.Argument}
+ * annotation.
+ *
+ * @see TargetMethodAnnotationDrivenBinder
+ */
+ enum Binder implements TargetMethodAnnotationDrivenBinder.ParameterBinder<Argument> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Class<Argument> getHandledType() {
+ return Argument.class;
+ }
+
+ @Override
+ public MethodDelegationBinder.ParameterBinding<?> bind(AnnotationDescription.Loadable<Argument> annotation,
+ MethodDescription source,
+ ParameterDescription target,
+ Implementation.Target implementationTarget,
+ Assigner assigner,
+ Assigner.Typing typing) {
+ Argument argument = annotation.loadSilent();
+ if (argument.value() < 0) {
+ throw new IllegalArgumentException("@Argument annotation on " + target + " specifies negative index");
+ } else if (source.getParameters().size() <= argument.value()) {
+ return MethodDelegationBinder.ParameterBinding.Illegal.INSTANCE;
+ }
+ return argument.bindingMechanic().makeBinding(source.getParameters().get(argument.value()).getType(),
+ target.getType(),
+ argument.value(),
+ assigner,
+ typing,
+ source.getParameters().get(argument.value()).getOffset());
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/BindingPriority.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/BindingPriority.java
new file mode 100644
index 0000000..c7e22e3
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/BindingPriority.java
@@ -0,0 +1,76 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+
+import java.lang.annotation.*;
+
+/**
+ * Defines a binding priority for a target method. If two target methods can be bound to a source method,
+ * the one with the higher priority will be selected.
+ *
+ * @see net.bytebuddy.implementation.MethodDelegation
+ * @see net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder
+ */
+ at Documented
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.METHOD)
+public @interface BindingPriority {
+
+ /**
+ * The default priority for methods not carrying the
+ * {@link net.bytebuddy.implementation.bind.annotation.BindingPriority}
+ * annotation.
+ */
+ int DEFAULT = 1;
+
+ /**
+ * The binding priority for the annotated method. A method of higher priority will be preferred over a method
+ * of lower priority.
+ *
+ * @return The priority for the annotated method.
+ */
+ int value();
+
+ /**
+ * An ambiguity resolver that considers the priority of a method as defined by the
+ * {@link net.bytebuddy.implementation.bind.annotation.BindingPriority}
+ * annotation.
+ */
+ enum Resolver implements MethodDelegationBinder.AmbiguityResolver {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ /**
+ * Resolves the explicitly stated binding priority of a method or returns the default value if no such
+ * explicit information can be found.
+ *
+ * @param bindingPriority The annotation of the method or {@code null} if no such annotation was found.
+ * @return The factual priority of the method under investigation.
+ */
+ private static int resolve(AnnotationDescription.Loadable<BindingPriority> bindingPriority) {
+ return bindingPriority == null
+ ? BindingPriority.DEFAULT
+ : bindingPriority.loadSilent().value();
+ }
+
+ @Override
+ public Resolution resolve(MethodDescription source,
+ MethodDelegationBinder.MethodBinding left,
+ MethodDelegationBinder.MethodBinding right) {
+ int leftPriority = resolve(left.getTarget().getDeclaredAnnotations().ofType(BindingPriority.class));
+ int rightPriority = resolve(right.getTarget().getDeclaredAnnotations().ofType(BindingPriority.class));
+ if (leftPriority == rightPriority) {
+ return Resolution.AMBIGUOUS;
+ } else if (leftPriority < rightPriority) {
+ return Resolution.RIGHT;
+ } else {
+ return Resolution.LEFT;
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/Default.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/Default.java
new file mode 100644
index 0000000..f8751e8
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/Default.java
@@ -0,0 +1,180 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.auxiliary.TypeProxy;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+
+import java.lang.annotation.*;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+
+/**
+ * Parameters that are annotated with this annotation are assigned an instance of an auxiliary proxy type that allows calling
+ * any default method of an interface of the instrumented type where the parameter type must be an interface that is
+ * directly implemented by the instrumented type. The generated proxy will directly implement the parameter's
+ * interface. If the interface of the annotation is not implemented by the instrumented type, the method with this
+ * parameter is not considered as a binding target.
+ *
+ * @see net.bytebuddy.implementation.MethodDelegation
+ * @see net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder
+ */
+ at Documented
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.PARAMETER)
+public @interface Default {
+
+ /**
+ * Determines if the generated proxy should be {@link java.io.Serializable}. If the annotated type
+ * already is serializable, such an explicit specification is not required.
+ *
+ * @return {@code true} if the generated proxy should be {@link java.io.Serializable}.
+ */
+ boolean serializableProxy() default false;
+
+ /**
+ * Determines the type that is implemented by the proxy. When this value is set to its default value
+ * {@code void}, the proxy is created as an instance of the parameter's type. It is <b>not</b> possible to
+ * set the value of this property to {@link net.bytebuddy.dynamic.TargetType} as a interface cannot implement itself.
+ *
+ * @return The type of the proxy or an indicator type, i.e. {@code void}.
+ */
+ Class<?> proxyType() default void.class;
+
+ /**
+ * A binder for the {@link net.bytebuddy.implementation.bind.annotation.Default} annotation.
+ */
+ enum Binder implements TargetMethodAnnotationDrivenBinder.ParameterBinder<Default> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ /**
+ * A method reference to the serializable proxy property.
+ */
+ private static final MethodDescription.InDefinedShape SERIALIZABLE_PROXY;
+
+ /**
+ * A method reference to the proxy type property.
+ */
+ private static final MethodDescription.InDefinedShape PROXY_TYPE;
+
+ /*
+ * Extracts method references of the default annotation.
+ */
+ static {
+ MethodList<MethodDescription.InDefinedShape> annotationProperties = new TypeDescription.ForLoadedType(Default.class).getDeclaredMethods();
+ SERIALIZABLE_PROXY = annotationProperties.filter(named("serializableProxy")).getOnly();
+ PROXY_TYPE = annotationProperties.filter(named("proxyType")).getOnly();
+ }
+
+ @Override
+ public Class<Default> getHandledType() {
+ return Default.class;
+ }
+
+ @Override
+ public MethodDelegationBinder.ParameterBinding<?> bind(AnnotationDescription.Loadable<Default> annotation,
+ MethodDescription source,
+ ParameterDescription target,
+ Implementation.Target implementationTarget,
+ Assigner assigner,
+ Assigner.Typing typing) {
+ TypeDescription proxyType = TypeLocator.ForType.of(annotation.getValue(PROXY_TYPE).resolve(TypeDescription.class)).resolve(target.getType());
+ if (!proxyType.isInterface()) {
+ throw new IllegalStateException(target + " uses the @Default annotation on an invalid type");
+ }
+ if (source.isStatic() || !implementationTarget.getInstrumentedType().getInterfaces().asErasures().contains(proxyType)) {
+ return MethodDelegationBinder.ParameterBinding.Illegal.INSTANCE;
+ } else {
+ return new MethodDelegationBinder.ParameterBinding.Anonymous(new TypeProxy.ForDefaultMethod(proxyType,
+ implementationTarget,
+ annotation.getValue(SERIALIZABLE_PROXY).resolve(Boolean.class)));
+ }
+ }
+
+ /**
+ * Locates the type which should be the base type of the created proxy.
+ */
+ protected interface TypeLocator {
+
+ /**
+ * Resolves the target type.
+ *
+ * @param parameterType The type of the target parameter.
+ * @return The proxy type.
+ */
+ TypeDescription resolve(TypeDescription.Generic parameterType);
+
+ /**
+ * A type locator that yields the target parameter's type.
+ */
+ enum ForParameterType implements TypeLocator {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public TypeDescription resolve(TypeDescription.Generic parameterType) {
+ return parameterType.asErasure();
+ }
+ }
+
+ /**
+ * A type locator that returns a given type.
+ */
+ @EqualsAndHashCode
+ class ForType implements TypeLocator {
+
+ /**
+ * The type to be returned upon resolution.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new type locator for a given type.
+ *
+ * @param typeDescription The type to be returned upon resolution.
+ */
+ protected ForType(TypeDescription typeDescription) {
+ this.typeDescription = typeDescription;
+ }
+
+ /**
+ * Resolves a type locator based upon an annotation value.
+ *
+ * @param typeDescription The annotation's value.
+ * @return The appropriate type locator.
+ */
+ protected static TypeLocator of(TypeDescription typeDescription) {
+ if (typeDescription.represents(void.class)) {
+ return ForParameterType.INSTANCE;
+ } else if (!typeDescription.isInterface()) {
+ throw new IllegalStateException("Cannot assign proxy to " + typeDescription);
+ } else {
+ return new ForType(typeDescription);
+ }
+ }
+
+ @Override
+ public TypeDescription resolve(TypeDescription.Generic parameterType) {
+ if (!typeDescription.isAssignableTo(parameterType.asErasure())) {
+ throw new IllegalStateException("Impossible to assign " + typeDescription + " to parameter of type " + parameterType);
+ }
+ return typeDescription;
+ }
+ }
+ }
+ }
+}
+
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/DefaultCall.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/DefaultCall.java
new file mode 100644
index 0000000..372b3d5
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/DefaultCall.java
@@ -0,0 +1,202 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.auxiliary.MethodCallProxy;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.constant.NullConstant;
+
+import java.lang.annotation.*;
+import java.util.concurrent.Callable;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+
+/**
+ * A parameter with this annotation is assigned a proxy for invoking a default method that fits the intercepted method.
+ * If no suitable default method for the intercepted method can be identified, the target method with the annotated
+ * parameter is considered to be unbindable.
+ *
+ * @see net.bytebuddy.implementation.MethodDelegation
+ * @see net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder
+ */
+ at Documented
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.PARAMETER)
+public @interface DefaultCall {
+
+ /**
+ * If this parameter is not explicitly set, a parameter with the
+ * {@link net.bytebuddy.implementation.bind.annotation.DefaultCall} is only bound to a
+ * source method if this source method directly represents an unambiguous, invokable default method. On the other
+ * hand, if a method is not defined unambiguously by an interface, not setting this parameter will exclude
+ * the target method with the annotated parameter from a binding to the source method.
+ * <p> </p>
+ * If this parameter is however set to an explicit interface type, a default method is always invoked on this given
+ * type as long as this type defines a method with a compatible signature. If this is not the case, the target
+ * method with the annotated parameter is not longer considered as a possible binding candidate of a source method.
+ *
+ * @return The target interface that a default method invocation is to be defined upon. If no such explicit target
+ * is set, this parameter should not be defined as the predefined {@code void} type encodes an implicit resolution.
+ */
+ Class<?> targetType() default void.class;
+
+ /**
+ * Determines if the generated proxy should be {@link java.io.Serializable}.
+ *
+ * @return {@code true} if the generated proxy should be {@link java.io.Serializable}.
+ */
+ boolean serializableProxy() default false;
+
+ /**
+ * Assigns {@code null} to the parameter if it is impossible to invoke the super method or a possible dominant default method, if permitted.
+ *
+ * @return {@code true} if a {@code null} constant should be assigned to this parameter in case that a legal binding is impossible.
+ */
+ boolean nullIfImpossible() default false;
+
+ /**
+ * A binder for handling the
+ * {@link net.bytebuddy.implementation.bind.annotation.DefaultCall}
+ * annotation.
+ *
+ * @see TargetMethodAnnotationDrivenBinder
+ */
+ enum Binder implements TargetMethodAnnotationDrivenBinder.ParameterBinder<DefaultCall> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ /**
+ * A reference to the target type method of the default call annotation.
+ */
+ private static final MethodDescription.InDefinedShape TARGET_TYPE;
+
+ /**
+ * A reference to the serializable proxy method of the default call annotation.
+ */
+ private static final MethodDescription.InDefinedShape SERIALIZABLE_PROXY;
+
+ /**
+ * A reference to the null if possible method of the default call annotation.
+ */
+ private static final MethodDescription.InDefinedShape NULL_IF_IMPOSSIBLE;
+
+ /*
+ * Looks up method constants of the default call annotation.
+ */
+ static {
+ MethodList<MethodDescription.InDefinedShape> annotationProperties = new TypeDescription.ForLoadedType(DefaultCall.class).getDeclaredMethods();
+ TARGET_TYPE = annotationProperties.filter(named("targetType")).getOnly();
+ SERIALIZABLE_PROXY = annotationProperties.filter(named("serializableProxy")).getOnly();
+ NULL_IF_IMPOSSIBLE = annotationProperties.filter(named("nullIfImpossible")).getOnly();
+ }
+
+ @Override
+ public Class<DefaultCall> getHandledType() {
+ return DefaultCall.class;
+ }
+
+ @Override
+ public MethodDelegationBinder.ParameterBinding<?> bind(AnnotationDescription.Loadable<DefaultCall> annotation,
+ MethodDescription source,
+ ParameterDescription target,
+ Implementation.Target implementationTarget,
+ Assigner assigner,
+ Assigner.Typing typing) {
+ TypeDescription targetType = target.getType().asErasure();
+ if (!targetType.represents(Runnable.class) && !targetType.represents(Callable.class) && !targetType.represents(Object.class)) {
+ throw new IllegalStateException("A default method call proxy can only be assigned to Runnable or Callable types: " + target);
+ } else if (source.isConstructor()) {
+ return annotation.getValue(NULL_IF_IMPOSSIBLE).resolve(Boolean.class)
+ ? new MethodDelegationBinder.ParameterBinding.Anonymous(NullConstant.INSTANCE)
+ : MethodDelegationBinder.ParameterBinding.Illegal.INSTANCE;
+ }
+ TypeDescription typeDescription = annotation.getValue(TARGET_TYPE).resolve(TypeDescription.class);
+ Implementation.SpecialMethodInvocation specialMethodInvocation = (typeDescription.represents(void.class)
+ ? DefaultMethodLocator.Implicit.INSTANCE
+ : new DefaultMethodLocator.Explicit(typeDescription)).resolve(implementationTarget, source);
+ StackManipulation stackManipulation;
+ if (specialMethodInvocation.isValid()) {
+ stackManipulation = new MethodCallProxy.AssignableSignatureCall(specialMethodInvocation, annotation.getValue(SERIALIZABLE_PROXY).resolve(Boolean.class));
+ } else if (annotation.loadSilent().nullIfImpossible()) {
+ stackManipulation = NullConstant.INSTANCE;
+ } else {
+ return MethodDelegationBinder.ParameterBinding.Illegal.INSTANCE;
+ }
+ return new MethodDelegationBinder.ParameterBinding.Anonymous(stackManipulation);
+ }
+
+ /**
+ * A default method locator is responsible for looking up a default method to a given source method.
+ */
+ protected interface DefaultMethodLocator {
+
+ /**
+ * Locates the correct default method to a given source method.
+ *
+ * @param implementationTarget The current implementation target.
+ * @param source The source method for which a default method should be looked up.
+ * @return A special method invocation of the default method or an illegal special method invocation,
+ * if no suitable invocation could be located.
+ */
+ Implementation.SpecialMethodInvocation resolve(Implementation.Target implementationTarget,
+ MethodDescription source);
+
+ /**
+ * An implicit default method locator that only permits the invocation of a default method if the source
+ * method itself represents a method that was defined on a default method interface.
+ */
+ enum Implicit implements DefaultMethodLocator {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Implementation.SpecialMethodInvocation resolve(Implementation.Target implementationTarget, MethodDescription source) {
+ return implementationTarget.invokeDefault(source.asSignatureToken());
+ }
+ }
+
+ /**
+ * An explicit default method locator attempts to look up a default method in the specified interface type.
+ */
+ @EqualsAndHashCode
+ class Explicit implements DefaultMethodLocator {
+
+ /**
+ * A description of the type on which the default method should be invoked.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new explicit default method locator.
+ *
+ * @param typeDescription The actual target interface as explicitly defined by
+ * {@link DefaultCall#targetType()}.
+ */
+ public Explicit(TypeDescription typeDescription) {
+ this.typeDescription = typeDescription;
+ }
+
+ @Override
+ public Implementation.SpecialMethodInvocation resolve(Implementation.Target implementationTarget, MethodDescription source) {
+ if (!typeDescription.isInterface()) {
+ throw new IllegalStateException(source + " method carries default method call parameter on non-interface type");
+ }
+ return implementationTarget.invokeDefault(source.asSignatureToken(), typeDescription);
+ }
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/DefaultMethod.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/DefaultMethod.java
new file mode 100644
index 0000000..9bff384
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/DefaultMethod.java
@@ -0,0 +1,230 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.TargetType;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.MethodAccessorFactory;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.constant.MethodConstant;
+import net.bytebuddy.implementation.bytecode.constant.NullConstant;
+import net.bytebuddy.implementation.bytecode.member.FieldAccess;
+import org.objectweb.asm.MethodVisitor;
+
+import java.lang.annotation.*;
+import java.lang.reflect.Method;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+
+/**
+ * A parameter with this annotation is assigned an instance of {@link Method} which invokes a default method implementation of this method.
+ * If such a method is not available, this annotation causes that this delegation target cannot be bound unless {@link DefaultMethod#nullIfImpossible()}
+ * is set to {@code true}. The method is declared as {@code public} and is invokable unless the instrumented type itself is not visible. Note that
+ * requesting such a method exposes the super method to reflection.
+ *
+ * @see net.bytebuddy.implementation.MethodDelegation
+ * @see net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder
+ */
+ at Documented
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.PARAMETER)
+public @interface DefaultMethod {
+
+ /**
+ * Indicates if the instance assigned to this parameter should be stored in a static field for reuse.
+ *
+ * @return {@code true} if this method instance should be cached.
+ */
+ boolean cached() default true;
+
+ /**
+ * Specifies an explicit type that declares the default method to invoke.
+ *
+ * @return The type declaring the method to invoke or {@link TargetType} to indicate that the instrumented method declared the method.
+ */
+ Class<?> targetType() default void.class;
+
+ /**
+ * Indicates that {@code null} should be assigned to this parameter if no default method is invokable.
+ *
+ * @return {@code true} if {@code null} should be assigned if no valid method can be assigned.
+ */
+ boolean nullIfImpossible() default false;
+
+ /**
+ * A binder for the {@link DefaultMethod} annotation.
+ */
+ enum Binder implements TargetMethodAnnotationDrivenBinder.ParameterBinder<DefaultMethod> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ /**
+ * The {@link DefaultMethod#cached()} property.
+ */
+ private static final MethodDescription.InDefinedShape CACHED;
+
+ /**
+ * The {@link DefaultMethod#targetType()} property.
+ */
+ private static final MethodDescription.InDefinedShape TARGET_TYPE;
+
+ /**
+ * The {@link DefaultMethod#nullIfImpossible()} property.
+ */
+ private static final MethodDescription.InDefinedShape NULL_IF_IMPOSSIBLE;
+
+ /*
+ * Locates method constants for properties of the default method annotation.
+ */
+ static {
+ MethodList<MethodDescription.InDefinedShape> methodList = new TypeDescription.ForLoadedType(DefaultMethod.class).getDeclaredMethods();
+ CACHED = methodList.filter(named("cached")).getOnly();
+ TARGET_TYPE = methodList.filter(named("targetType")).getOnly();
+ NULL_IF_IMPOSSIBLE = methodList.filter(named("nullIfImpossible")).getOnly();
+ }
+
+ @Override
+ public Class<DefaultMethod> getHandledType() {
+ return DefaultMethod.class;
+ }
+
+ @Override
+ public MethodDelegationBinder.ParameterBinding<?> bind(final AnnotationDescription.Loadable<DefaultMethod> annotation,
+ MethodDescription source,
+ ParameterDescription target,
+ Implementation.Target implementationTarget,
+ Assigner assigner,
+ Assigner.Typing typing) {
+ if (!target.getType().asErasure().isAssignableFrom(Method.class)) {
+ throw new IllegalStateException("Cannot assign Method type to " + target);
+ } else if (source.isMethod()) {
+ TypeDescription typeDescription = annotation.getValue(TARGET_TYPE).resolve(TypeDescription.class);
+ Implementation.SpecialMethodInvocation specialMethodInvocation = (typeDescription.represents(void.class)
+ ? MethodLocator.ForImplicitType.INSTANCE
+ : new MethodLocator.ForExplicitType(typeDescription)).resolve(implementationTarget, source);
+ if (specialMethodInvocation.isValid()) {
+ return new MethodDelegationBinder.ParameterBinding.Anonymous(new DelegationMethod(specialMethodInvocation, annotation.getValue(CACHED).resolve(Boolean.class)));
+ } else if (annotation.getValue(NULL_IF_IMPOSSIBLE).resolve(Boolean.class)) {
+ return new MethodDelegationBinder.ParameterBinding.Anonymous(NullConstant.INSTANCE);
+ } else {
+ return MethodDelegationBinder.ParameterBinding.Illegal.INSTANCE;
+ }
+ } else if (annotation.getValue(NULL_IF_IMPOSSIBLE).resolve(Boolean.class)) {
+ return new MethodDelegationBinder.ParameterBinding.Anonymous(NullConstant.INSTANCE);
+ } else {
+ return MethodDelegationBinder.ParameterBinding.Illegal.INSTANCE;
+ }
+ }
+
+ /**
+ * A method locator is responsible for creating the super method call.
+ */
+ protected interface MethodLocator {
+
+ /**
+ * Resolves the special method invocation to this target.
+ *
+ * @param implementationTarget The implementation target.
+ * @param source The method being instrumented.
+ * @return A special method invocation that represents the super call of this binding.
+ */
+ Implementation.SpecialMethodInvocation resolve(Implementation.Target implementationTarget, MethodDescription source);
+
+ /**
+ * A method locator for an implicit target type.
+ */
+ enum ForImplicitType implements MethodLocator {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Implementation.SpecialMethodInvocation resolve(Implementation.Target implementationTarget, MethodDescription source) {
+ return implementationTarget.invokeDefault(source.asSignatureToken());
+ }
+ }
+
+ /**
+ * A method locator for an explicit target type.
+ */
+ @EqualsAndHashCode
+ class ForExplicitType implements MethodLocator {
+
+ /**
+ * The explicit target type.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a method locator for an explicit target.
+ *
+ * @param typeDescription The explicit target type.
+ */
+ protected ForExplicitType(TypeDescription typeDescription) {
+ this.typeDescription = typeDescription;
+ }
+
+ @Override
+ public Implementation.SpecialMethodInvocation resolve(Implementation.Target implementationTarget, MethodDescription source) {
+ if (!typeDescription.isInterface()) {
+ throw new IllegalStateException(source + " method carries default method call parameter on non-interface type");
+ }
+ return implementationTarget.invokeDefault(source.asSignatureToken(), TargetType.resolve(typeDescription, implementationTarget.getInstrumentedType()));
+ }
+ }
+ }
+
+ /**
+ * Loads the delegation method constant onto the stack.
+ */
+ @EqualsAndHashCode
+ protected static class DelegationMethod implements StackManipulation {
+
+ /**
+ * The special method invocation that represents the super method call.
+ */
+ private final Implementation.SpecialMethodInvocation specialMethodInvocation;
+
+ /**
+ * {@code true} if the method constant should be cached.
+ */
+ private final boolean cached;
+
+ /**
+ * Creates a new delegation method.
+ *
+ * @param specialMethodInvocation The special method invocation that represents the super method call.
+ * @param cached {@code true} if the method constant should be cached.
+ */
+ protected DelegationMethod(Implementation.SpecialMethodInvocation specialMethodInvocation, boolean cached) {
+ this.specialMethodInvocation = specialMethodInvocation;
+ this.cached = cached;
+ }
+
+ @Override
+ public boolean isValid() {
+ return specialMethodInvocation.isValid();
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ StackManipulation stackManipulation = MethodConstant.forMethod(implementationContext.registerAccessorFor(specialMethodInvocation,
+ MethodAccessorFactory.AccessType.PUBLIC));
+ return (cached
+ ? FieldAccess.forField(implementationContext.cache(stackManipulation, new TypeDescription.ForLoadedType(Method.class))).read()
+ : stackManipulation).apply(methodVisitor, implementationContext);
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/Empty.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/Empty.java
new file mode 100644
index 0000000..8bb37a7
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/Empty.java
@@ -0,0 +1,50 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.constant.DefaultValue;
+
+import java.lang.annotation.*;
+
+/**
+ * Binds the parameter type's default value to the annotated parameter, i.e. {@code null} or a numeric value
+ * representing zero.
+ *
+ * @see net.bytebuddy.implementation.MethodDelegation
+ * @see net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder
+ */
+ at Documented
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.PARAMETER)
+public @interface Empty {
+
+ /**
+ * A binder for the {@link net.bytebuddy.implementation.bind.annotation.Empty} annotation.
+ */
+ enum Binder implements TargetMethodAnnotationDrivenBinder.ParameterBinder<Empty> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Class<Empty> getHandledType() {
+ return Empty.class;
+ }
+
+ @Override
+ public MethodDelegationBinder.ParameterBinding<?> bind(AnnotationDescription.Loadable<Empty> annotation,
+ MethodDescription source,
+ ParameterDescription target,
+ Implementation.Target implementationTarget,
+ Assigner assigner,
+ Assigner.Typing typing) {
+ return new MethodDelegationBinder.ParameterBinding.Anonymous(DefaultValue.of(target.getType().asErasure()));
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/FieldProxy.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/FieldProxy.java
new file mode 100644
index 0000000..8ad9231
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/FieldProxy.java
@@ -0,0 +1,1040 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
+import net.bytebuddy.implementation.ExceptionMethod;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.MethodAccessorFactory;
+import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.implementation.bytecode.Duplication;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.TypeCreation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.member.FieldAccess;
+import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
+import net.bytebuddy.implementation.bytecode.member.MethodReturn;
+import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.io.Serializable;
+import java.lang.annotation.*;
+import java.util.Collections;
+
+import static net.bytebuddy.matcher.ElementMatchers.*;
+
+/**
+ * Using this annotation it is possible to access fields by getter and setter types. Before this annotation can be
+ * used, it needs to be installed with two types. The getter type must be defined in a single-method interface
+ * with a single method that returns an {@link java.lang.Object} type and takes no arguments. The setter interface
+ * must similarly return {@code void} and take a single {@link java.lang.Object} argument. After installing these
+ * interfaces with the {@link FieldProxy.Binder}, this
+ * binder needs to be registered with a {@link net.bytebuddy.implementation.MethodDelegation} before it can be used.
+ *
+ * @see net.bytebuddy.implementation.MethodDelegation
+ * @see net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder
+ */
+ at Documented
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.PARAMETER)
+public @interface FieldProxy {
+
+ /**
+ * Determines if the proxy should be serializable.
+ *
+ * @return {@code true} if the proxy should be serializable.
+ */
+ boolean serializableProxy() default false;
+
+ /**
+ * Determines the name of the field that is to be accessed. If this property is not set, a field name is inferred
+ * by the intercepted method after the Java beans naming conventions.
+ *
+ * @return The name of the field to be accessed.
+ */
+ String value() default TargetMethodAnnotationDrivenBinder.ParameterBinder.ForFieldBinding.BEAN_PROPERTY;
+
+ /**
+ * Determines which type defines the field that is to be accessed. If this property is not set, the most field
+ * that is defined highest in the type hierarchy is accessed.
+ *
+ * @return The type that defines the accessed field.
+ */
+ Class<?> declaringType() default void.class;
+
+ /**
+ * A binder for the {@link FieldProxy} annotation.
+ */
+ @EqualsAndHashCode(callSuper = false)
+ class Binder extends TargetMethodAnnotationDrivenBinder.ParameterBinder.ForFieldBinding<FieldProxy> {
+
+ /**
+ * A reference to the method that declares the field annotation's defining type property.
+ */
+ private static final MethodDescription.InDefinedShape DECLARING_TYPE;
+
+ /**
+ * A reference to the method that declares the field annotation's field name property.
+ */
+ private static final MethodDescription.InDefinedShape FIELD_NAME;
+
+ /**
+ * A reference to the method that declares the field annotation's serializable proxy property.
+ */
+ private static final MethodDescription.InDefinedShape SERIALIZABLE_PROXY;
+
+ /*
+ * Fetches a reference to all annotation properties.
+ */
+ static {
+ MethodList<MethodDescription.InDefinedShape> methodList = new TypeDescription.ForLoadedType(FieldProxy.class).getDeclaredMethods();
+ DECLARING_TYPE = methodList.filter(named("declaringType")).getOnly();
+ FIELD_NAME = methodList.filter(named("value")).getOnly();
+ SERIALIZABLE_PROXY = methodList.filter(named("serializableProxy")).getOnly();
+ }
+
+ /**
+ * Creates a binder by installing a single proxy type where annotating a parameter with {@link FieldProxy} allows
+ * getting and setting values for a given field.
+ *
+ * @param type A type which declares exactly one abstract getter and an abstract setter for the {@link Object}
+ * type. The type is allowed to be generic.
+ * @return A binder for the {@link FieldProxy} annotation.
+ */
+ public static TargetMethodAnnotationDrivenBinder.ParameterBinder<FieldProxy> install(Class<?> type) {
+ return install(new TypeDescription.ForLoadedType(type));
+ }
+
+ /**
+ * Creates a binder by installing a single proxy type where annotating a parameter with {@link FieldProxy} allows
+ * getting and setting values for a given field.
+ *
+ * @param typeDescription A type which declares exactly one abstract getter and an abstract setter for the {@link Object}
+ * type. The type is allowed to be generic.
+ * @return A binder for the {@link FieldProxy} annotation.
+ */
+ public static TargetMethodAnnotationDrivenBinder.ParameterBinder<FieldProxy> install(TypeDescription typeDescription) {
+ if (!typeDescription.isInterface()) {
+ throw new IllegalArgumentException(typeDescription + " is not an interface");
+ } else if (!typeDescription.getInterfaces().isEmpty()) {
+ throw new IllegalArgumentException(typeDescription + " must not extend other interfaces");
+ } else if (!typeDescription.isPublic()) {
+ throw new IllegalArgumentException(typeDescription + " is not public");
+ }
+ MethodList<MethodDescription.InDefinedShape> methodCandidates = typeDescription.getDeclaredMethods().filter(isAbstract());
+ if (methodCandidates.size() != 2) {
+ throw new IllegalArgumentException(typeDescription + " does not declare exactly two non-abstract methods");
+ }
+ MethodList<MethodDescription.InDefinedShape> getterCandidates = methodCandidates.filter(isGetter(Object.class));
+ if (getterCandidates.size() != 1) {
+ throw new IllegalArgumentException(typeDescription + " does not declare a getter with an Object type");
+ }
+ MethodList<MethodDescription.InDefinedShape> setterCandidates = methodCandidates.filter(isSetter(Object.class));
+ if (setterCandidates.size() != 1) {
+ throw new IllegalArgumentException(typeDescription + " does not declare a setter with an Object type");
+ }
+ return new Binder(typeDescription, getterCandidates.getOnly(), setterCandidates.getOnly());
+ }
+
+ /**
+ * Creates a binder by installing two proxy types which are implemented by this binder if a field getter
+ * or a field setter is requested by using the
+ * {@link FieldProxy} annotation.
+ *
+ * @param getterType The type which should be used for getter proxies. The type must
+ * represent an interface which defines a single method which returns an
+ * {@link java.lang.Object} return type and does not take any arguments. The use of generics
+ * is permitted.
+ * @param setterType The type which should be uses for setter proxies. The type must
+ * represent an interface which defines a single method which returns {@code void}
+ * and takes a single {@link java.lang.Object}-typed argument. The use of generics
+ * is permitted.
+ * @return A binder for the {@link FieldProxy} annotation.
+ */
+ public static TargetMethodAnnotationDrivenBinder.ParameterBinder<FieldProxy> install(Class<?> getterType, Class<?> setterType) {
+ return install(new TypeDescription.ForLoadedType(getterType), new TypeDescription.ForLoadedType(setterType));
+ }
+
+ /**
+ * Creates a binder by installing two proxy types which are implemented by this binder if a field getter
+ * or a field setter is requested by using the
+ * {@link FieldProxy} annotation.
+ *
+ * @param getterType The type which should be used for getter proxies. The type must
+ * represent an interface which defines a single method which returns an
+ * {@link java.lang.Object} return type and does not take any arguments. The use of generics
+ * is permitted.
+ * @param setterType The type which should be uses for setter proxies. The type must
+ * represent an interface which defines a single method which returns {@code void}
+ * and takes a single {@link java.lang.Object}-typed argument. The use of generics
+ * is permitted.
+ * @return A binder for the {@link FieldProxy} annotation.
+ */
+ public static TargetMethodAnnotationDrivenBinder.ParameterBinder<FieldProxy> install(TypeDescription getterType, TypeDescription setterType) {
+ MethodDescription.InDefinedShape getterMethod = onlyMethod(getterType);
+ if (!getterMethod.getReturnType().asErasure().represents(Object.class)) {
+ throw new IllegalArgumentException(getterMethod + " must take a single Object-typed parameter");
+ } else if (getterMethod.getParameters().size() != 0) {
+ throw new IllegalArgumentException(getterMethod + " must not declare parameters");
+ }
+ MethodDescription.InDefinedShape setterMethod = onlyMethod(setterType);
+ if (!setterMethod.getReturnType().asErasure().represents(void.class)) {
+ throw new IllegalArgumentException(setterMethod + " must return void");
+ } else if (setterMethod.getParameters().size() != 1 || !setterMethod.getParameters().get(0).getType().asErasure().represents(Object.class)) {
+ throw new IllegalArgumentException(setterMethod + " must declare a single Object-typed parameters");
+ }
+ return new Binder(getterMethod, setterMethod);
+ }
+
+ /**
+ * Extracts the only method from a given type description which is validated for the required properties for
+ * using the type as a proxy base type.
+ *
+ * @param typeDescription The type description to evaluate.
+ * @return The only method which was found to be compatible to the proxy requirements.
+ */
+ private static MethodDescription.InDefinedShape onlyMethod(TypeDescription typeDescription) {
+ if (!typeDescription.isInterface()) {
+ throw new IllegalArgumentException(typeDescription + " is not an interface");
+ } else if (!typeDescription.getInterfaces().isEmpty()) {
+ throw new IllegalArgumentException(typeDescription + " must not extend other interfaces");
+ } else if (!typeDescription.isPublic()) {
+ throw new IllegalArgumentException(typeDescription + " is not public");
+ }
+ MethodList<MethodDescription.InDefinedShape> methodCandidates = typeDescription.getDeclaredMethods().filter(isAbstract());
+ if (methodCandidates.size() != 1) {
+ throw new IllegalArgumentException(typeDescription + " must declare exactly one abstract method");
+ }
+ return methodCandidates.getOnly();
+ }
+
+ /**
+ * The field resolver factory to apply by this binder.
+ */
+ private final FieldResolver.Factory fieldResolverFactory;
+
+ /**
+ * Creates a new binder for a {@link FieldProxy} in simplex mode.
+ *
+ * @param getterMethod The getter method.
+ * @param setterMethod The setter method.
+ */
+ protected Binder(MethodDescription.InDefinedShape getterMethod, MethodDescription.InDefinedShape setterMethod) {
+ this(new FieldResolver.Factory.Simplex(getterMethod, setterMethod));
+ }
+
+ /**
+ * Creates a new binder for a {@link FieldProxy} in duplex mode.
+ *
+ * @param proxyType The proxy type.
+ * @param getterMethod The getter method.
+ * @param setterMethod The setter method.
+ */
+ protected Binder(TypeDescription proxyType, MethodDescription.InDefinedShape getterMethod, MethodDescription.InDefinedShape setterMethod) {
+ this(new FieldResolver.Factory.Duplex(proxyType, getterMethod, setterMethod));
+ }
+
+ /**
+ * Creates a new binder for a {@link FieldProxy}.
+ *
+ * @param fieldResolverFactory The field resolver factory to apply by this binder.
+ */
+ protected Binder(FieldResolver.Factory fieldResolverFactory) {
+ this.fieldResolverFactory = fieldResolverFactory;
+ }
+
+ @Override
+ public Class<FieldProxy> getHandledType() {
+ return FieldProxy.class;
+ }
+
+ @Override
+ protected String fieldName(AnnotationDescription.Loadable<FieldProxy> annotation) {
+ return annotation.getValue(FIELD_NAME).resolve(String.class);
+ }
+
+ @Override
+ protected TypeDescription declaringType(AnnotationDescription.Loadable<FieldProxy> annotation) {
+ return annotation.getValue(DECLARING_TYPE).resolve(TypeDescription.class);
+ }
+
+ @Override
+ protected MethodDelegationBinder.ParameterBinding<?> bind(FieldDescription fieldDescription,
+ AnnotationDescription.Loadable<FieldProxy> annotation,
+ MethodDescription source,
+ ParameterDescription target,
+ Implementation.Target implementationTarget,
+ Assigner assigner) {
+ FieldResolver fieldResolver = fieldResolverFactory.resolve(target.getType().asErasure(), fieldDescription);
+ if (fieldResolver.isResolved()) {
+ return new MethodDelegationBinder.ParameterBinding.Anonymous(new AccessorProxy(fieldDescription,
+ implementationTarget.getInstrumentedType(),
+ fieldResolver,
+ assigner,
+ annotation.getValue(SERIALIZABLE_PROXY).resolve(Boolean.class)));
+ } else {
+ return MethodDelegationBinder.ParameterBinding.Illegal.INSTANCE;
+ }
+ }
+
+ /**
+ * A resolver for creating an instrumentation for a field access.
+ */
+ protected interface FieldResolver {
+
+ /**
+ * Returns {@code true} if the field access can be establised.
+ *
+ * @return {@code true} if the field access can be establised.
+ */
+ boolean isResolved();
+
+ /**
+ * Returns the type of the field access proxy.
+ *
+ * @return The type of the field access proxy.
+ */
+ TypeDescription getProxyType();
+
+ /**
+ * Applies this field resolver to a dynamic type.
+ *
+ * @param builder The dynamic type builder to use.
+ * @param fieldDescription The accessed field.
+ * @param assigner The assigner to use.
+ * @param methodAccessorFactory The method accessor factory to use.
+ * @return The builder for creating the field accessor proxy type.
+ */
+ DynamicType.Builder<?> apply(DynamicType.Builder<?> builder,
+ FieldDescription fieldDescription,
+ Assigner assigner,
+ MethodAccessorFactory methodAccessorFactory);
+
+ /**
+ * A factory for creating a field resolver.
+ */
+ interface Factory {
+
+ /**
+ * Creates a field resolver.
+ *
+ * @param parameterType The type of the annotated parameter.
+ * @param fieldDescription The field being proxied.
+ * @return An appropriate field resolver.
+ */
+ FieldResolver resolve(TypeDescription parameterType, FieldDescription fieldDescription);
+
+ /**
+ * A duplex factory for a type that both sets and gets a field value.
+ */
+ @EqualsAndHashCode
+ class Duplex implements Factory {
+
+ /**
+ * The type of the accessor proxy.
+ */
+ private final TypeDescription proxyType;
+
+ /**
+ * The getter method.
+ */
+ private final MethodDescription.InDefinedShape getterMethod;
+
+ /**
+ * The setter method.
+ */
+ private final MethodDescription.InDefinedShape setterMethod;
+
+ /**
+ * Creates a new duplex factory.
+ *
+ * @param proxyType The type of the accessor proxy.
+ * @param getterMethod The getter method.
+ * @param setterMethod The setter method.
+ */
+ protected Duplex(TypeDescription proxyType,
+ MethodDescription.InDefinedShape getterMethod,
+ MethodDescription.InDefinedShape setterMethod) {
+ this.proxyType = proxyType;
+ this.getterMethod = getterMethod;
+ this.setterMethod = setterMethod;
+ }
+
+ @Override
+ public FieldResolver resolve(TypeDescription parameterType, FieldDescription fieldDescription) {
+ if (parameterType.equals(proxyType)) {
+ return new ForGetterSetterPair(proxyType, getterMethod, setterMethod);
+ } else {
+ throw new IllegalStateException("Cannot use @FieldProxy on a non-installed type");
+ }
+ }
+ }
+
+ /**
+ * A simplex factory where field getters and setters both have their own type.
+ */
+ @EqualsAndHashCode
+ class Simplex implements Factory {
+
+ /**
+ * The getter method.
+ */
+ private final MethodDescription.InDefinedShape getterMethod;
+
+ /**
+ * The setter method.
+ */
+ private final MethodDescription.InDefinedShape setterMethod;
+
+ /**
+ * Creates a simplex factory.
+ *
+ * @param getterMethod The getter method.
+ * @param setterMethod The setter method.
+ */
+ protected Simplex(MethodDescription.InDefinedShape getterMethod, MethodDescription.InDefinedShape setterMethod) {
+ this.getterMethod = getterMethod;
+ this.setterMethod = setterMethod;
+ }
+
+ @Override
+ public FieldResolver resolve(TypeDescription parameterType, FieldDescription fieldDescription) {
+ if (parameterType.equals(getterMethod.getDeclaringType())) {
+ return new ForGetter(getterMethod);
+ } else if (parameterType.equals(setterMethod.getDeclaringType())) {
+ return fieldDescription.isFinal()
+ ? Unresolved.INSTANCE
+ : new ForSetter(setterMethod);
+ } else {
+ throw new IllegalStateException("Cannot use @FieldProxy on a non-installed type");
+ }
+ }
+ }
+ }
+
+ /**
+ * An unresolved field resolver.
+ */
+ enum Unresolved implements FieldResolver {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public boolean isResolved() {
+ return false;
+ }
+
+ @Override
+ public TypeDescription getProxyType() {
+ throw new IllegalStateException("Cannot read type for unresolved field resolver");
+ }
+
+ @Override
+ public DynamicType.Builder<?> apply(DynamicType.Builder<?> builder,
+ FieldDescription fieldDescription,
+ Assigner assigner,
+ MethodAccessorFactory methodAccessorFactory) {
+ throw new IllegalStateException("Cannot apply unresolved field resolver");
+ }
+ }
+
+ /**
+ * A field resolver for a getter accessor.
+ */
+ @EqualsAndHashCode
+ class ForGetter implements FieldResolver {
+
+ /**
+ * The getter method.
+ */
+ private final MethodDescription.InDefinedShape getterMethod;
+
+ /**
+ * Creates a new getter field resolver.
+ *
+ * @param getterMethod The getter method.
+ */
+ protected ForGetter(MethodDescription.InDefinedShape getterMethod) {
+ this.getterMethod = getterMethod;
+ }
+
+ @Override
+ public boolean isResolved() {
+ return true;
+ }
+
+ @Override
+ public TypeDescription getProxyType() {
+ return getterMethod.getDeclaringType();
+ }
+
+ @Override
+ public DynamicType.Builder<?> apply(DynamicType.Builder<?> builder,
+ FieldDescription fieldDescription,
+ Assigner assigner,
+ MethodAccessorFactory methodAccessorFactory) {
+ return builder.method(definedMethod(is(getterMethod))).intercept(new FieldGetter(fieldDescription, assigner, methodAccessorFactory));
+ }
+ }
+
+ /**
+ * A field resolver for a setter accessor.
+ */
+ @EqualsAndHashCode
+ class ForSetter implements FieldResolver {
+
+ /**
+ * The setter method.
+ */
+ private final MethodDescription.InDefinedShape setterMethod;
+
+ /**
+ * Creates a new field resolver for a setter accessor.
+ *
+ * @param setterMethod The setter method.
+ */
+ protected ForSetter(MethodDescription.InDefinedShape setterMethod) {
+ this.setterMethod = setterMethod;
+ }
+
+ @Override
+ public boolean isResolved() {
+ return true;
+ }
+
+ @Override
+ public TypeDescription getProxyType() {
+ return setterMethod.getDeclaringType();
+ }
+
+ @Override
+ public DynamicType.Builder<?> apply(DynamicType.Builder<?> builder,
+ FieldDescription fieldDescription,
+ Assigner assigner,
+ MethodAccessorFactory methodAccessorFactory) {
+ return builder.method(is(setterMethod)).intercept(new FieldSetter(fieldDescription, assigner, methodAccessorFactory));
+ }
+ }
+
+ /**
+ * A field resolver for an accessor that both gets and sets a field value.
+ */
+ @EqualsAndHashCode
+ class ForGetterSetterPair implements FieldResolver {
+
+ /**
+ * The type of the accessor proxy.
+ */
+ private final TypeDescription proxyType;
+
+ /**
+ * The getter method.
+ */
+ private final MethodDescription.InDefinedShape getterMethod;
+
+ /**
+ * The setter method.
+ */
+ private final MethodDescription.InDefinedShape setterMethod;
+
+ /**
+ * Creates a new field resolver for an accessor that both gets and sets a field value.
+ *
+ * @param proxyType The type of the accessor proxy.
+ * @param getterMethod The getter method.
+ * @param setterMethod The setter method.
+ */
+ protected ForGetterSetterPair(TypeDescription proxyType,
+ MethodDescription.InDefinedShape getterMethod,
+ MethodDescription.InDefinedShape setterMethod) {
+ this.proxyType = proxyType;
+ this.getterMethod = getterMethod;
+ this.setterMethod = setterMethod;
+ }
+
+ @Override
+ public boolean isResolved() {
+ return true;
+ }
+
+ @Override
+ public TypeDescription getProxyType() {
+ return proxyType;
+ }
+
+ @Override
+ public DynamicType.Builder<?> apply(DynamicType.Builder<?> builder,
+ FieldDescription fieldDescription,
+ Assigner assigner,
+ MethodAccessorFactory methodAccessorFactory) {
+ return builder
+ .method(is(getterMethod)).intercept(new FieldGetter(fieldDescription, assigner, methodAccessorFactory))
+ .method(is(setterMethod)).intercept(fieldDescription.isFinal()
+ ? ExceptionMethod.throwing(UnsupportedOperationException.class, "Cannot set final field " + fieldDescription)
+ : new FieldSetter(fieldDescription, assigner, methodAccessorFactory));
+ }
+ }
+ }
+
+ /**
+ * Represents an implementation for implementing a proxy type constructor when a static field is accessed.
+ */
+ protected enum StaticFieldConstructor implements Implementation {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ /**
+ * A reference of the {@link Object} type default constructor.
+ */
+ private final MethodDescription objectTypeDefaultConstructor;
+
+ /**
+ * Creates the constructor call singleton.
+ */
+ StaticFieldConstructor() {
+ objectTypeDefaultConstructor = TypeDescription.OBJECT.getDeclaredMethods().filter(isConstructor()).getOnly();
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return new ByteCodeAppender.Simple(MethodVariableAccess.loadThis(), MethodInvocation.invoke(objectTypeDefaultConstructor), MethodReturn.VOID);
+ }
+ }
+
+ /**
+ * Represents an implementation for implementing a proxy type constructor when a non-static field is accessed.
+ */
+ @EqualsAndHashCode
+ protected static class InstanceFieldConstructor implements Implementation {
+
+ /**
+ * The instrumented type from which a field is to be accessed.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * Creates a new implementation for implementing a field accessor proxy's constructor when accessing
+ * a non-static field.
+ *
+ * @param instrumentedType The instrumented type from which a field is to be accessed.
+ */
+ protected InstanceFieldConstructor(TypeDescription instrumentedType) {
+ this.instrumentedType = instrumentedType;
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType.withField(new FieldDescription.Token(AccessorProxy.FIELD_NAME,
+ Opcodes.ACC_FINAL | Opcodes.ACC_PRIVATE,
+ this.instrumentedType.asGenericType()));
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return new Appender(implementationTarget);
+ }
+
+ /**
+ * An appender for implementing an
+ * {@link FieldProxy.Binder.InstanceFieldConstructor}.
+ */
+ @EqualsAndHashCode
+ protected static class Appender implements ByteCodeAppender {
+
+ /**
+ * The field to be set within the constructor.
+ */
+ private final FieldDescription fieldDescription;
+
+ /**
+ * Creates a new appender.
+ *
+ * @param implementationTarget The implementation target of the current implementation.
+ */
+ protected Appender(Target implementationTarget) {
+ fieldDescription = implementationTarget.getInstrumentedType()
+ .getDeclaredFields()
+ .filter((named(AccessorProxy.FIELD_NAME)))
+ .getOnly();
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor,
+ Context implementationContext,
+ MethodDescription instrumentedMethod) {
+ StackManipulation.Size stackSize = new StackManipulation.Compound(
+ MethodVariableAccess.loadThis(),
+ MethodInvocation.invoke(StaticFieldConstructor.INSTANCE.objectTypeDefaultConstructor),
+ MethodVariableAccess.allArgumentsOf(instrumentedMethod.asDefined()).prependThisReference(),
+ FieldAccess.forField(fieldDescription).write(),
+ MethodReturn.VOID
+ ).apply(methodVisitor, implementationContext);
+ return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+ }
+ }
+
+ /**
+ * Implementation for a getter method.
+ */
+ @EqualsAndHashCode
+ protected static class FieldGetter implements Implementation {
+
+ /**
+ * The field that is being accessed.
+ */
+ private final FieldDescription fieldDescription;
+
+ /**
+ * The assigner to use.
+ */
+ private final Assigner assigner;
+
+ /**
+ * The accessed type's method accessor factory.
+ */
+ private final MethodAccessorFactory methodAccessorFactory;
+
+ /**
+ * Creates a new getter implementation.
+ *
+ * @param fieldDescription The field that is being accessed.
+ * @param assigner The assigner to use.
+ * @param methodAccessorFactory The accessed type's method accessor factory.
+ */
+ protected FieldGetter(FieldDescription fieldDescription,
+ Assigner assigner,
+ MethodAccessorFactory methodAccessorFactory) {
+ this.fieldDescription = fieldDescription;
+ this.assigner = assigner;
+ this.methodAccessorFactory = methodAccessorFactory;
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return new Appender(implementationTarget);
+ }
+
+ /**
+ * A byte code appender for a getter method.
+ */
+ protected class Appender implements ByteCodeAppender {
+
+ /**
+ * The generated accessor type.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new appender for a setter method.
+ *
+ * @param implementationTarget The implementation target of the current instrumentation.
+ */
+ protected Appender(Target implementationTarget) {
+ typeDescription = implementationTarget.getInstrumentedType();
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor,
+ Context implementationContext,
+ MethodDescription instrumentedMethod) {
+ MethodDescription getterMethod = methodAccessorFactory.registerGetterFor(fieldDescription, MethodAccessorFactory.AccessType.DEFAULT);
+ StackManipulation.Size stackSize = new StackManipulation.Compound(
+ fieldDescription.isStatic()
+ ? StackManipulation.Trivial.INSTANCE
+ : new StackManipulation.Compound(
+ MethodVariableAccess.loadThis(),
+ FieldAccess.forField(typeDescription.getDeclaredFields().filter((named(AccessorProxy.FIELD_NAME))).getOnly()).read()),
+ MethodInvocation.invoke(getterMethod),
+ assigner.assign(getterMethod.getReturnType(), instrumentedMethod.getReturnType(), Assigner.Typing.DYNAMIC),
+ MethodReturn.of(instrumentedMethod.getReturnType().asErasure())
+ ).apply(methodVisitor, implementationContext);
+ return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private FieldGetter getOuter() {
+ return FieldGetter.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object object) {
+ if (this == object) return true;
+ if (object == null || getClass() != object.getClass()) return false;
+ Appender appender = (Appender) object;
+ return typeDescription.equals(appender.typeDescription) && FieldGetter.this.equals(appender.getOuter());
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return typeDescription.hashCode() + 31 * FieldGetter.this.hashCode();
+ }
+ }
+ }
+
+ /**
+ * Implementation for a setter method.
+ */
+ @EqualsAndHashCode
+ protected static class FieldSetter implements Implementation {
+
+ /**
+ * The field that is being accessed.
+ */
+ private final FieldDescription fieldDescription;
+
+ /**
+ * The assigner to use.
+ */
+ private final Assigner assigner;
+
+ /**
+ * The accessed type's method accessor factory.
+ */
+ private final MethodAccessorFactory methodAccessorFactory;
+
+ /**
+ * Creates a new setter implementation.
+ *
+ * @param fieldDescription The field that is being accessed.
+ * @param assigner The assigner to use.
+ * @param methodAccessorFactory The accessed type's method accessor factory.
+ */
+ protected FieldSetter(FieldDescription fieldDescription,
+ Assigner assigner,
+ MethodAccessorFactory methodAccessorFactory) {
+ this.fieldDescription = fieldDescription;
+ this.assigner = assigner;
+ this.methodAccessorFactory = methodAccessorFactory;
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return new Appender(implementationTarget);
+ }
+
+ /**
+ * A byte code appender for a setter method.
+ */
+ protected class Appender implements ByteCodeAppender {
+
+ /**
+ * The generated accessor type.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new appender for a setter method.
+ *
+ * @param implementationTarget The implementation target of the current instrumentation.
+ */
+ protected Appender(Target implementationTarget) {
+ typeDescription = implementationTarget.getInstrumentedType();
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor,
+ Context implementationContext,
+ MethodDescription instrumentedMethod) {
+ TypeDescription.Generic parameterType = instrumentedMethod.getParameters().get(0).getType();
+ MethodDescription setterMethod = methodAccessorFactory.registerSetterFor(fieldDescription, MethodAccessorFactory.AccessType.DEFAULT);
+ StackManipulation.Size stackSize = new StackManipulation.Compound(
+ fieldDescription.isStatic()
+ ? StackManipulation.Trivial.INSTANCE
+ : new StackManipulation.Compound(
+ MethodVariableAccess.loadThis(),
+ FieldAccess.forField(typeDescription.getDeclaredFields()
+ .filter((named(AccessorProxy.FIELD_NAME))).getOnly()).read()),
+ MethodVariableAccess.of(parameterType).loadFrom(1),
+ assigner.assign(parameterType, setterMethod.getParameters().get(0).getType(), Assigner.Typing.DYNAMIC),
+ MethodInvocation.invoke(setterMethod),
+ MethodReturn.VOID
+ ).apply(methodVisitor, implementationContext);
+ return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private FieldSetter getOuter() {
+ return FieldSetter.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object object) {
+ if (this == object) return true;
+ if (object == null || getClass() != object.getClass()) return false;
+ Appender appender = (Appender) object;
+ return typeDescription.equals(appender.typeDescription) && FieldSetter.this.equals(appender.getOuter());
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return typeDescription.hashCode() + 31 * FieldSetter.this.hashCode();
+ }
+ }
+ }
+
+ /**
+ * A proxy type for accessing a field either by a getter or a setter.
+ */
+ protected class AccessorProxy implements AuxiliaryType, StackManipulation {
+
+ /**
+ * The name of the field that stores the accessed instance if any.
+ */
+ protected static final String FIELD_NAME = "instance";
+
+ /**
+ * The field that is being accessed.
+ */
+ private final FieldDescription fieldDescription;
+
+ /**
+ * The type which is accessed.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * The field resolver to use.
+ */
+ private final FieldResolver fieldResolver;
+
+ /**
+ * The assigner to use.
+ */
+ private final Assigner assigner;
+
+ /**
+ * {@code true} if the generated proxy should be serializable.
+ */
+ private final boolean serializableProxy;
+
+ /**
+ * @param fieldDescription The field that is being accessed.
+ * @param instrumentedType The type which is accessed.
+ * @param fieldResolver The field resolver to use.
+ * @param assigner The assigner to use.
+ * @param serializableProxy {@code true} if the generated proxy should be serializable.
+ */
+ protected AccessorProxy(FieldDescription fieldDescription,
+ TypeDescription instrumentedType,
+ FieldResolver fieldResolver,
+ Assigner assigner,
+ boolean serializableProxy) {
+ this.fieldDescription = fieldDescription;
+ this.instrumentedType = instrumentedType;
+ this.fieldResolver = fieldResolver;
+ this.assigner = assigner;
+ this.serializableProxy = serializableProxy;
+ }
+
+ @Override
+ public DynamicType make(String auxiliaryTypeName,
+ ClassFileVersion classFileVersion,
+ MethodAccessorFactory methodAccessorFactory) {
+ return fieldResolver.apply(new ByteBuddy(classFileVersion)
+ .subclass(fieldResolver.getProxyType(), ConstructorStrategy.Default.NO_CONSTRUCTORS)
+ .name(auxiliaryTypeName)
+ .modifiers(DEFAULT_TYPE_MODIFIER)
+ .implement(serializableProxy ? new Class<?>[]{Serializable.class} : new Class<?>[0])
+ .defineConstructor().withParameters(fieldDescription.isStatic()
+ ? Collections.<TypeDescription>emptyList()
+ : Collections.singletonList(instrumentedType))
+ .intercept(fieldDescription.isStatic()
+ ? StaticFieldConstructor.INSTANCE
+ : new InstanceFieldConstructor(instrumentedType)), fieldDescription, assigner, methodAccessorFactory).make();
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ TypeDescription auxiliaryType = implementationContext.register(this);
+ return new Compound(
+ TypeCreation.of(auxiliaryType),
+ Duplication.SINGLE,
+ fieldDescription.isStatic()
+ ? Trivial.INSTANCE
+ : MethodVariableAccess.loadThis(),
+ MethodInvocation.invoke(auxiliaryType.getDeclaredMethods().filter(isConstructor()).getOnly())
+ ).apply(methodVisitor, implementationContext);
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private Binder getOuter() {
+ return Binder.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object object) {
+ if (this == object) return true;
+ if (object == null || getClass() != object.getClass()) return false;
+ AccessorProxy that = (AccessorProxy) object;
+ return serializableProxy == that.serializableProxy
+ && fieldDescription.equals(that.fieldDescription)
+ && instrumentedType.equals(that.instrumentedType)
+ && fieldResolver.equals(that.fieldResolver)
+ && assigner.equals(that.assigner)
+ && Binder.this.equals(that.getOuter());
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ int result = fieldDescription.hashCode();
+ result = 31 * result + Binder.this.hashCode();
+ result = 31 * result + instrumentedType.hashCode();
+ result = 31 * result + fieldResolver.hashCode();
+ result = 31 * result + assigner.hashCode();
+ result = 31 * result + (serializableProxy ? 1 : 0);
+ return result;
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/FieldValue.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/FieldValue.java
new file mode 100644
index 0000000..555b6e5
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/FieldValue.java
@@ -0,0 +1,155 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.member.FieldAccess;
+import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+
+import java.lang.annotation.*;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+
+/**
+ * <p>
+ * Assigns the value of a field of the instrumented type to the annotated parameter. For a binding to be valid,
+ * the instrumented type must be able to access a field of the given name. Also, the parameter's type must be
+ * assignable to the given field. For attempting a type casting, the {@link RuntimeType} annotation can be
+ * applied to the parameter.
+ * </p>
+ * <p>
+ * Setting {@link FieldValue#value()} is optional. If the value is not set, the field value attempts to bind a setter's
+ * or getter's field if the intercepted method is an accessor method. Otherwise, the binding renders the target method
+ * to be an illegal candidate for binding.
+ * </p>
+ *
+ * @see net.bytebuddy.implementation.MethodDelegation
+ * @see net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder
+ * @see net.bytebuddy.implementation.bind.annotation.RuntimeType
+ */
+ at Documented
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.PARAMETER)
+public @interface FieldValue {
+
+ /**
+ * The name of the field to be accessed.
+ *
+ * @return The name of the field.
+ */
+ String value() default TargetMethodAnnotationDrivenBinder.ParameterBinder.ForFieldBinding.BEAN_PROPERTY;
+
+ /**
+ * Defines the type on which the field is declared. If this value is not set, the most specific type's field is read,
+ * if two fields with the same name exist in the same type hierarchy.
+ *
+ * @return The type that declares the accessed field.
+ */
+ Class<?> declaringType() default void.class;
+
+ /**
+ * Binds a {@link FieldValue} annotation.
+ */
+ enum Binder implements TargetMethodAnnotationDrivenBinder.ParameterBinder<FieldValue> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE(new Delegate());
+
+ /**
+ * The annotation method that for the defining type.
+ */
+ private static final MethodDescription.InDefinedShape DECLARING_TYPE;
+
+ /**
+ * The annotation method for the field's name.
+ */
+ private static final MethodDescription.InDefinedShape FIELD_NAME;
+
+ /*
+ * Initializes the methods of the annotation that is read by this binder.
+ */
+ static {
+ MethodList<MethodDescription.InDefinedShape> methodList = new TypeDescription.ForLoadedType(FieldValue.class).getDeclaredMethods();
+ DECLARING_TYPE = methodList.filter(named("declaringType")).getOnly();
+ FIELD_NAME = methodList.filter(named("value")).getOnly();
+ }
+
+ /**
+ * A delegate parameter binder responsible for binding the parameter.
+ */
+ private final TargetMethodAnnotationDrivenBinder.ParameterBinder<FieldValue> delegate;
+
+ /**
+ * Creates a new binder for a {@link FieldValue}.
+ *
+ * @param delegate A delegate parameter binder responsible for binding the parameter.
+ */
+ Binder(TargetMethodAnnotationDrivenBinder.ParameterBinder<FieldValue> delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public Class<FieldValue> getHandledType() {
+ return delegate.getHandledType();
+ }
+
+ @Override
+ public MethodDelegationBinder.ParameterBinding<?> bind(AnnotationDescription.Loadable<FieldValue> annotation,
+ MethodDescription source,
+ ParameterDescription target,
+ Implementation.Target implementationTarget,
+ Assigner assigner,
+ Assigner.Typing typing) {
+ return delegate.bind(annotation, source, target, implementationTarget, assigner, typing);
+ }
+
+ /**
+ * A delegate implementation for the {@link FieldValue.Binder}.
+ */
+ protected static class Delegate extends ForFieldBinding<FieldValue> {
+
+ @Override
+ public Class<FieldValue> getHandledType() {
+ return FieldValue.class;
+ }
+
+ @Override
+ protected MethodDelegationBinder.ParameterBinding<?> bind(FieldDescription fieldDescription,
+ AnnotationDescription.Loadable<FieldValue> annotation,
+ MethodDescription source,
+ ParameterDescription target,
+ Implementation.Target implementationTarget,
+ Assigner assigner) {
+ StackManipulation stackManipulation = new StackManipulation.Compound(
+ fieldDescription.isStatic()
+ ? StackManipulation.Trivial.INSTANCE
+ : MethodVariableAccess.loadThis(),
+ FieldAccess.forField(fieldDescription).read(),
+ assigner.assign(fieldDescription.getType(), target.getType(), RuntimeType.Verifier.check(target))
+ );
+ return stackManipulation.isValid()
+ ? new MethodDelegationBinder.ParameterBinding.Anonymous(stackManipulation)
+ : MethodDelegationBinder.ParameterBinding.Illegal.INSTANCE;
+ }
+
+ @Override
+ protected String fieldName(AnnotationDescription.Loadable<FieldValue> annotation) {
+ return annotation.getValue(FIELD_NAME).resolve(String.class);
+ }
+
+ @Override
+ protected TypeDescription declaringType(AnnotationDescription.Loadable<FieldValue> annotation) {
+ return annotation.getValue(DECLARING_TYPE).resolve(TypeDescription.class);
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/IgnoreForBinding.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/IgnoreForBinding.java
new file mode 100644
index 0000000..32bdbb5
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/IgnoreForBinding.java
@@ -0,0 +1,40 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.description.method.MethodDescription;
+
+import java.lang.annotation.*;
+
+/**
+ * Indicates that a given target method should never be considered for binding to a source method.
+ *
+ * @see net.bytebuddy.implementation.MethodDelegation
+ * @see net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder
+ */
+ at Documented
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.METHOD)
+public @interface IgnoreForBinding {
+
+ /**
+ * A non-instantiable type that allows to check if a method should be ignored for binding.
+ */
+ final class Verifier {
+
+ /**
+ * As this is merely a utility method, the constructor is not supposed to be invoked.
+ */
+ private Verifier() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Validates if a method should be ignored for binding.
+ *
+ * @param methodDescription The method to validate.
+ * @return {@code true} if the method should not be considered for binding.
+ */
+ public static boolean check(MethodDescription methodDescription) {
+ return methodDescription.getDeclaredAnnotations().isAnnotationPresent(IgnoreForBinding.class);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/Morph.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/Morph.java
new file mode 100644
index 0000000..a189445
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/Morph.java
@@ -0,0 +1,591 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.MethodAccessorFactory;
+import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.implementation.bytecode.Duplication;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.TypeCreation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.collection.ArrayAccess;
+import net.bytebuddy.implementation.bytecode.constant.IntegerConstant;
+import net.bytebuddy.implementation.bytecode.member.FieldAccess;
+import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
+import net.bytebuddy.implementation.bytecode.member.MethodReturn;
+import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+import net.bytebuddy.matcher.ElementMatchers;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.io.Serializable;
+import java.lang.annotation.*;
+import java.util.Collections;
+
+import static net.bytebuddy.matcher.ElementMatchers.*;
+
+/**
+ * This annotation instructs Byte Buddy to inject a proxy class that calls a method's super method with
+ * explicit arguments. For this, the {@link Morph.Binder}
+ * needs to be installed for an interface type that takes an argument of the array type {@link java.lang.Object} and
+ * returns a non-array type of {@link java.lang.Object}. This is an alternative to using the
+ * {@link net.bytebuddy.implementation.bind.annotation.SuperCall} or
+ * {@link net.bytebuddy.implementation.bind.annotation.DefaultCall} annotations which call a super
+ * method using the same arguments as the intercepted method was invoked with.
+ *
+ * @see net.bytebuddy.implementation.MethodDelegation
+ * @see net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder
+ */
+ at Documented
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.PARAMETER)
+public @interface Morph {
+
+ /**
+ * Determines if the injected proxy for this parameter should be serializable.
+ *
+ * @return {@code true} if the proxy should be serializable.
+ */
+ boolean serializableProxy() default false;
+
+ /**
+ * Determines if the proxy should attempt to invoke a default method. If the default method is ambiguous,
+ * use the {@link Morph#defaultTarget()} property instead which allows to determine an explicit interface
+ * on which the default method should be invoked on. If this other method is used, this property is ignored.
+ *
+ * @return {@code true} if a default method should be ignored.
+ */
+ boolean defaultMethod() default false;
+
+ /**
+ * The type on which a default method should be invoked. When this property is not set and the
+ * {@link Morph#defaultMethod()} property is set to {@code false}, a normal super method invocation is attempted.
+ *
+ * @return The target interface of a default method call.
+ */
+ Class<?> defaultTarget() default void.class;
+
+ /**
+ * A binder for the {@link net.bytebuddy.implementation.bind.annotation.Morph} annotation.
+ */
+ @EqualsAndHashCode
+ class Binder implements TargetMethodAnnotationDrivenBinder.ParameterBinder<Morph> {
+
+ /**
+ * A reference to the serializable proxy method.
+ */
+ private static final MethodDescription.InDefinedShape SERIALIZABLE_PROXY;
+
+ /**
+ * A reference to the default method method.
+ */
+ private static final MethodDescription.InDefinedShape DEFAULT_METHOD;
+
+ /**
+ * A reference to the default target method.
+ */
+ private static final MethodDescription.InDefinedShape DEFAULT_TARGET;
+
+ /*
+ * Looks up references for all annotation properties of the morph annotation.
+ */
+ static {
+ MethodList<MethodDescription.InDefinedShape> methodList = new TypeDescription.ForLoadedType(Morph.class).getDeclaredMethods();
+ SERIALIZABLE_PROXY = methodList.filter(named("serializableProxy")).getOnly();
+ DEFAULT_METHOD = methodList.filter(named("defaultMethod")).getOnly();
+ DEFAULT_TARGET = methodList.filter(named("defaultTarget")).getOnly();
+ }
+
+ /**
+ * The method which is overridden for generating the proxy class.
+ */
+ private final MethodDescription forwardingMethod;
+
+ /**
+ * Creates a new binder.
+ *
+ * @param forwardingMethod The method which is overridden for generating the proxy class.
+ */
+ protected Binder(MethodDescription forwardingMethod) {
+ this.forwardingMethod = forwardingMethod;
+ }
+
+ /**
+ * Installs a given type for use on a {@link net.bytebuddy.implementation.bind.annotation.Morph}
+ * annotation. The given type must be an interface without any super interfaces and a single method which
+ * maps an {@link java.lang.Object} array to a {@link java.lang.Object} type. The use of generics is
+ * permitted.
+ *
+ * @param type The type to install.
+ * @return A binder for the {@link net.bytebuddy.implementation.bind.annotation.Morph}
+ * annotation.
+ */
+ public static TargetMethodAnnotationDrivenBinder.ParameterBinder<Morph> install(Class<?> type) {
+ return install(new TypeDescription.ForLoadedType(type));
+ }
+
+ /**
+ * Installs a given type for use on a {@link net.bytebuddy.implementation.bind.annotation.Morph}
+ * annotation. The given type must be an interface without any super interfaces and a single method which
+ * maps an {@link java.lang.Object} array to a {@link java.lang.Object} type. The use of generics is
+ * permitted.
+ *
+ * @param typeDescription The type to install.
+ * @return A binder for the {@link net.bytebuddy.implementation.bind.annotation.Morph}
+ * annotation.
+ */
+ public static TargetMethodAnnotationDrivenBinder.ParameterBinder<Morph> install(TypeDescription typeDescription) {
+ return new Binder(onlyMethod(typeDescription));
+ }
+
+ /**
+ * Extracts the only method of a given type and validates to fit the constraints of the morph annotation.
+ *
+ * @param typeDescription The type to extract the method from.
+ * @return The only method after validation.
+ */
+ private static MethodDescription onlyMethod(TypeDescription typeDescription) {
+ if (!typeDescription.isInterface()) {
+ throw new IllegalArgumentException(typeDescription + " is not an interface");
+ } else if (!typeDescription.getInterfaces().isEmpty()) {
+ throw new IllegalArgumentException(typeDescription + " must not extend other interfaces");
+ } else if (!typeDescription.isPublic()) {
+ throw new IllegalArgumentException(typeDescription + " is mot public");
+ }
+ MethodList<?> methodCandidates = typeDescription.getDeclaredMethods().filter(isAbstract());
+ if (methodCandidates.size() != 1) {
+ throw new IllegalArgumentException(typeDescription + " must declare exactly one abstract method");
+ }
+ MethodDescription methodDescription = methodCandidates.getOnly();
+ if (!methodDescription.getReturnType().asErasure().represents(Object.class)) {
+ throw new IllegalArgumentException(methodDescription + " does not return an Object-type");
+ } else if (methodDescription.getParameters().size() != 1 || !methodDescription.getParameters().get(0).getType().asErasure().represents(Object[].class)) {
+ throw new IllegalArgumentException(methodDescription + " does not take a single argument of type Object[]");
+ }
+ return methodDescription;
+ }
+
+ @Override
+ public Class<Morph> getHandledType() {
+ return Morph.class;
+ }
+
+ @Override
+ public MethodDelegationBinder.ParameterBinding<?> bind(AnnotationDescription.Loadable<Morph> annotation,
+ MethodDescription source,
+ ParameterDescription target,
+ Implementation.Target implementationTarget,
+ Assigner assigner,
+ Assigner.Typing typing) {
+ if (!target.getType().asErasure().equals(forwardingMethod.getDeclaringType())) {
+ throw new IllegalStateException("Illegal use of @Morph for " + target + " which was installed for " + forwardingMethod.getDeclaringType());
+ }
+ Implementation.SpecialMethodInvocation specialMethodInvocation;
+ TypeDescription typeDescription = annotation.getValue(DEFAULT_TARGET).resolve(TypeDescription.class);
+ if (typeDescription.represents(void.class) && !annotation.getValue(DEFAULT_METHOD).resolve(Boolean.class)) {
+ specialMethodInvocation = implementationTarget.invokeSuper(source.asSignatureToken());
+ } else {
+ specialMethodInvocation = (typeDescription.represents(void.class)
+ ? DefaultMethodLocator.Implicit.INSTANCE
+ : new DefaultMethodLocator.Explicit(typeDescription)).resolve(implementationTarget, source);
+ }
+ return specialMethodInvocation.isValid()
+ ? new MethodDelegationBinder.ParameterBinding.Anonymous(new RedirectionProxy(forwardingMethod.getDeclaringType().asErasure(),
+ implementationTarget.getInstrumentedType(),
+ specialMethodInvocation,
+ assigner,
+ annotation.getValue(SERIALIZABLE_PROXY).resolve(Boolean.class)))
+ : MethodDelegationBinder.ParameterBinding.Illegal.INSTANCE;
+ }
+
+ /**
+ * A default method locator is responsible for looking up a default method to a given source method.
+ */
+ protected interface DefaultMethodLocator {
+
+ /**
+ * Locates the correct default method to a given source method.
+ *
+ * @param implementationTarget The current implementation target.
+ * @param source The source method for which a default method should be looked up.
+ * @return A special method invocation of the default method or an illegal special method invocation,
+ * if no suitable invocation could be located.
+ */
+ Implementation.SpecialMethodInvocation resolve(Implementation.Target implementationTarget,
+ MethodDescription source);
+
+ /**
+ * An implicit default method locator that only permits the invocation of a default method if the source
+ * method itself represents a method that was defined on a default method interface.
+ */
+ enum Implicit implements DefaultMethodLocator {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Implementation.SpecialMethodInvocation resolve(Implementation.Target implementationTarget, MethodDescription source) {
+ return implementationTarget.invokeDefault(source.asSignatureToken());
+ }
+ }
+
+ /**
+ * An explicit default method locator attempts to look up a default method in the specified interface type.
+ */
+ @EqualsAndHashCode
+ class Explicit implements DefaultMethodLocator {
+
+ /**
+ * A description of the type on which the default method should be invoked.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new explicit default method locator.
+ *
+ * @param typeDescription The actual target interface as explicitly defined by
+ * {@link DefaultCall#targetType()}.
+ */
+ public Explicit(TypeDescription typeDescription) {
+ this.typeDescription = typeDescription;
+ }
+
+ @Override
+ public Implementation.SpecialMethodInvocation resolve(Implementation.Target implementationTarget, MethodDescription source) {
+ if (!typeDescription.isInterface()) {
+ throw new IllegalStateException(source + " method carries default method call parameter on non-interface type");
+ }
+ return implementationTarget.invokeDefault(source.asSignatureToken(), typeDescription);
+ }
+ }
+ }
+
+ /**
+ * A proxy that implements the installed interface in order to allow for a morphed super method invocation.
+ */
+ @EqualsAndHashCode
+ protected static class RedirectionProxy implements AuxiliaryType, StackManipulation {
+
+ /**
+ * The name of the field that carries an instance for invoking a super method on.
+ */
+ protected static final String FIELD_NAME = "target";
+
+ /**
+ * The interface type that is implemented by the generated proxy.
+ */
+ private final TypeDescription morphingType;
+
+ /**
+ * The type that is instrumented on which the super method is invoked.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * The special method invocation to be executed by the morphing type via an accessor on the
+ * instrumented type.
+ */
+ private final Implementation.SpecialMethodInvocation specialMethodInvocation;
+
+ /**
+ * The assigner to use.
+ */
+ private final Assigner assigner;
+
+ /**
+ * Determines if the generated proxy should be {@link java.io.Serializable}.
+ */
+ private final boolean serializableProxy;
+
+ /**
+ * Creates a new redirection proxy.
+ *
+ * @param morphingType The interface type that is implemented by the generated proxy.
+ * @param instrumentedType The type that is instrumented on which the super method is invoked.
+ * @param specialMethodInvocation The special method invocation to be executed by the morphing type via
+ * an accessor on the instrumented type.
+ * @param assigner The assigner to use.
+ * @param serializableProxy {@code true} if the proxy should be serializable.
+ */
+ protected RedirectionProxy(TypeDescription morphingType,
+ TypeDescription instrumentedType,
+ Implementation.SpecialMethodInvocation specialMethodInvocation,
+ Assigner assigner,
+ boolean serializableProxy) {
+ this.morphingType = morphingType;
+ this.instrumentedType = instrumentedType;
+ this.specialMethodInvocation = specialMethodInvocation;
+ this.assigner = assigner;
+ this.serializableProxy = serializableProxy;
+ }
+
+ @Override
+ public DynamicType make(String auxiliaryTypeName,
+ ClassFileVersion classFileVersion,
+ MethodAccessorFactory methodAccessorFactory) {
+ return new ByteBuddy(classFileVersion)
+ .subclass(morphingType, ConstructorStrategy.Default.NO_CONSTRUCTORS)
+ .name(auxiliaryTypeName)
+ .modifiers(DEFAULT_TYPE_MODIFIER)
+ .implement(serializableProxy ? new Class<?>[]{Serializable.class} : new Class<?>[0])
+ .defineConstructor().withParameters(specialMethodInvocation.getMethodDescription().isStatic()
+ ? Collections.<TypeDescription>emptyList()
+ : Collections.singletonList(instrumentedType))
+ .intercept(specialMethodInvocation.getMethodDescription().isStatic()
+ ? StaticFieldConstructor.INSTANCE
+ : new InstanceFieldConstructor(instrumentedType))
+ .method(ElementMatchers.<MethodDescription>isAbstract().and(isDeclaredBy(morphingType)))
+ .intercept(new MethodCall(methodAccessorFactory.registerAccessorFor(specialMethodInvocation, MethodAccessorFactory.AccessType.DEFAULT), assigner))
+ .make();
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ TypeDescription forwardingType = implementationContext.register(this);
+ return new Compound(
+ TypeCreation.of(forwardingType),
+ Duplication.SINGLE,
+ specialMethodInvocation.getMethodDescription().isStatic()
+ ? Trivial.INSTANCE
+ : MethodVariableAccess.loadThis(),
+ MethodInvocation.invoke(forwardingType.getDeclaredMethods().filter(isConstructor()).getOnly())
+ ).apply(methodVisitor, implementationContext);
+ }
+
+ /**
+ * Creates an instance of the proxy when instrumenting a static method.
+ */
+ protected enum StaticFieldConstructor implements Implementation {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ /**
+ * A reference of the {@link Object} type default constructor.
+ */
+ private final MethodDescription objectTypeDefaultConstructor;
+
+ /**
+ * Creates the constructor call singleton.
+ */
+ StaticFieldConstructor() {
+ objectTypeDefaultConstructor = TypeDescription.OBJECT.getDeclaredMethods()
+ .filter(isConstructor())
+ .getOnly();
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return new ByteCodeAppender.Simple(MethodVariableAccess.loadThis(), MethodInvocation.invoke(objectTypeDefaultConstructor), MethodReturn.VOID);
+ }
+ }
+
+ /**
+ * Creates an instance of the proxy when instrumenting an instance method.
+ */
+ @EqualsAndHashCode
+ protected static class InstanceFieldConstructor implements Implementation {
+
+ /**
+ * The instrumented type.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * Creates a new instance field constructor implementation.
+ *
+ * @param instrumentedType The instrumented type.
+ */
+ protected InstanceFieldConstructor(TypeDescription instrumentedType) {
+ this.instrumentedType = instrumentedType;
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType.withField(new FieldDescription.Token(RedirectionProxy.FIELD_NAME,
+ Opcodes.ACC_FINAL | Opcodes.ACC_PRIVATE,
+ this.instrumentedType.asGenericType()));
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return new Appender(implementationTarget);
+ }
+
+ /**
+ * The byte code appender that implements the constructor.
+ */
+ @EqualsAndHashCode
+ protected static class Appender implements ByteCodeAppender {
+
+ /**
+ * The field that carries the instance on which the super method is invoked.
+ */
+ private final FieldDescription fieldDescription;
+
+ /**
+ * Creates a new appender.
+ *
+ * @param implementationTarget The current implementation target.
+ */
+ protected Appender(Target implementationTarget) {
+ fieldDescription = implementationTarget.getInstrumentedType()
+ .getDeclaredFields()
+ .filter((named(RedirectionProxy.FIELD_NAME)))
+ .getOnly();
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor,
+ Context implementationContext,
+ MethodDescription instrumentedMethod) {
+ StackManipulation.Size stackSize = new StackManipulation.Compound(
+ MethodVariableAccess.loadThis(),
+ MethodInvocation.invoke(StaticFieldConstructor.INSTANCE.objectTypeDefaultConstructor),
+ MethodVariableAccess.allArgumentsOf(instrumentedMethod).prependThisReference(),
+ FieldAccess.forField(fieldDescription).write(),
+ MethodReturn.VOID
+ ).apply(methodVisitor, implementationContext);
+ return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+ }
+ }
+
+ /**
+ * Implements a the method call of the morphing method.
+ */
+ @EqualsAndHashCode
+ protected static class MethodCall implements Implementation {
+
+ /**
+ * The accessor method to invoke from the proxy's method.
+ */
+ private final MethodDescription accessorMethod;
+
+ /**
+ * The assigner to be used.
+ */
+ private final Assigner assigner;
+
+ /**
+ * Creates a new method call implementation for a proxy method.
+ *
+ * @param accessorMethod The accessor method to invoke from the proxy's method.
+ * @param assigner The assigner to be used.
+ */
+ protected MethodCall(MethodDescription accessorMethod, Assigner assigner) {
+ this.accessorMethod = accessorMethod;
+ this.assigner = assigner;
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return new Appender(implementationTarget);
+ }
+
+ /**
+ * The byte code appender to implement the method.
+ */
+ protected class Appender implements ByteCodeAppender {
+
+ /**
+ * The proxy type.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new appender.
+ *
+ * @param implementationTarget The current implementation target.
+ */
+ protected Appender(Target implementationTarget) {
+ typeDescription = implementationTarget.getInstrumentedType();
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor,
+ Context implementationContext,
+ MethodDescription instrumentedMethod) {
+ StackManipulation arrayReference = MethodVariableAccess.REFERENCE.loadFrom(1);
+ StackManipulation[] parameterLoading = new StackManipulation[accessorMethod.getParameters().size()];
+ int index = 0;
+ for (TypeDescription.Generic parameterType : accessorMethod.getParameters().asTypeList()) {
+ parameterLoading[index] = new StackManipulation.Compound(arrayReference,
+ IntegerConstant.forValue(index),
+ ArrayAccess.REFERENCE.load(),
+ assigner.assign(TypeDescription.Generic.OBJECT, parameterType, Assigner.Typing.DYNAMIC));
+ index++;
+ }
+ StackManipulation.Size stackSize = new StackManipulation.Compound(
+ accessorMethod.isStatic()
+ ? Trivial.INSTANCE
+ : new StackManipulation.Compound(
+ MethodVariableAccess.loadThis(),
+ FieldAccess.forField(typeDescription.getDeclaredFields()
+ .filter((named(RedirectionProxy.FIELD_NAME)))
+ .getOnly()).read()),
+ new StackManipulation.Compound(parameterLoading),
+ MethodInvocation.invoke(accessorMethod),
+ assigner.assign(accessorMethod.getReturnType(), instrumentedMethod.getReturnType(), Assigner.Typing.DYNAMIC),
+ MethodReturn.REFERENCE
+ ).apply(methodVisitor, implementationContext);
+ return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private MethodCall getMethodCall() {
+ return MethodCall.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && MethodCall.this.equals(((Appender) other).getMethodCall())
+ && typeDescription.equals(((Appender) other).typeDescription);
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return typeDescription.hashCode() + 31 * MethodCall.this.hashCode();
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/Origin.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/Origin.java
new file mode 100644
index 0000000..114448e
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/Origin.java
@@ -0,0 +1,127 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.constant.*;
+import net.bytebuddy.utility.JavaConstant;
+import net.bytebuddy.utility.JavaType;
+
+import java.lang.annotation.*;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+/**
+ * <p>
+ * The origin annotation provides some meta information about the source method that is bound to this method where
+ * the binding is dependant of the parameter's type:
+ * </p>
+ * <ol>
+ * <li>If the annotated parameter is of type {@link java.lang.reflect.Method}, {@link java.lang.reflect.Constructor} or
+ * {@code java.lang.reflect.Executable}, the parameter is assigned a reference to the method or constructor it
+ * instruments. If the reference is not assignable to the sort of the intercepted source, the target is not considered
+ * for binding.</li>
+ * <li>If the annotated parameter is of type {@link java.lang.Class}, the parameter is assigned a reference of the
+ * type of the instrumented type.</li>
+ * <li>If the annotated parameter is of type {@link java.lang.String}, the parameter is assigned a string with
+ * the value that would be returned by the {@link Method#toString()} method.
+ * </li>
+ * <li>If the annotated parameter is a {@code int} type, it is assigned the intercepted method's modifiers.</li>
+ * <li>If the annotated type is {@code java.lang.invoke.MethodHandle}, a handle of the intercepted method is injected.
+ * A {@code java.lang.invoke.MethodHandle} is stored in a class's constant pool and does therefore not face the same
+ * runtime performance limitations as a (non-cached) {@link java.lang.reflect.Method} reference. Method handles are
+ * only supported for byte code versions starting from Java 7.</li>
+ * <li>If the annotated type is {@code java.lang.invoke.MethodType}, a description of the intercepted method's type
+ * is injected. Method type descriptions are only supported for byte code versions starting from Java 7.</li>
+ * </ol>
+ * <p>
+ * Any other parameter type will cause an {@link java.lang.IllegalStateException}.
+ * </p>
+ * <p>
+ * <b>Important:</b> A method handle or method type reference can only be used if the referenced method's types are all visible
+ * to the instrumented type or an {@link IllegalAccessError} will be thrown at runtime.
+ * </p>
+ *
+ * @see net.bytebuddy.implementation.MethodDelegation
+ * @see net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder
+ */
+ at Documented
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.PARAMETER)
+public @interface Origin {
+
+ /**
+ * Determines if the value that is assigned by this annotation is cached. For values that can be stored in the constant pool,
+ * this value is ignored as such values are cached implicitly. As a result, this value currently only affects caching of
+ * {@link Method} instances.
+ *
+ * @return {@code true} if the value for this parameter should be cached in a {@code static} field inside the instrumented class.
+ */
+ boolean cache() default true;
+
+ /**
+ * A binder for binding parameters that are annotated with
+ * {@link net.bytebuddy.implementation.bind.annotation.Origin}.
+ *
+ * @see TargetMethodAnnotationDrivenBinder
+ */
+ enum Binder implements TargetMethodAnnotationDrivenBinder.ParameterBinder<Origin> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Class<Origin> getHandledType() {
+ return Origin.class;
+ }
+
+ @Override
+ public MethodDelegationBinder.ParameterBinding<?> bind(AnnotationDescription.Loadable<Origin> annotation,
+ MethodDescription source,
+ ParameterDescription target,
+ Implementation.Target implementationTarget,
+ Assigner assigner,
+ Assigner.Typing typing) {
+ TypeDescription parameterType = target.getType().asErasure();
+ if (parameterType.represents(Class.class)) {
+ return new MethodDelegationBinder.ParameterBinding.Anonymous(ClassConstant.of(implementationTarget.getOriginType().asErasure()));
+ } else if (parameterType.represents(Method.class)) {
+ return source.isMethod()
+ ? new MethodDelegationBinder.ParameterBinding.Anonymous(
+ annotation.loadSilent().cache()
+ ? MethodConstant.forMethod(source.asDefined()).cached()
+ : MethodConstant.forMethod(source.asDefined()))
+ : MethodDelegationBinder.ParameterBinding.Illegal.INSTANCE;
+ } else if (parameterType.represents(Constructor.class)) {
+ return source.isConstructor()
+ ? new MethodDelegationBinder.ParameterBinding.Anonymous(
+ annotation.loadSilent().cache()
+ ? MethodConstant.forMethod(source.asDefined()).cached()
+ : MethodConstant.forMethod(source.asDefined()))
+ : MethodDelegationBinder.ParameterBinding.Illegal.INSTANCE;
+ } else if (JavaType.EXECUTABLE.getTypeStub().equals(parameterType)) {
+ return new MethodDelegationBinder.ParameterBinding.Anonymous(annotation.loadSilent().cache()
+ ? MethodConstant.forMethod(source.asDefined()).cached()
+ : MethodConstant.forMethod(source.asDefined()));
+ } else if (parameterType.represents(String.class)) {
+ return new MethodDelegationBinder.ParameterBinding.Anonymous(new TextConstant(source.toString()));
+ } else if (parameterType.represents(int.class)) {
+ return new MethodDelegationBinder.ParameterBinding.Anonymous(IntegerConstant.forValue(source.getModifiers()));
+ } else if (parameterType.equals(JavaType.METHOD_HANDLE.getTypeStub())) {
+ return new MethodDelegationBinder.ParameterBinding.Anonymous(JavaConstant.MethodHandle.of(source.asDefined()).asStackManipulation());
+ } else if (parameterType.equals(JavaType.METHOD_TYPE.getTypeStub())) {
+ return new MethodDelegationBinder.ParameterBinding.Anonymous(JavaConstant.MethodType.of(source.asDefined()).asStackManipulation());
+ } else {
+ throw new IllegalStateException("The " + target + " method's " + target.getIndex() +
+ " parameter is annotated with a Origin annotation with an argument not representing a Class," +
+ " Method, Constructor, String, int, MethodType or MethodHandle type");
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/Pipe.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/Pipe.java
new file mode 100644
index 0000000..dc35e8a
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/Pipe.java
@@ -0,0 +1,479 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.MethodAccessorFactory;
+import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.implementation.bytecode.Duplication;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.TypeCreation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.member.FieldAccess;
+import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
+import net.bytebuddy.implementation.bytecode.member.MethodReturn;
+import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+import net.bytebuddy.matcher.ElementMatchers;
+import org.objectweb.asm.MethodVisitor;
+
+import java.io.Serializable;
+import java.lang.annotation.*;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static net.bytebuddy.matcher.ElementMatchers.*;
+
+/**
+ * <p>
+ * A target method parameter that is annotated with this annotation allows to forward an intercepted method
+ * invocation to another instance. The instance to which a method call is forwarded must be of the most specific
+ * type that declares the intercepted method on the intercepted type.
+ * </p>
+ * <p>
+ * Unfortunately, before Java 8, the Java Class Library does not define any interface type which takes a single
+ * {@link java.lang.Object} type and returns another {@link java.lang.Object} type. For this reason, a
+ * {@link net.bytebuddy.implementation.bind.annotation.Pipe.Binder} needs to be installed explicitly
+ * and registered on a {@link net.bytebuddy.implementation.MethodDelegation}. The installed type is allowed to be an
+ * interface without any super types that declares a single method which maps an {@link java.lang.Object} type to
+ * a another {@link java.lang.Object} type as a result value. It is however not prohibited to use generics in the
+ * process.
+ * </p>
+ *
+ * @see net.bytebuddy.implementation.MethodDelegation
+ * @see net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder
+ */
+ at Documented
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.PARAMETER)
+public @interface Pipe {
+
+ /**
+ * Determines if the generated proxy should be {@link java.io.Serializable}.
+ *
+ * @return {@code true} if the generated proxy should be {@link java.io.Serializable}.
+ */
+ boolean serializableProxy() default false;
+
+ /**
+ * A {@link net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder.ParameterBinder}
+ * for binding the {@link net.bytebuddy.implementation.bind.annotation.Pipe} annotation.
+ */
+ @EqualsAndHashCode
+ class Binder implements TargetMethodAnnotationDrivenBinder.ParameterBinder<Pipe> {
+
+ /**
+ * The method which implements the behavior of forwarding a method invocation. This method needs to define
+ * a single non-static method with an {@link java.lang.Object} to {@link java.lang.Object} mapping.
+ */
+ private final MethodDescription forwardingMethod;
+
+ /**
+ * Creates a new binder. This constructor is not doing any validation of the forwarding method and its
+ * declaring type. Such validation is normally performed by the
+ * {@link net.bytebuddy.implementation.bind.annotation.Pipe.Binder#install(Class)}
+ * method.
+ *
+ * @param forwardingMethod The method which implements the behavior of forwarding a method invocation. This
+ * method needs to define a single non-static method with an {@link java.lang.Object}
+ * to {@link java.lang.Object} mapping.
+ */
+ protected Binder(MethodDescription forwardingMethod) {
+ this.forwardingMethod = forwardingMethod;
+ }
+
+ /**
+ * Installs a given type for use on a {@link net.bytebuddy.implementation.bind.annotation.Pipe}
+ * annotation. The given type must be an interface without any super interfaces and a single method which
+ * maps an {@link java.lang.Object} type to another {@link java.lang.Object} type. The use of generics is
+ * permitted.
+ *
+ * @param type The type to install.
+ * @return A binder for the {@link net.bytebuddy.implementation.bind.annotation.Pipe}
+ * annotation.
+ */
+ public static TargetMethodAnnotationDrivenBinder.ParameterBinder<Pipe> install(Class<?> type) {
+ return install(new TypeDescription.ForLoadedType(type));
+ }
+
+ /**
+ * Installs a given type for use on a {@link net.bytebuddy.implementation.bind.annotation.Pipe}
+ * annotation. The given type must be an interface without any super interfaces and a single method which
+ * maps an {@link java.lang.Object} type to another {@link java.lang.Object} type. The use of generics is
+ * permitted.
+ *
+ * @param typeDescription The type to install.
+ * @return A binder for the {@link net.bytebuddy.implementation.bind.annotation.Pipe}
+ * annotation.
+ */
+ public static TargetMethodAnnotationDrivenBinder.ParameterBinder<Pipe> install(TypeDescription typeDescription) {
+ return new Binder(onlyMethod(typeDescription));
+ }
+
+ /**
+ * Locates the only method of a type that is compatible to being overridden for invoking the proxy.
+ *
+ * @param typeDescription The type that is being installed.
+ * @return Its only method after validation.
+ */
+ private static MethodDescription onlyMethod(TypeDescription typeDescription) {
+ if (!typeDescription.isInterface()) {
+ throw new IllegalArgumentException(typeDescription + " is not an interface");
+ } else if (!typeDescription.getInterfaces().isEmpty()) {
+ throw new IllegalArgumentException(typeDescription + " must not extend other interfaces");
+ } else if (!typeDescription.isPublic()) {
+ throw new IllegalArgumentException(typeDescription + " is mot public");
+ }
+ MethodList<?> methodCandidates = typeDescription.getDeclaredMethods().filter(isAbstract());
+ if (methodCandidates.size() != 1) {
+ throw new IllegalArgumentException(typeDescription + " must declare exactly one abstract method");
+ }
+ MethodDescription methodDescription = methodCandidates.getOnly();
+ if (!methodDescription.getReturnType().asErasure().represents(Object.class)) {
+ throw new IllegalArgumentException(methodDescription + " does not return an Object-type");
+ } else if (methodDescription.getParameters().size() != 1 || !methodDescription.getParameters().getOnly().getType().asErasure().represents(Object.class)) {
+ throw new IllegalArgumentException(methodDescription + " does not take a single Object-typed argument");
+ }
+ return methodDescription;
+ }
+
+ @Override
+ public Class<Pipe> getHandledType() {
+ return Pipe.class;
+ }
+
+ @Override
+ public MethodDelegationBinder.ParameterBinding<?> bind(AnnotationDescription.Loadable<Pipe> annotation,
+ MethodDescription source,
+ ParameterDescription target,
+ Implementation.Target implementationTarget,
+ Assigner assigner,
+ Assigner.Typing typing) {
+ if (!target.getType().asErasure().equals(forwardingMethod.getDeclaringType())) {
+ throw new IllegalStateException("Illegal use of @Pipe for " + target + " which was installed for " + forwardingMethod.getDeclaringType());
+ } else if (source.isStatic()) {
+ return MethodDelegationBinder.ParameterBinding.Illegal.INSTANCE;
+ }
+ return new MethodDelegationBinder.ParameterBinding.Anonymous(new Redirection(forwardingMethod.getDeclaringType().asErasure(),
+ source,
+ assigner,
+ annotation.loadSilent().serializableProxy()));
+ }
+
+ /**
+ * An auxiliary type for performing the redirection of a method invocation as requested by the
+ * {@link net.bytebuddy.implementation.bind.annotation.Pipe} annotation.
+ */
+ @EqualsAndHashCode
+ protected static class Redirection implements AuxiliaryType, StackManipulation {
+
+ /**
+ * The prefix for naming fields to store method arguments.
+ */
+ private static final String FIELD_NAME_PREFIX = "argument";
+
+ /**
+ * The type that declares the method for forwarding a method invocation.
+ */
+ private final TypeDescription forwardingType;
+
+ /**
+ * The method that is to be forwarded.
+ */
+ private final MethodDescription sourceMethod;
+
+ /**
+ * The assigner to use.
+ */
+ private final Assigner assigner;
+
+ /**
+ * Determines if the generated proxy should be {@link java.io.Serializable}.
+ */
+ private final boolean serializableProxy;
+
+ /**
+ * Creates a new redirection.
+ *
+ * @param forwardingType The type that declares the method for forwarding a method invocation.
+ * @param sourceMethod The method that is to be forwarded.
+ * @param assigner The assigner to use.
+ * @param serializableProxy Determines if the generated proxy should be {@link java.io.Serializable}.
+ */
+ protected Redirection(TypeDescription forwardingType,
+ MethodDescription sourceMethod,
+ Assigner assigner,
+ boolean serializableProxy) {
+ this.forwardingType = forwardingType;
+ this.sourceMethod = sourceMethod;
+ this.assigner = assigner;
+ this.serializableProxy = serializableProxy;
+ }
+
+ /**
+ * Extracts all parameters of a method to fields.
+ *
+ * @param methodDescription The method to extract the parameters from.
+ * @return A linked hash map of field names to the types of these fields representing all parameters of the
+ * given method.
+ */
+ private static LinkedHashMap<String, TypeDescription> extractFields(MethodDescription methodDescription) {
+ TypeList parameterTypes = methodDescription.getParameters().asTypeList().asErasures();
+ LinkedHashMap<String, TypeDescription> typeDescriptions = new LinkedHashMap<String, TypeDescription>();
+ int currentIndex = 0;
+ for (TypeDescription parameterType : parameterTypes) {
+ typeDescriptions.put(fieldName(currentIndex++), parameterType);
+ }
+ return typeDescriptions;
+ }
+
+ /**
+ * Creates a new field name.
+ *
+ * @param index The index of the field.
+ * @return The field name that corresponds to the index.
+ */
+ private static String fieldName(int index) {
+ return String.format("%s%d", FIELD_NAME_PREFIX, index);
+ }
+
+ @Override
+ public DynamicType make(String auxiliaryTypeName,
+ ClassFileVersion classFileVersion,
+ MethodAccessorFactory methodAccessorFactory) {
+ LinkedHashMap<String, TypeDescription> parameterFields = extractFields(sourceMethod);
+ DynamicType.Builder<?> builder = new ByteBuddy(classFileVersion)
+ .subclass(forwardingType, ConstructorStrategy.Default.NO_CONSTRUCTORS)
+ .name(auxiliaryTypeName)
+ .modifiers(DEFAULT_TYPE_MODIFIER)
+ .implement(serializableProxy ? new Class<?>[]{Serializable.class} : new Class<?>[0])
+ .method(ElementMatchers.<MethodDescription>isAbstract().and(isDeclaredBy(forwardingType)))
+ .intercept(new MethodCall(sourceMethod, assigner))
+ .defineConstructor().withParameters(parameterFields.values())
+ .intercept(ConstructorCall.INSTANCE);
+ for (Map.Entry<String, TypeDescription> field : parameterFields.entrySet()) {
+ builder = builder.defineField(field.getKey(), field.getValue(), Visibility.PRIVATE);
+ }
+ return builder.make();
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ TypeDescription forwardingType = implementationContext.register(this);
+ return new Compound(
+ TypeCreation.of(forwardingType),
+ Duplication.SINGLE,
+ MethodVariableAccess.allArgumentsOf(sourceMethod),
+ MethodInvocation.invoke(forwardingType.getDeclaredMethods().filter(isConstructor()).getOnly())
+ ).apply(methodVisitor, implementationContext);
+ }
+
+ /**
+ * The implementation to implement a
+ * {@link net.bytebuddy.implementation.bind.annotation.Pipe.Binder.Redirection}'s
+ * constructor.
+ */
+ @SuppressFBWarnings(value = "SE_BAD_FIELD", justification = "Fields of enumerations are never serialized")
+ protected enum ConstructorCall implements Implementation {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ /**
+ * A reference of the {@link Object} type default constructor.
+ */
+ private final MethodDescription.InDefinedShape objectTypeDefaultConstructor;
+
+ /**
+ * Creates the constructor call singleton.
+ */
+ ConstructorCall() {
+ objectTypeDefaultConstructor = TypeDescription.OBJECT.getDeclaredMethods().filter(isConstructor()).getOnly();
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return new Appender(implementationTarget.getInstrumentedType());
+ }
+
+ /**
+ * The appender for implementing the
+ * {@link net.bytebuddy.implementation.bind.annotation.Pipe.Binder.Redirection.ConstructorCall}.
+ */
+ @EqualsAndHashCode
+ private static class Appender implements ByteCodeAppender {
+
+ /**
+ * The instrumented type being created.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * Creates a new appender.
+ *
+ * @param instrumentedType The instrumented type that is being created.
+ */
+ private Appender(TypeDescription instrumentedType) {
+ this.instrumentedType = instrumentedType;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ FieldList<?> fieldList = instrumentedType.getDeclaredFields();
+ StackManipulation[] fieldLoading = new StackManipulation[fieldList.size()];
+ int index = 0;
+ for (FieldDescription fieldDescription : fieldList) {
+ fieldLoading[index] = new StackManipulation.Compound(
+ MethodVariableAccess.loadThis(),
+ MethodVariableAccess.load(instrumentedMethod.getParameters().get(index)),
+ FieldAccess.forField(fieldDescription).write()
+ );
+ index++;
+ }
+ StackManipulation.Size stackSize = new StackManipulation.Compound(
+ MethodVariableAccess.loadThis(),
+ MethodInvocation.invoke(ConstructorCall.INSTANCE.objectTypeDefaultConstructor),
+ new StackManipulation.Compound(fieldLoading),
+ MethodReturn.VOID
+ ).apply(methodVisitor, implementationContext);
+ return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+ }
+ }
+
+ /**
+ * The implementation to implement a
+ * {@link net.bytebuddy.implementation.bind.annotation.Pipe.Binder.Redirection}'s
+ * forwarding method.
+ */
+ @EqualsAndHashCode
+ protected static class MethodCall implements Implementation {
+
+ /**
+ * The method that is invoked by the implemented method.
+ */
+ private final MethodDescription redirectedMethod;
+
+ /**
+ * The assigner to be used for invoking the forwarded method.
+ */
+ private final Assigner assigner;
+
+ /**
+ * Creates a new method call implementation.
+ *
+ * @param redirectedMethod The method that is invoked by the implemented method.
+ * @param assigner The assigner to be used for invoking the forwarded method.
+ */
+ private MethodCall(MethodDescription redirectedMethod, Assigner assigner) {
+ this.redirectedMethod = redirectedMethod;
+ this.assigner = assigner;
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ if (!redirectedMethod.isAccessibleTo(implementationTarget.getInstrumentedType())) {
+ throw new IllegalStateException("Cannot invoke " + redirectedMethod + " from outside of class via @Pipe proxy");
+ }
+ return new Appender(implementationTarget.getInstrumentedType());
+ }
+
+ /**
+ * The appender for implementing the
+ * {@link net.bytebuddy.implementation.bind.annotation.Pipe.Binder.Redirection.MethodCall}.
+ */
+ private class Appender implements ByteCodeAppender {
+
+ /**
+ * The instrumented type that is implemented.
+ */
+ private final TypeDescription instrumentedType;
+
+ /**
+ * Creates a new appender.
+ *
+ * @param instrumentedType The instrumented type to be implemented.
+ */
+ private Appender(TypeDescription instrumentedType) {
+ this.instrumentedType = instrumentedType;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor,
+ Context implementationContext,
+ MethodDescription instrumentedMethod) {
+ StackManipulation thisReference = MethodVariableAccess.of(instrumentedType).loadFrom(0);
+ FieldList<?> fieldList = instrumentedType.getDeclaredFields();
+ StackManipulation[] fieldLoading = new StackManipulation[fieldList.size()];
+ int index = 0;
+ for (FieldDescription fieldDescription : fieldList) {
+ fieldLoading[index++] = new StackManipulation.Compound(thisReference, FieldAccess.forField(fieldDescription).read());
+ }
+ StackManipulation.Size stackSize = new StackManipulation.Compound(
+ MethodVariableAccess.REFERENCE.loadFrom(1),
+ assigner.assign(TypeDescription.Generic.OBJECT, redirectedMethod.getDeclaringType().asGenericType(), Assigner.Typing.DYNAMIC),
+ new StackManipulation.Compound(fieldLoading),
+ MethodInvocation.invoke(redirectedMethod),
+ assigner.assign(redirectedMethod.getReturnType(), instrumentedMethod.getReturnType(), Assigner.Typing.DYNAMIC),
+ MethodReturn.REFERENCE
+ ).apply(methodVisitor, implementationContext);
+ return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private MethodCall getMethodCall() {
+ return MethodCall.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && instrumentedType.equals(((Appender) other).instrumentedType)
+ && MethodCall.this.equals(((Appender) other).getMethodCall());
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return 31 * MethodCall.this.hashCode() + instrumentedType.hashCode();
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/RuntimeType.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/RuntimeType.java
new file mode 100644
index 0000000..9af823e
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/RuntimeType.java
@@ -0,0 +1,49 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.description.annotation.AnnotationSource;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+
+import java.lang.annotation.*;
+
+/**
+ * Parameters that are annotated with this annotation will be assigned by also considering the runtime type of the
+ * target parameter. The same is true for a method's return type if a target method is annotated with this annotation.
+ * <p> </p>
+ * For example, if a source method {@code foo(Object)} is attempted to be bound to
+ * {@code bar(@RuntimeType String)}, the binding will attempt to cast the argument of {@code foo} to a {@code String}
+ * type before calling {@code bar} with this argument. If this is not possible, a {@link java.lang.ClassCastException}
+ * will be thrown at runtime. Similarly, if a method {@code foo} returns a type {@code String} but is bound to a method
+ * that returns a type {@code Object}, annotating the target method with {@code @RuntimeType} results in the
+ * {@code foo} method casting the target's method return value to {@code String} before returning a value itself.
+ *
+ * @see net.bytebuddy.implementation.MethodDelegation
+ * @see net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder
+ */
+ at Documented
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target({ElementType.PARAMETER, ElementType.METHOD})
+public @interface RuntimeType {
+
+ /**
+ * A non-instantiable type that allows to check if a method or parameter should consider a runtime type.
+ */
+ final class Verifier {
+
+ /**
+ * As this is merely a utility method, the constructor is not supposed to be invoked.
+ */
+ private Verifier() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Checks if an annotated element should be assigned a value by considering the runtime type.
+ *
+ * @param annotationSource The annotated element of interest.
+ * @return Indicates if dynamic type castings should be attempted for incompatible assignments.
+ */
+ public static Assigner.Typing check(AnnotationSource annotationSource) {
+ return Assigner.Typing.of(annotationSource.getDeclaredAnnotations().isAnnotationPresent(RuntimeType.class));
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/StubValue.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/StubValue.java
new file mode 100644
index 0000000..6b073a2
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/StubValue.java
@@ -0,0 +1,63 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.constant.DefaultValue;
+import net.bytebuddy.implementation.bytecode.constant.NullConstant;
+
+import java.lang.annotation.*;
+
+/**
+ * A stub value represents the (boxed) default value of the intercepted method's return type. This value can
+ * only be assigned to a {@link java.lang.Object} parameter. This annotation is useful to conditionally return a
+ * default value from a method when using an {@link java.lang.Object} return type in combination with the
+ * {@link net.bytebuddy.implementation.bind.annotation.RuntimeType} annotation. The value is either representing
+ * {@code null} if a method returns a reference type or {@code void} or a boxed primitive of the return type
+ * representing the numeric value {@code 0}.
+ *
+ * @see net.bytebuddy.implementation.MethodDelegation
+ * @see net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder
+ */
+ at Documented
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target({ElementType.PARAMETER})
+public @interface StubValue {
+
+ /**
+ * Binds the {@link net.bytebuddy.implementation.bind.annotation.StubValue} annotation.
+ */
+ enum Binder implements TargetMethodAnnotationDrivenBinder.ParameterBinder<StubValue> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Class<StubValue> getHandledType() {
+ return StubValue.class;
+ }
+
+ @Override
+ public MethodDelegationBinder.ParameterBinding<?> bind(AnnotationDescription.Loadable<StubValue> annotation,
+ MethodDescription source,
+ ParameterDescription target,
+ Implementation.Target implementationTarget,
+ Assigner assigner,
+ Assigner.Typing typing) {
+ if (!target.getType().represents(Object.class)) {
+ throw new IllegalStateException(target + " uses StubValue annotation on non-Object type");
+ }
+ return new MethodDelegationBinder.ParameterBinding.Anonymous(source.getReturnType().represents(void.class)
+ ? NullConstant.INSTANCE
+ : new StackManipulation.Compound(DefaultValue.of(source.getReturnType().asErasure()),
+ assigner.assign(source.getReturnType(), TypeDescription.Generic.OBJECT, typing)));
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/Super.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/Super.java
new file mode 100644
index 0000000..37f463c
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/Super.java
@@ -0,0 +1,338 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.enumeration.EnumerationDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.TargetType;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.auxiliary.TypeProxy;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+
+import java.lang.annotation.*;
+import java.util.Arrays;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+
+/**
+ * Parameters that are annotated with this annotation are assigned an instance of an auxiliary proxy type that allows calling
+ * any {@code super} methods of the instrumented type where the parameter type must be a super type of the instrumented type.
+ * The proxy type will be a direct subclass of the parameter's type such as for example a specific interface.
+ * <p> </p>
+ * Obviously, the proxy type must be instantiated before it is assigned to the intercepting method's parameter. For this
+ * purpose, two strategies are available which can be specified by setting the {@link Super#strategy()} parameter which can
+ * be assigned:
+ * <ol>
+ * <li>{@link net.bytebuddy.implementation.bind.annotation.Super.Instantiation#CONSTRUCTOR}:
+ * A constructor call is made where {@link Super#constructorParameters()} determines the constructor's signature. Any constructor
+ * parameter is assigned the parameter's default value when the constructor is called. Calling the default constructor is the
+ * preconfigured strategy.</li>
+ * <li>{@link net.bytebuddy.implementation.bind.annotation.Super.Instantiation#UNSAFE}:
+ * The proxy is created by making use of Java's {@link sun.reflect.ReflectionFactory} which is however not a public API which
+ * is why it should be used with care. No constructor is called when this strategy is used. If this option is set, the
+ * {@link Super#constructorParameters()} parameter is ignored.</li>
+ * </ol>
+ * Note that when for example intercepting a type {@code Foo} that implements some interface {@code Bar}, the proxy type
+ * will only implement {@code Bar} and therefore extend {@link java.lang.Object} what allows for calling the default
+ * constructor on the proxy. This implies that an interception by some method {@code qux(@Super Baz baz, @Super Bar bar)}
+ * would cause the creation of two super call proxies, one extending {@code Baz}, the other extending {@code Bar}, give
+ * that both types are super types of {@code Foo}.
+ * <p> </p>
+ * As an exception, no method calls to {@link Object#finalize()} are delegated by calling this method on the {@code super}-call
+ * proxy by default. If this is absolutely necessary, this can however be enabled by setting {@link Super#ignoreFinalizer()}
+ * to {@code false}.
+ * <p> </p>
+ * If a method parameter is not a super type of the instrumented type, the method with the parameter that is annoted by
+ * #{@code Super} is not considered a possible delegation target.
+ *
+ * @see net.bytebuddy.implementation.MethodDelegation
+ * @see net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder
+ */
+ at Documented
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.PARAMETER)
+public @interface Super {
+
+ /**
+ * Determines how the {@code super}call proxy type is instantiated.
+ *
+ * @return The instantiation strategy for this proxy.
+ */
+ Instantiation strategy() default Instantiation.CONSTRUCTOR;
+
+ /**
+ * If {@code true}, the proxy type will not implement {@code super} calls to {@link Object#finalize()} or any overridden methods.
+ *
+ * @return {@code false} if finalizer methods should be considered for {@code super}-call proxy type delegation.
+ */
+ boolean ignoreFinalizer() default true;
+
+ /**
+ * Determines if the generated proxy should be {@link java.io.Serializable}. If the annotated type
+ * already is serializable, such an explicit specification is not required.
+ *
+ * @return {@code true} if the generated proxy should be {@link java.io.Serializable}.
+ */
+ boolean serializableProxy() default false;
+
+ /**
+ * Defines the parameter types of the constructor to be called for the created {@code super}-call proxy type.
+ *
+ * @return The parameter types of the constructor to be called.
+ */
+ Class<?>[] constructorParameters() default {};
+
+ /**
+ * Determines the type that is implemented by the proxy. When this value is set to its default value
+ * {@code void}, the proxy is created as an instance of the parameter's type. When it is set to
+ * {@link TargetType}, it is created as an instance of the generated class. Otherwise, the proxy type
+ * is set to the given value.
+ *
+ * @return The type of the proxy or an indicator type, i.e. {@code void} or {@link TargetType}.
+ */
+ Class<?> proxyType() default void.class;
+
+ /**
+ * Determines the instantiation of the proxy type.
+ *
+ * @see net.bytebuddy.implementation.bind.annotation.Super
+ */
+ enum Instantiation {
+
+ /**
+ * A proxy instance is instantiated by its constructor. For the constructor's arguments, the parameters default
+ * values are used. The constructor can be identified by setting {@link Super#constructorParameters()}.
+ */
+ CONSTRUCTOR {
+ @Override
+ protected StackManipulation proxyFor(TypeDescription parameterType,
+ Implementation.Target implementationTarget,
+ AnnotationDescription.Loadable<Super> annotation) {
+ return new TypeProxy.ForSuperMethodByConstructor(parameterType,
+ implementationTarget,
+ Arrays.asList(annotation.getValue(CONSTRUCTOR_PARAMETERS).resolve(TypeDescription[].class)),
+ annotation.getValue(IGNORE_FINALIZER).resolve(Boolean.class),
+ annotation.getValue(SERIALIZABLE_PROXY).resolve(Boolean.class));
+ }
+ },
+
+ /**
+ * A proxy is instantiated by calling JVM internal methods and without calling a constructor. This strategy
+ * might fail on exotic JVM implementations.
+ */
+ UNSAFE {
+ @Override
+ protected StackManipulation proxyFor(TypeDescription parameterType,
+ Implementation.Target implementationTarget,
+ AnnotationDescription.Loadable<Super> annotation) {
+ return new TypeProxy.ForSuperMethodByReflectionFactory(parameterType,
+ implementationTarget,
+ annotation.getValue(IGNORE_FINALIZER).resolve(Boolean.class),
+ annotation.getValue(SERIALIZABLE_PROXY).resolve(Boolean.class));
+ }
+ };
+
+ /**
+ * A reference to the ignore finalizer method.
+ */
+ private static final MethodDescription.InDefinedShape IGNORE_FINALIZER;
+
+ /**
+ * A reference to the serializable proxy method.
+ */
+ private static final MethodDescription.InDefinedShape SERIALIZABLE_PROXY;
+
+ /**
+ * A reference to the constructor parameters method.
+ */
+ private static final MethodDescription.InDefinedShape CONSTRUCTOR_PARAMETERS;
+
+ /*
+ * Extracts method references to the annotation methods.
+ */
+ static {
+ MethodList<MethodDescription.InDefinedShape> annotationProperties = new TypeDescription.ForLoadedType(Super.class).getDeclaredMethods();
+ IGNORE_FINALIZER = annotationProperties.filter(named("ignoreFinalizer")).getOnly();
+ SERIALIZABLE_PROXY = annotationProperties.filter(named("serializableProxy")).getOnly();
+ CONSTRUCTOR_PARAMETERS = annotationProperties.filter(named("constructorParameters")).getOnly();
+ }
+
+ /**
+ * Creates a stack manipulation which loads a {@code super}-call proxy onto the stack.
+ *
+ * @param parameterType The type of the parameter that was annotated with
+ * {@link net.bytebuddy.implementation.bind.annotation.Super}
+ * @param implementationTarget The implementation target for the currently created type.
+ * @param annotation The annotation that caused this method call.
+ * @return A stack manipulation representing this instance's instantiation strategy.
+ */
+ protected abstract StackManipulation proxyFor(TypeDescription parameterType,
+ Implementation.Target implementationTarget,
+ AnnotationDescription.Loadable<Super> annotation);
+ }
+
+ /**
+ * A binder for handling the
+ * {@link net.bytebuddy.implementation.bind.annotation.Super}
+ * annotation.
+ *
+ * @see TargetMethodAnnotationDrivenBinder
+ */
+ enum Binder implements TargetMethodAnnotationDrivenBinder.ParameterBinder<Super> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ /**
+ * A method reference to the strategy property.
+ */
+ private static final MethodDescription.InDefinedShape STRATEGY;
+
+ /**
+ * A reference to the proxy type property.
+ */
+ private static final MethodDescription.InDefinedShape PROXY_TYPE;
+
+ /*
+ * Extracts method references of the super annotation.
+ */
+ static {
+ MethodList<MethodDescription.InDefinedShape> annotationProperties = new TypeDescription.ForLoadedType(Super.class).getDeclaredMethods();
+ STRATEGY = annotationProperties.filter(named("strategy")).getOnly();
+ PROXY_TYPE = annotationProperties.filter(named("proxyType")).getOnly();
+ }
+
+ @Override
+ public Class<Super> getHandledType() {
+ return Super.class;
+ }
+
+ @Override
+ public MethodDelegationBinder.ParameterBinding<?> bind(AnnotationDescription.Loadable<Super> annotation,
+ MethodDescription source,
+ ParameterDescription target,
+ Implementation.Target implementationTarget,
+ Assigner assigner,
+ Assigner.Typing typing) {
+ if (target.getType().isPrimitive() || target.getType().isArray()) {
+ throw new IllegalStateException(target + " uses the @Super annotation on an invalid type");
+ }
+ TypeDescription proxyType = TypeLocator.ForType
+ .of(annotation.getValue(PROXY_TYPE).resolve(TypeDescription.class))
+ .resolve(implementationTarget.getInstrumentedType(), target.getType());
+ if (proxyType.isFinal()) {
+ throw new IllegalStateException("Cannot extend final type as @Super proxy: " + proxyType);
+ } else if (source.isStatic() || !implementationTarget.getInstrumentedType().isAssignableTo(proxyType)) {
+ return MethodDelegationBinder.ParameterBinding.Illegal.INSTANCE;
+ } else {
+ return new MethodDelegationBinder.ParameterBinding.Anonymous(annotation
+ .getValue(STRATEGY).resolve(EnumerationDescription.class).load(Instantiation.class)
+ .proxyFor(proxyType, implementationTarget, annotation));
+ }
+ }
+
+ /**
+ * Locates the type which should be the base type of the created proxy.
+ */
+ protected interface TypeLocator {
+
+ /**
+ * Resolves the target type.
+ *
+ * @param instrumentedType The instrumented type.
+ * @param parameterType The type of the target parameter.
+ * @return The proxy type.
+ */
+ TypeDescription resolve(TypeDescription instrumentedType, TypeDescription.Generic parameterType);
+
+ /**
+ * A type locator that yields the instrumented type.
+ */
+ enum ForInstrumentedType implements TypeLocator {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public TypeDescription resolve(TypeDescription instrumentedType, TypeDescription.Generic parameterType) {
+ return instrumentedType;
+ }
+ }
+
+ /**
+ * A type locator that yields the target parameter's type.
+ */
+ enum ForParameterType implements TypeLocator {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public TypeDescription resolve(TypeDescription instrumentedType, TypeDescription.Generic parameterType) {
+ TypeDescription erasure = parameterType.asErasure();
+ return erasure.equals(instrumentedType)
+ ? instrumentedType
+ : erasure;
+ }
+ }
+
+ /**
+ * A type locator that returns a given type.
+ */
+ @EqualsAndHashCode
+ class ForType implements TypeLocator {
+
+ /**
+ * The type to be returned upon resolution.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new type locator for a given type.
+ *
+ * @param typeDescription The type to be returned upon resolution.
+ */
+ protected ForType(TypeDescription typeDescription) {
+ this.typeDescription = typeDescription;
+ }
+
+ /**
+ * Resolves a type locator based upon an annotation value.
+ *
+ * @param typeDescription The annotation's value.
+ * @return The appropriate type locator.
+ */
+ protected static TypeLocator of(TypeDescription typeDescription) {
+ if (typeDescription.represents(void.class)) {
+ return ForParameterType.INSTANCE;
+ } else if (typeDescription.represents(TargetType.class)) {
+ return ForInstrumentedType.INSTANCE;
+ } else if (typeDescription.isPrimitive() || typeDescription.isArray()) {
+ throw new IllegalStateException("Cannot assign proxy to " + typeDescription);
+ } else {
+ return new ForType(typeDescription);
+ }
+ }
+
+ @Override
+ public TypeDescription resolve(TypeDescription instrumentedType, TypeDescription.Generic parameterType) {
+ if (!typeDescription.isAssignableTo(parameterType.asErasure())) {
+ throw new IllegalStateException("Impossible to assign " + typeDescription + " to parameter of type " + parameterType);
+ }
+ return typeDescription;
+ }
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/SuperCall.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/SuperCall.java
new file mode 100644
index 0000000..3d091c4
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/SuperCall.java
@@ -0,0 +1,104 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.auxiliary.MethodCallProxy;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.constant.NullConstant;
+
+import java.lang.annotation.*;
+import java.util.concurrent.Callable;
+
+/**
+ * Parameters that are annotated with this annotation will be assigned a proxy for calling the instrumented method's
+ * {@code super} implementation. If a method does not have a super implementation, calling the annotated proxy will
+ * throw an exception.
+ * <p> </p>
+ * The proxy will both implement the {@link java.util.concurrent.Callable} and the {@link java.lang.Runnable} interfaces
+ * such that the annotated parameter must be assignable to any of those interfaces or be of the {@link java.lang.Object}
+ * type.
+ *
+ * @see net.bytebuddy.implementation.MethodDelegation
+ * @see net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder
+ */
+ at Documented
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.PARAMETER)
+public @interface SuperCall {
+
+ /**
+ * Determines if the generated proxy should be {@link java.io.Serializable}.
+ *
+ * @return {@code true} if the generated proxy should be {@link java.io.Serializable}.
+ */
+ boolean serializableProxy() default false;
+
+ /**
+ * Determines if the injected proxy should invoke the default method to the intercepted method if a common
+ * super method invocation is not applicable. For this to be possible, the default method must not be ambiguous.
+ *
+ * @return {@code true} if the invocation should fall back to invoking the default method.
+ */
+ boolean fallbackToDefault() default true;
+
+ /**
+ * Assigns {@code null} to the parameter if it is impossible to invoke the super method or a possible dominant default method, if permitted.
+ *
+ * @return {@code true} if a {@code null} constant should be assigned to this parameter in case that a legal binding is impossible.
+ */
+ boolean nullIfImpossible() default false;
+
+ /**
+ * A binder for handling the
+ * {@link net.bytebuddy.implementation.bind.annotation.SuperCall}
+ * annotation.
+ *
+ * @see TargetMethodAnnotationDrivenBinder
+ */
+ enum Binder implements TargetMethodAnnotationDrivenBinder.ParameterBinder<SuperCall> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Class<SuperCall> getHandledType() {
+ return SuperCall.class;
+ }
+
+ @Override
+ public MethodDelegationBinder.ParameterBinding<?> bind(AnnotationDescription.Loadable<SuperCall> annotation,
+ MethodDescription source,
+ ParameterDescription target,
+ Implementation.Target implementationTarget,
+ Assigner assigner,
+ Assigner.Typing typing) {
+ TypeDescription targetType = target.getType().asErasure();
+ if (!targetType.represents(Runnable.class) && !targetType.represents(Callable.class) && !targetType.represents(Object.class)) {
+ throw new IllegalStateException("A super method call proxy can only be assigned to Runnable or Callable types: " + target);
+ } else if (source.isConstructor()) {
+ return annotation.loadSilent().nullIfImpossible()
+ ? new MethodDelegationBinder.ParameterBinding.Anonymous(NullConstant.INSTANCE)
+ : MethodDelegationBinder.ParameterBinding.Illegal.INSTANCE;
+ }
+ Implementation.SpecialMethodInvocation specialMethodInvocation = annotation.loadSilent().fallbackToDefault()
+ ? implementationTarget.invokeDominant(source.asSignatureToken())
+ : implementationTarget.invokeSuper(source.asSignatureToken());
+ StackManipulation stackManipulation;
+ if (specialMethodInvocation.isValid()) {
+ stackManipulation = new MethodCallProxy.AssignableSignatureCall(specialMethodInvocation, annotation.loadSilent().serializableProxy());
+ } else if (annotation.loadSilent().nullIfImpossible()) {
+ stackManipulation = NullConstant.INSTANCE;
+ } else {
+ return MethodDelegationBinder.ParameterBinding.Illegal.INSTANCE;
+ }
+ return new MethodDelegationBinder.ParameterBinding.Anonymous(stackManipulation);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/SuperMethod.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/SuperMethod.java
new file mode 100644
index 0000000..ece7c08
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/SuperMethod.java
@@ -0,0 +1,140 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.MethodAccessorFactory;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.constant.MethodConstant;
+import net.bytebuddy.implementation.bytecode.constant.NullConstant;
+import net.bytebuddy.implementation.bytecode.member.FieldAccess;
+import org.objectweb.asm.MethodVisitor;
+
+import java.lang.annotation.*;
+import java.lang.reflect.Method;
+
+/**
+ * A parameter with this annotation is assigned an instance of {@link Method} which invokes the super implementation of this method.
+ * If such a method is not available, this annotation causes that this delegation target cannot be bound unless {@link SuperMethod#nullIfImpossible()}
+ * is set to {@code true}. The method is declared as {@code public} and is invokable unless the instrumented type itself is not visible. Note that
+ * requesting such a method exposes the super method to reflection.
+ *
+ * @see net.bytebuddy.implementation.MethodDelegation
+ * @see net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder
+ */
+ at Documented
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.PARAMETER)
+public @interface SuperMethod {
+
+ /**
+ * Indicates if the instance assigned to this parameter should be stored in a static field for reuse.
+ *
+ * @return {@code true} if this method instance should be cached.
+ */
+ boolean cached() default true;
+
+ /**
+ * Indicates that the assigned method should attemt the invocation of an unambiguous default method if no super method is available.
+ *
+ * @return {@code true} if a default method should be invoked if it is not ambiguous and no super class method is available.
+ */
+ boolean fallbackToDefault() default true;
+
+ /**
+ * Indicates that {@code null} should be assigned to this parameter if no super method is invokable.
+ *
+ * @return {@code true} if {@code null} should be assigned if no valid method can be assigned.
+ */
+ boolean nullIfImpossible() default false;
+
+ /**
+ * A binder for the {@link SuperMethod} annotation.
+ */
+ enum Binder implements TargetMethodAnnotationDrivenBinder.ParameterBinder<SuperMethod> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Class<SuperMethod> getHandledType() {
+ return SuperMethod.class;
+ }
+
+ @Override
+ public MethodDelegationBinder.ParameterBinding<?> bind(final AnnotationDescription.Loadable<SuperMethod> annotation,
+ MethodDescription source,
+ ParameterDescription target,
+ Implementation.Target implementationTarget,
+ Assigner assigner,
+ Assigner.Typing typing) {
+ if (!target.getType().asErasure().isAssignableFrom(Method.class)) {
+ throw new IllegalStateException("Cannot assign Method type to " + target);
+ } else if (source.isMethod()) {
+ Implementation.SpecialMethodInvocation specialMethodInvocation = annotation.loadSilent().fallbackToDefault()
+ ? implementationTarget.invokeDominant(source.asSignatureToken())
+ : implementationTarget.invokeSuper(source.asSignatureToken());
+ if (specialMethodInvocation.isValid()) {
+ return new MethodDelegationBinder.ParameterBinding.Anonymous(new DelegationMethod(specialMethodInvocation, annotation.loadSilent().cached()));
+ } else if (annotation.loadSilent().nullIfImpossible()) {
+ return new MethodDelegationBinder.ParameterBinding.Anonymous(NullConstant.INSTANCE);
+ } else {
+ return MethodDelegationBinder.ParameterBinding.Illegal.INSTANCE;
+ }
+ } else if (annotation.loadSilent().nullIfImpossible()) {
+ return new MethodDelegationBinder.ParameterBinding.Anonymous(NullConstant.INSTANCE);
+ } else {
+ return MethodDelegationBinder.ParameterBinding.Illegal.INSTANCE;
+ }
+ }
+
+ /**
+ * Loads the delegation method constant onto the stack.
+ */
+ @EqualsAndHashCode
+ protected static class DelegationMethod implements StackManipulation {
+
+ /**
+ * The special method invocation that represents the super method call.
+ */
+ private final Implementation.SpecialMethodInvocation specialMethodInvocation;
+
+ /**
+ * {@code true} if the method constant should be cached.
+ */
+ private final boolean cached;
+
+ /**
+ * Creates a new delegation method.
+ *
+ * @param specialMethodInvocation The special method invocation that represents the super method call.
+ * @param cached {@code true} if the method constant should be cached.
+ */
+ protected DelegationMethod(Implementation.SpecialMethodInvocation specialMethodInvocation, boolean cached) {
+ this.specialMethodInvocation = specialMethodInvocation;
+ this.cached = cached;
+ }
+
+ @Override
+ public boolean isValid() {
+ return specialMethodInvocation.isValid();
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ StackManipulation stackManipulation = MethodConstant.forMethod(implementationContext.registerAccessorFor(specialMethodInvocation,
+ MethodAccessorFactory.AccessType.PUBLIC));
+ return (cached
+ ? FieldAccess.forField(implementationContext.cache(stackManipulation, new TypeDescription.ForLoadedType(Method.class))).read()
+ : stackManipulation).apply(methodVisitor, implementationContext);
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/TargetMethodAnnotationDrivenBinder.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/TargetMethodAnnotationDrivenBinder.java
new file mode 100644
index 0000000..263d20b
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/TargetMethodAnnotationDrivenBinder.java
@@ -0,0 +1,712 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.scaffold.FieldLocator;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.constant.*;
+import net.bytebuddy.utility.JavaConstant;
+import net.bytebuddy.utility.JavaType;
+
+import java.lang.annotation.Annotation;
+import java.util.*;
+
+import static net.bytebuddy.matcher.ElementMatchers.isGetter;
+import static net.bytebuddy.matcher.ElementMatchers.isSetter;
+
+/**
+ * This {@link net.bytebuddy.implementation.bind.MethodDelegationBinder} binds
+ * method by analyzing annotations found on the <i>target</i> method that is subject to a method binding.
+ */
+ at EqualsAndHashCode
+public class TargetMethodAnnotationDrivenBinder implements MethodDelegationBinder {
+
+ /**
+ * The processor for performing an actual method delegation.
+ */
+ private final DelegationProcessor delegationProcessor;
+
+ /**
+ * Creates a new target method annotation-driven binder.
+ *
+ * @param delegationProcessor The delegation proessor to use.
+ */
+ protected TargetMethodAnnotationDrivenBinder(DelegationProcessor delegationProcessor) {
+ this.delegationProcessor = delegationProcessor;
+ }
+
+ /**
+ * Creates a new method delegation binder that binds method based on annotations found on the target method.
+ *
+ * @param parameterBinders A list of parameter binder delegates. Each such delegate is responsible for creating a
+ * {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.ParameterBinding}
+ * for a specific annotation.
+ * @return An appropriate method delegation binder.
+ */
+ public static MethodDelegationBinder of(List<? extends ParameterBinder<?>> parameterBinders) {
+ return new TargetMethodAnnotationDrivenBinder(DelegationProcessor.of(parameterBinders));
+ }
+
+ @Override
+ public MethodDelegationBinder.Record compile(MethodDescription candidate) {
+ if (IgnoreForBinding.Verifier.check(candidate)) {
+ return MethodDelegationBinder.Record.Illegal.INSTANCE;
+ }
+ List<DelegationProcessor.Handler> handlers = new ArrayList<DelegationProcessor.Handler>(candidate.getParameters().size());
+ for (ParameterDescription parameterDescription : candidate.getParameters()) {
+ handlers.add(delegationProcessor.prepare(parameterDescription));
+ }
+ return new Record(candidate, handlers, RuntimeType.Verifier.check(candidate));
+ }
+
+ /**
+ * A compiled record of a target method annotation-driven binder.
+ */
+ @EqualsAndHashCode
+ protected static class Record implements MethodDelegationBinder.Record {
+
+ /**
+ * The candidate method.
+ */
+ private final MethodDescription candidate;
+
+ /**
+ * A list of handlers for each parameter.
+ */
+ private final List<DelegationProcessor.Handler> handlers;
+
+ /**
+ * The typing to apply.
+ */
+ private final Assigner.Typing typing;
+
+ /**
+ * Creates a default compiled method delegation binder.
+ *
+ * @param candidate The candidate method.
+ * @param handlers A list of handlers for each parameter.
+ * @param typing The typing to apply.
+ */
+ protected Record(MethodDescription candidate, List<DelegationProcessor.Handler> handlers, Assigner.Typing typing) {
+ this.candidate = candidate;
+ this.handlers = handlers;
+ this.typing = typing;
+ }
+
+ @Override
+ public MethodBinding bind(Implementation.Target implementationTarget,
+ MethodDescription source,
+ MethodDelegationBinder.TerminationHandler terminationHandler,
+ MethodInvoker methodInvoker,
+ Assigner assigner) {
+ if (!candidate.isAccessibleTo(implementationTarget.getInstrumentedType())) {
+ return MethodBinding.Illegal.INSTANCE;
+ }
+ StackManipulation methodTermination = terminationHandler.resolve(assigner, typing, source, candidate);
+ if (!methodTermination.isValid()) {
+ return MethodBinding.Illegal.INSTANCE;
+ }
+ MethodBinding.Builder methodDelegationBindingBuilder = new MethodBinding.Builder(methodInvoker, candidate);
+ for (DelegationProcessor.Handler handler : handlers) {
+ ParameterBinding<?> parameterBinding = handler.bind(source, implementationTarget, assigner);
+ if (!parameterBinding.isValid() || !methodDelegationBindingBuilder.append(parameterBinding)) {
+ return MethodBinding.Illegal.INSTANCE;
+ }
+ }
+ return methodDelegationBindingBuilder.build(methodTermination);
+ }
+
+ @Override
+ public String toString() {
+ return candidate.toString();
+ }
+ }
+
+ /**
+ * A parameter binder is used as a delegate for binding a parameter according to a particular annotation type found
+ * on this parameter.
+ *
+ * @param <T> The {@link java.lang.annotation.Annotation#annotationType()} handled by this parameter binder.
+ */
+ @SuppressFBWarnings(value = "IC_SUPERCLASS_USES_SUBCLASS_DURING_INITIALIZATION", justification = "Safe initialization is implied")
+ public interface ParameterBinder<T extends Annotation> {
+
+ /**
+ * The default parameter binders to be used.
+ */
+ List<ParameterBinder<?>> DEFAULTS = Collections.unmodifiableList(Arrays.<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>>asList(
+ Argument.Binder.INSTANCE,
+ AllArguments.Binder.INSTANCE,
+ Origin.Binder.INSTANCE,
+ This.Binder.INSTANCE,
+ Super.Binder.INSTANCE,
+ Default.Binder.INSTANCE,
+ SuperCall.Binder.INSTANCE,
+ DefaultCall.Binder.INSTANCE,
+ SuperMethod.Binder.INSTANCE,
+ DefaultMethod.Binder.INSTANCE,
+ FieldValue.Binder.INSTANCE,
+ StubValue.Binder.INSTANCE,
+ Empty.Binder.INSTANCE));
+
+ /**
+ * The annotation type that is handled by this parameter binder.
+ *
+ * @return The {@link java.lang.annotation.Annotation#annotationType()} handled by this parameter binder.
+ */
+ Class<T> getHandledType();
+
+ /**
+ * Creates a parameter binding for the given target parameter.
+ *
+ * @param annotation The annotation that was cause for the delegation to this argument binder.
+ * @param source The intercepted source method.
+ * @param target Tge target parameter that is subject to be bound to
+ * intercepting the {@code source} method.
+ * @param implementationTarget The target of the current implementation that is subject to this binding.
+ * @param assigner An assigner that can be used for applying the binding.
+ * @param typing The typing to apply.
+ * @return A parameter binding for the requested target method parameter.
+ */
+ ParameterBinding<?> bind(AnnotationDescription.Loadable<T> annotation,
+ MethodDescription source,
+ ParameterDescription target,
+ Implementation.Target implementationTarget,
+ Assigner assigner,
+ Assigner.Typing typing);
+
+ /**
+ * <p>
+ * Implements a parameter binder that binds a fixed value to a parameter with a given annotation.
+ * </p>
+ * <p>
+ * This binder is only capable to store values that can either be expressed as Java byte code or as a constant pool value. This
+ * includes primitive types, {@link String} values, {@link Class} values which can also be expressed as {@link TypeDescription}
+ * instances or method handles and method types for classes of a version at least of Java 7. The latter instances can also be
+ * expressed as unloaded {@link JavaConstant} representations.
+ * </p>
+ * <p>
+ * <b>Important</b>: When supplying a method handle or a method type, all types that are implied must be visible to the instrumented
+ * type or an {@link IllegalAccessException} will be thrown at runtime.
+ * </p>
+ *
+ * @param <S> The bound annotation's type.
+ */
+ abstract class ForFixedValue<S extends Annotation> implements ParameterBinder<S> {
+
+ @Override
+ public ParameterBinding<?> bind(AnnotationDescription.Loadable<S> annotation,
+ MethodDescription source,
+ ParameterDescription target,
+ Implementation.Target implementationTarget,
+ Assigner assigner,
+ Assigner.Typing typing) {
+ Object value = bind(annotation, source, target);
+ if (value == null) {
+ return new ParameterBinding.Anonymous(DefaultValue.of(target.getType()));
+ }
+ StackManipulation stackManipulation;
+ TypeDescription suppliedType;
+ if (value instanceof Boolean) {
+ stackManipulation = IntegerConstant.forValue((Boolean) value);
+ suppliedType = new TypeDescription.ForLoadedType(boolean.class);
+ } else if (value instanceof Byte) {
+ stackManipulation = IntegerConstant.forValue((Byte) value);
+ suppliedType = new TypeDescription.ForLoadedType(byte.class);
+ } else if (value instanceof Short) {
+ stackManipulation = IntegerConstant.forValue((Short) value);
+ suppliedType = new TypeDescription.ForLoadedType(short.class);
+ } else if (value instanceof Character) {
+ stackManipulation = IntegerConstant.forValue((Character) value);
+ suppliedType = new TypeDescription.ForLoadedType(char.class);
+ } else if (value instanceof Integer) {
+ stackManipulation = IntegerConstant.forValue((Integer) value);
+ suppliedType = new TypeDescription.ForLoadedType(int.class);
+ } else if (value instanceof Long) {
+ stackManipulation = LongConstant.forValue((Long) value);
+ suppliedType = new TypeDescription.ForLoadedType(long.class);
+ } else if (value instanceof Float) {
+ stackManipulation = FloatConstant.forValue((Float) value);
+ suppliedType = new TypeDescription.ForLoadedType(float.class);
+ } else if (value instanceof Double) {
+ stackManipulation = DoubleConstant.forValue((Double) value);
+ suppliedType = new TypeDescription.ForLoadedType(double.class);
+ } else if (value instanceof String) {
+ stackManipulation = new TextConstant((String) value);
+ suppliedType = TypeDescription.STRING;
+ } else if (value instanceof Class) {
+ stackManipulation = ClassConstant.of(new TypeDescription.ForLoadedType((Class<?>) value));
+ suppliedType = TypeDescription.CLASS;
+ } else if (value instanceof TypeDescription) {
+ stackManipulation = ClassConstant.of((TypeDescription) value);
+ suppliedType = TypeDescription.CLASS;
+ } else if (JavaType.METHOD_HANDLE.getTypeStub().isInstance(value)) {
+ stackManipulation = JavaConstant.MethodHandle.ofLoaded(value).asStackManipulation();
+ suppliedType = JavaType.METHOD_HANDLE.getTypeStub();
+ } else if (value instanceof JavaConstant.MethodHandle) {
+ stackManipulation = new JavaConstantValue((JavaConstant.MethodHandle) value);
+ suppliedType = JavaType.METHOD_HANDLE.getTypeStub();
+ } else if (JavaType.METHOD_TYPE.getTypeStub().isInstance(value)) {
+ stackManipulation = new JavaConstantValue(JavaConstant.MethodType.ofLoaded(value));
+ suppliedType = JavaType.METHOD_HANDLE.getTypeStub();
+ } else if (value instanceof JavaConstant.MethodType) {
+ stackManipulation = new JavaConstantValue((JavaConstant.MethodType) value);
+ suppliedType = JavaType.METHOD_HANDLE.getTypeStub();
+ } else {
+ throw new IllegalStateException("Not able to save in class's constant pool: " + value);
+ }
+ return new ParameterBinding.Anonymous(new StackManipulation.Compound(
+ stackManipulation,
+ assigner.assign(suppliedType.asGenericType(), target.getType(), typing)
+ ));
+ }
+
+ /**
+ * Resolves a value for the given annotation on a parameter that is processed by a {@link net.bytebuddy.implementation.MethodDelegation}.
+ *
+ * @param annotation The annotation that triggered this binding.
+ * @param source The method for which a delegation is currently bound.
+ * @param target The parameter for which a value is bound.
+ * @return The constant pool value that is bound to this parameter or {@code null} for binding this value.
+ */
+ protected abstract Object bind(AnnotationDescription.Loadable<S> annotation, MethodDescription source, ParameterDescription target);
+
+ /**
+ * <p>
+ * A parameter binder that binds a fixed value to a parameter annotation when using a {@link net.bytebuddy.implementation.MethodDelegation}.
+ * </p>
+ * <p>
+ * This binder is only capable to store
+ * values that can either be expressed as Java byte code or as a constant pool value. This includes primitive types, {@link String} values,
+ * {@link Class} values which can also be expressed as {@link TypeDescription} instances or method handles and method types for classes of
+ * a version at least of Java 7. The latter instances can also be expressed as unloaded {@link JavaConstant} representations.
+ * </p>
+ *
+ * @param <U> The bound annotation's type.
+ */
+ @EqualsAndHashCode(callSuper = false)
+ public static class OfConstant<U extends Annotation> extends ForFixedValue<U> {
+
+ /**
+ * The type of the annotation that is bound by this binder.
+ */
+ private final Class<U> type;
+
+ /**
+ * The value that is assigned to any annotated parameter.
+ */
+ private final Object value;
+
+ /**
+ * Creates a binder for binding a fixed value to a parameter annotated with the given annotation.
+ *
+ * @param type The type of the annotation that is bound by this binder.
+ * @param value The value that is assigned to any annotated parameter.
+ */
+ protected OfConstant(Class<U> type, Object value) {
+ this.type = type;
+ this.value = value;
+ }
+
+ /**
+ * Creates a binder for binding a fixed value to a given annotation.
+ *
+ * @param type The type of the annotation that is bound by this binder.
+ * @param value The value that is assigned to any annotated parameter.
+ * @param <V> The bound annotation's type.
+ * @return A parameter binder that binds the given annotation to the supplied value.
+ */
+ public static <V extends Annotation> ParameterBinder<V> of(Class<V> type, Object value) {
+ return new OfConstant<V>(type, value);
+ }
+
+ @Override
+ public Class<U> getHandledType() {
+ return type;
+ }
+
+ @Override
+ protected Object bind(AnnotationDescription.Loadable<U> annotation, MethodDescription source, ParameterDescription target) {
+ return value;
+ }
+ }
+ }
+
+ /**
+ * A parameter binder that binds a field's value.
+ *
+ * @param <S> The {@link java.lang.annotation.Annotation#annotationType()} handled by this parameter binder.
+ */
+ abstract class ForFieldBinding<S extends Annotation> implements ParameterBinder<S> {
+
+ /**
+ * Indicates that a name should be extracted from an accessor method.
+ */
+ protected static final String BEAN_PROPERTY = "";
+
+ /**
+ * Resolves a field locator for a potential accessor method.
+ *
+ * @param fieldLocator The field locator to use.
+ * @param methodDescription The method description that is the potential accessor.
+ * @return A resolution for a field locator.
+ */
+ private static FieldLocator.Resolution resolveAccessor(FieldLocator fieldLocator, MethodDescription methodDescription) {
+ String fieldName;
+ if (isSetter().matches(methodDescription)) {
+ fieldName = methodDescription.getInternalName().substring(3);
+ } else if (isGetter().matches(methodDescription)) {
+ fieldName = methodDescription.getInternalName().substring(methodDescription.getInternalName().startsWith("is") ? 2 : 3);
+ } else {
+ return FieldLocator.Resolution.Illegal.INSTANCE;
+ }
+ return fieldLocator.locate(Character.toLowerCase(fieldName.charAt(0)) + fieldName.substring(1));
+ }
+
+ @Override
+ public ParameterBinding<?> bind(AnnotationDescription.Loadable<S> annotation,
+ MethodDescription source,
+ ParameterDescription target,
+ Implementation.Target implementationTarget,
+ Assigner assigner,
+ Assigner.Typing typing) {
+ if (!declaringType(annotation).represents(void.class)) {
+ if (declaringType(annotation).isPrimitive() || declaringType(annotation).isArray()) {
+ throw new IllegalStateException("A primitive type or array type cannot declare a field: " + source);
+ } else if (!implementationTarget.getInstrumentedType().isAssignableTo(declaringType(annotation))) {
+ return MethodDelegationBinder.ParameterBinding.Illegal.INSTANCE;
+ }
+ }
+ FieldLocator fieldLocator = declaringType(annotation).represents(void.class)
+ ? new FieldLocator.ForClassHierarchy(implementationTarget.getInstrumentedType())
+ : new FieldLocator.ForExactType(declaringType(annotation), implementationTarget.getInstrumentedType());
+ FieldLocator.Resolution resolution = fieldName(annotation).equals(BEAN_PROPERTY)
+ ? resolveAccessor(fieldLocator, source)
+ : fieldLocator.locate(fieldName(annotation));
+ return resolution.isResolved() && !(source.isStatic() && !resolution.getField().isStatic())
+ ? bind(resolution.getField(), annotation, source, target, implementationTarget, assigner)
+ : ParameterBinding.Illegal.INSTANCE;
+ }
+
+ /**
+ * Extracts the field name from an annotation.
+ *
+ * @param annotation The annotation from which to extract the field name.
+ * @return The field name defined by the handled annotation.
+ */
+ protected abstract String fieldName(AnnotationDescription.Loadable<S> annotation);
+
+ /**
+ * Extracts the declaring type from an annotation.
+ *
+ * @param annotation The annotation from which to extract the declaring type.
+ * @return The declaring type defined by the handled annotation.
+ */
+ protected abstract TypeDescription declaringType(AnnotationDescription.Loadable<S> annotation);
+
+ /**
+ * Creates a parameter binding for the given target parameter.
+ *
+ * @param fieldDescription The field for which this binder binds a value.
+ * @param annotation The annotation that was cause for the delegation to this argument binder.
+ * @param source The intercepted source method.
+ * @param target Tge target parameter that is subject to be bound to
+ * intercepting the {@code source} method.
+ * @param implementationTarget The target of the current implementation that is subject to this binding.
+ * @param assigner An assigner that can be used for applying the binding.
+ * @return A parameter binding for the requested target method parameter.
+ */
+ protected abstract ParameterBinding<?> bind(FieldDescription fieldDescription,
+ AnnotationDescription.Loadable<S> annotation,
+ MethodDescription source,
+ ParameterDescription target,
+ Implementation.Target implementationTarget,
+ Assigner assigner);
+ }
+ }
+
+ /**
+ * A delegation processor is a helper class for a
+ * {@link net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder}
+ * for performing its actual logic. By outsourcing this logic to this helper class, a cleaner implementation
+ * can be provided.
+ */
+ @EqualsAndHashCode
+ protected static class DelegationProcessor {
+
+ /**
+ * A map of registered annotation types to the binder that is responsible for binding a parameter
+ * that is annotated with the given annotation.
+ */
+ private final Map<? extends TypeDescription, ? extends ParameterBinder<?>> parameterBinders;
+
+ /**
+ * Creates a new delegation processor.
+ *
+ * @param parameterBinders A mapping of parameter binders by their handling type.
+ */
+ protected DelegationProcessor(Map<? extends TypeDescription, ? extends ParameterBinder<?>> parameterBinders) {
+ this.parameterBinders = parameterBinders;
+ }
+
+ /**
+ * Creates a new delegation processor.
+ *
+ * @param parameterBinders A list of parameter binder delegates. Each such delegate is responsible for creating
+ * a {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.ParameterBinding}
+ * for a specific annotation.
+ * @return A corresponding delegation processor.
+ */
+ protected static DelegationProcessor of(List<? extends ParameterBinder<?>> parameterBinders) {
+ Map<TypeDescription, ParameterBinder<?>> parameterBinderMap = new HashMap<TypeDescription, ParameterBinder<?>>();
+ for (ParameterBinder<?> parameterBinder : parameterBinders) {
+ if (parameterBinderMap.put(new TypeDescription.ForLoadedType(parameterBinder.getHandledType()), parameterBinder) != null) {
+ throw new IllegalArgumentException("Attempt to bind two handlers to " + parameterBinder.getHandledType());
+ }
+ }
+ return new DelegationProcessor(parameterBinderMap);
+ }
+
+ /**
+ * Locates a handler which is responsible for processing the given parameter. If no explicit handler can
+ * be located, a fallback handler is provided.
+ *
+ * @param target The target parameter being handled.
+ * @return A handler for processing the parameter with the given annotations.
+ */
+ protected Handler prepare(ParameterDescription target) {
+ Assigner.Typing typing = RuntimeType.Verifier.check(target);
+ Handler handler = new Handler.Unbound(target, typing);
+ for (AnnotationDescription annotation : target.getDeclaredAnnotations()) {
+ ParameterBinder<?> parameterBinder = parameterBinders.get(annotation.getAnnotationType());
+ if (parameterBinder != null && handler.isBound()) {
+ throw new IllegalStateException("Ambiguous binding for parameter annotated with two handled annotation types");
+ } else if (parameterBinder != null /* && !handler.isBound() */) {
+ handler = Handler.Bound.of(target, parameterBinder, annotation, typing);
+ }
+ }
+ return handler;
+ }
+
+ /**
+ * A handler is responsible for processing a parameter's binding.
+ */
+ protected interface Handler {
+
+ /**
+ * Indicates if this handler was explicitly bound.
+ *
+ * @return {@code true} if this handler was explicitly bound.
+ */
+ boolean isBound();
+
+ /**
+ * Handles a parameter binding.
+ *
+ * @param source The intercepted source method.
+ * @param implementationTarget The target of the current implementation.
+ * @param assigner The assigner to use.
+ * @return A parameter binding that reflects the given arguments.
+ */
+ ParameterBinding<?> bind(MethodDescription source, Implementation.Target implementationTarget, Assigner assigner);
+
+ /**
+ * An unbound handler is a fallback for returning an illegal binding for parameters for which no parameter
+ * binder could be located.
+ */
+ @EqualsAndHashCode
+ class Unbound implements Handler {
+
+ /**
+ * The target parameter being handled.
+ */
+ private final ParameterDescription target;
+
+ /**
+ * The typing to apply.
+ */
+ private final Assigner.Typing typing;
+
+ /**
+ * Creates a new unbound handler.
+ *
+ * @param target The target parameter being handled.
+ * @param typing The typing to apply.
+ */
+ protected Unbound(ParameterDescription target, Assigner.Typing typing) {
+ this.target = target;
+ this.typing = typing;
+ }
+
+ @Override
+ public boolean isBound() {
+ return false;
+ }
+
+ @Override
+ public ParameterBinding<?> bind(MethodDescription source, Implementation.Target implementationTarget, Assigner assigner) {
+ return Argument.Binder.INSTANCE.bind(AnnotationDescription.ForLoadedAnnotation.<Argument>of(new DefaultArgument(target.getIndex())),
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ typing);
+ }
+
+ /**
+ * A default implementation of an {@link net.bytebuddy.implementation.bind.annotation.Argument} annotation.
+ */
+ protected static class DefaultArgument implements Argument {
+
+ /**
+ * The name of the value annotation parameter.
+ */
+ private static final String VALUE = "value";
+
+ /**
+ * The name of the value binding mechanic parameter.
+ */
+ private static final String BINDING_MECHANIC = "bindingMechanic";
+
+ /**
+ * The index of the source method parameter to be bound.
+ */
+ private final int parameterIndex;
+
+ /**
+ * Creates a new instance of an argument annotation.
+ *
+ * @param parameterIndex The index of the source method parameter to be bound.
+ */
+ protected DefaultArgument(int parameterIndex) {
+ this.parameterIndex = parameterIndex;
+ }
+
+ @Override
+ public int value() {
+ return parameterIndex;
+ }
+
+ @Override
+ public BindingMechanic bindingMechanic() {
+ return BindingMechanic.UNIQUE;
+ }
+
+ @Override
+ public Class<Argument> annotationType() {
+ return Argument.class;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return this == other || other instanceof Argument && parameterIndex == ((Argument) other).value();
+ }
+
+ @Override
+ public int hashCode() {
+ return ((127 * BINDING_MECHANIC.hashCode()) ^ BindingMechanic.UNIQUE.hashCode())
+ + ((127 * VALUE.hashCode()) ^ parameterIndex);
+ }
+
+ @Override
+ public String toString() {
+ return "@" + Argument.class.getName()
+ + "(bindingMechanic=" + BindingMechanic.UNIQUE.toString()
+ + ", value=" + parameterIndex + ")";
+ }
+ }
+ }
+
+ /**
+ * A bound handler represents an unambiguous parameter binder that was located for a given array of
+ * annotations.
+ *
+ * @param <T> The annotation type of a given handler.
+ */
+ @EqualsAndHashCode
+ class Bound<T extends Annotation> implements Handler {
+
+ /**
+ * The target parameter being handled.
+ */
+ private final ParameterDescription target;
+
+ /**
+ * The parameter binder that is actually responsible for binding the parameter.
+ */
+ private final ParameterBinder<T> parameterBinder;
+
+ /**
+ * The annotation value that lead to the binding of this handler.
+ */
+ private final AnnotationDescription.Loadable<T> annotation;
+
+ /**
+ * The typing to apply.
+ */
+ private final Assigner.Typing typing;
+
+ /**
+ * Creates a new bound handler.
+ *
+ * @param target The target parameter being handled.
+ * @param parameterBinder The parameter binder that is actually responsible for binding the parameter.
+ * @param annotation The annotation value that lead to the binding of this handler.
+ * @param typing The typing to apply.
+ */
+ protected Bound(ParameterDescription target,
+ ParameterBinder<T> parameterBinder,
+ AnnotationDescription.Loadable<T> annotation,
+ Assigner.Typing typing) {
+ this.target = target;
+ this.parameterBinder = parameterBinder;
+ this.annotation = annotation;
+ this.typing = typing;
+ }
+
+ /**
+ * Creates a handler for a given annotation.
+ *
+ * @param target The target parameter being handled.
+ * @param parameterBinder The parameter binder that should process an annotation.
+ * @param annotation An annotation instance that can be understood by this parameter binder.
+ * @param typing The typing to apply.
+ * @return A handler for processing the given annotation.
+ */
+ @SuppressWarnings("unchecked")
+ protected static Handler of(ParameterDescription target,
+ ParameterBinder<?> parameterBinder,
+ AnnotationDescription annotation,
+ Assigner.Typing typing) {
+ return new Bound<Annotation>(target,
+ (ParameterBinder<Annotation>) parameterBinder,
+ (AnnotationDescription.Loadable<Annotation>) annotation.prepare(parameterBinder.getHandledType()),
+ typing);
+ }
+
+ @Override
+ public boolean isBound() {
+ return true;
+ }
+
+ @Override
+ public ParameterBinding<?> bind(MethodDescription source, Implementation.Target implementationTarget, Assigner assigner) {
+ return parameterBinder.bind(annotation,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ typing);
+ }
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/This.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/This.java
new file mode 100644
index 0000000..e16bb64
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/This.java
@@ -0,0 +1,75 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.constant.NullConstant;
+import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+
+import java.lang.annotation.*;
+
+/**
+ * Parameters that are annotated with this annotation will be assigned a reference to the instrumented object, if
+ * the instrumented method is not static. Otherwise, the method with this parameter annotation will be excluded from
+ * the list of possible binding candidates of the static source method.
+ *
+ * @see net.bytebuddy.implementation.MethodDelegation
+ * @see net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder
+ */
+ at Documented
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.PARAMETER)
+public @interface This {
+
+ /**
+ * Determines if the annotated parameter should be bound to {@code null} when intercepting a {@code static} method.
+ *
+ * @return {@code true} if the annotated parameter should be bound to {@code null} as a fallback.
+ */
+ boolean optional() default false;
+
+ /**
+ * A binder for handling the
+ * {@link net.bytebuddy.implementation.bind.annotation.This}
+ * annotation.
+ *
+ * @see TargetMethodAnnotationDrivenBinder
+ */
+ enum Binder implements TargetMethodAnnotationDrivenBinder.ParameterBinder<This> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Class<This> getHandledType() {
+ return This.class;
+ }
+
+ @Override
+ public MethodDelegationBinder.ParameterBinding<?> bind(AnnotationDescription.Loadable<This> annotation,
+ MethodDescription source,
+ ParameterDescription target,
+ Implementation.Target implementationTarget,
+ Assigner assigner,
+ Assigner.Typing typing) {
+ if (target.getType().isPrimitive()) {
+ throw new IllegalStateException(target + " uses a primitive type with a @This annotation");
+ } else if (target.getType().isArray()) {
+ throw new IllegalStateException(target + " uses an array type with a @This annotation");
+ } else if (source.isStatic() && !annotation.loadSilent().optional()) {
+ return MethodDelegationBinder.ParameterBinding.Illegal.INSTANCE;
+ }
+ return new MethodDelegationBinder.ParameterBinding.Anonymous(source.isStatic()
+ ? NullConstant.INSTANCE
+ : new StackManipulation.Compound(MethodVariableAccess.loadThis(),
+ assigner.assign(implementationTarget.getInstrumentedType().asGenericType(), target.getType(), typing)));
+ }
+ }
+}
+
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/package-info.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/package-info.java
new file mode 100644
index 0000000..74eaab6
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * This package contains annotations, types and classes that are responsible for binding a method to calling another
+ * method by interpreting annotations that indicate how a method should be bound to another method.
+ */
+package net.bytebuddy.implementation.bind.annotation;
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/package-info.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/package-info.java
new file mode 100644
index 0000000..c7465cb
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * The types and classes of this package are responsible for binding a method call to calling another method.
+ */
+package net.bytebuddy.implementation.bind;
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/Addition.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/Addition.java
new file mode 100644
index 0000000..d7cea1b
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/Addition.java
@@ -0,0 +1,62 @@
+package net.bytebuddy.implementation.bytecode;
+
+import net.bytebuddy.implementation.Implementation;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * A stack manipulation that adds to numbers on the operand stack.
+ */
+public enum Addition implements StackManipulation {
+
+ /**
+ * Adds two integers or integer-compatible values.
+ */
+ INTEGER(Opcodes.IADD, StackSize.SINGLE),
+
+ /**
+ * Adds two longs.
+ */
+ LONG(Opcodes.LADD, StackSize.DOUBLE),
+
+ /**
+ * Adds two floats.
+ */
+ FLOAT(Opcodes.FADD, StackSize.SINGLE),
+
+ /**
+ * Adds two doubles.
+ */
+ DOUBLE(Opcodes.DADD, StackSize.DOUBLE);
+
+ /**
+ * The opcode to apply.
+ */
+ private final int opcode;
+
+ /**
+ * The stack size of the added primitive.
+ */
+ private final StackSize stackSize;
+
+ /**
+ * Creates a new addition type.
+ * @param opcode The opcode to apply.
+ * @param stackSize The stack size of the added primitive.
+ */
+ Addition(int opcode, StackSize stackSize) {
+ this.opcode = opcode;
+ this.stackSize = stackSize;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitInsn(opcode);
+ return stackSize.toDecreasingSize();
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/ByteCodeAppender.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/ByteCodeAppender.java
new file mode 100644
index 0000000..fef1639
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/ByteCodeAppender.java
@@ -0,0 +1,177 @@
+package net.bytebuddy.implementation.bytecode;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.implementation.Implementation;
+import org.objectweb.asm.MethodVisitor;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * An appender that generates the byte code for a given method. This is done by writing the byte code instructions to
+ * the given ASM {@link org.objectweb.asm.MethodVisitor}.
+ * <p> </p>
+ * The {@code ByteCodeAppender} is not allowed to write
+ * annotations to the method or call the {@link org.objectweb.asm.MethodVisitor#visitCode()},
+ * {@link org.objectweb.asm.MethodVisitor#visitMaxs(int, int)} or {@link org.objectweb.asm.MethodVisitor#visitEnd()}
+ * methods which is both done by the entity delegating the call to the {@code ByteCodeAppender}. This is done in order
+ * to allow for the concatenation of several byte code appenders and therefore a more modular description of method
+ * implementations.
+ */
+public interface ByteCodeAppender {
+
+ /**
+ * Applies this byte code appender to a type creation process.
+ *
+ * @param methodVisitor The method visitor to which the byte code appender writes its code to.
+ * @param implementationContext The implementation context of the current type creation process.
+ * @param instrumentedMethod The method that is the target of the instrumentation.
+ * @return The required size for the applied byte code to run.
+ */
+ Size apply(MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ MethodDescription instrumentedMethod);
+
+ /**
+ * An immutable description of both the operand stack size and the size of the local variable array that is
+ * required to run the code generated by this {@code ByteCodeAppender}.
+ */
+ @EqualsAndHashCode
+ class Size {
+
+ /**
+ * The size of the operand stack.
+ */
+ private final int operandStackSize;
+
+ /**
+ * The size of the local variable array.
+ */
+ private final int localVariableSize;
+
+ /**
+ * @param operandStackSize The operand stack size that is required for running given byte code.
+ * @param localVariableSize The local variable array size that is required for running given byte code.
+ */
+ public Size(int operandStackSize, int localVariableSize) {
+ this.operandStackSize = operandStackSize;
+ this.localVariableSize = localVariableSize;
+ }
+
+ /**
+ * Returns the required operand stack size.
+ *
+ * @return The required operand stack size.
+ */
+ public int getOperandStackSize() {
+ return operandStackSize;
+ }
+
+ /**
+ * Returns the required size of the local variable array.
+ *
+ * @return The required size of the local variable array.
+ */
+ public int getLocalVariableSize() {
+ return localVariableSize;
+ }
+
+ /**
+ * Merges two sizes in order to describe the size that is required by both size descriptions.
+ *
+ * @param other The other size description.
+ * @return A size description incorporating both size requirements.
+ */
+ public Size merge(Size other) {
+ return new Size(Math.max(operandStackSize, other.operandStackSize), Math.max(localVariableSize, other.localVariableSize));
+ }
+ }
+
+ /**
+ * A compound appender that combines a given number of other byte code appenders.
+ */
+ @EqualsAndHashCode
+ class Compound implements ByteCodeAppender {
+
+ /**
+ * The byte code appenders that are represented by this compound appender in their application order.
+ */
+ private final List<ByteCodeAppender> byteCodeAppenders;
+
+ /**
+ * Creates a new compound byte code appender.
+ *
+ * @param byteCodeAppender The byte code appenders to combine in their order.
+ */
+ public Compound(ByteCodeAppender... byteCodeAppender) {
+ this(Arrays.asList(byteCodeAppender));
+ }
+
+ /**
+ * Creates a new compound byte code appender.
+ *
+ * @param byteCodeAppenders The byte code appenders to combine in their order.
+ */
+ public Compound(List<? extends ByteCodeAppender> byteCodeAppenders) {
+ this.byteCodeAppenders = new ArrayList<ByteCodeAppender>();
+ for (ByteCodeAppender byteCodeAppender : byteCodeAppenders) {
+ if (byteCodeAppender instanceof Compound) {
+ this.byteCodeAppenders.addAll(((Compound) byteCodeAppender).byteCodeAppenders);
+ } else {
+ this.byteCodeAppenders.add(byteCodeAppender);
+ }
+ }
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ MethodDescription instrumentedMethod) {
+ Size size = new Size(0, instrumentedMethod.getStackSize());
+ for (ByteCodeAppender byteCodeAppender : byteCodeAppenders) {
+ size = size.merge(byteCodeAppender.apply(methodVisitor, implementationContext, instrumentedMethod));
+ }
+ return size;
+ }
+ }
+
+ /**
+ * A simple byte code appender that only represents a given array of
+ * {@link StackManipulation}s.
+ */
+ @EqualsAndHashCode
+ class Simple implements ByteCodeAppender {
+
+ /**
+ * A compound stack manipulation to be applied for this byte code appender.
+ */
+ private final StackManipulation stackManipulation;
+
+ /**
+ * Creates a new simple byte code appender which represents the given stack manipulation.
+ *
+ * @param stackManipulation The stack manipulations to apply for this byte code appender in their application order.
+ */
+ public Simple(StackManipulation... stackManipulation) {
+ this(Arrays.asList(stackManipulation));
+ }
+
+ /**
+ * Creates a new simple byte code appender which represents the given stack manipulation.
+ *
+ * @param stackManipulations The stack manipulations to apply for this byte code appender in their application order.
+ */
+ public Simple(List<? extends StackManipulation> stackManipulations) {
+ this.stackManipulation = new StackManipulation.Compound(stackManipulations);
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ MethodDescription instrumentedMethod) {
+ return new Size(stackManipulation.apply(methodVisitor, implementationContext).getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/Duplication.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/Duplication.java
new file mode 100644
index 0000000..461b252
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/Duplication.java
@@ -0,0 +1,178 @@
+package net.bytebuddy.implementation.bytecode;
+
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.implementation.Implementation;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Duplicates a value that is lying on top of the stack.
+ */
+public enum Duplication implements StackManipulation {
+
+ /**
+ * A duplication of no values. This corresponds a no-op instruction.
+ */
+ ZERO(StackSize.ZERO, Opcodes.NOP) {
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ return size;
+ }
+
+ @Override
+ public StackManipulation flipOver(TypeDefinition typeDefinition) {
+ throw new IllegalStateException("Cannot flip zero value");
+ }
+ },
+
+ /**
+ * A duplication of a single-sized stack values.
+ */
+ SINGLE(StackSize.SINGLE, Opcodes.DUP) {
+ @Override
+ public StackManipulation flipOver(TypeDefinition typeDefinition) {
+ switch (typeDefinition.getStackSize()) {
+ case SINGLE:
+ return WithFlip.SINGLE_SINGLE;
+ case DOUBLE:
+ return WithFlip.SINGLE_DOUBLE;
+ default:
+ throw new IllegalArgumentException("Cannot flip: " + typeDefinition);
+ }
+ }
+ },
+
+ /**
+ * A duplication of a double-sized stack value.
+ */
+ DOUBLE(StackSize.DOUBLE, Opcodes.DUP2) {
+ @Override
+ public StackManipulation flipOver(TypeDefinition typeDefinition) {
+ switch (typeDefinition.getStackSize()) {
+ case SINGLE:
+ return WithFlip.DOUBLE_SINGLE;
+ case DOUBLE:
+ return WithFlip.DOUBLE_DOUBLE;
+ default:
+ throw new IllegalArgumentException("Cannot flip: " + typeDefinition);
+ }
+ }
+ };
+
+ /**
+ * The size representing the impact of applying the duplication onto the operand stack.
+ */
+ protected final Size size;
+
+ /**
+ * The opcode that represents the manipulation.
+ */
+ private final int opcode;
+
+ /**
+ * Creates a new duplication.
+ *
+ * @param stackSize The size representing the impact of applying the duplication onto the operand stack.
+ * @param opcode The opcode that represents the manipulation.
+ */
+ Duplication(StackSize stackSize, int opcode) {
+ size = stackSize.toIncreasingSize();
+ this.opcode = opcode;
+ }
+
+ /**
+ * Duplicates a value given its type.
+ *
+ * @param typeDefinition The type to be duplicated.
+ * @return A stack manipulation that duplicates the given type.
+ */
+ public static Duplication of(TypeDefinition typeDefinition) {
+ switch (typeDefinition.getStackSize()) {
+ case SINGLE:
+ return SINGLE;
+ case DOUBLE:
+ return DOUBLE;
+ case ZERO:
+ return ZERO;
+ default:
+ throw new AssertionError("Unexpected type: " + typeDefinition);
+ }
+ }
+
+ /**
+ * Creates a duplication that flips the stack's top value over the second stack element.
+ *
+ * @param typeDefinition The type of the second element on the operand stack.
+ * @return A stack manipulation that represents such a duplication flip.
+ */
+ public abstract StackManipulation flipOver(TypeDefinition typeDefinition);
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitInsn(opcode);
+ return size;
+ }
+
+ /**
+ * A duplication that flips a value over the second value on the operand stack.
+ */
+ protected enum WithFlip implements StackManipulation {
+
+ /**
+ * A flip instruction that flips a single-sized element over another single-size element.
+ */
+ SINGLE_SINGLE(Opcodes.DUP_X1, StackSize.SINGLE),
+
+ /**
+ * A flip instruction that flips a double-sized element over a single-size element.
+ */
+ SINGLE_DOUBLE(Opcodes.DUP_X2, StackSize.SINGLE),
+
+ /**
+ * A flip instruction that flips a single-sized element over a double-size element.
+ */
+ DOUBLE_SINGLE(Opcodes.DUP2_X1, StackSize.DOUBLE),
+
+ /**
+ * A flip instruction that flips a double-sized element over another double-size element.
+ */
+ DOUBLE_DOUBLE(Opcodes.DUP2_X2, StackSize.DOUBLE);
+
+ /**
+ * The opcode to apply.
+ */
+ private final int opcode;
+
+ /**
+ * The size that is added to the operand stack.
+ */
+ private final StackSize stackSize;
+
+ /**
+ * Creates a flip duplication.
+ *
+ * @param opcode The opcode to apply.
+ * @param stackSize The size that is added to the operand stack.
+ */
+ WithFlip(int opcode, StackSize stackSize) {
+ this.opcode = opcode;
+ this.stackSize = stackSize;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitInsn(opcode);
+ return stackSize.toIncreasingSize();
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/Removal.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/Removal.java
new file mode 100644
index 0000000..6d10295
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/Removal.java
@@ -0,0 +1,84 @@
+package net.bytebuddy.implementation.bytecode;
+
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.implementation.Implementation;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Removes a value from the operand stack.
+ */
+public enum Removal implements StackManipulation {
+
+ /**
+ * A removal of no value. This corresponds a no-op instruction.
+ */
+ ZERO(StackSize.ZERO, Opcodes.NOP) {
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ return new Size(0, 0);
+ }
+ },
+
+ /**
+ * A removal of a single-sized value.
+ */
+ SINGLE(StackSize.SINGLE, Opcodes.POP),
+
+ /**
+ * A removal of a double-sized value.
+ */
+ DOUBLE(StackSize.DOUBLE, Opcodes.POP2);
+
+ /**
+ * The size impact of the removal onto the operand stack.
+ */
+ private final Size size;
+
+ /**
+ * The opcode to execute for the removal.
+ */
+ private final int opcode;
+
+ /**
+ * Creates a new removal stack manipulation.
+ *
+ * @param stackSize The size impact of the removal onto the operand stack.
+ * @param opcode The opcode to execute for the removal.
+ */
+ Removal(StackSize stackSize, int opcode) {
+ size = stackSize.toDecreasingSize();
+ this.opcode = opcode;
+ }
+
+ /**
+ * Removes a value from the operand stack dependant of its size.
+ *
+ * @param typeDefinition The type to remove from the stack.
+ * @return A stack manipulation that represents the removal.
+ */
+ public static StackManipulation of(TypeDefinition typeDefinition) {
+ switch (typeDefinition.getStackSize()) {
+ case SINGLE:
+ return SINGLE;
+ case DOUBLE:
+ return DOUBLE;
+ case ZERO:
+ return ZERO;
+ default:
+ throw new AssertionError();
+ }
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitInsn(opcode);
+ return size;
+ }
+}
+
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/StackManipulation.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/StackManipulation.java
new file mode 100644
index 0000000..b4617de
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/StackManipulation.java
@@ -0,0 +1,203 @@
+package net.bytebuddy.implementation.bytecode;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.implementation.Implementation;
+import org.objectweb.asm.MethodVisitor;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Describes a manipulation of a method's operand stack that does not affect the frame's variable array.
+ */
+public interface StackManipulation {
+
+ /**
+ * Determines if this stack manipulation is valid.
+ *
+ * @return If {@code false}, this manipulation cannot be applied and should throw an exception.
+ */
+ boolean isValid();
+
+ /**
+ * Applies the stack manipulation that is described by this instance.
+ *
+ * @param methodVisitor The method visitor used to write the method implementation to.
+ * @param implementationContext The context of the current implementation.
+ * @return The changes to the size of the operand stack that are implied by this stack manipulation.
+ */
+ Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext);
+
+ /**
+ * Canonical representation of an illegal stack manipulation.
+ */
+ enum Illegal implements StackManipulation {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public boolean isValid() {
+ return false;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ throw new IllegalStateException("An illegal stack manipulation must not be applied");
+ }
+ }
+
+ /**
+ * Canonical representation of a legal stack manipulation which does not require any action.
+ */
+ enum Trivial implements StackManipulation {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ return StackSize.ZERO.toIncreasingSize();
+ }
+ }
+
+ /**
+ * A description of the size change that is imposed by some
+ * {@link StackManipulation}.
+ */
+ @EqualsAndHashCode
+ class Size {
+
+ /**
+ * The impact of any size operation onto the operand stack. This value can be negative if more values
+ * were consumed from the stack than added to it.
+ */
+ private final int sizeImpact;
+
+ /**
+ * The maximal size of stack slots this stack manipulation ever requires. If an operation for example pushes
+ * five values onto the stack and subsequently consumes three operations, this value should still be five
+ * to express that a stack operation requires at least five slots in order to be applicable.
+ */
+ private final int maximalSize;
+
+ /**
+ * Creates an immutable descriptor of the size change that is implied by some stack manipulation.
+ *
+ * @param sizeImpact The change of the size of the operand stack that is implied by some stack manipulation.
+ * @param maximalSize The maximal stack size that is required for executing this stack manipulation. Should
+ * never be negative number.
+ */
+ public Size(int sizeImpact, int maximalSize) {
+ this.sizeImpact = sizeImpact;
+ this.maximalSize = maximalSize;
+ }
+
+ /**
+ * Returns the size change on the operand stack that is represented by this instance.
+ *
+ * @return The size change on the operand stack that is represented by this instance.
+ */
+ public int getSizeImpact() {
+ return sizeImpact;
+ }
+
+ /**
+ * Returns the maximal interim size of the operand stack that is represented by this instance.
+ *
+ * @return The maximal interim size of the operand stack that is represented by this instance.
+ */
+ public int getMaximalSize() {
+ return maximalSize;
+ }
+
+ /**
+ * Concatenates this size representation with another size representation in order to represent the size
+ * change that is represented by both alterations of the operand stack size.
+ *
+ * @param other The other size representation.
+ * @return A new size representation representing both stack size requirements.
+ */
+ public Size aggregate(Size other) {
+ return aggregate(other.sizeImpact, other.maximalSize);
+ }
+
+ /**
+ * Aggregates a size change with this stack manipulation size.
+ *
+ * @param sizeChange The change in size the other operation implies.
+ * @param interimMaximalSize The interim maximal size of the operand stack that the other operation requires
+ * at least to function.
+ * @return The aggregated size.
+ */
+ private Size aggregate(int sizeChange, int interimMaximalSize) {
+ return new Size(sizeImpact + sizeChange, Math.max(maximalSize, sizeImpact + interimMaximalSize));
+ }
+ }
+
+ /**
+ * An immutable stack manipulation that aggregates a sequence of other stack manipulations.
+ */
+ @EqualsAndHashCode
+ class Compound implements StackManipulation {
+
+ /**
+ * The stack manipulations this compound operation represents in their application order.
+ */
+ private final List<StackManipulation> stackManipulations;
+
+ /**
+ * Creates a new compound stack manipulation.
+ *
+ * @param stackManipulation The stack manipulations to be composed in the order of their composition.
+ */
+ public Compound(StackManipulation... stackManipulation) {
+ this(Arrays.asList(stackManipulation));
+ }
+
+ /**
+ * Creates a new compound stack manipulation.
+ *
+ * @param stackManipulations The stack manipulations to be composed in the order of their composition.
+ */
+ public Compound(List<? extends StackManipulation> stackManipulations) {
+ this.stackManipulations = new ArrayList<StackManipulation>();
+ for (StackManipulation stackManipulation : stackManipulations) {
+ if (stackManipulation instanceof Compound) {
+ this.stackManipulations.addAll(((Compound) stackManipulation).stackManipulations);
+ } else if (!(stackManipulation instanceof Trivial)) {
+ this.stackManipulations.add(stackManipulation);
+ }
+ }
+ }
+
+ @Override
+ public boolean isValid() {
+ for (StackManipulation stackManipulation : stackManipulations) {
+ if (!stackManipulation.isValid()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ Size size = new Size(0, 0);
+ for (StackManipulation stackManipulation : stackManipulations) {
+ size = size.aggregate(stackManipulation.apply(methodVisitor, implementationContext));
+ }
+ return size;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/StackSize.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/StackSize.java
new file mode 100644
index 0000000..8772cdb
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/StackSize.java
@@ -0,0 +1,143 @@
+package net.bytebuddy.implementation.bytecode;
+
+/**
+ * Represents the size of a Java type on the operand stack.
+ */
+public enum StackSize {
+
+ /**
+ * An empty stack size.
+ */
+ ZERO(0),
+
+ /**
+ * A single slot stack size.
+ */
+ SINGLE(1),
+
+ /**
+ * A double slot stack size which is required by {@code long} and {@code double} values.
+ */
+ DOUBLE(2);
+
+ /**
+ * The size of the stack this instance represents.
+ */
+ private final int size;
+
+ /**
+ * Creates a new stack size.
+ *
+ * @param size The size of the stack this instance represents.
+ */
+ StackSize(int size) {
+ this.size = size;
+ }
+
+ /**
+ * Finds the operand stack size of a given Java type.
+ *
+ * @param type The type of interest.
+ * @return The given type's operand stack size.
+ */
+ public static StackSize of(Class<?> type) {
+ if (type == void.class) {
+ return ZERO;
+ } else if (type == double.class || type == long.class) {
+ return DOUBLE;
+ } else {
+ return SINGLE;
+ }
+ }
+
+ /**
+ * Represents a numeric size as a {@link StackSize}.
+ *
+ * @param size The size to represent. Must be {@code 0}, {@code 1} or {@code 2}.
+ * @return A stack size representation for the given value.
+ */
+ public static StackSize of(int size) {
+ switch (size) {
+ case 0:
+ return ZERO;
+ case 1:
+ return SINGLE;
+ case 2:
+ return DOUBLE;
+ default:
+ throw new IllegalArgumentException("Unexpected stack size value: " + size);
+ }
+ }
+
+ /**
+ * Returns the sum of all operand stack sizes.
+ *
+ * @param types The types of interest.
+ * @return The sum of their sizes.
+ */
+ public static int sizeOf(Iterable<? extends Class<?>> types) {
+ int size = 0;
+ for (Class<?> type : types) {
+ size += of(type).getSize();
+ }
+ return size;
+ }
+
+ /**
+ * The numeric value of this stack size representation.
+ *
+ * @return An integer representing the operand stack size.
+ */
+ public int getSize() {
+ return size;
+ }
+
+ /**
+ * Creates an instance of a
+ * {@link StackManipulation.Size}
+ * that describes a stack growth of this size.
+ *
+ * @return A stack size growth by the size represented by this stack size.
+ */
+ public StackManipulation.Size toIncreasingSize() {
+ return new StackManipulation.Size(getSize(), getSize());
+ }
+
+ /**
+ * Creates an instance of a
+ * {@link StackManipulation.Size}
+ * that describes a stack decrease of this size.
+ *
+ * @return A stack size decrease by the size represented by this stack size.
+ */
+ public StackManipulation.Size toDecreasingSize() {
+ return new StackManipulation.Size(-1 * getSize(), 0);
+ }
+
+ /**
+ * Determines the maximum of two stack size representations.
+ *
+ * @param stackSize The other stack size representation.
+ * @return The maximum of this and the other stack size.
+ */
+ public StackSize maximum(StackSize stackSize) {
+ switch (this) {
+ case ZERO:
+ return stackSize;
+ case SINGLE:
+ switch (stackSize) {
+ case DOUBLE:
+ return stackSize;
+ case SINGLE:
+ case ZERO:
+ return this;
+ default:
+ throw new AssertionError();
+ }
+ case DOUBLE:
+ return this;
+ default:
+ throw new AssertionError();
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/Throw.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/Throw.java
new file mode 100644
index 0000000..dc8fd67
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/Throw.java
@@ -0,0 +1,27 @@
+package net.bytebuddy.implementation.bytecode;
+
+import net.bytebuddy.implementation.Implementation;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Throws a {@link java.lang.Throwable} which must lie on top of the stack when this stack manipulation is called.
+ */
+public enum Throw implements StackManipulation {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitInsn(Opcodes.ATHROW);
+ return StackSize.SINGLE.toDecreasingSize();
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/TypeCreation.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/TypeCreation.java
new file mode 100644
index 0000000..52dd531
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/TypeCreation.java
@@ -0,0 +1,52 @@
+package net.bytebuddy.implementation.bytecode;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * A stack manipulation for creating an <i>undefined</i> type on which a constructor is to be called.
+ */
+ at EqualsAndHashCode
+public class TypeCreation implements StackManipulation {
+
+ /**
+ * The type that is being created.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Constructs a new type creation.
+ *
+ * @param typeDescription The type to be create.
+ */
+ protected TypeCreation(TypeDescription typeDescription) {
+ this.typeDescription = typeDescription;
+ }
+
+ /**
+ * Creates a type creation for the given type.
+ *
+ * @param typeDescription The type to be create.
+ * @return A stack manipulation that represents the creation of the given type.
+ */
+ public static StackManipulation of(TypeDescription typeDescription) {
+ if (typeDescription.isArray() || typeDescription.isPrimitive() || typeDescription.isAbstract()) {
+ throw new IllegalArgumentException(typeDescription + " is not instantiable");
+ }
+ return new TypeCreation(typeDescription);
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitTypeInsn(Opcodes.NEW, typeDescription.getInternalName());
+ return new Size(1, 1);
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/assign/Assigner.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/assign/Assigner.java
new file mode 100644
index 0000000..337fc06
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/assign/Assigner.java
@@ -0,0 +1,132 @@
+package net.bytebuddy.implementation.bytecode.assign;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.primitive.PrimitiveTypeAwareAssigner;
+import net.bytebuddy.implementation.bytecode.assign.primitive.VoidAwareAssigner;
+import net.bytebuddy.implementation.bytecode.assign.reference.ReferenceTypeAwareAssigner;
+
+/**
+ * An assigner is responsible for converting some type {@code A} to another type {@code B} if possible.
+ * <p> </p>
+ * An assigner is for example responsible for type casting, auto boxing or unboxing or for the widening of primitive
+ * types.
+ */
+ at SuppressFBWarnings(value = "IC_SUPERCLASS_USES_SUBCLASS_DURING_INITIALIZATION", justification = "Safe initialization is implied")
+public interface Assigner {
+
+ /**
+ * A default assigner that can handle {@code void}, primitive types and references.
+ */
+ Assigner DEFAULT = new VoidAwareAssigner(new PrimitiveTypeAwareAssigner(ReferenceTypeAwareAssigner.INSTANCE));
+
+ /**
+ * @param source The original type that is to be transformed into the {@code targetType}.
+ * @param target The target type into which the {@code sourceType} is to be converted.
+ * @param typing A hint whether the assignment should consider the runtime type of the source type,
+ * i.e. if type down or cross castings are allowed. If this hint is set, this is
+ * also an indication that {@code void} to non-{@code void} assignments are permitted.
+ * @return A stack manipulation that transforms the {@code sourceType} into the {@code targetType} if this
+ * is possible. An illegal stack manipulation otherwise.
+ */
+ StackManipulation assign(TypeDescription.Generic source, TypeDescription.Generic target, Typing typing);
+
+ /**
+ * Indicates for a type assignment, if a type casting should be applied in case that two types are not statically assignable.
+ * Also, a dynamic typing indicates that void values are assignable to other types by assigning the target type's default value.
+ */
+ enum Typing {
+
+ /**
+ * Requires static typing.
+ */
+ STATIC(false),
+
+ /**
+ * Allows dynamic typing.
+ */
+ DYNAMIC(true);
+
+ /**
+ * {@code true} if dynamic typing is a legitimate choice.
+ */
+ private final boolean dynamic;
+
+ /**
+ * Creates a new typing hint.
+ *
+ * @param dynamic {@code true} if dynamic typing is a legitimate choice.
+ */
+ Typing(boolean dynamic) {
+ this.dynamic = dynamic;
+ }
+
+ /**
+ * Resolves a typing constant for the presented boolean where {@code true} indicates that dynamic typing is a legitimate choice.
+ *
+ * @param dynamic An indicator for if dynamic typing is a legitimate choice.
+ * @return A corresponding typing constant.
+ */
+ public static Typing of(boolean dynamic) {
+ return dynamic
+ ? DYNAMIC
+ : STATIC;
+ }
+
+ /**
+ * Checks if this instance's typing behavior permits dynamic typing.
+ *
+ * @return {@code true} if dynamic typing is a legitimate choice.
+ */
+ public boolean isDynamic() {
+ return dynamic;
+ }
+ }
+
+ /**
+ * An assigner that only allows to assign types if they are equal to another.
+ */
+ enum EqualTypesOnly implements Assigner {
+
+ /**
+ * An type assigner that only considers equal generic types to be assignable.
+ */
+ GENERIC {
+ @Override
+ public StackManipulation assign(TypeDescription.Generic source, TypeDescription.Generic target, Typing typing) {
+ return source.equals(target)
+ ? StackManipulation.Trivial.INSTANCE
+ : StackManipulation.Illegal.INSTANCE;
+ }
+ },
+
+ /**
+ * A type assigner that considers two generic types to be equal if their erasure is equal.
+ */
+ ERASURE {
+ @Override
+ public StackManipulation assign(TypeDescription.Generic source, TypeDescription.Generic target, Typing typing) {
+ return source.asErasure().equals(target.asErasure())
+ ? StackManipulation.Trivial.INSTANCE
+ : StackManipulation.Illegal.INSTANCE;
+ }
+ };
+ }
+
+ /**
+ * An assigner that does not allow any assignments.
+ */
+ enum Refusing implements Assigner {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public StackManipulation assign(TypeDescription.Generic source, TypeDescription.Generic target, Typing typing) {
+ return StackManipulation.Illegal.INSTANCE;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/assign/TypeCasting.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/assign/TypeCasting.java
new file mode 100644
index 0000000..3f63ee2
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/assign/TypeCasting.java
@@ -0,0 +1,56 @@
+package net.bytebuddy.implementation.bytecode.assign;
+
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * A stack manipulation for a type down casting. Such castings are not implicit but must be performed explicitly.
+ */
+ at EqualsAndHashCode
+public class TypeCasting implements StackManipulation {
+
+ /**
+ * The type description to which a value should be casted.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new type casting.
+ *
+ * @param typeDescription The type description to which a value should be casted.
+ */
+ protected TypeCasting(TypeDescription typeDescription) {
+ this.typeDescription = typeDescription;
+ }
+
+ /**
+ * Creates a casting to the given, non-primitive type.
+ *
+ * @param typeDefinition The type to which a value should be casted.
+ * @return A stack manipulation that represents the casting.
+ */
+ public static StackManipulation to(TypeDefinition typeDefinition) {
+ if (typeDefinition.isPrimitive()) {
+ throw new IllegalArgumentException("Cannot cast to primitive type: " + typeDefinition);
+ }
+ return new TypeCasting(typeDefinition.asErasure());
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, typeDescription.getInternalName());
+ return StackSize.ZERO.toIncreasingSize();
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/assign/package-info.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/assign/package-info.java
new file mode 100644
index 0000000..01c237b
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/assign/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * An {@link net.bytebuddy.implementation.bytecode.assign.Assigner} is responsible for transforming
+ * a given {@link net.bytebuddy.description.type.TypeDescription} into another one. In doing so, an assigner is also
+ * able to determine that some assignment is illegal.
+ */
+package net.bytebuddy.implementation.bytecode.assign;
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveBoxingDelegate.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveBoxingDelegate.java
new file mode 100644
index 0000000..891619c
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveBoxingDelegate.java
@@ -0,0 +1,171 @@
+package net.bytebuddy.implementation.bytecode.assign.primitive;
+
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * This delegate is responsible for boxing a primitive types to their wrapper equivalents.
+ */
+public enum PrimitiveBoxingDelegate {
+
+ /**
+ * The boxing delegate for {@code boolean} values.
+ */
+ BOOLEAN(Boolean.class, StackSize.ZERO, "valueOf", "(Z)Ljava/lang/Boolean;"),
+
+ /**
+ * The boxing delegate for {@code byte} values.
+ */
+ BYTE(Byte.class, StackSize.ZERO, "valueOf", "(B)Ljava/lang/Byte;"),
+
+ /**
+ * The boxing delegate for {@code short} values.
+ */
+ SHORT(Short.class, StackSize.ZERO, "valueOf", "(S)Ljava/lang/Short;"),
+
+ /**
+ * The boxing delegate for {@code char} values.
+ */
+ CHARACTER(Character.class, StackSize.ZERO, "valueOf", "(C)Ljava/lang/Character;"),
+
+ /**
+ * The boxing delegate for {@code int} values.
+ */
+ INTEGER(Integer.class, StackSize.ZERO, "valueOf", "(I)Ljava/lang/Integer;"),
+
+ /**
+ * The boxing delegate for {@code long} values.
+ */
+ LONG(Long.class, StackSize.SINGLE, "valueOf", "(J)Ljava/lang/Long;"),
+
+ /**
+ * The boxing delegate for {@code float} values.
+ */
+ FLOAT(Float.class, StackSize.ZERO, "valueOf", "(F)Ljava/lang/Float;"),
+
+ /**
+ * The boxing delegate for {@code double} values.
+ */
+ DOUBLE(Double.class, StackSize.SINGLE, "valueOf", "(D)Ljava/lang/Double;");
+
+ /**
+ * A description of a wrapper type.
+ */
+ private final TypeDescription wrapperType;
+
+ /**
+ * The size decrease after a primitive type was wrapped.
+ */
+ private final StackManipulation.Size size;
+
+ /**
+ * The name of the method for boxing a primitive value as its wrapper type.
+ */
+ private final String boxingMethodName;
+
+ /**
+ * The descriptor of the method for boxing a primitive value as its wrapper type.
+ */
+ private final String boxingMethodDescriptor;
+
+ /**
+ * Creates a new primitive boxing delegate.
+ *
+ * @param wrapperType A description of a wrapper type.
+ * @param sizeDifference The size difference between a primitive type and its wrapper type.
+ * @param boxingMethodName The name of the method for boxing a primitive value as its wrapper type.
+ * @param boxingMethodDescriptor The descriptor of the method for boxing a primitive value as its wrapper type.
+ */
+ PrimitiveBoxingDelegate(Class<?> wrapperType,
+ StackSize sizeDifference,
+ String boxingMethodName,
+ String boxingMethodDescriptor) {
+ this.wrapperType = new TypeDescription.ForLoadedType(wrapperType);
+ this.size = sizeDifference.toDecreasingSize();
+ this.boxingMethodName = boxingMethodName;
+ this.boxingMethodDescriptor = boxingMethodDescriptor;
+ }
+
+ /**
+ * Locates a boxing delegate for a given primitive type.
+ *
+ * @param typeDefinition A non-void primitive type.
+ * @return A delegate capable of boxing the given primitve type.
+ */
+ public static PrimitiveBoxingDelegate forPrimitive(TypeDefinition typeDefinition) {
+ if (typeDefinition.represents(boolean.class)) {
+ return BOOLEAN;
+ } else if (typeDefinition.represents(byte.class)) {
+ return BYTE;
+ } else if (typeDefinition.represents(short.class)) {
+ return SHORT;
+ } else if (typeDefinition.represents(char.class)) {
+ return CHARACTER;
+ } else if (typeDefinition.represents(int.class)) {
+ return INTEGER;
+ } else if (typeDefinition.represents(long.class)) {
+ return LONG;
+ } else if (typeDefinition.represents(float.class)) {
+ return FLOAT;
+ } else if (typeDefinition.represents(double.class)) {
+ return DOUBLE;
+ } else {
+ throw new IllegalArgumentException("Not a non-void, primitive type: " + typeDefinition);
+ }
+ }
+
+ /**
+ * Creates a stack manipulation that boxes the represented primitive type and applies a chained assignment
+ * to the result of this boxing operation.
+ *
+ * @param target The type that is target of the assignment operation.
+ * @param chainedAssigner The assigner that is to be used to perform the chained assignment.
+ * @param typing Determines if an assignment to an incompatible type should be enforced by a casting.
+ * @return A stack manipulation that represents the described assignment operation.
+ */
+ public StackManipulation assignBoxedTo(TypeDescription.Generic target, Assigner chainedAssigner, Assigner.Typing typing) {
+ return new BoxingStackManipulation(chainedAssigner.assign(wrapperType.asGenericType(), target, typing));
+ }
+
+ /**
+ * A stack manipulation for boxing a primitive type into its wrapper type.
+ */
+ private class BoxingStackManipulation implements StackManipulation {
+
+ /**
+ * A stack manipulation that is applied after the boxing of the top-most value on the operand stack.
+ */
+ private final StackManipulation stackManipulation;
+
+ /**
+ * Creates a new boxing stack manipulation.
+ *
+ * @param stackManipulation A stack manipulation that is applied after the boxing of the top-most value on
+ * the operand stack.
+ */
+ public BoxingStackManipulation(StackManipulation stackManipulation) {
+ this.stackManipulation = stackManipulation;
+ }
+
+ @Override
+ public boolean isValid() {
+ return stackManipulation.isValid();
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
+ wrapperType.getInternalName(),
+ boxingMethodName,
+ boxingMethodDescriptor,
+ false);
+ return size.aggregate(stackManipulation.apply(methodVisitor, implementationContext));
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveTypeAwareAssigner.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveTypeAwareAssigner.java
new file mode 100644
index 0000000..32c670f
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveTypeAwareAssigner.java
@@ -0,0 +1,54 @@
+package net.bytebuddy.implementation.bytecode.assign.primitive;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+
+/**
+ * This assigner is able to handle non-{@code void}, primitive types. This means:
+ * <ol>
+ * <li>If a primitive type is assigned to a non-primitive type, it will attempt to widen the source type into the
+ * target type.</li>
+ * <li>If a primitive type is assigned to a non-primitive type, it will attempt to box the source type and then
+ * query the chained assigner for assigning the boxed type to the target type.</li>
+ * <li>If a non-primitive type is assigned to a primitive type, it will unbox the source type and then attempt a
+ * widening of the unboxed type into the target type. If the source type does not represent a wrapper type, it will
+ * attempt to infer the boxing type from the target type and check if the source type is assignable to this wrapper
+ * type.</li>
+ * <li>If two non-primitive types are subject of the assignment, it will delegate the assignment to its chained
+ * assigner.</li>
+ * </ol>
+ */
+ at EqualsAndHashCode
+public class PrimitiveTypeAwareAssigner implements Assigner {
+
+ /**
+ * Another assigner that is aware of assigning reference types. This assigner is queried for assigning
+ * non-primitive types or for assigning a boxed type to another non-primitive type.
+ */
+ private final Assigner referenceTypeAwareAssigner;
+
+ /**
+ * Creates a new assigner with the given delegate.
+ *
+ * @param referenceTypeAwareAssigner A chained assigner that is queried for assignments not involving primitive
+ * types.
+ */
+ public PrimitiveTypeAwareAssigner(Assigner referenceTypeAwareAssigner) {
+ this.referenceTypeAwareAssigner = referenceTypeAwareAssigner;
+ }
+
+ @Override
+ public StackManipulation assign(TypeDescription.Generic source, TypeDescription.Generic target, Typing typing) {
+ if (source.isPrimitive() && target.isPrimitive()) {
+ return PrimitiveWideningDelegate.forPrimitive(source).widenTo(target);
+ } else if (source.isPrimitive() /* && !target.isPrimitive() */) {
+ return PrimitiveBoxingDelegate.forPrimitive(source).assignBoxedTo(target, referenceTypeAwareAssigner, typing);
+ } else if (/* !source.isPrimitive() && */ target.isPrimitive()) {
+ return PrimitiveUnboxingDelegate.forReferenceType(source).assignUnboxedTo(target, referenceTypeAwareAssigner, typing);
+ } else /* !source.isPrimitive() && !target.isPrimitive()) */ {
+ return referenceTypeAwareAssigner.assign(source, target, typing);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveUnboxingDelegate.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveUnboxingDelegate.java
new file mode 100644
index 0000000..0f7f6ea
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveUnboxingDelegate.java
@@ -0,0 +1,309 @@
+package net.bytebuddy.implementation.bytecode.assign.primitive;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * This delegate is responsible for unboxing a wrapper type to their primitive equivalents.
+ */
+public enum PrimitiveUnboxingDelegate implements StackManipulation {
+
+ /**
+ * The unboxing delegate for {@code Boolean} types.
+ */
+ BOOLEAN(Boolean.class, boolean.class, StackSize.ZERO, "booleanValue", "()Z"),
+
+ /**
+ * The unboxing delegate for {@code Byte} types.
+ */
+ BYTE(Byte.class, byte.class, StackSize.ZERO, "byteValue", "()B"),
+
+ /**
+ * The unboxing delegate for {@code Short} types.
+ */
+ SHORT(Short.class, short.class, StackSize.ZERO, "shortValue", "()S"),
+
+ /**
+ * The unboxing delegate for {@code Character} types.
+ */
+ CHARACTER(Character.class, char.class, StackSize.ZERO, "charValue", "()C"),
+
+ /**
+ * The unboxing delegate for {@code Integer} types.
+ */
+ INTEGER(Integer.class, int.class, StackSize.ZERO, "intValue", "()I"),
+
+ /**
+ * The unboxing delegate for {@code Long} types.
+ */
+ LONG(Long.class, long.class, StackSize.SINGLE, "longValue", "()J"),
+
+ /**
+ * The unboxing delegate for {@code Float} types.
+ */
+ FLOAT(Float.class, float.class, StackSize.ZERO, "floatValue", "()F"),
+
+ /**
+ * The unboxing delegate for {@code Double} types.
+ */
+ DOUBLE(Double.class, double.class, StackSize.SINGLE, "doubleValue", "()D");
+
+ /**
+ * The wrapper type of the represented primitive type.
+ */
+ private final TypeDescription wrapperType;
+
+ /**
+ * The represented primitive type.
+ */
+ private final TypeDescription primitiveType;
+
+ /**
+ * The size increase after a wrapper type was unwrapped.
+ */
+ private final Size size;
+
+ /**
+ * The name of the method for unboxing a wrapper value to its primitive value.
+ */
+ private final String unboxingMethodName;
+
+ /**
+ * The descriptor of the method for unboxing a wrapper value to its primitive value.
+ */
+ private final String unboxingMethodDescriptor;
+
+ /**
+ * Creates a new primitive unboxing delegate.
+ *
+ * @param wrapperType The wrapper type of the represented primitive type.
+ * @param primitiveType The represented primitive type.
+ * @param sizeDifference The size difference between the wrapper type and its primitive value.
+ * @param unboxingMethodName The name of the method for unboxing a wrapper value to its primitive value.
+ * @param unboxingMethodDescriptor The descriptor of the method for unboxing a wrapper value to its primitive value.
+ */
+ PrimitiveUnboxingDelegate(Class<?> wrapperType,
+ Class<?> primitiveType,
+ StackSize sizeDifference,
+ String unboxingMethodName,
+ String unboxingMethodDescriptor) {
+ this.size = sizeDifference.toIncreasingSize();
+ this.wrapperType = new TypeDescription.ForLoadedType(wrapperType);
+ this.primitiveType = new TypeDescription.ForLoadedType(primitiveType);
+ this.unboxingMethodName = unboxingMethodName;
+ this.unboxingMethodDescriptor = unboxingMethodDescriptor;
+ }
+
+ /**
+ * Locates a primitive unboxing delegate for a given primitive type.
+ *
+ * @param typeDescription A description of the primitive type.
+ * @return A corresponding primitive unboxing delegate.
+ */
+ protected static PrimitiveUnboxingDelegate forPrimitive(TypeDescription.Generic typeDescription) {
+ if (typeDescription.represents(boolean.class)) {
+ return BOOLEAN;
+ } else if (typeDescription.represents(byte.class)) {
+ return BYTE;
+ } else if (typeDescription.represents(short.class)) {
+ return SHORT;
+ } else if (typeDescription.represents(char.class)) {
+ return CHARACTER;
+ } else if (typeDescription.represents(int.class)) {
+ return INTEGER;
+ } else if (typeDescription.represents(long.class)) {
+ return LONG;
+ } else if (typeDescription.represents(float.class)) {
+ return FLOAT;
+ } else if (typeDescription.represents(double.class)) {
+ return DOUBLE;
+ } else {
+ throw new IllegalArgumentException("Expected non-void primitive type instead of " + typeDescription);
+ }
+ }
+
+ /**
+ * Creates an unboxing responsible that is capable of unboxing a wrapper type.
+ * <ol>
+ * <li>If the reference type represents a wrapper type, the wrapper type will simply be unboxed.</li>
+ * <li>If the reference type does not represent a wrapper type, the wrapper type will be inferred by the primitive target
+ * type that is later given to the
+ * {@link net.bytebuddy.implementation.bytecode.assign.primitive.PrimitiveUnboxingDelegate.UnboxingResponsible}
+ * in order to then check if the given type is assignable to the inferred wrapper type.</li>
+ * </ol>
+ *
+ * @param typeDefinition A non-primitive type.
+ * @return An unboxing responsible capable of performing an unboxing operation while considering a further assignment
+ * of the unboxed value.
+ */
+ public static UnboxingResponsible forReferenceType(TypeDefinition typeDefinition) {
+ if (typeDefinition.isPrimitive()) {
+ throw new IllegalArgumentException("Expected reference type instead of " + typeDefinition);
+ } else if (typeDefinition.represents(Boolean.class)) {
+ return ExplicitlyTypedUnboxingResponsible.BOOLEAN;
+ } else if (typeDefinition.represents(Byte.class)) {
+ return ExplicitlyTypedUnboxingResponsible.BYTE;
+ } else if (typeDefinition.represents(Short.class)) {
+ return ExplicitlyTypedUnboxingResponsible.SHORT;
+ } else if (typeDefinition.represents(Character.class)) {
+ return ExplicitlyTypedUnboxingResponsible.CHARACTER;
+ } else if (typeDefinition.represents(Integer.class)) {
+ return ExplicitlyTypedUnboxingResponsible.INTEGER;
+ } else if (typeDefinition.represents(Long.class)) {
+ return ExplicitlyTypedUnboxingResponsible.LONG;
+ } else if (typeDefinition.represents(Float.class)) {
+ return ExplicitlyTypedUnboxingResponsible.FLOAT;
+ } else if (typeDefinition.represents(Double.class)) {
+ return ExplicitlyTypedUnboxingResponsible.DOUBLE;
+ } else {
+ return new ImplicitlyTypedUnboxingResponsible(typeDefinition.asGenericType());
+ }
+ }
+
+ /**
+ * Returns the wrapper type that this unboxing delegate represents.
+ *
+ * @return A generic version of this delegate's wrapper type.
+ */
+ protected TypeDescription.Generic getWrapperType() {
+ return wrapperType.asGenericType();
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
+ wrapperType.asErasure().getInternalName(),
+ unboxingMethodName,
+ unboxingMethodDescriptor,
+ false);
+ return size;
+ }
+
+ /**
+ * An explicitly types unboxing responsible is applied for directly unboxing a wrapper type.
+ */
+ protected enum ExplicitlyTypedUnboxingResponsible implements UnboxingResponsible {
+
+ /**
+ * An unboxing responsible for unboxing a {@link java.lang.Boolean} type.
+ */
+ BOOLEAN(PrimitiveUnboxingDelegate.BOOLEAN),
+
+ /**
+ * An unboxing responsible for unboxing a {@link java.lang.Byte} type.
+ */
+ BYTE(PrimitiveUnboxingDelegate.BYTE),
+
+ /**
+ * An unboxing responsible for unboxing a {@link java.lang.Short} type.
+ */
+ SHORT(PrimitiveUnboxingDelegate.SHORT),
+
+ /**
+ * An unboxing responsible for unboxing a {@link java.lang.Character} type.
+ */
+ CHARACTER(PrimitiveUnboxingDelegate.CHARACTER),
+
+ /**
+ * An unboxing responsible for unboxing a {@link java.lang.Integer} type.
+ */
+ INTEGER(PrimitiveUnboxingDelegate.INTEGER),
+
+ /**
+ * An unboxing responsible for unboxing a {@link java.lang.Long} type.
+ */
+ LONG(PrimitiveUnboxingDelegate.LONG),
+
+ /**
+ * An unboxing responsible for unboxing a {@link java.lang.Float} type.
+ */
+ FLOAT(PrimitiveUnboxingDelegate.FLOAT),
+
+ /**
+ * An unboxing responsible for unboxing a {@link java.lang.Double} type.
+ */
+ DOUBLE(PrimitiveUnboxingDelegate.DOUBLE);
+
+ /**
+ * The primitive unboxing delegate for handling the given wrapper type.
+ */
+ private final PrimitiveUnboxingDelegate primitiveUnboxingDelegate;
+
+ /**
+ * Creates a new explicitly typed unboxing responsible.
+ *
+ * @param primitiveUnboxingDelegate The primitive unboxing delegate for handling the given wrapper type.
+ */
+ ExplicitlyTypedUnboxingResponsible(PrimitiveUnboxingDelegate primitiveUnboxingDelegate) {
+ this.primitiveUnboxingDelegate = primitiveUnboxingDelegate;
+ }
+
+ @Override
+ public StackManipulation assignUnboxedTo(TypeDescription.Generic targetType, Assigner assigner, Assigner.Typing typing) {
+ return new Compound(
+ primitiveUnboxingDelegate,
+ PrimitiveWideningDelegate.forPrimitive(primitiveUnboxingDelegate.primitiveType).widenTo(targetType));
+ }
+ }
+
+ /**
+ * Implementations represent an unboxing delegate that is able to perform the unboxing operation.
+ */
+ public interface UnboxingResponsible {
+
+ /**
+ * Attempts to unbox the represented type in order to assign the unboxed value to the given target type
+ * while using the assigner that is provided by the method call.
+ *
+ * @param target The type that is the desired outcome of the assignment.
+ * @param assigner The assigner used to assign the unboxed type to the target type.
+ * @param typing Determines if a type-casting should be attempted for incompatible types.
+ * @return A stack manipulation representing this assignment if such an assignment is possible. An illegal
+ * assignment otherwise.
+ */
+ StackManipulation assignUnboxedTo(TypeDescription.Generic target, Assigner assigner, Assigner.Typing typing);
+ }
+
+ /**
+ * An unboxing responsible for an implicitly typed value. This implementation is applied for source types that
+ * were not found to be of a given wrapper type. Instead, this unboxing responsible tries to assign the
+ * source type to the primitive target type's wrapper type before performing an unboxing operation.
+ */
+ @EqualsAndHashCode
+ protected static class ImplicitlyTypedUnboxingResponsible implements UnboxingResponsible {
+
+ /**
+ * The original type which should be unboxed but is not of any known wrapper type.
+ */
+ private final TypeDescription.Generic originalType;
+
+ /**
+ * Creates a new implicitly typed unboxing responsible.
+ *
+ * @param originalType The original type which should be unboxed but is not of any known wrapper type.
+ */
+ protected ImplicitlyTypedUnboxingResponsible(TypeDescription.Generic originalType) {
+ this.originalType = originalType;
+ }
+
+ @Override
+ public StackManipulation assignUnboxedTo(TypeDescription.Generic target, Assigner assigner, Assigner.Typing typing) {
+ PrimitiveUnboxingDelegate primitiveUnboxingDelegate = PrimitiveUnboxingDelegate.forPrimitive(target);
+ return new Compound(
+ assigner.assign(originalType, primitiveUnboxingDelegate.getWrapperType(), typing),
+ primitiveUnboxingDelegate);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveWideningDelegate.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveWideningDelegate.java
new file mode 100644
index 0000000..811905b
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveWideningDelegate.java
@@ -0,0 +1,285 @@
+package net.bytebuddy.implementation.bytecode.assign.primitive;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * This delegate is responsible for widening a primitive type to represent a <i>larger</i> primitive type. The
+ * rules for this widening are equivalent to those in the <a href="http://docs.oracle.com/javase/specs/">JLS</a>.
+ */
+public enum PrimitiveWideningDelegate {
+
+ /**
+ * The widening delegate for {@code boolean} values.
+ */
+ BOOLEAN(StackManipulation.Trivial.INSTANCE, // to boolean
+ StackManipulation.Illegal.INSTANCE, // to byte
+ StackManipulation.Illegal.INSTANCE, // to short
+ StackManipulation.Illegal.INSTANCE, // to character
+ StackManipulation.Illegal.INSTANCE, // to integer
+ StackManipulation.Illegal.INSTANCE, // to long
+ StackManipulation.Illegal.INSTANCE, // to float
+ StackManipulation.Illegal.INSTANCE), // to double
+
+ /**
+ * The widening delegate for {@code byte} values.
+ */
+ BYTE(StackManipulation.Illegal.INSTANCE, // to boolean
+ StackManipulation.Trivial.INSTANCE, // to byte
+ StackManipulation.Trivial.INSTANCE, // to short
+ StackManipulation.Illegal.INSTANCE, // to character
+ StackManipulation.Trivial.INSTANCE, // to integer
+ new WideningStackManipulation(Opcodes.I2L, StackSize.SINGLE.toIncreasingSize()), // to long
+ new WideningStackManipulation(Opcodes.I2F, StackSize.ZERO.toIncreasingSize()), // to float
+ new WideningStackManipulation(Opcodes.I2L, StackSize.SINGLE.toIncreasingSize())), // to double
+
+ /**
+ * The widening delegate for {@code short} values.
+ */
+ SHORT(StackManipulation.Illegal.INSTANCE, // to boolean
+ StackManipulation.Illegal.INSTANCE, // to byte
+ StackManipulation.Trivial.INSTANCE, // to short
+ StackManipulation.Illegal.INSTANCE, // to character
+ StackManipulation.Trivial.INSTANCE, // to integer
+ new WideningStackManipulation(Opcodes.I2L, StackSize.SINGLE.toIncreasingSize()), // to long
+ new WideningStackManipulation(Opcodes.I2F, StackSize.ZERO.toIncreasingSize()), // to float
+ new WideningStackManipulation(Opcodes.I2D, StackSize.SINGLE.toIncreasingSize())), // to double
+
+ /**
+ * The widening delegate for {@code char} values.
+ */
+ CHARACTER(StackManipulation.Illegal.INSTANCE, // to boolean
+ StackManipulation.Illegal.INSTANCE, // to byte
+ StackManipulation.Illegal.INSTANCE, // to short
+ StackManipulation.Trivial.INSTANCE, // to character
+ StackManipulation.Trivial.INSTANCE, // to integer
+ new WideningStackManipulation(Opcodes.I2L, StackSize.SINGLE.toIncreasingSize()), // to long
+ new WideningStackManipulation(Opcodes.I2F, StackSize.ZERO.toIncreasingSize()), // to float
+ new WideningStackManipulation(Opcodes.I2D, StackSize.SINGLE.toIncreasingSize())), // to double
+
+ /**
+ * The widening delegate for {@code int} values.
+ */
+ INTEGER(StackManipulation.Illegal.INSTANCE, // to boolean
+ StackManipulation.Illegal.INSTANCE, // to byte
+ StackManipulation.Illegal.INSTANCE, // to short
+ StackManipulation.Illegal.INSTANCE, // to character
+ StackManipulation.Trivial.INSTANCE, // to integer
+ new WideningStackManipulation(Opcodes.I2L, StackSize.SINGLE.toIncreasingSize()), // to long
+ new WideningStackManipulation(Opcodes.I2F, StackSize.ZERO.toIncreasingSize()), // to float
+ new WideningStackManipulation(Opcodes.I2D, StackSize.SINGLE.toIncreasingSize())), // to double
+
+ /**
+ * The widening delegate for {@code long} values.
+ */
+ LONG(StackManipulation.Illegal.INSTANCE, // to boolean
+ StackManipulation.Illegal.INSTANCE, // to byte
+ StackManipulation.Illegal.INSTANCE, // to short
+ StackManipulation.Illegal.INSTANCE, // to character
+ StackManipulation.Illegal.INSTANCE, // to integer
+ StackManipulation.Trivial.INSTANCE, // to long
+ new WideningStackManipulation(Opcodes.L2F, StackSize.SINGLE.toDecreasingSize()), // to float
+ new WideningStackManipulation(Opcodes.L2D, StackSize.ZERO.toIncreasingSize())), // to double
+
+ /**
+ * The widening delegate for {@code float} values.
+ */
+ FLOAT(StackManipulation.Illegal.INSTANCE, // to boolean
+ StackManipulation.Illegal.INSTANCE, // to byte
+ StackManipulation.Illegal.INSTANCE, // to short
+ StackManipulation.Illegal.INSTANCE, // to character
+ StackManipulation.Illegal.INSTANCE, // to integer
+ StackManipulation.Illegal.INSTANCE, // to long
+ StackManipulation.Trivial.INSTANCE, // to float
+ new WideningStackManipulation(Opcodes.F2D, StackSize.SINGLE.toIncreasingSize())), // to double
+
+ /**
+ * The widening delegate for {@code double} values.
+ */
+ DOUBLE(StackManipulation.Illegal.INSTANCE, // to boolean
+ StackManipulation.Illegal.INSTANCE, // to byte
+ StackManipulation.Illegal.INSTANCE, // to short
+ StackManipulation.Illegal.INSTANCE, // to character
+ StackManipulation.Illegal.INSTANCE, // to integer
+ StackManipulation.Illegal.INSTANCE, // to long
+ StackManipulation.Illegal.INSTANCE, // to float
+ StackManipulation.Trivial.INSTANCE); // to double
+
+ /**
+ * A stack manipulation that widens the type that is represented by this instance to a {@code boolean}.
+ */
+ private final StackManipulation toBooleanStackManipulation;
+
+ /**
+ * A stack manipulation that widens the type that is represented by this instance to a {@code byte}.
+ */
+ private final StackManipulation toByteStackManipulation;
+
+ /**
+ * A stack manipulation that widens the type that is represented by this instance to a {@code short}.
+ */
+ private final StackManipulation toShortStackManipulation;
+
+ /**
+ * A stack manipulation that widens the type that is represented by this instance to a {@code char}.
+ */
+ private final StackManipulation toCharacterStackManipulation;
+
+ /**
+ * A stack manipulation that widens the type that is represented by this instance to a {@code int}.
+ */
+ private final StackManipulation toIntegerStackManipulation;
+
+ /**
+ * A stack manipulation that widens the type that is represented by this instance to a {@code long}.
+ */
+ private final StackManipulation toLongStackManipulation;
+
+ /**
+ * A stack manipulation that widens the type that is represented by this instance to a {@code float}.
+ */
+ private final StackManipulation toFloatStackManipulation;
+
+ /**
+ * A stack manipulation that widens the type that is represented by this instance to a {@code double}.
+ */
+ private final StackManipulation toDoubleStackManipulation;
+
+ /**
+ * Creates a new primitive widening delegate.
+ *
+ * @param toBooleanStackManipulation A stack manipulation that widens the type that is represented by this
+ * instance to a {@code boolean}.
+ * @param toByteStackManipulation A stack manipulation that widens the type that is represented by this
+ * instance to a {@code byte}.
+ * @param toShortStackManipulation A stack manipulation that widens the type that is represented by this
+ * instance to a {@code short}.
+ * @param toCharacterStackManipulation A stack manipulation that widens the type that is represented by this
+ * instance to a {@code char}.
+ * @param toIntegerStackManipulation A stack manipulation that widens the type that is represented by this
+ * instance to a {@code int}.
+ * @param toLongStackManipulation A stack manipulation that widens the type that is represented by this
+ * instance to a {@code long}.
+ * @param toFloatStackManipulation A stack manipulation that widens the type that is represented by this
+ * instance to a {@code float}.
+ * @param toDoubleStackManipulation A stack manipulation that widens the type that is represented by this
+ * instance to a {@code double}.
+ */
+ PrimitiveWideningDelegate(StackManipulation toBooleanStackManipulation,
+ StackManipulation toByteStackManipulation,
+ StackManipulation toShortStackManipulation,
+ StackManipulation toCharacterStackManipulation,
+ StackManipulation toIntegerStackManipulation,
+ StackManipulation toLongStackManipulation,
+ StackManipulation toFloatStackManipulation,
+ StackManipulation toDoubleStackManipulation) {
+ this.toBooleanStackManipulation = toBooleanStackManipulation;
+ this.toByteStackManipulation = toByteStackManipulation;
+ this.toShortStackManipulation = toShortStackManipulation;
+ this.toCharacterStackManipulation = toCharacterStackManipulation;
+ this.toIntegerStackManipulation = toIntegerStackManipulation;
+ this.toLongStackManipulation = toLongStackManipulation;
+ this.toFloatStackManipulation = toFloatStackManipulation;
+ this.toDoubleStackManipulation = toDoubleStackManipulation;
+ }
+
+ /**
+ * Locates the delegate that is capable of widening the given type into another type.
+ *
+ * @param typeDefinition A non-void primitive type that is to be widened into another type.
+ * @return A delegate for the given type.
+ */
+ public static PrimitiveWideningDelegate forPrimitive(TypeDefinition typeDefinition) {
+ if (typeDefinition.represents(boolean.class)) {
+ return BOOLEAN;
+ } else if (typeDefinition.represents(byte.class)) {
+ return BYTE;
+ } else if (typeDefinition.represents(short.class)) {
+ return SHORT;
+ } else if (typeDefinition.represents(char.class)) {
+ return CHARACTER;
+ } else if (typeDefinition.represents(int.class)) {
+ return INTEGER;
+ } else if (typeDefinition.represents(long.class)) {
+ return LONG;
+ } else if (typeDefinition.represents(float.class)) {
+ return FLOAT;
+ } else if (typeDefinition.represents(double.class)) {
+ return DOUBLE;
+ } else {
+ throw new IllegalArgumentException("Not a primitive, non-void type: " + typeDefinition);
+ }
+ }
+
+ /**
+ * Attempts to widen the represented type into another type.
+ *
+ * @param typeDefinition A non-void primitive type that is the expected result of the widening operation.
+ * @return A widening instruction or an illegal stack manipulation if such widening is not legitimate.
+ */
+ public StackManipulation widenTo(TypeDefinition typeDefinition) {
+ if (typeDefinition.represents(boolean.class)) {
+ return toBooleanStackManipulation;
+ } else if (typeDefinition.represents(byte.class)) {
+ return toByteStackManipulation;
+ } else if (typeDefinition.represents(short.class)) {
+ return toShortStackManipulation;
+ } else if (typeDefinition.represents(char.class)) {
+ return toCharacterStackManipulation;
+ } else if (typeDefinition.represents(int.class)) {
+ return toIntegerStackManipulation;
+ } else if (typeDefinition.represents(long.class)) {
+ return toLongStackManipulation;
+ } else if (typeDefinition.represents(float.class)) {
+ return toFloatStackManipulation;
+ } else if (typeDefinition.represents(double.class)) {
+ return toDoubleStackManipulation;
+ } else {
+ throw new IllegalArgumentException("Not a primitive non-void type: " + typeDefinition);
+ }
+ }
+
+ /**
+ * A stack manipulation that widens a primitive type into a more general primitive type.
+ */
+ @EqualsAndHashCode
+ protected static class WideningStackManipulation implements StackManipulation {
+
+ /**
+ * The opcode for executing the conversion.
+ */
+ private final int conversionOpcode;
+
+ /**
+ * The size change of applying the conversion.
+ */
+ private final Size size;
+
+ /**
+ * Creates a new widening stack manipulation.
+ *
+ * @param conversionOpcode The opcode for executing the conversion.
+ * @param size The size change of applying the conversion.
+ */
+ protected WideningStackManipulation(int conversionOpcode, Size size) {
+ this.conversionOpcode = conversionOpcode;
+ this.size = size;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitInsn(conversionOpcode);
+ return size;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/assign/primitive/VoidAwareAssigner.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/assign/primitive/VoidAwareAssigner.java
new file mode 100644
index 0000000..295f1a5
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/assign/primitive/VoidAwareAssigner.java
@@ -0,0 +1,53 @@
+package net.bytebuddy.implementation.bytecode.assign.primitive;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.bytecode.Removal;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.constant.DefaultValue;
+
+/**
+ * This assigner is able to handle the {@code void} type. This means:
+ * <ol>
+ * <li>If a {@code void} type is assigned to the {@code void} it will consider this a trivial operation.</li>
+ * <li>If a {@code void} type is assigned to a non-{@code void} type, it will pop the top value from the stack.</li>
+ * <li>If a non-{@code void} type is assigned to a {@code void} type, it will load the target type's default value
+ * only if this was configured at the assigner's construction.</li>
+ * <li>If two non-{@code void} types are subject of the assignment, it will delegate the assignment to its chained
+ * assigner.</li>
+ * </ol>
+ */
+ at EqualsAndHashCode
+public class VoidAwareAssigner implements Assigner {
+
+ /**
+ * An assigner that is capable of handling assignments that do not involve {@code void} types.
+ */
+ private final Assigner chained;
+
+ /**
+ * Creates a new assigner that is capable of handling void types.
+ *
+ * @param chained A chained assigner which will be queried by this assigner to handle assignments that
+ * do not involve a {@code void} type.
+ */
+ public VoidAwareAssigner(Assigner chained) {
+ this.chained = chained;
+ }
+
+ @Override
+ public StackManipulation assign(TypeDescription.Generic source, TypeDescription.Generic target, Typing typing) {
+ if (source.represents(void.class) && target.represents(void.class)) {
+ return StackManipulation.Trivial.INSTANCE;
+ } else if (source.represents(void.class) /* && target != void.class */) {
+ return typing.isDynamic()
+ ? DefaultValue.of(target)
+ : StackManipulation.Illegal.INSTANCE;
+ } else if (/* source != void.class && */ target.represents(void.class)) {
+ return Removal.of(source);
+ } else /* source != void.class && target != void.class */ {
+ return chained.assign(source, target, typing);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/assign/primitive/package-info.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/assign/primitive/package-info.java
new file mode 100644
index 0000000..34f3868
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/assign/primitive/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * {@link net.bytebuddy.implementation.bytecode.assign.Assigner} implementations of this package
+ * are capable of handling primitive types or the {@code void} type. On assignments from or to reference types,
+ * these assigners usually delegate a boxed assignment to a reference aware assigner.
+ */
+package net.bytebuddy.implementation.bytecode.assign.primitive;
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/assign/reference/ReferenceTypeAwareAssigner.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/assign/reference/ReferenceTypeAwareAssigner.java
new file mode 100644
index 0000000..e10927b
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/assign/reference/ReferenceTypeAwareAssigner.java
@@ -0,0 +1,34 @@
+package net.bytebuddy.implementation.bytecode.assign.reference;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.assign.TypeCasting;
+
+/**
+ * A simple assigner that is capable of handling the casting of reference types. Primitives can only be assigned to
+ * each other if they represent the same type.
+ */
+public enum ReferenceTypeAwareAssigner implements Assigner {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public StackManipulation assign(TypeDescription.Generic source, TypeDescription.Generic target, Typing typing) {
+ if (source.isPrimitive() || target.isPrimitive()) {
+ return source.equals(target)
+ ? StackManipulation.Trivial.INSTANCE
+ : StackManipulation.Illegal.INSTANCE;
+
+ } else if (source.asErasure().isAssignableTo(target.asErasure())) {
+ return StackManipulation.Trivial.INSTANCE;
+ } else if (typing.isDynamic()) {
+ return TypeCasting.to(target);
+ } else {
+ return StackManipulation.Illegal.INSTANCE;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/assign/reference/package-info.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/assign/reference/package-info.java
new file mode 100644
index 0000000..b61f39d
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/assign/reference/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * {@link net.bytebuddy.implementation.bytecode.assign.Assigner} implementations of this package
+ * are capable of assigning non-primitive types to each other.
+ */
+package net.bytebuddy.implementation.bytecode.assign.reference;
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/collection/ArrayAccess.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/collection/ArrayAccess.java
new file mode 100644
index 0000000..b65d9aa
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/collection/ArrayAccess.java
@@ -0,0 +1,227 @@
+package net.bytebuddy.implementation.bytecode.collection;
+
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.Duplication;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.implementation.bytecode.constant.IntegerConstant;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Allows accessing array values.
+ */
+public enum ArrayAccess {
+
+ /**
+ * Access for a {@code byte}- or {@code boolean}-typed array.
+ */
+ BYTE(Opcodes.BALOAD, Opcodes.BASTORE, StackSize.SINGLE),
+
+ /**
+ * Access for a {@code short}-typed array.
+ */
+ SHORT(Opcodes.SALOAD, Opcodes.SASTORE, StackSize.SINGLE),
+
+ /**
+ * Access for a {@code char}-typed array.
+ */
+ CHARACTER(Opcodes.CALOAD, Opcodes.CASTORE, StackSize.SINGLE),
+
+ /**
+ * Access for a {@code int}-typed array.
+ */
+ INTEGER(Opcodes.IALOAD, Opcodes.IASTORE, StackSize.SINGLE),
+
+ /**
+ * Access for a {@code long}-typed array.
+ */
+ LONG(Opcodes.LALOAD, Opcodes.LASTORE, StackSize.DOUBLE),
+
+ /**
+ * Access for a {@code float}-typed array.
+ */
+ FLOAT(Opcodes.FALOAD, Opcodes.FASTORE, StackSize.SINGLE),
+
+ /**
+ * Access for a {@code double}-typed array.
+ */
+ DOUBLE(Opcodes.DALOAD, Opcodes.DASTORE, StackSize.DOUBLE),
+
+ /**
+ * Access for a reference-typed array.
+ */
+ REFERENCE(Opcodes.AALOAD, Opcodes.AASTORE, StackSize.SINGLE);
+
+ /**
+ * The opcode used for loading a value.
+ */
+ private final int loadOpcode;
+
+ /**
+ * The opcode used for storing a value.
+ */
+ private final int storeOpcode;
+
+ /**
+ * The size of the array's component value.
+ */
+ private final StackSize stackSize;
+
+ /**
+ * Creates a new array access.
+ *
+ * @param loadOpcode The opcode used for loading a value.
+ * @param storeOpcode The opcode used for storing a value.
+ * @param stackSize The size of the array's component value.
+ */
+ ArrayAccess(int loadOpcode, int storeOpcode, StackSize stackSize) {
+ this.loadOpcode = loadOpcode;
+ this.storeOpcode = storeOpcode;
+ this.stackSize = stackSize;
+ }
+
+ /**
+ * Locates an array accessor by the array's component type.
+ *
+ * @param componentType The array's component type.
+ * @return An array accessor for the given type.
+ */
+ public static ArrayAccess of(TypeDefinition componentType) {
+ if (componentType.represents(boolean.class) || componentType.represents(byte.class)) {
+ return BYTE;
+ } else if (componentType.represents(short.class)) {
+ return SHORT;
+ } else if (componentType.represents(char.class)) {
+ return CHARACTER;
+ } else if (componentType.represents(int.class)) {
+ return INTEGER;
+ } else if (componentType.represents(long.class)) {
+ return LONG;
+ } else if (componentType.represents(float.class)) {
+ return FLOAT;
+ } else if (componentType.represents(double.class)) {
+ return DOUBLE;
+ } else if (componentType.represents(void.class)) {
+ throw new IllegalArgumentException("void is no legal array type");
+ } else {
+ return REFERENCE;
+ }
+ }
+
+ /**
+ * Creates a value-loading stack manipulation.
+ *
+ * @return A value-loading stack manipulation.
+ */
+ public StackManipulation load() {
+ return new Loader();
+ }
+
+ /**
+ * Creates a value-storing stack manipulation.
+ *
+ * @return A value-storing stack manipulation.
+ */
+ public StackManipulation store() {
+ return new Putter();
+ }
+
+ /**
+ * Applies a stack manipulation to the values of an array. The array must have at least as many values as the list has elements.
+ *
+ * @param processInstructions The elements to apply.
+ * @return A stack manipulation that applies the supplied instructions.
+ */
+ public StackManipulation forEach(List<? extends StackManipulation> processInstructions) {
+ List<StackManipulation> stackManipulations = new ArrayList<StackManipulation>(processInstructions.size());
+ int index = 0;
+ for (StackManipulation processInstruction : processInstructions) {
+ stackManipulations.add(new StackManipulation.Compound(
+ Duplication.SINGLE,
+ IntegerConstant.forValue(index++),
+ new Loader(),
+ processInstruction
+ ));
+ }
+ return new StackManipulation.Compound(stackManipulations);
+ }
+
+ /**
+ * A stack manipulation for loading an array's value.
+ */
+ protected class Loader implements StackManipulation {
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitInsn(loadOpcode);
+ return stackSize.toIncreasingSize().aggregate(new Size(-2, 0));
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private ArrayAccess getArrayAccess() {
+ return ArrayAccess.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return ArrayAccess.this.hashCode();
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ return this == other || (other != null && other.getClass() == getClass()
+ && getArrayAccess() == ((Loader) other).getArrayAccess());
+ }
+ }
+
+ /**
+ * A stack manipulation for storing an array's value.
+ */
+ protected class Putter implements StackManipulation {
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitInsn(storeOpcode);
+ return stackSize.toDecreasingSize().aggregate(new Size(-2, 0));
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private ArrayAccess getArrayAccess() {
+ return ArrayAccess.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return ArrayAccess.this.hashCode();
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ return this == other || (other != null && other.getClass() == getClass()
+ && getArrayAccess() == ((Putter) other).getArrayAccess());
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/collection/ArrayFactory.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/collection/ArrayFactory.java
new file mode 100644
index 0000000..56f19ab
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/collection/ArrayFactory.java
@@ -0,0 +1,315 @@
+package net.bytebuddy.implementation.bytecode.collection;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.implementation.bytecode.constant.IntegerConstant;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.List;
+
+/**
+ * A {@link net.bytebuddy.implementation.bytecode.collection.CollectionFactory} that is capable of
+ * creating an array of a given type with any number of given values.
+ */
+ at EqualsAndHashCode(of = {"componentType", "arrayCreator"})
+public class ArrayFactory implements CollectionFactory {
+
+ /**
+ * The component type of the array this array factory is creating.
+ */
+ private final TypeDescription.Generic componentType;
+
+ /**
+ * The array creator delegate that supplies suitable opcodes for the creation of an array and the storage of
+ * values inside it.
+ */
+ private final ArrayCreator arrayCreator;
+
+ /**
+ * The decrease of stack size after each value storage operation.
+ */
+ private final StackManipulation.Size sizeDecrease;
+
+ /**
+ * Creates a new array factory with a given
+ * {@link net.bytebuddy.implementation.bytecode.collection.ArrayFactory.ArrayCreator}
+ * without inferring the type from the component type. Normally,
+ * {@link net.bytebuddy.implementation.bytecode.collection.ArrayFactory#forType(net.bytebuddy.description.type.TypeDescription.Generic)}
+ * should be used.
+ *
+ * @param componentType The component type of the array factory.
+ * @param arrayCreator The array creator responsible for providing the correct byte code instructions.
+ */
+ protected ArrayFactory(TypeDescription.Generic componentType, ArrayCreator arrayCreator) {
+ this.componentType = componentType;
+ this.arrayCreator = arrayCreator;
+ // Size decreases by index and array reference (2) and array element (1, 2) after each element storage.
+ sizeDecrease = StackSize.DOUBLE.toDecreasingSize().aggregate(componentType.getStackSize().toDecreasingSize());
+ }
+
+ /**
+ * Creates a new array factory for a given component type.
+ *
+ * @param componentType The component type of the array that is to be build.
+ * @return A new array factory for the given type.
+ */
+ public static ArrayFactory forType(TypeDescription.Generic componentType) {
+ return new ArrayFactory(componentType, makeArrayCreatorFor(componentType));
+ }
+
+ /**
+ * Creates a suitable array creator for the given component type.
+ *
+ * @param componentType The component type of the array to be created.
+ * @return A suitable array creator.
+ */
+ private static ArrayCreator makeArrayCreatorFor(TypeDefinition componentType) {
+ if (componentType.isPrimitive()) {
+ if (componentType.represents(boolean.class)) {
+ return ArrayCreator.ForPrimitiveType.BOOLEAN;
+ } else if (componentType.represents(byte.class)) {
+ return ArrayCreator.ForPrimitiveType.BYTE;
+ } else if (componentType.represents(short.class)) {
+ return ArrayCreator.ForPrimitiveType.SHORT;
+ } else if (componentType.represents(char.class)) {
+ return ArrayCreator.ForPrimitiveType.CHARACTER;
+ } else if (componentType.represents(int.class)) {
+ return ArrayCreator.ForPrimitiveType.INTEGER;
+ } else if (componentType.represents(long.class)) {
+ return ArrayCreator.ForPrimitiveType.LONG;
+ } else if (componentType.represents(float.class)) {
+ return ArrayCreator.ForPrimitiveType.FLOAT;
+ } else if (componentType.represents(double.class)) {
+ return ArrayCreator.ForPrimitiveType.DOUBLE;
+ } else {
+ throw new IllegalArgumentException("Cannot create array of type " + componentType);
+ }
+ } else {
+ return new ArrayCreator.ForReferenceType(componentType.asErasure());
+ }
+ }
+
+ @Override
+ public StackManipulation withValues(List<? extends StackManipulation> stackManipulations) {
+ return new ArrayStackManipulation(stackManipulations);
+ }
+
+ @Override
+ public TypeDescription.Generic getComponentType() {
+ return componentType;
+ }
+
+ /**
+ * An array creator is responsible for providing correct byte code instructions for creating an array
+ * and for storing values into it.
+ */
+ protected interface ArrayCreator extends StackManipulation {
+
+ /**
+ * The creation of an array consumes one slot on the operand stack and adds a new value to it.
+ * Therefore, the operand stack's size is not altered.
+ */
+ StackManipulation.Size ARRAY_CREATION_SIZE_CHANGE = StackSize.ZERO.toDecreasingSize();
+
+ /**
+ * The opcode instruction for storing a value of the component type inside an array.
+ *
+ * @return The correct storage opcode for the represented type.
+ */
+ int getStorageOpcode();
+
+ /**
+ * An array creator implementation for primitive types.
+ */
+ enum ForPrimitiveType implements ArrayCreator {
+
+ /**
+ * An array creator for creating {@code boolean[]} arrays.
+ */
+ BOOLEAN(Opcodes.T_BOOLEAN, Opcodes.BASTORE),
+
+ /**
+ * An array creator for creating {@code byte[]} arrays.
+ */
+ BYTE(Opcodes.T_BYTE, Opcodes.BASTORE),
+
+ /**
+ * An array creator for creating {@code short[]} arrays.
+ */
+ SHORT(Opcodes.T_SHORT, Opcodes.SASTORE),
+
+ /**
+ * An array creator for creating {@code char[]} arrays.
+ */
+ CHARACTER(Opcodes.T_CHAR, Opcodes.CASTORE),
+
+ /**
+ * An array creator for creating {@code int[]} arrays.
+ */
+ INTEGER(Opcodes.T_INT, Opcodes.IASTORE),
+
+ /**
+ * An array creator for creating {@code long[]} arrays.
+ */
+ LONG(Opcodes.T_LONG, Opcodes.LASTORE),
+
+ /**
+ * An array creator for creating {@code float[]} arrays.
+ */
+ FLOAT(Opcodes.T_FLOAT, Opcodes.FASTORE),
+
+ /**
+ * An array creator for creating {@code double[]} arrays.
+ */
+ DOUBLE(Opcodes.T_DOUBLE, Opcodes.DASTORE);
+
+ /**
+ * The opcode for creating an array of this type.
+ */
+ private final int creationOpcode;
+
+ /**
+ * The opcode for storing a value in an array of this type.
+ */
+ private final int storageOpcode;
+
+ /**
+ * Creates a new primitive array creator.
+ *
+ * @param creationOpcode The opcode for creating an array of this type.
+ * @param storageOpcode The opcode for storing a value in an array of this type.
+ */
+ ForPrimitiveType(int creationOpcode, int storageOpcode) {
+ this.creationOpcode = creationOpcode;
+ this.storageOpcode = storageOpcode;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitIntInsn(Opcodes.NEWARRAY, creationOpcode);
+ return ARRAY_CREATION_SIZE_CHANGE;
+ }
+
+ @Override
+ public int getStorageOpcode() {
+ return storageOpcode;
+ }
+ }
+
+ /**
+ * An array creator implementation for reference types.
+ */
+ @EqualsAndHashCode
+ class ForReferenceType implements ArrayCreator {
+
+ /**
+ * The internal name of this array's non-primitive component type.
+ */
+ private final String internalTypeName;
+
+ /**
+ * Creates a new array creator for a reference type.
+ *
+ * @param referenceType The internal name of this array's non-primitive component type.
+ */
+ protected ForReferenceType(TypeDescription referenceType) {
+ this.internalTypeName = referenceType.getInternalName();
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, internalTypeName);
+ return ARRAY_CREATION_SIZE_CHANGE;
+ }
+
+ @Override
+ public int getStorageOpcode() {
+ return Opcodes.AASTORE;
+ }
+ }
+ }
+
+ /**
+ * A stack manipulation for creating an array as defined by the enclosing array factory.
+ */
+ protected class ArrayStackManipulation implements StackManipulation {
+
+ /**
+ * A list of value load instructions that are to be stored inside the created array.
+ */
+ private final List<? extends StackManipulation> stackManipulations;
+
+ /**
+ * Creates a new array loading instruction.
+ *
+ * @param stackManipulations A list of value load instructions that are to be stored inside the created array.
+ */
+ protected ArrayStackManipulation(List<? extends StackManipulation> stackManipulations) {
+ this.stackManipulations = stackManipulations;
+ }
+
+ @Override
+ public boolean isValid() {
+ for (StackManipulation stackManipulation : stackManipulations) {
+ if (!stackManipulation.isValid()) {
+ return false;
+ }
+ }
+ return arrayCreator.isValid();
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ Size size = IntegerConstant.forValue(stackManipulations.size()).apply(methodVisitor, implementationContext);
+ // The array's construction does not alter the stack's size.
+ size = size.aggregate(arrayCreator.apply(methodVisitor, implementationContext));
+ int index = 0;
+ for (StackManipulation stackManipulation : stackManipulations) {
+ methodVisitor.visitInsn(Opcodes.DUP);
+ size = size.aggregate(StackSize.SINGLE.toIncreasingSize());
+ size = size.aggregate(IntegerConstant.forValue(index++).apply(methodVisitor, implementationContext));
+ size = size.aggregate(stackManipulation.apply(methodVisitor, implementationContext));
+ methodVisitor.visitInsn(arrayCreator.getStorageOpcode());
+ size = size.aggregate(sizeDecrease);
+ }
+ return size;
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private ArrayFactory getArrayFactory() {
+ return ArrayFactory.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && ArrayFactory.this.equals(((ArrayStackManipulation) other).getArrayFactory())
+ && stackManipulations.equals(((ArrayStackManipulation) other).stackManipulations);
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return stackManipulations.hashCode();
+ }
+ }
+}
+
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/collection/CollectionFactory.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/collection/CollectionFactory.java
new file mode 100644
index 0000000..c1231dc
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/collection/CollectionFactory.java
@@ -0,0 +1,30 @@
+package net.bytebuddy.implementation.bytecode.collection;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+
+import java.util.List;
+
+/**
+ * Implementations of this interface are able to create collection types (including arrays) by sequentially
+ * storing given values that must be compatible to the collection factory's component type.
+ */
+public interface CollectionFactory {
+
+ /**
+ * The component type of this factory.
+ *
+ * @return A type description of this factory's component type.
+ */
+ TypeDescription.Generic getComponentType();
+
+ /**
+ * Applies this collection factory in order to build a new collection where each element is represented by
+ * the given stack manipulations.
+ *
+ * @param stackManipulations A list of stack manipulations loading the values to be stored in the collection that is
+ * created by this factory in their given order.
+ * @return A stack manipulation that creates the collection represented by this collection factory.
+ */
+ StackManipulation withValues(List<? extends StackManipulation> stackManipulations);
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/collection/package-info.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/collection/package-info.java
new file mode 100644
index 0000000..8750911
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/collection/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * This package is dedicated to creating {@link net.bytebuddy.implementation.bytecode.StackManipulation}s
+ * that create collections or arrays from a given number of values.
+ */
+package net.bytebuddy.implementation.bytecode.collection;
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/ClassConstant.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/ClassConstant.java
new file mode 100644
index 0000000..629e54b
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/ClassConstant.java
@@ -0,0 +1,170 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+/**
+ * Represents a constant representing any loaded Java {@link java.lang.Class}.
+ */
+public enum ClassConstant implements StackManipulation {
+
+ /**
+ * The {@code void} type.
+ */
+ VOID(Void.class),
+
+ /**
+ * The {@code boolean} type.
+ */
+ BOOLEAN(Boolean.class),
+
+ /**
+ * The {@code byte} type.
+ */
+ BYTE(Byte.class),
+
+ /**
+ * The {@code short} type.
+ */
+ SHORT(Short.class),
+
+ /**
+ * The {@code char} type.
+ */
+ CHARACTER(Character.class),
+
+ /**
+ * The {@code int} type.
+ */
+ INTEGER(Integer.class),
+
+ /**
+ * The {@code long} type.
+ */
+ LONG(Long.class),
+
+ /**
+ * The {@code float} type.
+ */
+ FLOAT(Float.class),
+
+ /**
+ * The {@code double} type.
+ */
+ DOUBLE(Double.class);
+
+ /**
+ * The size of a {@link java.lang.Class} on the operand stack.
+ */
+ private static final Size SIZE = StackSize.SINGLE.toIncreasingSize();
+
+ /**
+ * The field name that stores a reference to the primitive type representation.
+ */
+ private static final String PRIMITIVE_TYPE_FIELD = "TYPE";
+
+ /**
+ * The descriptor of the {@link java.lang.Class} type.
+ */
+ private static final String CLASS_TYPE_INTERNAL_NAME = "Ljava/lang/Class;";
+
+ /**
+ * The internal name of the type owning the field.
+ */
+ private final String fieldOwnerInternalName;
+
+ /**
+ * Creates a new class constant for a primitive type.
+ *
+ * @param type The primitive type to represent.
+ */
+ ClassConstant(Class<?> type) {
+ fieldOwnerInternalName = Type.getInternalName(type);
+ }
+
+ /**
+ * Returns a stack manipulation that loads a {@link java.lang.Class} type onto the operand stack which
+ * represents the given type.
+ *
+ * @param typeDescription The type to load onto the operand stack.
+ * @return The corresponding stack manipulation.
+ */
+ public static StackManipulation of(TypeDescription typeDescription) {
+ if (typeDescription.represents(void.class)) {
+ return VOID;
+ } else if (typeDescription.represents(boolean.class)) {
+ return BOOLEAN;
+ } else if (typeDescription.represents(byte.class)) {
+ return BYTE;
+ } else if (typeDescription.represents(short.class)) {
+ return SHORT;
+ } else if (typeDescription.represents(char.class)) {
+ return CHARACTER;
+ } else if (typeDescription.represents(int.class)) {
+ return INTEGER;
+ } else if (typeDescription.represents(long.class)) {
+ return LONG;
+ } else if (typeDescription.represents(float.class)) {
+ return FLOAT;
+ } else if (typeDescription.represents(double.class)) {
+ return DOUBLE;
+ } else {
+ return new ForReferenceType(typeDescription);
+ }
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, fieldOwnerInternalName, PRIMITIVE_TYPE_FIELD, CLASS_TYPE_INTERNAL_NAME);
+ return SIZE;
+ }
+
+ /**
+ * A class constant for a non-primitive {@link java.lang.Class}.
+ */
+ @EqualsAndHashCode
+ protected static class ForReferenceType implements StackManipulation {
+
+ /**
+ * The type which should be loaded onto the operand stack as a class value.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a stack manipulation that represents loading a class constant onto the stack.
+ *
+ * @param typeDescription A description of the class to load onto the stack.
+ */
+ protected ForReferenceType(TypeDescription typeDescription) {
+ this.typeDescription = typeDescription;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ if (implementationContext.getClassFileVersion().isAtLeast(ClassFileVersion.JAVA_V5) && typeDescription.isVisibleTo(implementationContext.getInstrumentedType())) {
+ methodVisitor.visitLdcInsn(Type.getType(typeDescription.getDescriptor()));
+ } else {
+ methodVisitor.visitLdcInsn(typeDescription.getName());
+ methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;", false);
+ }
+ return SIZE;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/DefaultValue.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/DefaultValue.java
new file mode 100644
index 0000000..9287e68
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/DefaultValue.java
@@ -0,0 +1,92 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import org.objectweb.asm.MethodVisitor;
+
+/**
+ * Represents a stack assignment that loads the default value of a given type onto the stack.
+ */
+public enum DefaultValue implements StackManipulation {
+
+ /**
+ * The default value of a JVM integer which covers Java's {@code int}, {@code boolean}, {@code byte},
+ * {@code short} and {@code char} values.
+ */
+ INTEGER(IntegerConstant.ZERO),
+
+ /**
+ * The default value of a {@code long}.
+ */
+ LONG(LongConstant.ZERO),
+
+ /**
+ * The default value of a {@code float}.
+ */
+ FLOAT(FloatConstant.ZERO),
+
+ /**
+ * The default value of a {@code double}.
+ */
+ DOUBLE(DoubleConstant.ZERO),
+
+ /**
+ * The default value of a {@code void} which resembles a no-op manipulation.
+ */
+ VOID(Trivial.INSTANCE),
+
+ /**
+ * The default value of a reference type which resembles the {@code null} reference.
+ */
+ REFERENCE(NullConstant.INSTANCE);
+
+ /**
+ * The stack manipulation that represents the loading of a given default value onto the operand stack.
+ */
+ private final StackManipulation stackManipulation;
+
+ /**
+ * Creates a new default value load operation.
+ *
+ * @param stackManipulation The stack manipulation that represents the loading of a given default value onto the
+ * operand stack.
+ */
+ DefaultValue(StackManipulation stackManipulation) {
+ this.stackManipulation = stackManipulation;
+ }
+
+ /**
+ * Creates a stack assignment that loads the default value for a given type.
+ *
+ * @param typeDefinition The type for which a default value should be loaded onto the operand stack.
+ * @return A stack manipulation loading the default value for the given type.
+ */
+ public static StackManipulation of(TypeDefinition typeDefinition) {
+ if (typeDefinition.isPrimitive()) {
+ if (typeDefinition.represents(long.class)) {
+ return LONG;
+ } else if (typeDefinition.represents(double.class)) {
+ return DOUBLE;
+ } else if (typeDefinition.represents(float.class)) {
+ return FLOAT;
+ } else if (typeDefinition.represents(void.class)) {
+ return VOID;
+ } else {
+ return INTEGER;
+ }
+ } else {
+ return REFERENCE;
+ }
+ }
+
+ @Override
+ public boolean isValid() {
+ return stackManipulation.isValid();
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ return stackManipulation.apply(methodVisitor, implementationContext);
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/DoubleConstant.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/DoubleConstant.java
new file mode 100644
index 0000000..2b1af86
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/DoubleConstant.java
@@ -0,0 +1,104 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * This class is responsible for loading any {@code double} constant onto the operand stack.
+ */
+public enum DoubleConstant implements StackManipulation {
+
+ /**
+ * A {@code double} constant of value {@code 0.0}.
+ */
+ ZERO(Opcodes.DCONST_0),
+
+ /**
+ * A {@code double} constant of value {@code 1.0}.
+ */
+ ONE(Opcodes.DCONST_1);
+
+ /**
+ * The size impact of loading a {@code double} constant onto the operand stack.
+ */
+ private static final StackManipulation.Size SIZE = StackSize.DOUBLE.toIncreasingSize();
+
+ /**
+ * The shortcut opcode for loading a {@code double} constant.
+ */
+ private final int opcode;
+
+ /**
+ * Creates a new shortcut operation for loading a common {@code double} onto the operand stack.
+ *
+ * @param opcode The shortcut opcode for loading a {@code double} constant.
+ */
+ DoubleConstant(int opcode) {
+ this.opcode = opcode;
+ }
+
+ /**
+ * Creates a stack manipulation for loading a {@code double} value onto the operand stack.
+ * <p> </p>
+ * This is achieved either by invoking a specific opcode, if any, or by creating a constant pool entry.
+ *
+ * @param value The {@code double} value to load onto the stack.
+ * @return A stack manipulation for loading the given {@code double} value.
+ */
+ public static StackManipulation forValue(double value) {
+ if (value == 0d) {
+ return ZERO;
+ } else if (value == 1d) {
+ return ONE;
+ } else {
+ return new ConstantPool(value);
+ }
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public StackManipulation.Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitInsn(opcode);
+ return SIZE;
+ }
+
+ /**
+ * A stack manipulation for loading a {@code double} value from a class's constant pool onto the operand stack.
+ */
+ @EqualsAndHashCode
+ protected static class ConstantPool implements StackManipulation {
+
+ /**
+ * The {@code double} value to be loaded onto the operand stack.
+ */
+ private final double value;
+
+ /**
+ * Creates a new constant pool load operation.
+ *
+ * @param value The {@code double} value to be loaded onto the operand stack.
+ */
+ protected ConstantPool(double value) {
+ this.value = value;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitLdcInsn(value);
+ return SIZE;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/FieldConstant.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/FieldConstant.java
new file mode 100644
index 0000000..95a76b2
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/FieldConstant.java
@@ -0,0 +1,94 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.member.FieldAccess;
+import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
+import org.objectweb.asm.MethodVisitor;
+
+import java.lang.reflect.Field;
+
+/**
+ * Represents a {@link Field} constant for a given type.
+ */
+ at EqualsAndHashCode
+public class FieldConstant implements StackManipulation {
+
+ /**
+ * The field to be represent as a {@link Field}.
+ */
+ private final FieldDescription.InDefinedShape fieldDescription;
+
+ /**
+ * Creates a new field constant.
+ *
+ * @param fieldDescription The field to be represent as a {@link Field}.
+ */
+ public FieldConstant(FieldDescription.InDefinedShape fieldDescription) {
+ this.fieldDescription = fieldDescription;
+ }
+
+ /**
+ * Retruns a cached version of this field constant.
+ *
+ * @return A cached version of this field constant.
+ */
+ public StackManipulation cached() {
+ return new Cached(this);
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ try {
+ return new Compound(
+ ClassConstant.of(fieldDescription.getDeclaringType()),
+ new TextConstant(fieldDescription.getInternalName()),
+ MethodInvocation.invoke(new MethodDescription.ForLoadedMethod(Class.class.getMethod("getDeclaredField", String.class)))
+ ).apply(methodVisitor, implementationContext);
+ } catch (NoSuchMethodException exception) {
+ throw new IllegalStateException("Cannot locate Class::getDeclaredField", exception);
+ }
+ }
+
+ /**
+ * A cached version of a {@link FieldConstant}.
+ */
+ @EqualsAndHashCode
+ protected static class Cached implements StackManipulation {
+
+ /**
+ * The field constant stack manipulation.
+ */
+ private final StackManipulation fieldConstant;
+
+ /**
+ * Creates a new cached version of a field constant.
+ *
+ * @param fieldConstant The field constant stack manipulation.
+ */
+ public Cached(StackManipulation fieldConstant) {
+ this.fieldConstant = fieldConstant;
+ }
+
+ @Override
+ public boolean isValid() {
+ return fieldConstant.isValid();
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ return FieldAccess.forField(implementationContext.cache(fieldConstant, new TypeDescription.ForLoadedType(Field.class)))
+ .read()
+ .apply(methodVisitor, implementationContext);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/FloatConstant.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/FloatConstant.java
new file mode 100644
index 0000000..6842e2a
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/FloatConstant.java
@@ -0,0 +1,111 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * This class is responsible for loading any {@code float} constant onto the operand stack.
+ */
+public enum FloatConstant implements StackManipulation {
+
+ /**
+ * A {@code float} constant of value {@code 0.0f}.
+ */
+ ZERO(Opcodes.FCONST_0),
+
+ /**
+ * A {@code float} constant of value {@code 1.0f}.
+ */
+ ONE(Opcodes.FCONST_1),
+
+ /**
+ * A {@code float} constant of value {@code 2.0f}.
+ */
+ TWO(Opcodes.FCONST_2);
+
+ /**
+ * The size impact of loading a {@code float} constant onto the operand stack.
+ */
+ private static final Size SIZE = StackSize.SINGLE.toIncreasingSize();
+
+ /**
+ * The shortcut opcode for loading a {@code float} constant.
+ */
+ private final int opcode;
+
+ /**
+ * Creates a new shortcut operation for loading a common {@code float} onto the operand stack.
+ *
+ * @param opcode The shortcut opcode for loading a {@code float} constant.
+ */
+ FloatConstant(int opcode) {
+ this.opcode = opcode;
+ }
+
+ /**
+ * Creates a stack manipulation for loading a {@code float} value onto the operand stack.
+ * <p> </p>
+ * This is achieved either by invoking a specific opcode, if any, or by creating a constant pool entry.
+ *
+ * @param value The {@code float} value to load onto the stack.
+ * @return A stack manipulation for loading the given {@code float} value.
+ */
+ public static StackManipulation forValue(float value) {
+ if (value == 0f) {
+ return ZERO;
+ } else if (value == 1f) {
+ return ONE;
+ } else if (value == 2f) {
+ return TWO;
+ } else {
+ return new ConstantPool(value);
+ }
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitInsn(opcode);
+ return SIZE;
+ }
+
+ /**
+ * A stack manipulation for loading a {@code float} value from a class's constant pool onto the operand stack.
+ */
+ @EqualsAndHashCode
+ protected static class ConstantPool implements StackManipulation {
+
+ /**
+ * The {@code float} value to be loaded onto the operand stack.
+ */
+ private final float value;
+
+ /**
+ * Creates a new constant pool load operation.
+ *
+ * @param value The {@code float} value to be loaded onto the operand stack.
+ */
+ protected ConstantPool(float value) {
+ this.value = value;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitLdcInsn(value);
+ return SIZE;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/IntegerConstant.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/IntegerConstant.java
new file mode 100644
index 0000000..16f0c46
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/IntegerConstant.java
@@ -0,0 +1,225 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+
+/**
+ * This class is responsible for loading any {@code int} constant onto the operand stack. Note that within the JVM,
+ * {@code boolean}, {@code byte}, {@code short} and {@code char} values are represented by integers and can therefore
+ * be loaded by using this class.
+ */
+public enum IntegerConstant implements StackManipulation {
+
+ /**
+ * A JVM-type {@code int} constant of value {@code -1}.
+ */
+ MINUS_ONE(Opcodes.ICONST_M1),
+
+ /**
+ * A JVM-type {@code int} constant of value {@code 0}.
+ */
+ ZERO(Opcodes.ICONST_0),
+
+ /**
+ * A JVM-type {@code int} constant of value {@code 1}.
+ */
+ ONE(Opcodes.ICONST_1),
+
+ /**
+ * A JVM-type {@code int} constant of value {@code 2}.
+ */
+ TWO(Opcodes.ICONST_2),
+
+ /**
+ * A JVM-type {@code int} constant of value {@code 3}.
+ */
+ THREE(Opcodes.ICONST_3),
+
+ /**
+ * A JVM-type {@code int} constant of value {@code 4}.
+ */
+ FOUR(Opcodes.ICONST_4),
+
+ /**
+ * A JVM-type {@code int} constant of value {@code 5}.
+ */
+ FIVE(Opcodes.ICONST_5);
+
+ /**
+ * The size impact of loading an {@code int} value onto the operand stack.
+ */
+ private static final Size SIZE = StackSize.SINGLE.toIncreasingSize();
+
+ /**
+ * The shortcut opcode for loading a common {@code int}-compatible JVM value onto the operand stack.
+ */
+ private final int opcode;
+
+ /**
+ * Creates a new JVM-integer constant loading stack manipulation for a given shortcut opcode.
+ *
+ * @param opcode The shortcut opcode for loading a common {@code int}-compatible JVM value onto the operand stack.
+ */
+ IntegerConstant(int opcode) {
+ this.opcode = opcode;
+ }
+
+ /**
+ * Creates a stack manipulation for loading a boolean value onto the stack.
+ *
+ * @param value The {@code boolean} to load onto the stack.
+ * @return The stack manipulation for loading this {@code boolean}.
+ */
+ public static StackManipulation forValue(boolean value) {
+ return value ? ONE : ZERO;
+ }
+
+ /**
+ * Creates a stack manipulation for loading an {@code int} value onto the stack.
+ * <p> </p>
+ * This is achieved either by invoking a constant opcode, if any, or by creating a binary push operation.
+ *
+ * @param value The {@code int} (or {@code byte}, {@code short}, {@code char}) value to load onto the stack.
+ * @return A stack manipulation for loading the given value.
+ */
+ public static StackManipulation forValue(int value) {
+ switch (value) {
+ case -1:
+ return MINUS_ONE;
+ case 0:
+ return ZERO;
+ case 1:
+ return ONE;
+ case 2:
+ return TWO;
+ case 3:
+ return THREE;
+ case 4:
+ return FOUR;
+ case 5:
+ return FIVE;
+ default:
+ if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
+ return new SingleBytePush((byte) value);
+ } else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
+ return new TwoBytePush((short) value);
+ } else {
+ return new ConstantPool(value);
+ }
+ }
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitInsn(opcode);
+ return SIZE;
+ }
+
+ /**
+ * A stack manipulation that loads a JVM-integer value by a {@code BIPUSH} operation which is
+ * legal for single byte integer values.
+ */
+ @EqualsAndHashCode
+ protected static class SingleBytePush implements StackManipulation {
+
+ /**
+ * The single byte value to be loaded onto the operand stack.
+ */
+ private final byte value;
+
+ /**
+ * Creates a new {@code BIPUSH} stack manipulation for the given value.
+ *
+ * @param value The single byte value to be loaded onto the operand stack.
+ */
+ protected SingleBytePush(byte value) {
+ this.value = value;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitIntInsn(Opcodes.BIPUSH, value);
+ return SIZE;
+ }
+ }
+
+ /**
+ * A stack manipulation that loads a JVM-integer value by a {@code SIPUSH} operation which is
+ * legal for up to two byte integer values.
+ */
+ @EqualsAndHashCode
+ protected static class TwoBytePush implements StackManipulation {
+
+ /**
+ * The two byte value to be loaded onto the operand stack.
+ */
+ private final short value;
+
+ /**
+ * Creates a new {@code SIPUSH} stack manipulation for the given value.
+ *
+ * @param value The two byte value to be loaded onto the operand stack.
+ */
+ protected TwoBytePush(short value) {
+ this.value = value;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitIntInsn(Opcodes.SIPUSH, value);
+ return SIZE;
+ }
+ }
+
+ /**
+ * A stack manipulation that loads a JVM-integer value from a constant pool value onto the operand stack.
+ */
+ @EqualsAndHashCode
+ protected static class ConstantPool implements StackManipulation {
+
+ /**
+ * The JVM-integer value to load onto the operand stack.
+ */
+ private final int value;
+
+ /**
+ * Creates a new constant pool loading operation for a given JVM-integer.
+ *
+ * @param value The JVM-integer value to load onto the operand stack.
+ */
+ protected ConstantPool(int value) {
+ this.value = value;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitLdcInsn(value);
+ return SIZE;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/JavaConstantValue.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/JavaConstantValue.java
new file mode 100644
index 0000000..a20dd24
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/JavaConstantValue.java
@@ -0,0 +1,40 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.utility.JavaConstant;
+import org.objectweb.asm.MethodVisitor;
+
+/**
+ * A constant representing a {@link JavaConstant}.
+ */
+ at EqualsAndHashCode
+public class JavaConstantValue implements StackManipulation {
+
+ /**
+ * The instance to load onto the operand stack.
+ */
+ private final JavaConstant javaConstant;
+
+ /**
+ * Creates a constant pool value representing a {@link JavaConstant}.
+ *
+ * @param javaConstant The instance to load onto the operand stack.
+ */
+ public JavaConstantValue(JavaConstant javaConstant) {
+ this.javaConstant = javaConstant;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitLdcInsn(javaConstant.asConstantPoolValue());
+ return StackSize.SINGLE.toIncreasingSize();
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/LongConstant.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/LongConstant.java
new file mode 100644
index 0000000..5025357
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/LongConstant.java
@@ -0,0 +1,104 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * This class is responsible for loading any {@code long} constant onto the operand stack.
+ */
+public enum LongConstant implements StackManipulation {
+
+ /**
+ * A {@code long} constant of value {@code 0L}.
+ */
+ ZERO(Opcodes.LCONST_0),
+
+ /**
+ * A {@code long} constant of value {@code 1L}.
+ */
+ ONE(Opcodes.LCONST_1);
+
+ /**
+ * The size impact of loading a {@code double} constant onto the operand stack.
+ */
+ private static final Size SIZE = StackSize.DOUBLE.toIncreasingSize();
+
+ /**
+ * The shortcut opcode for loading a {@code long} constant.
+ */
+ private final int opcode;
+
+ /**
+ * Creates a new shortcut operation for loading a common {@code long} onto the operand stack.
+ *
+ * @param opcode The shortcut opcode for loading a {@code long} constant.
+ */
+ LongConstant(int opcode) {
+ this.opcode = opcode;
+ }
+
+ /**
+ * Creates a stack manipulation for loading a {@code long} value onto the operand stack.
+ * <p> </p>
+ * This is achieved either by invoking a specific opcode, if any, or by creating a constant pool entry.
+ *
+ * @param value The {@code long} value to load onto the stack.
+ * @return A stack manipulation for loading the given {@code long} value.
+ */
+ public static StackManipulation forValue(long value) {
+ if (value == 0L) {
+ return ZERO;
+ } else if (value == 1L) {
+ return ONE;
+ } else {
+ return new ConstantPool(value);
+ }
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitInsn(opcode);
+ return SIZE;
+ }
+
+ /**
+ * A stack manipulation for loading a {@code long} value from a class's constant pool onto the operand stack.
+ */
+ @EqualsAndHashCode
+ protected static class ConstantPool implements StackManipulation {
+
+ /**
+ * The {@code long} value to be loaded onto the operand stack.
+ */
+ private final long value;
+
+ /**
+ * Creates a new constant pool load operation.
+ *
+ * @param value The {@code long} value to be loaded onto the operand stack.
+ */
+ protected ConstantPool(long value) {
+ this.value = value;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitLdcInsn(value);
+ return SIZE;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/MethodConstant.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/MethodConstant.java
new file mode 100644
index 0000000..caf670f
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/MethodConstant.java
@@ -0,0 +1,296 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.collection.ArrayFactory;
+import net.bytebuddy.implementation.bytecode.member.FieldAccess;
+import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
+import org.objectweb.asm.MethodVisitor;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents the creation of a {@link java.lang.reflect.Method} value which can be created from a given
+ * set of constant pool values and can therefore be considered a constant in the broader meaning.
+ */
+ at EqualsAndHashCode
+public abstract class MethodConstant implements StackManipulation {
+
+ /**
+ * The internal name of the {@link Class} type.
+ */
+ private static final String CLASS_TYPE_INTERNAL_NAME = "java/lang/Class";
+
+ /**
+ * A description of the method to be loaded onto the stack.
+ */
+ protected final MethodDescription.InDefinedShape methodDescription;
+
+ /**
+ * Creates a new method constant.
+ *
+ * @param methodDescription The method description for which the {@link java.lang.reflect.Method} representation
+ * should be created.
+ */
+ protected MethodConstant(MethodDescription.InDefinedShape methodDescription) {
+ this.methodDescription = methodDescription;
+ }
+
+ /**
+ * Creates a stack manipulation that loads a method constant onto the operand stack.
+ *
+ * @param methodDescription The method to be loaded onto the stack.
+ * @return A stack manipulation that assigns a method constant for the given method description.
+ */
+ public static CanCache forMethod(MethodDescription.InDefinedShape methodDescription) {
+ if (methodDescription.isTypeInitializer()) {
+ return CanCacheIllegal.INSTANCE;
+ } else if (methodDescription.isConstructor()) {
+ return new ForConstructor(methodDescription);
+ } else {
+ return new ForMethod(methodDescription);
+ }
+ }
+
+ /**
+ * Returns a list of type constant load operations for the given list of parameters.
+ *
+ * @param parameterTypes A list of all type descriptions that should be represented as type constant
+ * load operations.
+ * @return A corresponding list of type constant load operations.
+ */
+ private static List<StackManipulation> typeConstantsFor(List<TypeDescription> parameterTypes) {
+ List<StackManipulation> typeConstants = new ArrayList<StackManipulation>(parameterTypes.size());
+ for (TypeDescription parameterType : parameterTypes) {
+ typeConstants.add(ClassConstant.of(parameterType));
+ }
+ return typeConstants;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ return new Compound(
+ preparation(),
+ ArrayFactory.forType(new TypeDescription.Generic.OfNonGenericType.ForLoadedType(Class.class))
+ .withValues(typeConstantsFor(methodDescription.getParameters().asTypeList().asErasures())),
+ MethodInvocation.invoke(accessorMethod())
+ ).apply(methodVisitor, implementationContext);
+ }
+
+ /**
+ * Returns a stack manipulation that loads the values that are required for loading a method constant onto the operand stack.
+ *
+ * @return A stack manipulation for loading a method or constructor onto the operand stack.
+ */
+ protected abstract StackManipulation preparation();
+
+ /**
+ * Returns the method for loading a declared method or constructor onto the operand stack.
+ *
+ * @return The method for loading a declared method or constructor onto the operand stack.
+ */
+ protected abstract MethodDescription accessorMethod();
+
+ /**
+ * Returns a cached version of this method constant as specified by {@link CachedMethod} and {@link CachedConstructor}.
+ *
+ * @return A cached version of this method constant.
+ */
+ public StackManipulation cached() {
+ return methodDescription.isConstructor()
+ ? new CachedConstructor(this)
+ : new CachedMethod(this);
+ }
+
+ /**
+ * Represents a method constant that cannot be represented by Java's reflection API.
+ */
+ protected enum CanCacheIllegal implements CanCache {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public StackManipulation cached() {
+ return Illegal.INSTANCE;
+ }
+
+ @Override
+ public boolean isValid() {
+ return false;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ return Illegal.INSTANCE.apply(methodVisitor, implementationContext);
+ }
+ }
+
+ /**
+ * Represents a {@link net.bytebuddy.implementation.bytecode.constant.MethodConstant} that is
+ * directly loaded onto the operand stack without caching the value. Since the look-up of a Java method bares
+ * some costs that sometimes need to be avoided, such a stack manipulation offers a convenience method for
+ * defining this loading instruction as the retrieval of a field value that is initialized in the instrumented
+ * type's type initializer.
+ */
+ public interface CanCache extends StackManipulation {
+
+ /**
+ * Returns this method constant as a cached version.
+ *
+ * @return A cached version of the method constant that is represented by this instance.
+ */
+ StackManipulation cached();
+ }
+
+ /**
+ * Creates a {@link net.bytebuddy.implementation.bytecode.constant.MethodConstant} for loading
+ * a {@link java.lang.reflect.Method} instance onto the operand stack.
+ */
+ protected static class ForMethod extends MethodConstant implements CanCache {
+
+ /**
+ * Creates a new {@link net.bytebuddy.implementation.bytecode.constant.MethodConstant} for
+ * creating a {@link java.lang.reflect.Method} instance.
+ *
+ * @param methodDescription The method to be loaded onto the stack.
+ */
+ protected ForMethod(MethodDescription.InDefinedShape methodDescription) {
+ super(methodDescription);
+ }
+
+ @Override
+ protected StackManipulation preparation() {
+ return new Compound(ClassConstant.of(methodDescription.getDeclaringType()), new TextConstant(methodDescription.getInternalName()));
+ }
+
+ @Override
+ protected MethodDescription accessorMethod() {
+ try {
+ return new MethodDescription.ForLoadedMethod(Class.class.getMethod("getDeclaredMethod", String.class, Class[].class));
+ } catch (NoSuchMethodException exception) {
+ throw new IllegalStateException("Cannot locate Class::getDeclaredMethod", exception);
+ }
+ }
+ }
+
+ /**
+ * Creates a {@link net.bytebuddy.implementation.bytecode.constant.MethodConstant} for loading
+ * a {@link java.lang.reflect.Constructor} instance onto the operand stack.
+ */
+ protected static class ForConstructor extends MethodConstant implements CanCache {
+
+ /**
+ * Creates a new {@link net.bytebuddy.implementation.bytecode.constant.MethodConstant} for
+ * creating a {@link java.lang.reflect.Constructor} instance.
+ *
+ * @param methodDescription The constructor to be loaded onto the stack.
+ */
+ protected ForConstructor(MethodDescription.InDefinedShape methodDescription) {
+ super(methodDescription);
+ }
+
+ @Override
+ protected StackManipulation preparation() {
+ return ClassConstant.of(methodDescription.getDeclaringType());
+ }
+
+ @Override
+ protected MethodDescription accessorMethod() {
+ try {
+ return new MethodDescription.ForLoadedMethod(Class.class.getMethod("getDeclaredConstructor", Class[].class));
+ } catch (NoSuchMethodException exception) {
+ throw new IllegalStateException("Cannot locate Class::getDeclaredConstructor", exception);
+ }
+ }
+ }
+
+ /**
+ * Represents a cached method for a {@link net.bytebuddy.implementation.bytecode.constant.MethodConstant}.
+ */
+ @EqualsAndHashCode
+ protected static class CachedMethod implements StackManipulation {
+
+ /**
+ * A description of the {@link java.lang.reflect.Method} type.
+ */
+ private static final TypeDescription METHOD_TYPE = new TypeDescription.ForLoadedType(Method.class);
+
+ /**
+ * The stack manipulation that is represented by this caching wrapper.
+ */
+ private final StackManipulation methodConstant;
+
+ /**
+ * Creates a new cached {@link net.bytebuddy.implementation.bytecode.constant.MethodConstant}.
+ *
+ * @param methodConstant The method constant to store in the field cache.
+ */
+ protected CachedMethod(StackManipulation methodConstant) {
+ this.methodConstant = methodConstant;
+ }
+
+ @Override
+ public boolean isValid() {
+ return methodConstant.isValid();
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ return FieldAccess.forField(implementationContext.cache(methodConstant, METHOD_TYPE))
+ .read()
+ .apply(methodVisitor, implementationContext);
+ }
+ }
+
+ /**
+ * Represents a cached constructor for a {@link net.bytebuddy.implementation.bytecode.constant.MethodConstant}.
+ */
+ @EqualsAndHashCode
+ protected static class CachedConstructor implements StackManipulation {
+
+ /**
+ * A description of the {@link java.lang.reflect.Constructor} type.
+ */
+ private static final TypeDescription CONSTRUCTOR_TYPE = new TypeDescription.ForLoadedType(Constructor.class);
+
+ /**
+ * The stack manipulation that is represented by this caching wrapper.
+ */
+ private final StackManipulation constructorConstant;
+
+ /**
+ * Creates a new cached {@link net.bytebuddy.implementation.bytecode.constant.MethodConstant}.
+ *
+ * @param constructorConstant The method constant to store in the field cache.
+ */
+ protected CachedConstructor(StackManipulation constructorConstant) {
+ this.constructorConstant = constructorConstant;
+ }
+
+ @Override
+ public boolean isValid() {
+ return constructorConstant.isValid();
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ return FieldAccess.forField(implementationContext.cache(constructorConstant, CONSTRUCTOR_TYPE))
+ .read()
+ .apply(methodVisitor, implementationContext);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/NullConstant.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/NullConstant.java
new file mode 100644
index 0000000..87e8b5f
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/NullConstant.java
@@ -0,0 +1,43 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Represents a stack manipulation to load a {@code null} pointer onto the operand stack.
+ */
+public enum NullConstant implements StackManipulation {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE(StackSize.SINGLE);
+
+ /**
+ * The size impact of loading the {@code null} reference onto the operand stack.
+ */
+ private final Size size;
+
+ /**
+ * Creates a null constant.
+ *
+ * @param size The size of the constant on the operand stack.
+ */
+ NullConstant(StackSize size) {
+ this.size = size.toIncreasingSize();
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitInsn(Opcodes.ACONST_NULL);
+ return size;
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/SerializedConstant.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/SerializedConstant.java
new file mode 100644
index 0000000..48da24a
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/SerializedConstant.java
@@ -0,0 +1,88 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.Duplication;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.TypeCreation;
+import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
+import org.objectweb.asm.MethodVisitor;
+
+import java.io.*;
+
+/**
+ * A constant that represents a value in its serialized form.
+ */
+ at EqualsAndHashCode
+public class SerializedConstant implements StackManipulation {
+
+ /**
+ * A charset that does not change the supplied byte array upon encoding or decoding.
+ */
+ private static final String CHARSET = "ISO-8859-1";
+
+ /**
+ * The serialized value.
+ */
+ private final String serialization;
+
+ /**
+ * Creates a new constant for a serialized value.
+ *
+ * @param serialization The serialized value.
+ */
+ protected SerializedConstant(String serialization) {
+ this.serialization = serialization;
+ }
+
+ /**
+ * Creates a new stack manipulation to load the supplied value onto the stack.
+ *
+ * @param value The value to serialize.
+ * @return A stack manipulation to serialize the supplied value.
+ */
+ public static StackManipulation of(Serializable value) {
+ if (value == null) {
+ return NullConstant.INSTANCE;
+ }
+ try {
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
+ try {
+ objectOutputStream.writeObject(value);
+ } finally {
+ objectOutputStream.close();
+ }
+ return new SerializedConstant(byteArrayOutputStream.toString(CHARSET));
+ } catch (IOException exception) {
+ throw new IllegalStateException("Cannot serialize " + value, exception);
+ }
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ try {
+ return new StackManipulation.Compound(
+ TypeCreation.of(new TypeDescription.ForLoadedType(ObjectInputStream.class)),
+ Duplication.SINGLE,
+ TypeCreation.of(new TypeDescription.ForLoadedType(ByteArrayInputStream.class)),
+ Duplication.SINGLE,
+ new TextConstant(serialization),
+ new TextConstant(CHARSET),
+ MethodInvocation.invoke(new MethodDescription.ForLoadedMethod(String.class.getMethod("getBytes", String.class))),
+ MethodInvocation.invoke(new MethodDescription.ForLoadedConstructor(ByteArrayInputStream.class.getConstructor(byte[].class))),
+ MethodInvocation.invoke(new MethodDescription.ForLoadedConstructor(ObjectInputStream.class.getConstructor(InputStream.class))),
+ MethodInvocation.invoke(new MethodDescription.ForLoadedMethod(ObjectInputStream.class.getMethod("readObject")))
+ ).apply(methodVisitor, implementationContext);
+ } catch (NoSuchMethodException exception) {
+ throw new IllegalStateException("Could not locate Java API method", exception);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/TextConstant.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/TextConstant.java
new file mode 100644
index 0000000..4e90ce8
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/TextConstant.java
@@ -0,0 +1,39 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import org.objectweb.asm.MethodVisitor;
+
+/**
+ * Represents a {@link java.lang.String} value that is stored in a type's constant pool.
+ */
+ at EqualsAndHashCode
+public class TextConstant implements StackManipulation {
+
+ /**
+ * The text value to load onto the operand stack.
+ */
+ private final String text;
+
+ /**
+ * Creates a new stack manipulation to load a {@code String} constant onto the operand stack.
+ *
+ * @param text The value of the {@code String} to be loaded.
+ */
+ public TextConstant(String text) {
+ this.text = text;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitLdcInsn(text);
+ return StackSize.SINGLE.toIncreasingSize();
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/package-info.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/package-info.java
new file mode 100644
index 0000000..a333e59
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/constant/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * {@link net.bytebuddy.implementation.bytecode.StackManipulation}s in this package are responsible for
+ * creating compile-time constants and pushing them onto the operand stack.
+ */
+package net.bytebuddy.implementation.bytecode.constant;
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/member/FieldAccess.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/member/FieldAccess.java
new file mode 100644
index 0000000..4ba75bc
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/member/FieldAccess.java
@@ -0,0 +1,330 @@
+package net.bytebuddy.implementation.bytecode.member;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.enumeration.EnumerationDescription;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.implementation.bytecode.assign.TypeCasting;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+
+/**
+ * An access representation to a given field.
+ */
+public enum FieldAccess {
+
+ /**
+ * The representation of field access to a static field.
+ */
+ STATIC(Opcodes.PUTSTATIC, Opcodes.GETSTATIC, StackSize.ZERO),
+
+ /**
+ * The representation of field access to an instance field.
+ */
+ INSTANCE(Opcodes.PUTFIELD, Opcodes.GETFIELD, StackSize.SINGLE);
+
+ /**
+ * The opcode for setting a field value.
+ */
+ private final int putterOpcode;
+
+ /**
+ * The opcode for getting a field value.
+ */
+ private final int getterOpcode;
+
+ /**
+ * The amount of operand slots this field access operation consumes when it is applied before eventually
+ * adding new values onto the operand stack.
+ */
+ private final int targetSizeChange;
+
+ /**
+ * Creates a new field access.
+ *
+ * @param putterOpcode The opcode for setting a field value.
+ * @param getterOpcode The opcode for getting a field value.
+ * @param targetSizeChange The amount of operand slots this field access operation consumes when it is applied
+ * before eventually adding new values onto the operand stack.
+ */
+ FieldAccess(int putterOpcode, int getterOpcode, StackSize targetSizeChange) {
+ this.putterOpcode = putterOpcode;
+ this.getterOpcode = getterOpcode;
+ this.targetSizeChange = targetSizeChange.getSize();
+ }
+
+ /**
+ * Creates an accessor to read an enumeration value.
+ *
+ * @param enumerationDescription The description of the enumeration.
+ * @return A stack manipulation for reading the enumeration.
+ */
+ public static StackManipulation forEnumeration(EnumerationDescription enumerationDescription) {
+ FieldList<FieldDescription.InDefinedShape> fieldList = enumerationDescription.getEnumerationType()
+ .getDeclaredFields()
+ .filter(named(enumerationDescription.getValue()));
+ return fieldList.size() != 1 || !fieldList.getOnly().isStatic() || !fieldList.getOnly().isPublic() || !fieldList.getOnly().isEnum()
+ ? StackManipulation.Illegal.INSTANCE
+ : STATIC.new AccessDispatcher(fieldList.getOnly()).read();
+ }
+
+ /**
+ * Creates a field access representation for a given field.
+ *
+ * @param fieldDescription The field to be accessed.
+ * @return A field access definition for the given field.
+ */
+ public static Defined forField(FieldDescription.InDefinedShape fieldDescription) {
+ return fieldDescription.isStatic()
+ ? STATIC.new AccessDispatcher(fieldDescription)
+ : INSTANCE.new AccessDispatcher(fieldDescription);
+ }
+
+ /**
+ * Creates a field access representation for a given field. If the field's return type derives from its declared shape, the value
+ * is additionally casted to the generically resolved field.
+ *
+ * @param fieldDescription The field to be accessed.
+ * @return A field access definition for the given field.
+ */
+ public static Defined forField(FieldDescription fieldDescription) {
+ FieldDescription.InDefinedShape declaredField = fieldDescription.asDefined();
+ return fieldDescription.getType().asErasure().equals(declaredField.getType().asErasure())
+ ? forField(declaredField)
+ : OfGenericField.of(fieldDescription, forField(declaredField));
+ }
+
+ /**
+ * Representation of a field access for which a getter and a setter can be created.
+ */
+ public interface Defined {
+
+ /**
+ * Creates a getter representation for a given field.
+ *
+ * @return A stack manipulation representing the retrieval of a field value.
+ */
+ StackManipulation read();
+
+ /**
+ * Creates a setter representation for a given field.
+ *
+ * @return A stack manipulation representing the setting of a field value.
+ */
+ StackManipulation write();
+ }
+
+ /**
+ * A dispatcher for implementing a generic read or write access on a field.
+ */
+ @EqualsAndHashCode
+ protected static class OfGenericField implements Defined {
+
+ /**
+ * The resolved generic field type.
+ */
+ private final TypeDefinition targetType;
+
+ /**
+ * An accessor for the field in its defined shape.
+ */
+ private final Defined defined;
+
+ /**
+ * Creates a new dispatcher for a generic field.
+ *
+ * @param targetType The resolved generic field type.
+ * @param defined An accessor for the field in its defined shape.
+ */
+ protected OfGenericField(TypeDefinition targetType, Defined defined) {
+ this.targetType = targetType;
+ this.defined = defined;
+ }
+
+ /**
+ * Creates a generic access dispatcher for a given field.
+ *
+ * @param fieldDescription The field that is being accessed.
+ * @param fieldAccess A field accessor for the field in its defined shape.
+ * @return A field access dispatcher for the given field.
+ */
+ protected static Defined of(FieldDescription fieldDescription, Defined fieldAccess) {
+ return new OfGenericField(fieldDescription.getType(), fieldAccess);
+ }
+
+ @Override
+ public StackManipulation read() {
+ return new StackManipulation.Compound(defined.read(), TypeCasting.to(targetType));
+ }
+
+ @Override
+ public StackManipulation write() {
+ return defined.write();
+ }
+ }
+
+ /**
+ * A dispatcher for implementing a non-generic read or write access on a field.
+ */
+ protected class AccessDispatcher implements Defined {
+
+ /**
+ * A description of the accessed field.
+ */
+ private final FieldDescription.InDefinedShape fieldDescription;
+
+ /**
+ * Creates a new access dispatcher.
+ *
+ * @param fieldDescription A description of the accessed field.
+ */
+ protected AccessDispatcher(FieldDescription.InDefinedShape fieldDescription) {
+ this.fieldDescription = fieldDescription;
+ }
+
+ @Override
+ public StackManipulation read() {
+ return new FieldGetInstruction();
+ }
+
+ @Override
+ public StackManipulation write() {
+ return new FieldPutInstruction();
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && FieldAccess.this.equals(((AccessDispatcher) other).getFieldAccess())
+ && fieldDescription.equals(((AccessDispatcher) other).fieldDescription);
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return fieldDescription.hashCode() + 31 * FieldAccess.this.hashCode();
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private FieldAccess getFieldAccess() {
+ return FieldAccess.this;
+ }
+
+ /**
+ * An abstract base implementation for accessing a field value.
+ */
+ private abstract class AbstractFieldInstruction implements StackManipulation {
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitFieldInsn(getOpcode(),
+ fieldDescription.getDeclaringType().getInternalName(),
+ fieldDescription.getInternalName(),
+ fieldDescription.getDescriptor());
+ return resolveSize(fieldDescription.getType().getStackSize());
+ }
+
+ /**
+ * Returns the opcode for implementing the field access.
+ *
+ * @return The opcode for implementing the field access.
+ */
+ protected abstract int getOpcode();
+
+ /**
+ * Resolves the actual size of this field access operation.
+ *
+ * @param fieldSize The size of the accessed field.
+ * @return The size of the field access operation based on the field's size.
+ */
+ protected abstract Size resolveSize(StackSize fieldSize);
+ }
+
+ /**
+ * A reading field access operation.
+ */
+ protected class FieldGetInstruction extends AbstractFieldInstruction {
+
+ @Override
+ protected int getOpcode() {
+ return getterOpcode;
+ }
+
+ @Override
+ protected Size resolveSize(StackSize fieldSize) {
+ int sizeChange = fieldSize.getSize() - targetSizeChange;
+ return new Size(sizeChange, sizeChange);
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && getAccessDispatcher().equals(((FieldGetInstruction) other).getAccessDispatcher());
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return getAccessDispatcher().hashCode() + 7;
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private AccessDispatcher getAccessDispatcher() {
+ return AccessDispatcher.this;
+ }
+ }
+
+ /**
+ * A writing field access operation.
+ */
+ protected class FieldPutInstruction extends AbstractFieldInstruction {
+
+ @Override
+ protected int getOpcode() {
+ return putterOpcode;
+ }
+
+ @Override
+ protected Size resolveSize(StackSize fieldSize) {
+ return new Size(-1 * (fieldSize.getSize() + targetSizeChange), 0);
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && getAccessDispatcher().equals(((FieldPutInstruction) other).getAccessDispatcher());
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return getAccessDispatcher().hashCode() + 14;
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private AccessDispatcher getAccessDispatcher() {
+ return AccessDispatcher.this;
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/member/HandleInvocation.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/member/HandleInvocation.java
new file mode 100644
index 0000000..76e028a
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/member/HandleInvocation.java
@@ -0,0 +1,51 @@
+package net.bytebuddy.implementation.bytecode.member;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.utility.JavaConstant;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * An exact invocation of a method handle with a polymorphic signature.
+ */
+ at EqualsAndHashCode
+public class HandleInvocation implements StackManipulation {
+
+ /**
+ * The name of the {@code java.lang.invoke.MethodHandle} type.
+ */
+ private static final String METHOD_HANDLE_NAME = "java/lang/invoke/MethodHandle";
+
+ /**
+ * The name of the {@code invokeExact} method.
+ */
+ private static final String INVOKE_EXACT = "invokeExact";
+
+ /**
+ * The method type of the invoked handle.
+ */
+ private final JavaConstant.MethodType methodType;
+
+ /**
+ * Creates a public invocation of a method handle.
+ *
+ * @param methodType The method type of the invoked handle.
+ */
+ public HandleInvocation(JavaConstant.MethodType methodType) {
+ this.methodType = methodType;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, METHOD_HANDLE_NAME, INVOKE_EXACT, methodType.getDescriptor(), false);
+ int size = methodType.getReturnType().getStackSize().getSize() - methodType.getParameterTypes().getStackSize();
+ return new Size(size, Math.max(size, 0));
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/member/MethodInvocation.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/member/MethodInvocation.java
new file mode 100644
index 0000000..7206074
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/member/MethodInvocation.java
@@ -0,0 +1,574 @@
+package net.bytebuddy.implementation.bytecode.member;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.TypeCasting;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.List;
+
+/**
+ * A builder for a method invocation.
+ */
+public enum MethodInvocation {
+
+ /**
+ * A virtual method invocation.
+ */
+ VIRTUAL(Opcodes.INVOKEVIRTUAL, Opcodes.H_INVOKEVIRTUAL),
+
+ /**
+ * An interface-typed virtual method invocation.
+ */
+ INTERFACE(Opcodes.INVOKEINTERFACE, Opcodes.H_INVOKEINTERFACE),
+
+ /**
+ * A static method invocation.
+ */
+ STATIC(Opcodes.INVOKESTATIC, Opcodes.H_INVOKESTATIC),
+
+ /**
+ * A specialized pseudo-virtual method invocation for a non-constructor.
+ */
+ SPECIAL(Opcodes.INVOKESPECIAL, Opcodes.H_INVOKESPECIAL),
+
+ /**
+ * A specialized pseudo-virtual method invocation for a constructor.
+ */
+ SPECIAL_CONSTRUCTOR(Opcodes.INVOKESPECIAL, Opcodes.H_NEWINVOKESPECIAL);
+
+ /**
+ * The opcode for invoking a method.
+ */
+ private final int invocationOpcode;
+
+ /**
+ * The handle being used for a dynamic method invocation.
+ */
+ private final int handle;
+
+ /**
+ * Creates a new type of method invocation.
+ *
+ * @param callOpcode The opcode for invoking a method.
+ * @param handle The handle being used for a dynamic method invocation.
+ */
+ MethodInvocation(int callOpcode, int handle) {
+ this.invocationOpcode = callOpcode;
+ this.handle = handle;
+ }
+
+ /**
+ * Creates a method invocation with an implicitly determined invocation type.
+ *
+ * @param methodDescription The method to be invoked.
+ * @return A stack manipulation with implicitly determined invocation type.
+ */
+ public static WithImplicitInvocationTargetType invoke(MethodDescription.InDefinedShape methodDescription) {
+ if (methodDescription.isTypeInitializer()) {
+ return IllegalInvocation.INSTANCE;
+ } else if (methodDescription.isStatic()) { // Check this property first, private static methods must use INVOKESTATIC
+ return STATIC.new Invocation(methodDescription);
+ } else if (methodDescription.isConstructor()) {
+ return SPECIAL_CONSTRUCTOR.new Invocation(methodDescription); // Check this property second, constructors might be private
+ } else if (methodDescription.isPrivate()) {
+ return SPECIAL.new Invocation(methodDescription);
+ } else if (methodDescription.getDeclaringType().isInterface()) { // Check this property last, default methods must be called by INVOKESPECIAL
+ return INTERFACE.new Invocation(methodDescription);
+ } else {
+ return VIRTUAL.new Invocation(methodDescription);
+ }
+ }
+
+ /**
+ * Creates a method invocation with an implicitly determined invocation type. If the method's return type derives from its declared shape, the value
+ * is additionally casted to the value of the generically resolved method.
+ *
+ * @param methodDescription The method to be invoked.
+ * @return A stack manipulation with implicitly determined invocation type.
+ */
+ public static WithImplicitInvocationTargetType invoke(MethodDescription methodDescription) {
+ MethodDescription.InDefinedShape declaredMethod = methodDescription.asDefined();
+ return declaredMethod.getReturnType().asErasure().equals(methodDescription.getReturnType().asErasure())
+ ? invoke(declaredMethod)
+ : OfGenericMethod.of(methodDescription, invoke(declaredMethod));
+ }
+
+ /**
+ * An illegal implicit method invocation.
+ */
+ protected enum IllegalInvocation implements WithImplicitInvocationTargetType {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public StackManipulation virtual(TypeDescription invocationTarget) {
+ return Illegal.INSTANCE;
+ }
+
+ @Override
+ public StackManipulation special(TypeDescription invocationTarget) {
+ return Illegal.INSTANCE;
+ }
+
+ @Override
+ public StackManipulation dynamic(String methodName,
+ TypeDescription returnType,
+ List<? extends TypeDescription> methodType,
+ List<?> arguments) {
+ return Illegal.INSTANCE;
+ }
+
+ @Override
+ public StackManipulation onHandle(HandleType type) {
+ return Illegal.INSTANCE;
+ }
+
+ @Override
+ public boolean isValid() {
+ return false;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ return Illegal.INSTANCE.apply(methodVisitor, implementationContext);
+ }
+ }
+
+ /**
+ * Represents a method invocation where the invocation type (static, virtual, special, interface) is derived
+ * from the given method's description.
+ */
+ public interface WithImplicitInvocationTargetType extends StackManipulation {
+
+ /**
+ * Transforms this method invocation into a virtual (or interface) method invocation on the given type.
+ *
+ * @param invocationTarget The type on which the method is to be invoked virtually on.
+ * @return A stack manipulation representing this method invocation.
+ */
+ StackManipulation virtual(TypeDescription invocationTarget);
+
+ /**
+ * Transforms this method invocation into a special invocation on the given type.
+ *
+ * @param invocationTarget The type on which the method is to be invoked specially on.
+ * @return A stack manipulation representing this method invocation.
+ */
+ StackManipulation special(TypeDescription invocationTarget);
+
+ /**
+ * Invokes the method as a bootstrap method to bind a call site with the given properties. Note that the
+ * Java virtual machine currently only knows how to resolve bootstrap methods that link static methods
+ * or a constructor.
+ *
+ * @param methodName The name of the method to be bound.
+ * @param returnType The return type of the method to be bound.
+ * @param methodType The parameter types of the method to be bound.
+ * @param arguments The arguments to be passed to the bootstrap method.
+ * @return A stack manipulation that represents the dynamic method invocation.
+ */
+ StackManipulation dynamic(String methodName,
+ TypeDescription returnType,
+ List<? extends TypeDescription> methodType,
+ List<?> arguments);
+
+ /**
+ * Invokes the method via a {@code MethodHandle}.
+ *
+ * @param type The type of invocation.
+ * @return A stack manipulation that represents a method call of the specified method via a method handle.
+ */
+ StackManipulation onHandle(HandleType type);
+ }
+
+ /**
+ * A method invocation of a generically resolved method.
+ */
+ @EqualsAndHashCode
+ protected static class OfGenericMethod implements WithImplicitInvocationTargetType {
+
+ /**
+ * The generically resolved return type of the method.
+ */
+ private final TypeDescription targetType;
+
+ /**
+ * The invocation of the method in its defined shape.
+ */
+ private final WithImplicitInvocationTargetType invocation;
+
+ /**
+ * Creates a generic method invocation.
+ *
+ * @param targetType The generically resolved return type of the method.
+ * @param invocation The invocation of the method in its defined shape.
+ */
+ protected OfGenericMethod(TypeDescription targetType, WithImplicitInvocationTargetType invocation) {
+ this.targetType = targetType;
+ this.invocation = invocation;
+ }
+
+ /**
+ * Creates a generic access dispatcher for a given method.
+ *
+ * @param methodDescription The generically resolved return type of the method.
+ * @param invocation The invocation of the method in its defined shape.
+ * @return A method access dispatcher for the given method.
+ */
+ protected static WithImplicitInvocationTargetType of(MethodDescription methodDescription, WithImplicitInvocationTargetType invocation) {
+ return new OfGenericMethod(methodDescription.getReturnType().asErasure(), invocation);
+ }
+
+ @Override
+ public StackManipulation virtual(TypeDescription invocationTarget) {
+ return new StackManipulation.Compound(invocation.virtual(invocationTarget), TypeCasting.to(targetType));
+ }
+
+ @Override
+ public StackManipulation special(TypeDescription invocationTarget) {
+ return new StackManipulation.Compound(invocation.special(invocationTarget), TypeCasting.to(targetType));
+ }
+
+ @Override
+ public StackManipulation dynamic(String methodName, TypeDescription returnType, List<? extends TypeDescription> methodType, List<?> arguments) {
+ return invocation.dynamic(methodName, returnType, methodType, arguments);
+ }
+
+ @Override
+ public StackManipulation onHandle(HandleType type) {
+ return new Compound(invocation.onHandle(type), TypeCasting.to(targetType));
+ }
+
+ @Override
+ public boolean isValid() {
+ return invocation.isValid();
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ return new Compound(invocation, TypeCasting.to(targetType)).apply(methodVisitor, implementationContext);
+ }
+ }
+
+ /**
+ * An implementation of a method invoking stack manipulation.
+ */
+ protected class Invocation implements WithImplicitInvocationTargetType {
+
+ /**
+ * The method to be invoked.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * The type on which this method is to be invoked.
+ */
+ private final MethodDescription.InDefinedShape methodDescription;
+
+ /**
+ * Creates an invocation of a given method on its declaring type as an invocation target.
+ *
+ * @param methodDescription The method to be invoked.
+ */
+ protected Invocation(MethodDescription.InDefinedShape methodDescription) {
+ this(methodDescription, methodDescription.getDeclaringType());
+ }
+
+ /**
+ * Creates an invocation of a given method on a given invocation target type.
+ *
+ * @param methodDescription The method to be invoked.
+ * @param typeDescription The type on which this method is to be invoked.
+ */
+ protected Invocation(MethodDescription.InDefinedShape methodDescription, TypeDescription typeDescription) {
+ this.typeDescription = typeDescription;
+ this.methodDescription = methodDescription;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitMethodInsn(invocationOpcode,
+ typeDescription.getInternalName(),
+ methodDescription.getInternalName(),
+ methodDescription.getDescriptor(),
+ typeDescription.isInterface());
+ int parameterSize = methodDescription.getStackSize(), returnValueSize = methodDescription.getReturnType().getStackSize().getSize();
+ return new Size(returnValueSize - parameterSize, Math.max(0, returnValueSize - parameterSize));
+ }
+
+ @Override
+ public StackManipulation virtual(TypeDescription invocationTarget) {
+ if (methodDescription.isPrivate() || methodDescription.isConstructor() || methodDescription.isStatic()) {
+ return Illegal.INSTANCE;
+ }
+ if (invocationTarget.isInterface()) {
+ return INTERFACE.new Invocation(methodDescription, invocationTarget);
+ } else {
+ return VIRTUAL.new Invocation(methodDescription, invocationTarget);
+ }
+ }
+
+ @Override
+ public StackManipulation special(TypeDescription invocationTarget) {
+ return methodDescription.isSpecializableFor(invocationTarget)
+ ? SPECIAL.new Invocation(methodDescription, invocationTarget)
+ : Illegal.INSTANCE;
+ }
+
+ @Override
+ public StackManipulation dynamic(String methodName,
+ TypeDescription returnType,
+ List<? extends TypeDescription> methodType,
+ List<?> arguments) {
+ return methodDescription.isBootstrap()
+ ? new DynamicInvocation(methodName, returnType, new TypeList.Explicit(methodType), methodDescription.asDefined(), arguments)
+ : Illegal.INSTANCE;
+ }
+
+ @Override
+ public StackManipulation onHandle(HandleType type) {
+ return new HandleInvocation(methodDescription, type);
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private MethodInvocation getOuterInstance() {
+ return MethodInvocation.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (other == null || getClass() != other.getClass()) return false;
+ Invocation that = (Invocation) other;
+ return MethodInvocation.this.equals(((Invocation) other).getOuterInstance())
+ && methodDescription.asSignatureToken().equals(that.methodDescription.asSignatureToken())
+ && typeDescription.equals(that.typeDescription);
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ int result = typeDescription.hashCode();
+ result = 31 * result + MethodInvocation.this.hashCode();
+ result = 31 * result + methodDescription.asSignatureToken().hashCode();
+ return result;
+ }
+ }
+
+ /**
+ * Performs a dynamic method invocation of the given method.
+ */
+ protected class DynamicInvocation implements StackManipulation {
+
+ /**
+ * The internal name of the method that is to be bootstrapped.
+ */
+ private final String methodName;
+
+ /**
+ * The return type of the method to be bootstrapped.
+ */
+ private final TypeDescription returnType;
+
+ /**
+ * The parameter types of the method to be bootstrapped.
+ */
+ private final TypeList parameterTypes;
+
+ /**
+ * The bootstrap method.
+ */
+ private final MethodDescription.InDefinedShape bootstrapMethod;
+
+ /**
+ * The list of arguments to be handed over to the bootstrap method.
+ */
+ private final List<?> arguments;
+
+ /**
+ * Creates a new dynamic method invocation.
+ *
+ * @param methodName The internal name of the method that is to be bootstrapped.
+ * @param returnType The return type of the method to be bootstrapped.
+ * @param parameterTypes The type of the parameters to be bootstrapped.
+ * @param bootstrapMethod The bootstrap method.
+ * @param arguments The list of arguments to be handed over to the bootstrap method.
+ */
+ public DynamicInvocation(String methodName,
+ TypeDescription returnType,
+ TypeList parameterTypes,
+ MethodDescription.InDefinedShape bootstrapMethod,
+ List<?> arguments) {
+ this.methodName = methodName;
+ this.returnType = returnType;
+ this.parameterTypes = parameterTypes;
+ this.bootstrapMethod = bootstrapMethod;
+ this.arguments = arguments;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ StringBuilder stringBuilder = new StringBuilder("(");
+ for (TypeDescription parameterType : parameterTypes) {
+ stringBuilder.append(parameterType.getDescriptor());
+ }
+ String methodDescriptor = stringBuilder.append(')').append(returnType.getDescriptor()).toString();
+ methodVisitor.visitInvokeDynamicInsn(methodName,
+ methodDescriptor,
+ new Handle(handle,
+ bootstrapMethod.getDeclaringType().getInternalName(),
+ bootstrapMethod.getInternalName(),
+ bootstrapMethod.getDescriptor(),
+ bootstrapMethod.getDeclaringType().isInterface()),
+ arguments.toArray(new Object[arguments.size()]));
+ int stackSize = returnType.getStackSize().getSize() - parameterTypes.getStackSize();
+ return new Size(stackSize, Math.max(stackSize, 0));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (other == null || getClass() != other.getClass()) return false;
+ DynamicInvocation that = (DynamicInvocation) other;
+ return MethodInvocation.this == that.getOuter()
+ && arguments.equals(that.arguments)
+ && bootstrapMethod.equals(that.bootstrapMethod)
+ && returnType.equals(that.returnType)
+ && parameterTypes.equals(that.parameterTypes)
+ && methodName.equals(that.methodName);
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private MethodInvocation getOuter() {
+ return MethodInvocation.this;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = methodName.hashCode();
+ result = 31 * result + MethodInvocation.this.hashCode();
+ result = 31 * result + returnType.hashCode();
+ result = 31 * result + parameterTypes.hashCode();
+ result = 31 * result + bootstrapMethod.hashCode();
+ result = 31 * result + arguments.hashCode();
+ return result;
+ }
+ }
+
+ /**
+ * Performs a method invocation on a method handle with a polymorphic type signature.
+ */
+ @EqualsAndHashCode
+ protected static class HandleInvocation implements StackManipulation {
+
+ /**
+ * The internal name of the method handle type.
+ */
+ private static final String METHOD_HANDLE = "java/lang/invoke/MethodHandle";
+
+ /**
+ * The invoked method.
+ */
+ private final MethodDescription.InDefinedShape methodDescription;
+
+ /**
+ * The type of method handle invocation.
+ */
+ private final HandleType type;
+
+ /**
+ * Creates a new method handle invocation.
+ *
+ * @param methodDescription The invoked method.
+ * @param type The type of method handle invocation.
+ */
+ protected HandleInvocation(MethodDescription.InDefinedShape methodDescription, HandleType type) {
+ this.methodDescription = methodDescription;
+ this.type = type;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
+ METHOD_HANDLE,
+ type.getMethodName(),
+ methodDescription.isStatic() || methodDescription.isConstructor()
+ ? methodDescription.getDescriptor()
+ : "(" + methodDescription.getDeclaringType().getDescriptor() + methodDescription.getDescriptor().substring(1),
+ false);
+ int parameterSize = 1 + methodDescription.getStackSize(), returnValueSize = methodDescription.getReturnType().getStackSize().getSize();
+ return new Size(returnValueSize - parameterSize, Math.max(0, returnValueSize - parameterSize));
+ }
+ }
+
+ /**
+ * The type of method handle invocation.
+ */
+ public enum HandleType {
+
+ /**
+ * An exact invocation without type adjustments.
+ */
+ EXACT("invokeExact"),
+
+ /**
+ * A regular invocation with standard type adjustments.
+ */
+ REGULAR("invoke");
+
+ /**
+ * The name of the invoked method.
+ */
+ private final String methodName;
+
+ /**
+ * Creates a new handle type.
+ *
+ * @param methodName The name of the invoked method.
+ */
+ HandleType(String methodName) {
+ this.methodName = methodName;
+ }
+
+ /**
+ * Returns the name of the represented method.
+ *
+ * @return The name of the invoked method.
+ */
+ protected String getMethodName() {
+ return methodName;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/member/MethodReturn.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/member/MethodReturn.java
new file mode 100644
index 0000000..e020a14
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/member/MethodReturn.java
@@ -0,0 +1,100 @@
+package net.bytebuddy.implementation.bytecode.member;
+
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * A stack manipulation returning a value of a given type.
+ */
+public enum MethodReturn implements StackManipulation {
+
+ /**
+ * The method return handler for returning a JVM-integer.
+ */
+ INTEGER(Opcodes.IRETURN, StackSize.SINGLE),
+
+ /**
+ * The method return handler for returning a {@code double}.
+ */
+ DOUBLE(Opcodes.DRETURN, StackSize.DOUBLE),
+
+ /**
+ * The method return handler for returning a {@code float}.
+ */
+ FLOAT(Opcodes.FRETURN, StackSize.SINGLE),
+
+ /**
+ * The method return handler for returning a {@code long}.
+ */
+ LONG(Opcodes.LRETURN, StackSize.DOUBLE),
+
+ /**
+ * The method return handler for returning {@code void}.
+ */
+ VOID(Opcodes.RETURN, StackSize.ZERO),
+
+ /**
+ * The method return handler for returning a reference type.
+ */
+ REFERENCE(Opcodes.ARETURN, StackSize.SINGLE);
+
+ /**
+ * The opcode of this operation.
+ */
+ private final int returnOpcode;
+
+ /**
+ * The operand stack size change that is implied by this operation.
+ */
+ private final Size size;
+
+ /**
+ * Creates a new method return manipulation.
+ *
+ * @param returnOpcode The opcode of this operation.
+ * @param stackSize The operand stack size change that is implied by this operation.
+ */
+ MethodReturn(int returnOpcode, StackSize stackSize) {
+ this.returnOpcode = returnOpcode;
+ size = stackSize.toDecreasingSize();
+ }
+
+ /**
+ * Returns a method return corresponding to a given type.
+ *
+ * @param typeDefinition The type to be returned.
+ * @return The stack manipulation representing the method return.
+ */
+ public static StackManipulation of(TypeDefinition typeDefinition) {
+ if (typeDefinition.isPrimitive()) {
+ if (typeDefinition.represents(long.class)) {
+ return LONG;
+ } else if (typeDefinition.represents(double.class)) {
+ return DOUBLE;
+ } else if (typeDefinition.represents(float.class)) {
+ return FLOAT;
+ } else if (typeDefinition.represents(void.class)) {
+ return VOID;
+ } else {
+ return INTEGER;
+ }
+ } else {
+ return REFERENCE;
+ }
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitInsn(returnOpcode);
+ return size;
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/member/MethodVariableAccess.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/member/MethodVariableAccess.java
new file mode 100644
index 0000000..ad884bd
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/member/MethodVariableAccess.java
@@ -0,0 +1,455 @@
+package net.bytebuddy.implementation.bytecode.member;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.implementation.bytecode.assign.TypeCasting;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A stack assignment that loads a method variable from a given index of the local variable array.
+ */
+public enum MethodVariableAccess {
+
+ /**
+ * The accessor handler for a JVM-integer.
+ */
+ INTEGER(Opcodes.ILOAD, Opcodes.ISTORE, StackSize.SINGLE),
+
+ /**
+ * The accessor handler for a {@code long}.
+ */
+ LONG(Opcodes.LLOAD, Opcodes.LSTORE, StackSize.DOUBLE),
+
+ /**
+ * The accessor handler for a {@code float}.
+ */
+ FLOAT(Opcodes.FLOAD, Opcodes.FSTORE, StackSize.SINGLE),
+
+ /**
+ * The accessor handler for a {@code double}.
+ */
+ DOUBLE(Opcodes.DLOAD, Opcodes.DSTORE, StackSize.DOUBLE),
+
+ /**
+ * The accessor handler for a reference type.
+ */
+ REFERENCE(Opcodes.ALOAD, Opcodes.ASTORE, StackSize.SINGLE);
+
+ /**
+ * The opcode for loading this variable type.
+ */
+ private final int loadOpcode;
+
+ /**
+ * The opcode for storing a local variable type.
+ */
+ private final int storeOpcode;
+
+ /**
+ * The size of the local variable on the JVM stack.
+ */
+ private final StackSize size;
+
+ /**
+ * Creates a new method variable access for a given JVM type.
+ *
+ * @param loadOpcode The opcode for loading this variable type.
+ * @param storeOpcode The opcode for storing this variable type.
+ * @param stackSize The size of the JVM type.
+ */
+ MethodVariableAccess(int loadOpcode, int storeOpcode, StackSize stackSize) {
+ this.loadOpcode = loadOpcode;
+ this.size = stackSize;
+ this.storeOpcode = storeOpcode;
+ }
+
+ /**
+ * Locates the correct accessor for a variable of a given type.
+ *
+ * @param typeDefinition The type of the variable to be loaded.
+ * @return An accessor for the given type.
+ */
+ public static MethodVariableAccess of(TypeDefinition typeDefinition) {
+ if (typeDefinition.isPrimitive()) {
+ if (typeDefinition.represents(long.class)) {
+ return LONG;
+ } else if (typeDefinition.represents(double.class)) {
+ return DOUBLE;
+ } else if (typeDefinition.represents(float.class)) {
+ return FLOAT;
+ } else if (typeDefinition.represents(void.class)) {
+ throw new IllegalArgumentException("Variable type cannot be void");
+ } else {
+ return INTEGER;
+ }
+ } else {
+ return REFERENCE;
+ }
+ }
+
+ /**
+ * Loads all arguments of the provided method onto the operand stack.
+ *
+ * @param methodDescription The method for which all parameters are to be loaded onto the operand stack.
+ * @return A stack manipulation that loads all parameters of the provided method onto the operand stack.
+ */
+ public static MethodLoading allArgumentsOf(MethodDescription methodDescription) {
+ return new MethodLoading(methodDescription, MethodLoading.TypeCastingHandler.NoOp.INSTANCE);
+ }
+
+ /**
+ * Loads a reference to the {@code this} reference what is only meaningful for a non-static method.
+ *
+ * @return A stack manipulation loading the {@code this} reference.
+ */
+ public static StackManipulation loadThis() {
+ return MethodVariableAccess.REFERENCE.loadFrom(0);
+ }
+
+ /**
+ * Creates a stack assignment for a reading given offset of the local variable array.
+ *
+ * @param offset The offset of the variable where {@code double} and {@code long} types count two slots.
+ * @return A stack manipulation representing the variable read.
+ */
+ public StackManipulation loadFrom(int offset) {
+ return new OffsetLoading(offset);
+ }
+
+ /**
+ * Creates a stack assignment for writing to a given offset of the local variable array.
+ *
+ * @param offset The offset of the variable where {@code double} and {@code long} types count two slots.
+ * @return A stack manipulation representing the variable write.
+ */
+ public StackManipulation storeAt(int offset) {
+ return new OffsetWriting(offset);
+ }
+
+ /**
+ * Creates a stack assignment for incrementing the given offset of the local variable array.
+ *
+ * @param offset The offset of the variable where {@code double} and {@code long} types count two slots.
+ * @param value The incremented value.
+ * @return A stack manipulation representing the variable write.
+ */
+ public StackManipulation increment(int offset, int value) {
+ if (this != INTEGER) {
+ throw new IllegalStateException("Cannot increment type: " + this);
+ }
+ return new OffsetIncrementing(offset, value);
+ }
+
+ /**
+ * Loads a parameter's value onto the operand stack.
+ *
+ * @param parameterDescription The parameter which to load onto the operand stack.
+ * @return A stack manipulation loading a parameter onto the operand stack.
+ */
+ public static StackManipulation load(ParameterDescription parameterDescription) {
+ return of(parameterDescription.getType()).loadFrom(parameterDescription.getOffset());
+ }
+
+ /**
+ * Stores the top operand stack value at the supplied parameter.
+ *
+ * @param parameterDescription The parameter which to store a value for.
+ * @return A stack manipulation storing the top operand stack value at this parameter.
+ */
+ public static StackManipulation store(ParameterDescription parameterDescription) {
+ return of(parameterDescription.getType()).storeAt(parameterDescription.getOffset());
+ }
+
+ /**
+ * Increments the value of the supplied parameter.
+ *
+ * @param parameterDescription The parameter which to increment.
+ * @param value The value to increment with.
+ * @return A stack manipulation incrementing the supplied parameter.
+ */
+ public static StackManipulation increment(ParameterDescription parameterDescription, int value) {
+ return of(parameterDescription.getType()).increment(parameterDescription.getOffset(), value);
+ }
+
+ /**
+ * A stack manipulation that loads all parameters of a given method onto the operand stack.
+ */
+ @EqualsAndHashCode
+ public static class MethodLoading implements StackManipulation {
+
+ /**
+ * The method for which all parameters are loaded onto the operand stack.
+ */
+ private final MethodDescription methodDescription;
+
+ /**
+ * A type casting handler which is capable of transforming all method parameters.
+ */
+ private final TypeCastingHandler typeCastingHandler;
+
+ /**
+ * Creates a new method loading stack manipulation.
+ *
+ * @param methodDescription The method for which all parameters are loaded onto the operand stack.
+ * @param typeCastingHandler A type casting handler which is capable of transforming all method parameters.
+ */
+ protected MethodLoading(MethodDescription methodDescription, TypeCastingHandler typeCastingHandler) {
+ this.methodDescription = methodDescription;
+ this.typeCastingHandler = typeCastingHandler;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ List<StackManipulation> stackManipulations = new ArrayList<StackManipulation>();
+ for (ParameterDescription parameterDescription : methodDescription.getParameters()) {
+ TypeDescription parameterType = parameterDescription.getType().asErasure();
+ stackManipulations.add(of(parameterType).loadFrom(parameterDescription.getOffset()));
+ stackManipulations.add(typeCastingHandler.ofIndex(parameterType, parameterDescription.getIndex()));
+ }
+ return new Compound(stackManipulations).apply(methodVisitor, implementationContext);
+ }
+
+ /**
+ * Prepends a reference to the {@code this} instance to the loaded parameters if the represented method is non-static.
+ *
+ * @return A stack manipulation that loads all method parameters onto the operand stack while additionally loading a reference
+ * to {@code this} if the represented is non-static. Any potential parameter transformation is preserved.
+ */
+ public StackManipulation prependThisReference() {
+ return methodDescription.isStatic()
+ ? this
+ : new Compound(MethodVariableAccess.loadThis(), this);
+ }
+
+ /**
+ * Applies a transformation to all loaded arguments of the method being loaded to be casted to the corresponding parameter of
+ * the provided method. This way, the parameters can be used for invoking a bridge target method.
+ *
+ * @param bridgeTarget The method that is the target of the bridge method for which the parameters are being loaded.
+ * @return A stack manipulation that loads all parameters casted to the types of the supplied bridge target.
+ */
+ public MethodLoading asBridgeOf(MethodDescription bridgeTarget) {
+ return new MethodLoading(methodDescription, new TypeCastingHandler.ForBridgeTarget(bridgeTarget));
+ }
+
+ /**
+ * A type casting handler allows a type transformation of all arguments of a method after loading them onto the operand stack.
+ */
+ protected interface TypeCastingHandler {
+
+ /**
+ * Yields a stack transformation to transform the given argument of the method for which the arguments are loaded onto the operand stack.
+ *
+ * @param parameterType The parameter type that is to be transformed.
+ * @param index The index of the transformed parameter.
+ * @return A transformation to apply after loading the parameter onto the operand stack.
+ */
+ StackManipulation ofIndex(TypeDescription parameterType, int index);
+
+ /**
+ * A non-operative type casting handler.
+ */
+ enum NoOp implements TypeCastingHandler {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public StackManipulation ofIndex(TypeDescription parameterType, int index) {
+ return Trivial.INSTANCE;
+ }
+ }
+
+ /**
+ * A type casting handler that casts all parameters of a method to the parameter types of a compatible method
+ * with covariant parameter types. This allows a convenient implementation of bridge methods.
+ */
+ @EqualsAndHashCode
+ class ForBridgeTarget implements TypeCastingHandler {
+
+ /**
+ * The target of the method bridge.
+ */
+ private final MethodDescription bridgeTarget;
+
+ /**
+ * Creates a new type casting handler for a bridge target.
+ *
+ * @param bridgeTarget The target of the method bridge.
+ */
+ public ForBridgeTarget(MethodDescription bridgeTarget) {
+ this.bridgeTarget = bridgeTarget;
+ }
+
+ @Override
+ public StackManipulation ofIndex(TypeDescription parameterType, int index) {
+ TypeDescription targetType = bridgeTarget.getParameters().get(index).getType().asErasure();
+ return parameterType.equals(targetType)
+ ? Trivial.INSTANCE
+ : TypeCasting.to(targetType);
+ }
+ }
+ }
+ }
+
+ /**
+ * A stack manipulation for loading a variable of a method's local variable array onto the operand stack.
+ */
+ protected class OffsetLoading implements StackManipulation {
+
+ /**
+ * The offset of the local variable array from which the variable should be loaded.
+ */
+ private final int offset;
+
+ /**
+ * Creates a new argument loading stack manipulation.
+ *
+ * @param offset The offset of the local variable array from which the variable should be loaded.
+ */
+ protected OffsetLoading(int offset) {
+ this.offset = offset;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitVarInsn(loadOpcode, offset);
+ return size.toIncreasingSize();
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private MethodVariableAccess getMethodVariableAccess() {
+ return MethodVariableAccess.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && MethodVariableAccess.this == ((OffsetLoading) other).getMethodVariableAccess()
+ && offset == ((OffsetLoading) other).offset;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return MethodVariableAccess.this.hashCode() + 31 * offset;
+ }
+ }
+
+ /**
+ * A stack manipulation for storing a variable into a method's local variable array.
+ */
+ protected class OffsetWriting implements StackManipulation {
+
+ /**
+ * The offset of the local variable array to which the value should be written.
+ */
+ private final int offset;
+
+ /**
+ * Creates a new argument writing stack manipulation.
+ *
+ * @param offset The offset of the local variable array to which the value should be written.
+ */
+ protected OffsetWriting(int offset) {
+ this.offset = offset;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitVarInsn(storeOpcode, offset);
+ return size.toDecreasingSize();
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private MethodVariableAccess getMethodVariableAccess() {
+ return MethodVariableAccess.this;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && MethodVariableAccess.this == ((OffsetWriting) other).getMethodVariableAccess()
+ && offset == ((OffsetWriting) other).offset;
+ }
+
+ @Override
+ public int hashCode() {
+ return MethodVariableAccess.this.hashCode() + 31 * offset;
+ }
+ }
+
+ /**
+ * A stack manipulation that increments an integer variable.
+ */
+ @EqualsAndHashCode
+ protected static class OffsetIncrementing implements StackManipulation {
+
+ /**
+ * The index of the local variable array from which the variable should be loaded.
+ */
+ private final int offset;
+
+ /**
+ * The value to increment.
+ */
+ private final int value;
+
+ /**
+ * Creates a new argument loading stack manipulation.
+ *
+ * @param offset The index of the local variable array from which the variable should be loaded.
+ * @param value The value to increment.
+ */
+ protected OffsetIncrementing(int offset, int value) {
+ this.offset = offset;
+ this.value = value;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitIincInsn(offset, value);
+ return new Size(0, 0);
+ }
+ }
+}
+
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/member/package-info.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/member/package-info.java
new file mode 100644
index 0000000..7e42cf4
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/member/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * {@link net.bytebuddy.implementation.bytecode.StackManipulation}s of this package are responsible for
+ * accessing type or method members, i.e. reading and writing of fields, invoking of methods, access of local variables
+ * within a method invocation or returning values from method invocations.
+ */
+package net.bytebuddy.implementation.bytecode.member;
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/package-info.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/package-info.java
new file mode 100644
index 0000000..2df2c67
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bytecode/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Types and classes in this package are responsible for creating Java byte code for a given byte code target
+ * which is represented by a {@link net.bytebuddy.description.method.MethodDescription}.
+ */
+package net.bytebuddy.implementation.bytecode;
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/package-info.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/package-info.java
new file mode 100644
index 0000000..4171843
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * The implementation package contains any logic for intercepting method calls.
+ */
+package net.bytebuddy.implementation;
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/AccessibilityMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/AccessibilityMatcher.java
new file mode 100644
index 0000000..1ba0137
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/AccessibilityMatcher.java
@@ -0,0 +1,38 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.ByteCodeElement;
+import net.bytebuddy.description.type.TypeDescription;
+
+/**
+ * An element matcher that validates that a given byte code element is accessible to a given type.
+ *
+ * @param <T>The type of the matched entity.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class AccessibilityMatcher<T extends ByteCodeElement> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The type that is to be checked for its viewing rights.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a matcher that validates that a byte code element can be seen by a given type.
+ *
+ * @param typeDescription The type that is to be checked for its viewing rights.
+ */
+ public AccessibilityMatcher(TypeDescription typeDescription) {
+ this.typeDescription = typeDescription;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ return target.isAccessibleTo(typeDescription);
+ }
+
+ @Override
+ public String toString() {
+ return "isAccessibleTo(" + typeDescription + ")";
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/AnnotationTypeMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/AnnotationTypeMatcher.java
new file mode 100644
index 0000000..7de0224
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/AnnotationTypeMatcher.java
@@ -0,0 +1,38 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.type.TypeDescription;
+
+/**
+ * An element matcher that matches the type of an annotation description.
+ *
+ * @param <T> The exact type of the annotation description that is matched.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class AnnotationTypeMatcher<T extends AnnotationDescription> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The type matcher to apply to an annotation's type.
+ */
+ private final ElementMatcher<? super TypeDescription> matcher;
+
+ /**
+ * Creates a new matcher for an annotation description's type.
+ *
+ * @param matcher The type matcher to apply to an annotation's type.
+ */
+ public AnnotationTypeMatcher(ElementMatcher<? super TypeDescription> matcher) {
+ this.matcher = matcher;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ return matcher.matches(target.getAnnotationType());
+ }
+
+ @Override
+ public String toString() {
+ return "ofAnnotationType(" + matcher + ')';
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/BooleanMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/BooleanMatcher.java
new file mode 100644
index 0000000..9a46eb7
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/BooleanMatcher.java
@@ -0,0 +1,36 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+
+/**
+ * An element matcher that returns a fixed result.
+ *
+ * @param <T> The actual matched type of this matcher.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class BooleanMatcher<T> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The predefined result.
+ */
+ private final boolean matches;
+
+ /**
+ * Creates a new boolean element matcher.
+ *
+ * @param matches The predefined result.
+ */
+ public BooleanMatcher(boolean matches) {
+ this.matches = matches;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ return matches;
+ }
+
+ @Override
+ public String toString() {
+ return Boolean.toString(matches);
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/CachingMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/CachingMatcher.java
new file mode 100644
index 0000000..0919ded
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/CachingMatcher.java
@@ -0,0 +1,113 @@
+package net.bytebuddy.matcher;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * A matcher that remembers the results of previously matching an equal target.
+ *
+ * @param <T> The actual matched type of this matcher.
+ */
+public class CachingMatcher<T> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The underlying matcher to apply for non-cached targets.
+ */
+ private final ElementMatcher<? super T> matcher;
+
+ /**
+ * A map that serves as a cache for previous matches.
+ */
+ protected final ConcurrentMap<? super T, Boolean> map;
+
+ /**
+ * Creates a new caching matcher.
+ *
+ * @param matcher The underlying matcher to apply for non-cached targets.
+ * @param map A map that serves as a cache for previous matches. This match is strongly referenced and
+ * can cause a memory leak if it is not evicted while keeping this matcher alive.
+ */
+ public CachingMatcher(ElementMatcher<? super T> matcher, ConcurrentMap<? super T, Boolean> map) {
+ this.matcher = matcher;
+ this.map = map;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ Boolean cached = map.get(target);
+ if (cached == null) {
+ cached = onCacheMiss(target);
+ }
+ return cached;
+ }
+
+ /**
+ * Invoked if the cache is not hit.
+ *
+ * @param target The element to be matched.
+ * @return {@code true} if the element is matched.
+ */
+ protected boolean onCacheMiss(T target) {
+ boolean cached = matcher.matches(target);
+ map.put(target, cached);
+ return cached;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (this == object) return true;
+ if (!(object instanceof CachingMatcher)) return false;
+ CachingMatcher<?> that = (CachingMatcher<?>) object;
+ return matcher.equals(that.matcher);
+ }
+
+ @Override
+ public int hashCode() {
+ return matcher.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "cached(" + matcher + ")";
+ }
+
+ /**
+ * A caching matcher with inline cache eviction.
+ *
+ * @param <S> The actual matched type of this matcher.
+ */
+ @SuppressFBWarnings(value = "EQ_DOESNT_OVERRIDE_EQUALS", justification = "Caching mechanism is not supposed to decide on equality")
+ public static class WithInlineEviction<S> extends CachingMatcher<S> {
+
+ /**
+ * The maximum amount of entries in this map before removing a random entry from the map.
+ */
+ private final int evictionSize;
+
+ /**
+ * Creates a new caching matcher with inlined cache eviction.
+ *
+ * @param matcher The underlying matcher to apply for non-cached targets.
+ * @param map A map that serves as a cache for previous matches. This match is strongly referenced and
+ * can cause a memory leak if it is not evicted while keeping this matcher alive.
+ * @param evictionSize The maximum amount of entries in this map before removing a random entry from the map.
+ */
+ public WithInlineEviction(ElementMatcher<? super S> matcher, ConcurrentMap<? super S, Boolean> map, int evictionSize) {
+ super(matcher, map);
+ this.evictionSize = evictionSize;
+ }
+
+ @Override
+ protected boolean onCacheMiss(S target) {
+ if (map.size() >= evictionSize) {
+ Iterator<?> iterator = map.entrySet().iterator();
+ iterator.next();
+ iterator.remove();
+ }
+ return super.onCacheMiss(target);
+ }
+ }
+}
+
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/ClassLoaderHierarchyMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/ClassLoaderHierarchyMatcher.java
new file mode 100644
index 0000000..25f663a
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/ClassLoaderHierarchyMatcher.java
@@ -0,0 +1,44 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+
+/**
+ * An element matcher that matches all {@link java.lang.ClassLoader}s in the matched class loaders hierarchy
+ * against a given matcher.
+ *
+ * @param <T> The exact type of the class loader that is matched.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class ClassLoaderHierarchyMatcher<T extends ClassLoader> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The matcher to apply on each class loader in the hierarchy.
+ */
+ private final ElementMatcher<? super ClassLoader> matcher;
+
+ /**
+ * Creates a new class loader hierarchy matcher.
+ *
+ * @param matcher The matcher to apply on each class loader in the hierarchy.
+ */
+ public ClassLoaderHierarchyMatcher(ElementMatcher<? super ClassLoader> matcher) {
+ this.matcher = matcher;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ ClassLoader current = target;
+ while (current != null) {
+ if (matcher.matches(current)) {
+ return true;
+ }
+ current = current.getParent();
+ }
+ return matcher.matches(null);
+ }
+
+ @Override
+ public String toString() {
+ return "hasChild(" + matcher + ')';
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/ClassLoaderParentMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/ClassLoaderParentMatcher.java
new file mode 100644
index 0000000..0ec7f52
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/ClassLoaderParentMatcher.java
@@ -0,0 +1,43 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+
+/**
+ * An element matcher that matches a class loader for being a parent of the given class loader.
+ *
+ * @param <T> The exact type of the class loader that is matched.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class ClassLoaderParentMatcher<T extends ClassLoader> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The class loader that is matched for being a child of the matched class loader.
+ */
+ private final ClassLoader classLoader;
+
+ /**
+ * Creates a class loader parent element matcher.
+ *
+ * @param classLoader The class loader that is matched for being a child of the matched class loader.
+ */
+ public ClassLoaderParentMatcher(ClassLoader classLoader) {
+ this.classLoader = classLoader;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ ClassLoader current = classLoader;
+ while (current != null) {
+ if (current == target) {
+ return true;
+ }
+ current = current.getParent();
+ }
+ return target == null;
+ }
+
+ @Override
+ public String toString() {
+ return "isParentOf(" + classLoader + ')';
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/CollectionElementMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/CollectionElementMatcher.java
new file mode 100644
index 0000000..ecc36eb
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/CollectionElementMatcher.java
@@ -0,0 +1,54 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+
+import java.util.Iterator;
+
+/**
+ * A matcher that matches a given element of a collection. If no such element is contained by the matched iterable, the matcher
+ * returns {@code false}.
+ *
+ * @param <T> The type of the elements contained by the collection.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class CollectionElementMatcher<T> extends ElementMatcher.Junction.AbstractBase<Iterable<? extends T>> {
+
+ /**
+ * The index of the matched element.
+ */
+ private final int index;
+
+ /**
+ * The matcher for the given element, if it exists.
+ */
+ private final ElementMatcher<? super T> matcher;
+
+ /**
+ * Creates a new matcher for an element in a collection.
+ *
+ * @param index The index of the matched element.
+ * @param matcher The matcher for the given element, if it exists.
+ */
+ public CollectionElementMatcher(int index, ElementMatcher<? super T> matcher) {
+ this.index = index;
+ this.matcher = matcher;
+ }
+
+ @Override
+ public boolean matches(Iterable<? extends T> target) {
+ Iterator<? extends T> iterator = target.iterator();
+ for (int index = 0; index < this.index; index++) {
+ if (iterator.hasNext()) {
+ iterator.next();
+ } else {
+ return false;
+ }
+ }
+ return iterator.hasNext() && matcher.matches(iterator.next());
+ }
+
+ @Override
+ public String toString() {
+ return "with(" + index + " matches " + matcher + ")";
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/CollectionErasureMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/CollectionErasureMatcher.java
new file mode 100644
index 0000000..fbd827c
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/CollectionErasureMatcher.java
@@ -0,0 +1,45 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An element matcher that matches a collection of types by their erasures.
+ *
+ * @param <T> The type of the matched entity.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class CollectionErasureMatcher<T extends Iterable<? extends TypeDefinition>> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The matcher to be applied to the raw types.
+ */
+ private final ElementMatcher<? super Iterable<? extends TypeDescription>> matcher;
+
+ /**
+ * Creates a new raw type matcher.
+ *
+ * @param matcher The matcher to be applied to the raw types.
+ */
+ public CollectionErasureMatcher(ElementMatcher<? super Iterable<? extends TypeDescription>> matcher) {
+ this.matcher = matcher;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ List<TypeDescription> typeDescriptions = new ArrayList<TypeDescription>();
+ for (TypeDefinition typeDefinition : target) {
+ typeDescriptions.add(typeDefinition.asErasure());
+ }
+ return matcher.matches(typeDescriptions);
+ }
+
+ @Override
+ public String toString() {
+ return "erasures(" + matcher + ')';
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/CollectionItemMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/CollectionItemMatcher.java
new file mode 100644
index 0000000..e9d0ef3
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/CollectionItemMatcher.java
@@ -0,0 +1,42 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+
+/**
+ * A list item matcher matches any element of a collection to a given matcher and assures that at least one
+ * element matches the supplied iterable condition.
+ *
+ * @param <T> The type of the matched entity.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class CollectionItemMatcher<T> extends ElementMatcher.Junction.AbstractBase<Iterable<? extends T>> {
+
+ /**
+ * The element matcher to apply to each element of a collection.
+ */
+ private final ElementMatcher<? super T> matcher;
+
+ /**
+ * Creates a new matcher that applies another matcher to each element of a matched iterable collection.
+ *
+ * @param matcher The element matcher to apply to each element of a iterable collection.
+ */
+ public CollectionItemMatcher(ElementMatcher<? super T> matcher) {
+ this.matcher = matcher;
+ }
+
+ @Override
+ public boolean matches(Iterable<? extends T> target) {
+ for (T value : target) {
+ if (matcher.matches(value)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "whereOne(" + matcher + ")";
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/CollectionOneToOneMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/CollectionOneToOneMatcher.java
new file mode 100644
index 0000000..7d2a373
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/CollectionOneToOneMatcher.java
@@ -0,0 +1,63 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * An element matcher that matches a given iterable collection to a list of matchers on a per-element basis. For a
+ * successful match, any element of the matched iterable collection must be successfully matched by a next
+ * matcher of the supplied list of element matchers. For this to be possible, the matched iterable collection
+ * and the supplied list of element matchers contain the same number of elements.
+ *
+ * @param <T> The type of the matched entity.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class CollectionOneToOneMatcher<T> extends ElementMatcher.Junction.AbstractBase<Iterable<? extends T>> {
+
+ /**
+ * The list of element matchers to match any elements of the matched iterable collection against.
+ */
+ private final List<? extends ElementMatcher<? super T>> matchers;
+
+ /**
+ * Creates a new matcher that compares a matched iterable collection against a list of element matchers.
+ *
+ * @param matchers The list of element matchers to match any elements of the matched iterable collection
+ * against.
+ */
+ public CollectionOneToOneMatcher(List<? extends ElementMatcher<? super T>> matchers) {
+ this.matchers = matchers;
+ }
+
+ @Override
+ public boolean matches(Iterable<? extends T> target) {
+ if ((target instanceof Collection) && ((Collection<?>) target).size() != matchers.size()) {
+ return false;
+ }
+ Iterator<? extends ElementMatcher<? super T>> iterator = matchers.iterator();
+ for (T value : target) {
+ if (!iterator.hasNext() || !iterator.next().matches(value)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder("containing(");
+ boolean first = true;
+ for (Object value : matchers) {
+ if (first) {
+ first = false;
+ } else {
+ stringBuilder.append(", ");
+ }
+ stringBuilder.append(value);
+ }
+ return stringBuilder.append(")").toString();
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/CollectionSizeMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/CollectionSizeMatcher.java
new file mode 100644
index 0000000..26a15e0
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/CollectionSizeMatcher.java
@@ -0,0 +1,48 @@
+package net.bytebuddy.matcher;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+
+import java.util.Collection;
+
+/**
+ * An element matcher that matches a collection by its size.
+ *
+ * @param <T> The type of the matched entity.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class CollectionSizeMatcher<T extends Iterable<?>> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The expected size of the matched collection.
+ */
+ private final int size;
+
+ /**
+ * Creates a new matcher that matches the size of a matched collection.
+ *
+ * @param size The expected size of the matched collection.
+ */
+ public CollectionSizeMatcher(int size) {
+ this.size = size;
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "DLS_DEAD_LOCAL_STORE", justification = "Iteration required to count size of an iterable")
+ public boolean matches(T target) {
+ if (target instanceof Collection) {
+ return ((Collection<?>) target).size() == size;
+ } else {
+ int size = 0;
+ for (Object ignored : target) {
+ size++;
+ }
+ return size == this.size;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "ofSize(" + size + ')';
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/DeclaringAnnotationMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/DeclaringAnnotationMatcher.java
new file mode 100644
index 0000000..6c633a9
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/DeclaringAnnotationMatcher.java
@@ -0,0 +1,38 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.annotation.AnnotationSource;
+import net.bytebuddy.description.annotation.AnnotationList;
+
+/**
+ * An element matcher that matches the list of annotations that are provided by an annotated element.
+ *
+ * @param <T> The actual matched type of this matcher.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class DeclaringAnnotationMatcher<T extends AnnotationSource> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The matcher to be applied to the provided annotation list.
+ */
+ private final ElementMatcher<? super AnnotationList> matcher;
+
+ /**
+ * Creates a new matcher for the annotations of an annotated element.
+ *
+ * @param matcher The matcher to be applied to the provided annotation list.
+ */
+ public DeclaringAnnotationMatcher(ElementMatcher<? super AnnotationList> matcher) {
+ this.matcher = matcher;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ return matcher.matches(target.getDeclaredAnnotations());
+ }
+
+ @Override
+ public String toString() {
+ return "declaresAnnotations(" + matcher + ")";
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/DeclaringFieldMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/DeclaringFieldMatcher.java
new file mode 100644
index 0000000..73abdba
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/DeclaringFieldMatcher.java
@@ -0,0 +1,39 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.type.TypeDefinition;
+
+/**
+ * An element matcher that checks if a type description declares fields of a given property.
+ *
+ * @param <T> The exact type of the annotated element that is matched.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class DeclaringFieldMatcher<T extends TypeDefinition> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The field matcher to apply to the declared fields of the matched type description.
+ */
+ private final ElementMatcher<? super FieldList<?>> matcher;
+
+ /**
+ * Creates a new matcher for a type's declared fields.
+ *
+ * @param matcher The field matcher to apply to the declared fields of the matched type description.
+ */
+ public DeclaringFieldMatcher(ElementMatcher<? super FieldList<? extends FieldDescription>> matcher) {
+ this.matcher = matcher;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ return matcher.matches(target.getDeclaredFields());
+ }
+
+ @Override
+ public String toString() {
+ return "declaresFields(" + matcher + ")";
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/DeclaringMethodMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/DeclaringMethodMatcher.java
new file mode 100644
index 0000000..a6f9aba
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/DeclaringMethodMatcher.java
@@ -0,0 +1,39 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.type.TypeDefinition;
+
+/**
+ * An element matcher that checks if a type description declares methods of a given property.
+ *
+ * @param <T> The exact type of the annotated element that is matched.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class DeclaringMethodMatcher<T extends TypeDefinition> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The field matcher to apply to the declared fields of the matched type description.
+ */
+ private final ElementMatcher<? super MethodList<?>> matcher;
+
+ /**
+ * Creates a new matcher for a type's declared methods.
+ *
+ * @param matcher The method matcher to apply to the declared methods of the matched type description.
+ */
+ public DeclaringMethodMatcher(ElementMatcher<? super MethodList<? extends MethodDescription>> matcher) {
+ this.matcher = matcher;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ return matcher.matches(target.getDeclaredMethods());
+ }
+
+ @Override
+ public String toString() {
+ return "declaresMethods(" + matcher + ")";
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/DeclaringTypeMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/DeclaringTypeMatcher.java
new file mode 100644
index 0000000..bef7b7e
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/DeclaringTypeMatcher.java
@@ -0,0 +1,41 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.DeclaredByType;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+
+/**
+ * An element matcher that matches the declaring type of another element, only if this element is actually declared
+ * in a type.
+ *
+ * @param <T> The exact type of the element being matched.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class DeclaringTypeMatcher<T extends DeclaredByType> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The type matcher to be applied if the target element is declared in a type.
+ */
+ private final ElementMatcher<? super TypeDescription.Generic> matcher;
+
+ /**
+ * Creates a new matcher for the declaring type of an element.
+ *
+ * @param matcher The type matcher to be applied if the target element is declared in a type.
+ */
+ public DeclaringTypeMatcher(ElementMatcher<? super TypeDescription.Generic> matcher) {
+ this.matcher = matcher;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ TypeDefinition declaringType = target.getDeclaringType();
+ return declaringType != null && matcher.matches(declaringType.asGenericType());
+ }
+
+ @Override
+ public String toString() {
+ return "declaredBy(" + matcher + ")";
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/DefinedShapeMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/DefinedShapeMatcher.java
new file mode 100644
index 0000000..c848c3b
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/DefinedShapeMatcher.java
@@ -0,0 +1,39 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.ByteCodeElement;
+
+/**
+ * An element matcher that matches a byte code's element's token against a matcher for such a token.
+ *
+ * @param <T> The type of the matched entity.
+ * @param <S> The type of the defined shape of the matched entity.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class DefinedShapeMatcher<T extends ByteCodeElement.TypeDependant<S, ?>, S extends ByteCodeElement.TypeDependant<?, ?>>
+ extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The matcher to apply onto the defined shape of the matched entity.
+ */
+ private final ElementMatcher<? super S> matcher;
+
+ /**
+ * Creates a new matcher for a byte code element's defined shape.
+ *
+ * @param matcher The matcher to apply onto the defined shape of the matched entity.
+ */
+ public DefinedShapeMatcher(ElementMatcher<? super S> matcher) {
+ this.matcher = matcher;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ return matcher.matches(target.asDefined());
+ }
+
+ @Override
+ public String toString() {
+ return "isDefinedAs(" + matcher + ')';
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/DescriptorMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/DescriptorMatcher.java
new file mode 100644
index 0000000..34e0651
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/DescriptorMatcher.java
@@ -0,0 +1,37 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.ByteCodeElement;
+
+/**
+ * An element matcher that matches a Java descriptor.
+ *
+ * @param <T> The type of the matched entity.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class DescriptorMatcher<T extends ByteCodeElement> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * A matcher to apply to the descriptor.
+ */
+ private final ElementMatcher<String> matcher;
+
+ /**
+ * Creates a new matcher for an element's descriptor.
+ *
+ * @param matcher A matcher to apply to the descriptor.
+ */
+ public DescriptorMatcher(ElementMatcher<String> matcher) {
+ this.matcher = matcher;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ return matcher.matches(target.getDescriptor());
+ }
+
+ @Override
+ public String toString() {
+ return "hasDescriptor(" + matcher + ")";
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/ElementMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/ElementMatcher.java
new file mode 100644
index 0000000..64ce3d0
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/ElementMatcher.java
@@ -0,0 +1,146 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+
+/**
+ * An element matcher is used as a predicate for identifying code elements such as types, methods, fields or
+ * annotations. They are similar to Java 8's {@code Predicate}s but compatible to Java 6 and Java 7 and represent
+ * a functional interface. They can be chained by using instances of
+ * {@link net.bytebuddy.matcher.ElementMatcher.Junction}.
+ *
+ * @param <T> The type of the object that is being matched.
+ */
+public interface ElementMatcher<T> {
+
+ /**
+ * Matches a target against this element matcher.
+ *
+ * @param target The instance to be matched.
+ * @return {@code true} if the given element is matched by this matcher or {@code false} otherwise.
+ */
+ boolean matches(T target);
+
+ /**
+ * A junctions allows to chain different {@link net.bytebuddy.matcher.ElementMatcher}s in a readable manner.
+ *
+ * @param <S> The type of the object that is being matched.
+ */
+ interface Junction<S> extends ElementMatcher<S> {
+
+ /**
+ * Creates a conjunction where this matcher and the {@code other} matcher must both be matched in order
+ * to constitute a successful match. The other matcher is only invoked if this matcher constitutes a successful
+ * match.
+ *
+ * @param other The second matcher to consult.
+ * @param <U> The type of the object that is being matched. Note that Java's type inference might not
+ * be able to infer the common subtype of this instance and the {@code other} matcher such that
+ * this type must need to be named explicitly.
+ * @return A conjunction of this matcher and the other matcher.
+ */
+ <U extends S> Junction<U> and(ElementMatcher<? super U> other);
+
+ /**
+ * Creates a disjunction where either this matcher or the {@code other} matcher must be matched in order
+ * to constitute a successful match. The other matcher is only invoked if this matcher constitutes an
+ * unsuccessful match.
+ *
+ * @param other The second matcher to consult.
+ * @param <U> The type of the object that is being matched. Note that Java's type inference might not
+ * be able to infer the common subtype of this instance and the {@code other} matcher such that
+ * this type must need to be named explicitly.
+ * @return A disjunction of this matcher and the other matcher.
+ */
+ <U extends S> Junction<U> or(ElementMatcher<? super U> other);
+
+ /**
+ * A base implementation of {@link net.bytebuddy.matcher.ElementMatcher.Junction}.
+ *
+ * @param <V> The type of the object that is being matched.
+ */
+ abstract class AbstractBase<V> implements Junction<V> {
+
+ @Override
+ public <U extends V> Junction<U> and(ElementMatcher<? super U> other) {
+ return new Conjunction<U>(this, other);
+ }
+
+ @Override
+ public <U extends V> Junction<U> or(ElementMatcher<? super U> other) {
+ return new Disjunction<U>(this, other);
+ }
+ }
+
+ /**
+ * A conjunction matcher which only matches an element if both represented matchers constitute a match.
+ *
+ * @param <W> The type of the object that is being matched.
+ */
+ @EqualsAndHashCode(callSuper = false)
+ class Conjunction<W> extends AbstractBase<W> {
+
+ /**
+ * The element matchers that constitute this conjunction.
+ */
+ private final ElementMatcher<? super W> left, right;
+
+ /**
+ * Creates a new conjunction matcher.
+ *
+ * @param left The first matcher to consult for a match.
+ * @param right The second matcher to consult for a match. This matcher is only consulted
+ * if the {@code first} matcher constituted a match.
+ */
+ public Conjunction(ElementMatcher<? super W> left, ElementMatcher<? super W> right) {
+ this.left = left;
+ this.right = right;
+ }
+
+ @Override
+ public boolean matches(W target) {
+ return left.matches(target) && right.matches(target);
+ }
+
+ @Override
+ public String toString() {
+ return "(" + left + " and " + right + ')';
+ }
+ }
+
+ /**
+ * A disjunction matcher which only matches an element if both represented matchers constitute a match.
+ *
+ * @param <W> The type of the object that is being matched.
+ */
+ @EqualsAndHashCode(callSuper = false)
+ class Disjunction<W> extends AbstractBase<W> {
+
+ /**
+ * The element matchers that constitute this disjunction.
+ */
+ private final ElementMatcher<? super W> left, right;
+
+ /**
+ * Creates a new disjunction matcher.
+ *
+ * @param left The first matcher to consult for a match.
+ * @param right The second matcher to consult for a match. This matcher is only consulted
+ * if the {@code first} matcher did not already constitute a match.
+ */
+ public Disjunction(ElementMatcher<? super W> left, ElementMatcher<? super W> right) {
+ this.left = left;
+ this.right = right;
+ }
+
+ @Override
+ public boolean matches(W target) {
+ return left.matches(target) || right.matches(target);
+ }
+
+ @Override
+ public String toString() {
+ return "(" + left + " or " + right + ')';
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/ElementMatchers.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/ElementMatchers.java
new file mode 100644
index 0000000..0934847
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/ElementMatchers.java
@@ -0,0 +1,2199 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.ByteCodeElement;
+import net.bytebuddy.description.ModifierReviewable;
+import net.bytebuddy.description.NamedElement;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.annotation.AnnotationSource;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.utility.JavaModule;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * A utility class that contains a human-readable language for creating {@link net.bytebuddy.matcher.ElementMatcher}s.
+ */
+public final class ElementMatchers {
+
+ /**
+ * A readable reference to the bootstrap class loader which is represented by {@code null}.
+ */
+ private static final ClassLoader BOOTSTRAP_CLASSLOADER = null;
+
+ /**
+ * A private constructor that must not be invoked.
+ */
+ private ElementMatchers() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Wraps another matcher to assure that an element is not matched in case that the matching causes an {@link Exception}.
+ *
+ * @param matcher The element matcher that potentially throws an exception.
+ * @param <T> The type of the matched object.
+ * @return A matcher that returns {@code false} in case that the given matcher throws an exception.
+ */
+ public static <T> ElementMatcher.Junction<T> failSafe(ElementMatcher<? super T> matcher) {
+ return new FailSafeMatcher<T>(matcher, false);
+ }
+
+ /**
+ * <p>
+ * Wraps another matcher but caches the result of previously matched elements. Caching can be important if a
+ * matcher requires expensive calculations.
+ * </p>
+ * <p>
+ * <b>Warning</b>: The supplied map can however introduce a memory leak as the matched elements are stored within the map.
+ * It is therefore important to dereference this matcher at some point or to regularly evict entries from the supplied map.
+ * </p>
+ *
+ * @param matcher The actual matcher for which the results are cached.
+ * @param map The map for storing results of previously matched elements.
+ * @param <T> The type of the matched object.
+ * @return A matcher that stores the results of a previous matching in the supplied map.
+ */
+ public static <T> ElementMatcher.Junction<T> cached(ElementMatcher<? super T> matcher, ConcurrentMap<? super T, Boolean> map) {
+ return new CachingMatcher<T>(matcher, map);
+ }
+
+ /**
+ * <p>
+ * Wraps another matcher but caches the result of previously matched elements. Caching can be important if a
+ * matcher requires expensive calculations.
+ * </p>
+ * <p>
+ * <b>Warning</b>: The cache will hold {@code evictionSize} elements and evict a random element once the cache
+ * contains more than the specified amount of elements. Cached elements are referenced strongly and might cause
+ * a memory leak if instance are of a significant size. Using {@link ElementMatchers#cached(ElementMatcher, ConcurrentMap)}
+ * allows for explicit control over cache eviction.
+ * </p>
+ *
+ * @param matcher The actual matcher for which the results are cached.
+ * @param evictionSize The maximum amount of elements that are stored in the cache. Must be a positive number.
+ * @param <T> The type of the matched object.
+ * @return A matcher that stores the results of a previous matching in the supplied map.
+ */
+ public static <T> ElementMatcher.Junction<T> cached(ElementMatcher<? super T> matcher, int evictionSize) {
+ if (evictionSize < 1) {
+ throw new IllegalArgumentException("Eviction size must be a positive number: " + evictionSize);
+ }
+ return new CachingMatcher.WithInlineEviction<T>(matcher, new ConcurrentHashMap<T, Boolean>(), evictionSize);
+ }
+
+ /**
+ * Matches the given value which can also be {@code null} by the {@link java.lang.Object#equals(Object)} method or
+ * by a null-check.
+ *
+ * @param value The value that is to be matched.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches an exact value.
+ */
+ public static <T> ElementMatcher.Junction<T> is(Object value) {
+ return value == null
+ ? new NullMatcher<T>()
+ : new EqualityMatcher<T>(value);
+ }
+
+ /**
+ * Exactly matches a given field as a {@link FieldDescription} in its defined shape.
+ *
+ * @param field The field to match by its description
+ * @param <T> The type of the matched object.
+ * @return An element matcher that exactly matches the given field in its defined shape.
+ */
+ public static <T extends FieldDescription> ElementMatcher.Junction<T> is(Field field) {
+ return is(new FieldDescription.ForLoadedField(field));
+ }
+
+ /**
+ * Exactly matches a given field as a {@link FieldDescription} in its defined shape.
+ *
+ * @param field The field to match by its description
+ * @param <T> The type of the matched object.
+ * @return An element matcher that exactly matches the given field in its defined shape.
+ */
+ public static <T extends FieldDescription> ElementMatcher.Junction<T> is(FieldDescription.InDefinedShape field) {
+ return definedField(new EqualityMatcher<FieldDescription.InDefinedShape>(field));
+ }
+
+ /**
+ * Matches a field in its defined shape.
+ *
+ * @param matcher The matcher to apply to the matched field's defined shape.
+ * @param <T> The matched object's type.
+ * @return A matcher that matches a matched field's defined shape.
+ */
+ public static <T extends FieldDescription> ElementMatcher.Junction<T> definedField(ElementMatcher<? super FieldDescription.InDefinedShape> matcher) {
+ return new DefinedShapeMatcher<T, FieldDescription.InDefinedShape>(matcher);
+ }
+
+ /**
+ * Exactly matches a given method as a {@link MethodDescription} in its defined shape.
+ *
+ * @param method The method to match by its description
+ * @param <T> The type of the matched object.
+ * @return An element matcher that exactly matches the given method in its defined shape.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> is(Method method) {
+ return is(new MethodDescription.ForLoadedMethod(method));
+ }
+
+ /**
+ * Exactly matches a given constructor as a {@link MethodDescription} in its defined shape.
+ *
+ * @param constructor The constructor to match by its description
+ * @param <T> The type of the matched object.
+ * @return An element matcher that exactly matches the given constructor in its defined shape.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> is(Constructor<?> constructor) {
+ return is(new MethodDescription.ForLoadedConstructor(constructor));
+ }
+
+ /**
+ * Exactly matches a given method or constructor as a {@link MethodDescription} in its defined shape.
+ *
+ * @param method The method to match by its description
+ * @param <T> The type of the matched object.
+ * @return An element matcher that exactly matches the given method or constructor in its defined shape.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> is(MethodDescription.InDefinedShape method) {
+ return definedMethod(new EqualityMatcher<MethodDescription.InDefinedShape>(method));
+ }
+
+ /**
+ * Matches a method in its defined shape.
+ *
+ * @param matcher The matcher to apply to the matched method's defined shape.
+ * @param <T> The matched object's type.
+ * @return A matcher that matches a matched method's defined shape.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> definedMethod(ElementMatcher<? super MethodDescription.InDefinedShape> matcher) {
+ return new DefinedShapeMatcher<T, MethodDescription.InDefinedShape>(matcher);
+ }
+
+ /**
+ * Exactly matches a given parameter as a {@link ParameterDescription} in its defined shape.
+ *
+ * @param parameter The parameter to match by its description
+ * @param <T> The type of the matched object.
+ * @return An element matcher that exactly matches the given parameter in its defined shape.
+ */
+ public static <T extends ParameterDescription> ElementMatcher.Junction<T> is(ParameterDescription.InDefinedShape parameter) {
+ return definedParameter(new EqualityMatcher<ParameterDescription.InDefinedShape>(parameter));
+ }
+
+ /**
+ * Matches a parameter in its defined shape.
+ *
+ * @param matcher The matcher to apply to the matched parameter's defined shape.
+ * @param <T> The matched object's type.
+ * @return A matcher that matches a matched parameter's defined shape.
+ */
+ public static <T extends ParameterDescription> ElementMatcher.Junction<T> definedParameter(ElementMatcher<? super ParameterDescription.InDefinedShape> matcher) {
+ return new DefinedShapeMatcher<T, ParameterDescription.InDefinedShape>(matcher);
+ }
+
+ /**
+ * Matches a parameter's type by the given matcher.
+ *
+ * @param matcher The matcher to apply to the parameter's type.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches a parameter's type by the given matcher.
+ */
+ public static <T extends ParameterDescription> ElementMatcher.Junction<T> hasType(ElementMatcher<? super TypeDescription> matcher) {
+ return hasGenericType(erasure(matcher));
+ }
+
+ /**
+ * Matches a method parameter by its generic type.
+ *
+ * @param matcher The matcher to apply to a parameter's generic type.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches the matched parameter's generic type.
+ */
+ public static <T extends ParameterDescription> ElementMatcher.Junction<T> hasGenericType(ElementMatcher<? super TypeDescription.Generic> matcher) {
+ return new MethodParameterTypeMatcher<T>(matcher);
+ }
+
+ /**
+ * Matches a parameter description for a {@code mandated} parameter.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher for a {@code mandated} parameter.
+ */
+ public static <T extends ParameterDescription> ElementMatcher.Junction<T> isMandated() {
+ return new ModifierMatcher<T>(ModifierMatcher.Mode.MANDATED);
+ }
+
+ /**
+ * Exactly matches a given type as a {@link TypeDescription}.
+ *
+ * @param type The type to match by its description
+ * @param <T> The type of the matched object.
+ * @return An element matcher that exactly matches the given type.
+ */
+ public static <T extends TypeDefinition> ElementMatcher.Junction<T> is(Type type) {
+ return is(TypeDefinition.Sort.describe(type));
+ }
+
+ /**
+ * Exactly matches a given annotation as an {@link AnnotationDescription}.
+ *
+ * @param annotation The annotation to match by its description.
+ * @param <T> The type of the matched object.
+ * @return An element matcher that exactly matches the given annotation.
+ */
+ public static <T extends AnnotationDescription> ElementMatcher.Junction<T> is(Annotation annotation) {
+ return is(AnnotationDescription.ForLoadedAnnotation.of(annotation));
+ }
+
+ /**
+ * Inverts another matcher.
+ *
+ * @param matcher The matcher to invert.
+ * @param <T> The type of the matched object.
+ * @return An inverted version of the given {@code matcher}.
+ */
+ public static <T> ElementMatcher.Junction<T> not(ElementMatcher<? super T> matcher) {
+ return new NegatingMatcher<T>(matcher);
+ }
+
+ /**
+ * Creates a matcher that always returns {@code true}.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches anything.
+ */
+ public static <T> ElementMatcher.Junction<T> any() {
+ return new BooleanMatcher<T>(true);
+ }
+
+ /**
+ * Creates a matcher that always returns {@code false}.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches nothing.
+ */
+ public static <T> ElementMatcher.Junction<T> none() {
+ return new BooleanMatcher<T>(false);
+ }
+
+ /**
+ * <p>
+ * Creates a matcher that matches any of the given objects by the {@link java.lang.Object#equals(Object)} method.
+ * None of the values must be {@code null}.
+ * </p>
+ * <p>
+ * <b>Important</b>: This method cannot be used interchangably with any of its overloaded versions which also apply a type
+ * conversion.
+ * </p>
+ *
+ * @param value The input values to be compared against.
+ * @param <T> The type of the matched object.
+ * @return A matcher that checks for the equality with any of the given objects.
+ */
+ public static <T> ElementMatcher.Junction<T> anyOf(Object... value) {
+ return anyOf(Arrays.asList(value));
+ }
+
+ /**
+ * <p>
+ * Creates a matcher that matches any of the given objects by the {@link java.lang.Object#equals(Object)} method.
+ * None of the values must be {@code null}.
+ * </p>
+ * <p>
+ * <b>Important</b>: This method cannot be used interchangably with any of the overloaded versions of {@link ElementMatchers#anyOf(Object...)}
+ * which also apply a type conversion.
+ * </p>
+ *
+ * @param values The input values to be compared against.
+ * @param <T> The type of the matched object.
+ * @return A matcher that checks for the equality with any of the given objects.
+ */
+ public static <T> ElementMatcher.Junction<T> anyOf(Iterable<?> values) {
+ ElementMatcher.Junction<T> matcher = none();
+ for (Object value : values) {
+ matcher = matcher.or(is(value));
+ }
+ return matcher;
+ }
+
+ /**
+ * Creates a matcher that matches any of the given types as {@link TypeDescription}s
+ * by the {@link java.lang.Object#equals(Object)} method. None of the values must be {@code null}.
+ *
+ * @param value The input values to be compared against.
+ * @param <T> The type of the matched object.
+ * @return A matcher that checks for the equality with any of the given objects.
+ */
+ public static <T extends TypeDefinition> ElementMatcher.Junction<T> anyOf(Type... value) {
+ return anyOf(new TypeList.Generic.ForLoadedTypes(value));
+ }
+
+ /**
+ * Creates a matcher that matches any of the given constructors as {@link MethodDescription}s
+ * by the {@link java.lang.Object#equals(Object)} method. None of the values must be {@code null}.
+ *
+ * @param value The input values to be compared against.
+ * @param <T> The type of the matched object.
+ * @return A matcher that checks for the equality with any of the given objects.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> anyOf(Constructor<?>... value) {
+ return definedMethod(anyOf(new MethodList.ForLoadedMethods(value, new Method[0])));
+ }
+
+ /**
+ * Creates a matcher that matches any of the given methods as {@link MethodDescription}s
+ * by the {@link java.lang.Object#equals(Object)} method. None of the values must be {@code null}.
+ *
+ * @param value The input values to be compared against.
+ * @param <T> The type of the matched object.
+ * @return A matcher that checks for the equality with any of the given objects.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> anyOf(Method... value) {
+ return definedMethod(anyOf(new MethodList.ForLoadedMethods(new Constructor<?>[0], value)));
+ }
+
+ /**
+ * Creates a matcher that matches any of the given fields as {@link FieldDescription}s
+ * by the {@link java.lang.Object#equals(Object)} method. None of the values must be {@code null}.
+ *
+ * @param value The input values to be compared against.
+ * @param <T> The type of the matched object.
+ * @return A matcher that checks for the equality with any of the given objects.
+ */
+ public static <T extends FieldDescription> ElementMatcher.Junction<T> anyOf(Field... value) {
+ return definedField(anyOf(new FieldList.ForLoadedFields(value)));
+ }
+
+ /**
+ * Creates a matcher that matches any of the given annotations as {@link AnnotationDescription}s
+ * by the {@link java.lang.Object#equals(Object)} method. None of the values must be {@code null}.
+ *
+ * @param value The input values to be compared against.
+ * @param <T> The type of the matched object.
+ * @return A matcher that checks for the equality with any of the given objects.
+ */
+ public static <T extends AnnotationDescription> ElementMatcher.Junction<T> anyOf(Annotation... value) {
+ return anyOf(new AnnotationList.ForLoadedAnnotations(value));
+ }
+
+ /**
+ * Creates a matcher that matches none of the given objects by the {@link java.lang.Object#equals(Object)} method.
+ * None of the values must be {@code null}.
+ *
+ * @param value The input values to be compared against.
+ * @param <T> The type of the matched object.
+ * @return A matcher that checks for the equality with none of the given objects.
+ */
+ public static <T> ElementMatcher.Junction<T> noneOf(Object... value) {
+ return noneOf(Arrays.asList(value));
+ }
+
+ /**
+ * Creates a matcher that matches none of the given objects by the {@link java.lang.Object#equals(Object)} method.
+ * None of the values must be {@code null}.
+ *
+ * @param values The input values to be compared against.
+ * @param <T> The type of the matched object.
+ * @return A matcher that checks for the equality with none of the given objects.
+ */
+ public static <T> ElementMatcher.Junction<T> noneOf(Iterable<?> values) {
+ ElementMatcher.Junction<T> matcher = any();
+ for (Object value : values) {
+ matcher = matcher.and(not(is(value)));
+ }
+ return matcher;
+ }
+
+ /**
+ * Creates a matcher that matches none of the given types as {@link TypeDescription}s
+ * by the {@link java.lang.Object#equals(Object)} method. None of the values must be {@code null}.
+ *
+ * @param value The input values to be compared against.
+ * @param <T> The type of the matched object.
+ * @return A matcher that checks for the equality with none of the given objects.
+ */
+ public static <T extends TypeDefinition> ElementMatcher.Junction<T> noneOf(Type... value) {
+ return noneOf(new TypeList.Generic.ForLoadedTypes(value));
+ }
+
+ /**
+ * Creates a matcher that matches none of the given constructors as {@link MethodDescription}s
+ * by the {@link java.lang.Object#equals(Object)} method. None of the values must be {@code null}.
+ *
+ * @param value The input values to be compared against.
+ * @param <T> The type of the matched object.
+ * @return A matcher that checks for the equality with none of the given objects.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> noneOf(Constructor<?>... value) {
+ return definedMethod(noneOf(new MethodList.ForLoadedMethods(value, new Method[0])));
+ }
+
+ /**
+ * Creates a matcher that matches none of the given methods as {@link MethodDescription}s
+ * by the {@link java.lang.Object#equals(Object)} method. None of the values must be {@code null}.
+ *
+ * @param value The input values to be compared against.
+ * @param <T> The type of the matched object.
+ * @return A matcher that checks for the equality with none of the given objects.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> noneOf(Method... value) {
+ return definedMethod(noneOf(new MethodList.ForLoadedMethods(new Constructor<?>[0], value)));
+ }
+
+ /**
+ * Creates a matcher that matches none of the given methods as {@link FieldDescription}s
+ * by the {@link java.lang.Object#equals(Object)} method. None of the values must be {@code null}.
+ *
+ * @param value The input values to be compared against.
+ * @param <T> The type of the matched object.
+ * @return A matcher that checks for the equality with none of the given objects.
+ */
+ public static <T extends FieldDescription> ElementMatcher.Junction<T> noneOf(Field... value) {
+ return definedField(noneOf(new FieldList.ForLoadedFields(value)));
+ }
+
+ /**
+ * Creates a matcher that matches none of the given annotations as {@link AnnotationDescription}s
+ * by the {@link java.lang.Object#equals(Object)} method. None of the values must be {@code null}.
+ *
+ * @param value The input values to be compared against.
+ * @param <T> The type of the matched object.
+ * @return A matcher that checks for the equality with any of the given objects.
+ */
+ public static <T extends AnnotationDescription> ElementMatcher.Junction<T> noneOf(Annotation... value) {
+ return noneOf(new AnnotationList.ForLoadedAnnotations(value));
+ }
+
+ /**
+ * Matches an iterable by assuring that at least one element of the iterable collection matches the
+ * provided matcher.
+ *
+ * @param matcher The matcher to apply to each element.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches an iterable if at least one element matches the provided matcher.
+ */
+ public static <T> ElementMatcher.Junction<Iterable<? extends T>> whereAny(ElementMatcher<? super T> matcher) {
+ return new CollectionItemMatcher<T>(matcher);
+ }
+
+ /**
+ * Matches an iterable by assuring that no element of the iterable collection matches the provided matcher.
+ *
+ * @param matcher The matcher to apply to each element.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches an iterable if no element matches the provided matcher.
+ */
+ public static <T> ElementMatcher.Junction<Iterable<? extends T>> whereNone(ElementMatcher<? super T> matcher) {
+ return not(whereAny(matcher));
+ }
+
+ /**
+ * Matches a generic type's erasure against the provided type. As a wildcard does not define an erasure, a runtime exception is thrown when
+ * this matcher is applied to a wildcard.
+ *
+ * @param type The type to match a generic type's erasure against.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches a generic type's raw type against the provided non-generic type.
+ */
+ public static <T extends TypeDescription.Generic> ElementMatcher.Junction<T> erasure(Class<?> type) {
+ return erasure(is(type));
+ }
+
+ /**
+ * Matches a generic type's erasure against the provided type. As a wildcard does not define an erasure, a runtime exception is thrown
+ * when this matcher is applied to a wildcard.
+ *
+ * @param type The type to match a generic type's erasure against.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches a generic type's raw type against the provided non-generic type.
+ */
+ public static <T extends TypeDescription.Generic> ElementMatcher.Junction<T> erasure(TypeDescription type) {
+ return erasure(is(type));
+ }
+
+ /**
+ * Converts a matcher for a type description into a matcher for the matched type's erasure. As a wildcard does not define an erasure,
+ * a runtime exception is thrown when this matcher is applied to a wildcard.
+ *
+ * @param matcher The matcher to match the matched object's raw type against.
+ * @param <T> The type of the matched object.
+ * @return A type matcher for a generic type that matches the matched type's raw type against the given type description matcher.
+ */
+ public static <T extends TypeDescription.Generic> ElementMatcher.Junction<T> erasure(ElementMatcher<? super TypeDescription> matcher) {
+ return new ErasureMatcher<T>(matcher);
+ }
+
+ /**
+ * Matches an iteration of generic types' erasures against the provided types. As a wildcard does not define an erasure, a runtime
+ * exception is thrown when this matcher is applied to a wildcard.
+ *
+ * @param type The types to match.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches an iteration of generic types' raw types against the provided non-generic types.
+ */
+ public static <T extends Iterable<? extends TypeDescription.Generic>> ElementMatcher.Junction<T> erasures(Class<?>... type) {
+ return erasures(new TypeList.ForLoadedTypes(type));
+ }
+
+ /**
+ * Matches an iteration of generic types' erasures against the provided types. As a wildcard does not define an erasure, a runtime
+ * exception is thrown when this matcher is applied to a wildcard.
+ *
+ * @param type The types to match.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches an iteration of generic types' raw types against the provided non-generic types.
+ */
+ public static <T extends Iterable<? extends TypeDescription.Generic>> ElementMatcher.Junction<T> erasures(TypeDescription... type) {
+ return erasures(Arrays.asList(type));
+ }
+
+ /**
+ * Matches an iteration of generic types' erasures against the provided types. As a wildcard does not define an erasure, a runtime
+ * exception is thrown when this matcher is applied to a wildcard.
+ *
+ * @param types The types to match.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches an iteration of generic types' raw types against the provided non-generic types.
+ */
+ public static <T extends Iterable<? extends TypeDescription.Generic>> ElementMatcher.Junction<T> erasures(
+ Iterable<? extends TypeDescription> types) {
+ List<ElementMatcher<? super TypeDescription>> typeMatchers = new ArrayList<ElementMatcher<? super TypeDescription>>();
+ for (TypeDescription type : types) {
+ typeMatchers.add(is(type));
+ }
+ return erasures(new CollectionOneToOneMatcher<TypeDescription>(typeMatchers));
+ }
+
+ /**
+ * Applies the provided matchers to an iteration og generic types' erasures. As a wildcard does not define an erasure, a runtime
+ * exception is thrown when this matcher is applied to a wildcard.
+ *
+ * @param matcher The matcher to apply at the erased types.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches an iteration of generic types' raw types against the provided matcher.
+ */
+ public static <T extends Iterable<? extends TypeDescription.Generic>> ElementMatcher.Junction<T> erasures(
+ ElementMatcher<? super Iterable<? extends TypeDescription>> matcher) {
+ return new CollectionErasureMatcher<T>(matcher);
+ }
+
+ /**
+ * Matches a type variable with the given name.
+ *
+ * @param symbol The name of the type variable to be match.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches type variables with the given name.
+ */
+ public static <T extends TypeDefinition> ElementMatcher.Junction<T> isVariable(String symbol) {
+ return isVariable(named(symbol));
+ }
+
+ /**
+ * Matches a type variable with the given name.
+ *
+ * @param matcher A matcher for the type variable's name.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches type variables with the given name.
+ */
+ public static <T extends TypeDefinition> ElementMatcher.Junction<T> isVariable(ElementMatcher<? super NamedElement> matcher) {
+ return new TypeSortMatcher<T>(anyOf(TypeDefinition.Sort.VARIABLE, TypeDefinition.Sort.VARIABLE_SYMBOLIC)).and(matcher);
+ }
+
+ /**
+ * Matches a {@link NamedElement} for its exact name.
+ *
+ * @param name The expected name.
+ * @param <T> The type of the matched object.
+ * @return An element matcher for a named element's exact name.
+ */
+ public static <T extends NamedElement> ElementMatcher.Junction<T> named(String name) {
+ return new NameMatcher<T>(new StringMatcher(name, StringMatcher.Mode.EQUALS_FULLY));
+ }
+
+ /**
+ * Matches a {@link NamedElement} for its name. The name's
+ * capitalization is ignored.
+ *
+ * @param name The expected name.
+ * @param <T> The type of the matched object.
+ * @return An element matcher for a named element's name.
+ */
+ public static <T extends NamedElement> ElementMatcher.Junction<T> namedIgnoreCase(String name) {
+ return new NameMatcher<T>(new StringMatcher(name, StringMatcher.Mode.EQUALS_FULLY_IGNORE_CASE));
+ }
+
+ /**
+ * Matches a {@link NamedElement} for its name's prefix.
+ *
+ * @param prefix The expected name's prefix.
+ * @param <T> The type of the matched object.
+ * @return An element matcher for a named element's name's prefix.
+ */
+ public static <T extends NamedElement> ElementMatcher.Junction<T> nameStartsWith(String prefix) {
+ return new NameMatcher<T>(new StringMatcher(prefix, StringMatcher.Mode.STARTS_WITH));
+ }
+
+ /**
+ * Matches a {@link NamedElement} for its name's prefix. The name's
+ * capitalization is ignored.
+ *
+ * @param prefix The expected name's prefix.
+ * @param <T> The type of the matched object.
+ * @return An element matcher for a named element's name's prefix.
+ */
+ public static <T extends NamedElement> ElementMatcher.Junction<T> nameStartsWithIgnoreCase(String prefix) {
+ return new NameMatcher<T>(new StringMatcher(prefix, StringMatcher.Mode.STARTS_WITH_IGNORE_CASE));
+ }
+
+ /**
+ * Matches a {@link NamedElement} for its name's suffix.
+ *
+ * @param suffix The expected name's suffix.
+ * @param <T> The type of the matched object.
+ * @return An element matcher for a named element's name's suffix.
+ */
+ public static <T extends NamedElement> ElementMatcher.Junction<T> nameEndsWith(String suffix) {
+ return new NameMatcher<T>(new StringMatcher(suffix, StringMatcher.Mode.ENDS_WITH));
+ }
+
+ /**
+ * Matches a {@link NamedElement} for its name's suffix. The name's
+ * capitalization is ignored.
+ *
+ * @param suffix The expected name's suffix.
+ * @param <T> The type of the matched object.
+ * @return An element matcher for a named element's name's suffix.
+ */
+ public static <T extends NamedElement> ElementMatcher.Junction<T> nameEndsWithIgnoreCase(String suffix) {
+ return new NameMatcher<T>(new StringMatcher(suffix, StringMatcher.Mode.ENDS_WITH_IGNORE_CASE));
+ }
+
+ /**
+ * Matches a {@link NamedElement} for an infix of its name.
+ *
+ * @param infix The expected infix of the name.
+ * @param <T> The type of the matched object.
+ * @return An element matcher for a named element's name's infix.
+ */
+ public static <T extends NamedElement> ElementMatcher.Junction<T> nameContains(String infix) {
+ return new NameMatcher<T>(new StringMatcher(infix, StringMatcher.Mode.CONTAINS));
+ }
+
+ /**
+ * Matches a {@link NamedElement} for an infix of its name. The name's
+ * capitalization is ignored.
+ *
+ * @param infix The expected infix of the name.
+ * @param <T> The type of the matched object.
+ * @return An element matcher for a named element's name's infix.
+ */
+ public static <T extends NamedElement> ElementMatcher.Junction<T> nameContainsIgnoreCase(String infix) {
+ return new NameMatcher<T>(new StringMatcher(infix, StringMatcher.Mode.CONTAINS_IGNORE_CASE));
+ }
+
+ /**
+ * Matches a {@link NamedElement} name against a regular expression.
+ *
+ * @param regex The regular expression to match the name against.
+ * @param <T> The type of the matched object.
+ * @return An element matcher for a named element's name's against the given regular expression.
+ */
+ public static <T extends NamedElement> ElementMatcher.Junction<T> nameMatches(String regex) {
+ return new NameMatcher<T>(new StringMatcher(regex, StringMatcher.Mode.MATCHES));
+ }
+
+ /**
+ * Matches a {@link NamedElement.WithOptionalName} for having an explicit name.
+ *
+ * @param <T> The type of the matched object.
+ * @return An element matcher that checks if the matched optionally named element has an explicit name.
+ */
+ public static <T extends NamedElement.WithOptionalName> ElementMatcher.Junction<T> isNamed() {
+ return new IsNamedMatcher<T>();
+ }
+
+ /**
+ * Matches a {@link ByteCodeElement}'s descriptor against a given value.
+ *
+ * @param descriptor The expected descriptor.
+ * @param <T> The type of the matched object.
+ * @return A matcher for the given {@code descriptor}.
+ */
+ public static <T extends ByteCodeElement> ElementMatcher.Junction<T> hasDescriptor(String descriptor) {
+ return new DescriptorMatcher<T>(new StringMatcher(descriptor, StringMatcher.Mode.EQUALS_FULLY));
+ }
+
+ /**
+ * Matches a {@link ByteCodeElement} for being declared by a given {@link java.lang.Class}. This matcher matches
+ * a declared element's raw declaring type.
+ *
+ * @param type The type that is expected to declare the matched byte code element.
+ * @param <T> The type of the matched object.
+ * @return A matcher for byte code elements being declared by the given {@code type}.
+ */
+ public static <T extends ByteCodeElement> ElementMatcher.Junction<T> isDeclaredBy(Class<?> type) {
+ return isDeclaredBy(new TypeDescription.ForLoadedType(type));
+ }
+
+ /**
+ * Matches a {@link ByteCodeElement} for being declared by a given {@link TypeDescription}. This matcher matches
+ * a declared element's raw declaring type.
+ *
+ * @param type The type that is expected to declare the matched byte code element.
+ * @param <T> The type of the matched object.
+ * @return A matcher for byte code elements being declared by the given {@code type}.
+ */
+ public static <T extends ByteCodeElement> ElementMatcher.Junction<T> isDeclaredBy(TypeDescription type) {
+ return isDeclaredBy(is(type));
+ }
+
+ /**
+ * Matches a {@link ByteCodeElement} for being declared by a {@link TypeDescription} that is matched by the given matcher. This matcher matches
+ * a declared element's raw declaring type.
+ *
+ * @param matcher A matcher for the declaring type of the matched byte code element as long as it
+ * is not {@code null}.
+ * @param <T> The type of the matched object.
+ * @return A matcher for byte code elements being declared by a type matched by the given {@code matcher}.
+ */
+ public static <T extends ByteCodeElement> ElementMatcher.Junction<T> isDeclaredBy(ElementMatcher<? super TypeDescription> matcher) {
+ return isDeclaredByGeneric(erasure(matcher));
+ }
+
+ /**
+ * Matches a {@link ByteCodeElement} for being declared by a given generic {@link Type}.
+ *
+ * @param type The type that is expected to declare the matched byte code element.
+ * @param <T> The type of the matched object.
+ * @return A matcher for byte code elements being declared by the given {@code type}.
+ */
+ public static <T extends ByteCodeElement> ElementMatcher.Junction<T> isDeclaredByGeneric(Type type) {
+ return isDeclaredByGeneric(TypeDefinition.Sort.describe(type));
+ }
+
+ /**
+ * Matches a {@link ByteCodeElement} for being declared by a given {@link TypeDescription.Generic}.
+ *
+ * @param type The type that is expected to declare the matched byte code element.
+ * @param <T> The type of the matched object.
+ * @return A matcher for byte code elements being declared by the given {@code type}.
+ */
+ public static <T extends ByteCodeElement> ElementMatcher.Junction<T> isDeclaredByGeneric(TypeDescription.Generic type) {
+ return isDeclaredByGeneric(is(type));
+ }
+
+ /**
+ * Matches a {@link ByteCodeElement} for being declared by a {@link TypeDescription.Generic} that is matched by the given matcher.
+ *
+ * @param matcher A matcher for the declaring type of the matched byte code element as long as it is not {@code null}.
+ * @param <T> The type of the matched object.
+ * @return A matcher for byte code elements being declared by a type matched by the given {@code matcher}.
+ */
+ public static <T extends ByteCodeElement> ElementMatcher.Junction<T> isDeclaredByGeneric(ElementMatcher<? super TypeDescription.Generic> matcher) {
+ return new DeclaringTypeMatcher<T>(matcher);
+ }
+
+ /**
+ * Matches a {@link ByteCodeElement} that is visible to a given {@link java.lang.Class}.
+ *
+ * @param type The type that a matched byte code element is expected to be visible to.
+ * @param <T> The type of the matched object.
+ * @return A matcher for a byte code element to be visible to a given {@code type}.
+ */
+ public static <T extends ByteCodeElement> ElementMatcher.Junction<T> isVisibleTo(Class<?> type) {
+ return isVisibleTo(new TypeDescription.ForLoadedType(type));
+ }
+
+ /**
+ * Matches a {@link ByteCodeElement} that is visible to a given {@link TypeDescription}.
+ *
+ * @param type The type that a matched byte code element is expected to be visible to.
+ * @param <T> The type of the matched object.
+ * @return A matcher for a byte code element to be visible to a given {@code type}.
+ */
+ public static <T extends ByteCodeElement> ElementMatcher.Junction<T> isVisibleTo(TypeDescription type) {
+ return new VisibilityMatcher<T>(type);
+ }
+
+
+ /**
+ * Matches a {@link ByteCodeElement} that is accessible to a given {@link java.lang.Class}.
+ *
+ * @param type The type that a matched byte code element is expected to be accessible to.
+ * @param <T> The type of the matched object.
+ * @return A matcher for a byte code element to be accessible to a given {@code type}.
+ */
+ public static <T extends ByteCodeElement> ElementMatcher.Junction<T> isAccessibleTo(Class<?> type) {
+ return isAccessibleTo(new TypeDescription.ForLoadedType(type));
+ }
+
+ /**
+ * Matches a {@link ByteCodeElement} that is accessible to a given {@link java.lang.Class}.
+ *
+ * @param type The type that a matched byte code element is expected to be accessible to.
+ * @param <T> The type of the matched object.
+ * @return A matcher for a byte code element to be accessible to a given {@code type}.
+ */
+ public static <T extends ByteCodeElement> ElementMatcher.Junction<T> isAccessibleTo(TypeDescription type) {
+ return new AccessibilityMatcher<T>(type);
+ }
+
+ /**
+ * Matches a {@link ModifierReviewable.OfAbstraction} that is {@code abstract}.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher for a {@code abstract} modifier reviewable.
+ */
+ public static <T extends ModifierReviewable.OfAbstraction> ElementMatcher.Junction<T> isAbstract() {
+ return new ModifierMatcher<T>(ModifierMatcher.Mode.ABSTRACT);
+ }
+
+ /**
+ * Matches a {@link ModifierReviewable.OfEnumeration} that is an {@code enum} or a field holding an enum.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher for an enum.
+ */
+ public static <T extends ModifierReviewable.OfEnumeration> ElementMatcher.Junction<T> isEnum() {
+ return new ModifierMatcher<T>(ModifierMatcher.Mode.ENUMERATION);
+ }
+
+ /**
+ * Matches an {@link AnnotationSource} for declared annotations.
+ * This matcher does not match inherited annotations which only exist for classes. Use
+ * {@link net.bytebuddy.matcher.ElementMatchers#inheritsAnnotation(Class)} for matching inherited annotations.
+ *
+ * @param type The annotation type to match against.
+ * @param <T> The type of the matched object.
+ * @return A matcher that validates that an annotated element is annotated with an annotation of {@code type}.
+ */
+ public static <T extends AnnotationSource> ElementMatcher.Junction<T> isAnnotatedWith(Class<? extends Annotation> type) {
+ return isAnnotatedWith(new TypeDescription.ForLoadedType(type));
+ }
+
+ /**
+ * Matches an {@link AnnotationSource} for declared annotations.
+ * This matcher does not match inherited annotations which only exist for classes. Use
+ * {@link net.bytebuddy.matcher.ElementMatchers#inheritsAnnotation(TypeDescription)}
+ * for matching inherited annotations.
+ *
+ * @param type The annotation type to match against.
+ * @param <T> The type of the matched object.
+ * @return A matcher that validates that an annotated element is annotated with an annotation of {@code type}.
+ */
+ public static <T extends AnnotationSource> ElementMatcher.Junction<T> isAnnotatedWith(TypeDescription type) {
+ return isAnnotatedWith(is(type));
+ }
+
+ /**
+ * Matches an {@link AnnotationSource} for declared annotations.
+ * This matcher does not match inherited annotations which only exist for classes. Use
+ * {@link net.bytebuddy.matcher.ElementMatchers#inheritsAnnotation(net.bytebuddy.matcher.ElementMatcher)}
+ * for matching inherited annotations.
+ *
+ * @param matcher The matcher to apply to any annotation's type found on the matched annotated element.
+ * @param <T> The type of the matched object.
+ * @return A matcher that validates that an annotated element is annotated with an annotation of a type
+ * that matches the given {@code matcher}.
+ */
+ public static <T extends AnnotationSource> ElementMatcher.Junction<T> isAnnotatedWith(ElementMatcher<? super TypeDescription> matcher) {
+ return declaresAnnotation(annotationType(matcher));
+ }
+
+ /**
+ * Matches an {@link AnnotationSource} to declare any annotation
+ * that matches the given matcher. Note that this matcher does not match inherited annotations that only exist
+ * for types. Use {@link net.bytebuddy.matcher.ElementMatchers#inheritsAnnotation(net.bytebuddy.matcher.ElementMatcher)}
+ * for matching inherited annotations.
+ *
+ * @param matcher A matcher to apply on any declared annotation of the matched annotated element.
+ * @param <T> The type of the matched object.
+ * @return A matcher that validates that an annotated element is annotated with an annotation that matches
+ * the given {@code matcher}.
+ */
+ public static <T extends AnnotationSource> ElementMatcher.Junction<T> declaresAnnotation(ElementMatcher<? super AnnotationDescription> matcher) {
+ return new DeclaringAnnotationMatcher<T>(new CollectionItemMatcher<AnnotationDescription>(matcher));
+ }
+
+ /**
+ * Matches a {@link ModifierReviewable} that is {@code public}.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher for a {@code public} modifier reviewable.
+ */
+ public static <T extends ModifierReviewable.OfByteCodeElement> ElementMatcher.Junction<T> isPublic() {
+ return new ModifierMatcher<T>(ModifierMatcher.Mode.PUBLIC);
+ }
+
+ /**
+ * Matches a {@link ModifierReviewable} that is {@code protected}.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher for a {@code protected} modifier reviewable.
+ */
+ public static <T extends ModifierReviewable.OfByteCodeElement> ElementMatcher.Junction<T> isProtected() {
+ return new ModifierMatcher<T>(ModifierMatcher.Mode.PROTECTED);
+ }
+
+ /**
+ * Matches a {@link ModifierReviewable} that is package-private.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher for a package-private modifier reviewable.
+ */
+ public static <T extends ModifierReviewable.OfByteCodeElement> ElementMatcher.Junction<T> isPackagePrivate() {
+ return not(isPublic().or(isProtected()).or(isPrivate()));
+ }
+
+ /**
+ * Matches a {@link ModifierReviewable} that is {@code private}.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher for a {@code private} modifier reviewable.
+ */
+ public static <T extends ModifierReviewable.OfByteCodeElement> ElementMatcher.Junction<T> isPrivate() {
+ return new ModifierMatcher<T>(ModifierMatcher.Mode.PRIVATE);
+ }
+
+ /**
+ * Matches a {@link ModifierReviewable} that is {@code static}.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher for a {@code static} modifier reviewable.
+ */
+ public static <T extends ModifierReviewable.OfByteCodeElement> ElementMatcher.Junction<T> isStatic() {
+ return new ModifierMatcher<T>(ModifierMatcher.Mode.STATIC);
+ }
+
+ /**
+ * Matches a {@link ModifierReviewable} that is {@code final}.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher for a {@code final} modifier reviewable.
+ */
+ public static <T extends ModifierReviewable> ElementMatcher.Junction<T> isFinal() {
+ return new ModifierMatcher<T>(ModifierMatcher.Mode.FINAL);
+ }
+
+ /**
+ * Matches a {@link ModifierReviewable} that is synthetic.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher for a synthetic modifier reviewable.
+ */
+ public static <T extends ModifierReviewable> ElementMatcher.Junction<T> isSynthetic() {
+ return new ModifierMatcher<T>(ModifierMatcher.Mode.SYNTHETIC);
+ }
+
+ /**
+ * Matches a {@link MethodDescription} that is {@code synchronized}.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher for a {@code synchronized} method description.
+ */
+ public static <T extends ModifierReviewable.ForMethodDescription> ElementMatcher.Junction<T> isSynchronized() {
+ return new ModifierMatcher<T>(ModifierMatcher.Mode.SYNCHRONIZED);
+ }
+
+ /**
+ * Matches a {@link MethodDescription} that is {@code native}.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher for a {@code native} method description.
+ */
+ public static <T extends ModifierReviewable.ForMethodDescription> ElementMatcher.Junction<T> isNative() {
+ return new ModifierMatcher<T>(ModifierMatcher.Mode.NATIVE);
+ }
+
+ /**
+ * Matches a {@link MethodDescription} that is {@code strictfp}.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher for a {@code strictfp} method description.
+ */
+ public static <T extends ModifierReviewable.ForMethodDescription> ElementMatcher.Junction<T> isStrict() {
+ return new ModifierMatcher<T>(ModifierMatcher.Mode.STRICT);
+ }
+
+ /**
+ * Matches a {@link MethodDescription} that is a var-args.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher for a var-args method description.
+ */
+ public static <T extends ModifierReviewable.ForMethodDescription> ElementMatcher.Junction<T> isVarArgs() {
+ return new ModifierMatcher<T>(ModifierMatcher.Mode.VAR_ARGS);
+ }
+
+ /**
+ * Matches a {@link MethodDescription} that is a bridge.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher for a bridge method.
+ */
+ public static <T extends ModifierReviewable.ForMethodDescription> ElementMatcher.Junction<T> isBridge() {
+ return new ModifierMatcher<T>(ModifierMatcher.Mode.BRIDGE);
+ }
+
+ /**
+ * Matches {@link MethodDescription}s that return a given generic type.
+ *
+ * @param type The generic type the matched method is expected to return.
+ * @param <T> The type of the matched object.
+ * @return An element matcher that matches a given generic return type for a method description.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> returnsGeneric(Type type) {
+ return returnsGeneric(TypeDefinition.Sort.describe(type));
+ }
+
+ /**
+ * Matches {@link MethodDescription}s that returns a given
+ * {@link TypeDescription}.
+ *
+ * @param type The type the matched method is expected to return.
+ * @param <T> The type of the matched object.
+ * @return An element matcher that matches a given return type for a method description.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> returnsGeneric(TypeDescription.Generic type) {
+ return returnsGeneric(is(type));
+ }
+
+ /**
+ * Matches {@link MethodDescription}s that return a given erasure type.
+ *
+ * @param type The raw type the matched method is expected to return.
+ * @param <T> The type of the matched object.
+ * @return An element matcher that matches a given return type for a method description.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> returns(Class<?> type) {
+ return returnsGeneric(erasure(type));
+ }
+
+ /**
+ * Matches {@link MethodDescription}s that return a given erasure type.
+ *
+ * @param type The raw type the matched method is expected to return.
+ * @param <T> The type of the matched object.
+ * @return An element matcher that matches a given return type for a method description.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> returns(TypeDescription type) {
+ return returns(is(type));
+ }
+
+ /**
+ * Matches a method's return type's erasure by the given matcher.
+ *
+ * @param matcher The matcher to apply to a method's return type's erasure.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches the matched method's return type's erasure.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> returns(ElementMatcher<? super TypeDescription> matcher) {
+ return returnsGeneric(erasure(matcher));
+ }
+
+ /**
+ * Matches {@link MethodDescription}s that match a matched method's return type.
+ *
+ * @param matcher A matcher to apply onto a matched method's return type.
+ * @param <T> The type of the matched object.
+ * @return An element matcher that matches a given return type against another {@code matcher}.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> returnsGeneric(ElementMatcher<? super TypeDescription.Generic> matcher) {
+ return new MethodReturnTypeMatcher<T>(matcher);
+ }
+
+ /**
+ * Matches {@link MethodDescription}s that define a given generic type as a parameter at the given index.
+ *
+ * @param index The index of the parameter.
+ * @param type The generic type the matched method is expected to define as a parameter type.
+ * @param <T> The type of the matched object.
+ * @return An element matcher that matches a given generic return type for a method description.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> takesGenericArgument(int index, Type type) {
+ return takesGenericArgument(index, TypeDefinition.Sort.describe(type));
+ }
+
+ /**
+ * Matches {@link MethodDescription}s that define a given generic type as a parameter at the given index.
+ *
+ * @param index The index of the parameter.
+ * @param type The generic type the matched method is expected to define as a parameter type.
+ * @param <T> The type of the matched object.
+ * @return An element matcher that matches a given generic return type for a method description.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> takesGenericArgument(int index, TypeDescription.Generic type) {
+ return takesGenericArgument(index, is(type));
+ }
+
+ /**
+ * Matches {@link MethodDescription}s that define a given generic type as a parameter at the given index.
+ *
+ * @param index The index of the parameter.
+ * @param matcher A matcher for the generic type the matched method is expected to define as a parameter type.
+ * @param <T> The type of the matched object.
+ * @return An element matcher that matches a given generic return type for a method description.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> takesGenericArgument(int index, ElementMatcher<? super TypeDescription.Generic> matcher) {
+ return takesGenericArguments(new CollectionElementMatcher<TypeDescription.Generic>(index, matcher));
+ }
+
+ /**
+ * Matches a method description that takes the provided generic arguments.
+ *
+ * @param type The arguments to match against the matched method.
+ * @param <T> The type of the matched object.
+ * @return A method matcher that matches a method's generic parameter types against the supplied arguments.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> takesGenericArguments(Type... type) {
+ return takesGenericArguments(new TypeList.Generic.ForLoadedTypes(type));
+ }
+
+ /**
+ * Matches a method description that takes the provided generic arguments.
+ *
+ * @param type The arguments to match against the matched method.
+ * @param <T> The type of the matched object.
+ * @return A method matcher that matches a method's generic parameter types against the supplied arguments.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> takesGenericArguments(TypeDefinition... type) {
+ return takesGenericArguments((Arrays.asList(type)));
+ }
+
+ /**
+ * Matches a method description that takes the provided generic arguments.
+ *
+ * @param types The arguments to match against the matched method.
+ * @param <T> The type of the matched object.
+ * @return A method matcher that matches a method's generic parameter types against the supplied arguments.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> takesGenericArguments(List<? extends TypeDefinition> types) {
+ List<ElementMatcher<? super TypeDescription.Generic>> typeMatchers = new ArrayList<ElementMatcher<? super TypeDescription.Generic>>();
+ for (TypeDefinition type : types) {
+ typeMatchers.add(is(type));
+ }
+ return takesGenericArguments(new CollectionOneToOneMatcher<TypeDescription.Generic>(typeMatchers));
+ }
+
+ /**
+ * Matches a {@link MethodDescription} by applying an iterable collection of element matcher on any parameter's {@link TypeDescription.Generic}.
+ *
+ * @param matchers The matcher that are applied onto the parameter types of the matched method description.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches a method description by applying another element matcher onto each parameter's type.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> takesGenericArguments(ElementMatcher<? super Iterable<? extends TypeDescription.Generic>> matchers) {
+ return new MethodParametersMatcher<T>(new MethodParameterTypesMatcher<ParameterList<?>>(matchers));
+ }
+
+ /**
+ * Matches {@link MethodDescription}s that define a given generic type as a parameter at the given index.
+ *
+ * @param index The index of the parameter.
+ * @param type The erasure of the type the matched method is expected to define as a parameter type.
+ * @param <T> The type of the matched object.
+ * @return An element matcher that matches a given argument type for a method description.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> takesArgument(int index, Class<?> type) {
+ return takesArgument(index, new TypeDescription.ForLoadedType(type));
+ }
+
+ /**
+ * Matches {@link MethodDescription}s that define a given type erasure as a parameter at the given index.
+ *
+ * @param index The index of the parameter.
+ * @param type The erasure of the type the matched method is expected to define as a parameter type.
+ * @param <T> The type of the matched object.
+ * @return An element matcher that matches a given argument type for a method description.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> takesArgument(int index, TypeDescription type) {
+ return takesArgument(index, is(type));
+ }
+
+ /**
+ * Matches {@link MethodDescription}s that define a type erasure as a parameter at the given index that matches the supplied matcher.
+ *
+ * @param index The index of the parameter.
+ * @param matcher A matcher to apply to the argument at the specified index.
+ * @param <T> The type of the matched object.
+ * @return An element matcher that matches a given argument type for a method description.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> takesArgument(int index, ElementMatcher<? super TypeDescription> matcher) {
+ return takesGenericArgument(index, erasure(matcher));
+ }
+
+ /**
+ * Matches a method description that takes the provided raw arguments.
+ *
+ * @param type The arguments to match against the matched method.
+ * @param <T> The type of the matched object.
+ * @return A method matcher that matches a method's raw parameter types against the supplied arguments.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> takesArguments(Class<?>... type) {
+ return takesGenericArguments(erasures(type));
+ }
+
+ /**
+ * Matches a method description that takes the provided raw arguments.
+ *
+ * @param type The arguments to match against the matched method.
+ * @param <T> The type of the matched object.
+ * @return A method matcher that matches a method's raw parameter types against the supplied arguments.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> takesArguments(TypeDescription... type) {
+ return takesGenericArguments(erasures(type));
+ }
+
+ /**
+ * Matches a method description that takes the provided raw arguments.
+ *
+ * @param types The arguments to match against the matched method.
+ * @param <T> The type of the matched object.
+ * @return A method matcher that matches a method's raw parameter types against the supplied arguments.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> takesArguments(Iterable<? extends TypeDescription> types) {
+ List<ElementMatcher<? super TypeDescription.Generic>> typeMatchers = new ArrayList<ElementMatcher<? super TypeDescription.Generic>>();
+ for (TypeDescription type : types) {
+ typeMatchers.add(erasure(type));
+ }
+ return takesGenericArguments(new CollectionOneToOneMatcher<TypeDescription.Generic>(typeMatchers));
+ }
+
+ /**
+ * Matches a {@link MethodDescription} by applying an iterable collection of element matcher on any parameter's {@link TypeDescription}.
+ *
+ * @param matchers The matcher that are applied onto the parameter types of the matched method description.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches a method description by applying another element matcher onto each parameter's type.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> takesArguments(ElementMatcher<? super Iterable<? extends TypeDescription>> matchers) {
+ return new MethodParametersMatcher<T>(new MethodParameterTypesMatcher<ParameterList<?>>(erasures(matchers)));
+ }
+
+ /**
+ * Matches a {@link MethodDescription} by the number of its parameters.
+ *
+ * @param length The expected length.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches a method description by the number of its parameters.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> takesArguments(int length) {
+ return new MethodParametersMatcher<T>(new CollectionSizeMatcher<ParameterList<?>>(length));
+ }
+
+ /**
+ * Matches a {@link MethodDescription} by validating that its parameters
+ * fulfill a given constraint.
+ *
+ * @param matcher The matcher to apply for validating the parameters.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches a method description's parameters against the given constraint.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> hasParameters(
+ ElementMatcher<? super Iterable<? extends ParameterDescription>> matcher) {
+ return new MethodParametersMatcher<T>(matcher);
+ }
+
+ /**
+ * Matches a {@link MethodDescription} by its capability to throw a given
+ * checked exception. For specifying a non-checked exception, any method is matched.
+ *
+ * @param exceptionType The type of the exception that should be declared by the method to be matched.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches a method description by its declaration of throwing a checked exception.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> canThrow(Class<? extends Throwable> exceptionType) {
+ return canThrow(new TypeDescription.ForLoadedType(exceptionType));
+ }
+
+ /**
+ * Matches a {@link MethodDescription} by its capability to throw a given
+ * checked exception. For specifying a non-checked exception, any method is matched.
+ *
+ * @param exceptionType The type of the exception that should be declared by the method to be matched.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches a method description by its declaration of throwing a checked exception.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> canThrow(TypeDescription exceptionType) {
+ return exceptionType.isAssignableTo(RuntimeException.class) || exceptionType.isAssignableTo(Error.class)
+ ? new BooleanMatcher<T>(true)
+ : ElementMatchers.<T>declaresGenericException(new CollectionItemMatcher<TypeDescription.Generic>(erasure(isSuperTypeOf(exceptionType))));
+ }
+
+ /**
+ * Matches a method that declares the given generic exception type. For non-generic type, this matcher behaves identically to
+ * {@link ElementMatchers#declaresException(Class)}. For exceptions that are expressed as type variables, only exceptions
+ * that are represented as this type variable are matched.
+ *
+ * @param exceptionType The generic exception type that is matched exactly.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches any method that exactly matches the provided generic exception.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> declaresGenericException(Type exceptionType) {
+ return declaresGenericException(TypeDefinition.Sort.describe(exceptionType));
+ }
+
+ /**
+ * Matches a method that declares the given generic exception type. For non-generic type, this matcher behaves identically to
+ * {@link ElementMatchers#declaresException(TypeDescription)}. For exceptions that are expressed as type variables, only exceptions
+ * that are represented as this type variable are matched.
+ *
+ * @param exceptionType The generic exception type that is matched exactly.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches any method that exactly matches the provided generic exception.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> declaresGenericException(TypeDescription.Generic exceptionType) {
+ return !exceptionType.getSort().isWildcard() && exceptionType.asErasure().isAssignableTo(Throwable.class)
+ ? ElementMatchers.<T>declaresGenericException(new CollectionItemMatcher<TypeDescription.Generic>(is(exceptionType)))
+ : new BooleanMatcher<T>(false);
+ }
+
+ /**
+ * Matches a method that declares the given generic exception type as a (erased) exception type.
+ *
+ * @param exceptionType The exception type that is matched.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches any method that exactly matches the provided exception.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> declaresException(Class<? extends Throwable> exceptionType) {
+ return declaresException(new TypeDescription.ForLoadedType(exceptionType));
+ }
+
+ /**
+ * Matches a method that declares the given generic exception type as a (erased) exception type.
+ *
+ * @param exceptionType The exception type that is matched.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches any method that exactly matches the provided exception.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> declaresException(TypeDescription exceptionType) {
+ return exceptionType.isAssignableTo(Throwable.class)
+ ? ElementMatchers.<T>declaresGenericException(new CollectionItemMatcher<TypeDescription.Generic>(erasure(exceptionType)))
+ : new BooleanMatcher<T>(false);
+ }
+
+ /**
+ * Matches a method's generic exception types against the provided matcher.
+ *
+ * @param matcher The exception matcher to apply onto the matched method's generic exceptions.
+ * @param <T> The type of the matched object.
+ * @return A matcher that applies the provided matcher to a method's generic exception types.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> declaresGenericException(
+ ElementMatcher<? super Iterable<? extends TypeDescription.Generic>> matcher) {
+ return new MethodExceptionTypeMatcher<T>(matcher);
+ }
+
+ /**
+ * Matches any virtual method with a signature that is compatible to a method that is declared the supplied type.
+ *
+ * @param type The super type of interest for which to check if it declares a method with the same signature as the matched method.
+ * @param <T> The type of the matched object.
+ * @return A matcher that checks a method's signature equality for any method declared by the declaring type.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isOverriddenFrom(Class<?> type) {
+ return isOverriddenFrom(new TypeDescription.ForLoadedType(type));
+ }
+
+ /**
+ * Matches any virtual method with a signature that is compatible to a method that is declared the supplied type.
+ *
+ * @param type The super type of interest for which to check if it declares a method with the same signature as the matched method.
+ * @param <T> The type of the matched object.
+ * @return A matcher that checks a method's signature equality for any method declared by the declaring type.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isOverriddenFrom(TypeDescription type) {
+ return isOverriddenFrom(is(type));
+ }
+
+ /**
+ * Matches any virtual method with a signature that is compatible to a method that is declared by a type that matches the supplied matcher.
+ *
+ * @param matcher A matcher for a method's declaring type that needs to be matched if that type declares a method with the same signature
+ * as the matched method.
+ * @param <T> The type of the matched object.
+ * @return A matcher that checks a method's signature equality for any method declared by the declaring type.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isOverriddenFrom(ElementMatcher<? super TypeDescription> matcher) {
+ return isOverriddenFromGeneric(erasure(matcher));
+ }
+
+ /**
+ * Matches any virtual method with a signature that is compatible to a method that is declared the supplied type.
+ *
+ * @param type The super type of interest for which to check if it declares a method with the same signature as the matched method.
+ * @param <T> The type of the matched object.
+ * @return A matcher that checks a method's signature equality for any method declared by the declaring type.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isOverriddenFromGeneric(Type type) {
+ return isOverriddenFromGeneric(TypeDescription.Generic.Sort.describe(type));
+ }
+
+ /**
+ * Matches any virtual method with a signature that is compatible to a method that is declared the supplied type.
+ *
+ * @param type The super type of interest for which to check if it declares a method with the same signature as the matched method.
+ * @param <T> The type of the matched object.
+ * @return A matcher that checks a method's signature equality for any method declared by the declaring type.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isOverriddenFromGeneric(TypeDescription.Generic type) {
+ return isOverriddenFromGeneric(is(type));
+ }
+
+ /**
+ * Matches any virtual method with a signature that is compatible to a method that is declared by a type that matches the supplied matcher.
+ *
+ * @param matcher A matcher for a method's declaring type that needs to be matched if that type declares a method with the same signature
+ * as the matched method.
+ * @param <T> The type of the matched object.
+ * @return A matcher that checks a method's signature equality for any method declared by the declaring type.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isOverriddenFromGeneric(ElementMatcher<? super TypeDescription.Generic> matcher) {
+ return new MethodOverrideMatcher<T>(matcher);
+ }
+
+ /**
+ * Matches a {@link TypeDescription} that is an interface. Annotation types are also considered interface types.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher for an interface.
+ * @see ElementMatchers#isAnnotation()
+ */
+ public static <T extends TypeDescription> ElementMatcher.Junction<T> isInterface() {
+ return new ModifierMatcher<T>(ModifierMatcher.Mode.INTERFACE);
+ }
+
+ /**
+ * Matches a {@link TypeDescription} that is an annotation type.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher for an annotation type.
+ */
+ public static <T extends TypeDescription> ElementMatcher.Junction<T> isAnnotation() {
+ return new ModifierMatcher<T>(ModifierMatcher.Mode.ANNOTATION);
+ }
+
+ /**
+ * Only matches method descriptions that represent a {@link java.lang.reflect.Method}.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher that only matches method descriptions that represent a Java method.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isMethod() {
+ return new MethodSortMatcher<T>(MethodSortMatcher.Sort.METHOD);
+ }
+
+ /**
+ * Only matches method descriptions that represent a {@link java.lang.reflect.Constructor}.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher that only matches method descriptions that represent a Java constructor.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isConstructor() {
+ return new MethodSortMatcher<T>(MethodSortMatcher.Sort.CONSTRUCTOR);
+ }
+
+ /**
+ * Only matches method descriptions that represent a {@link java.lang.Class} type initializer.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher that only matches method descriptions that represent the type initializer.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isTypeInitializer() {
+ return new MethodSortMatcher<T>(MethodSortMatcher.Sort.TYPE_INITIALIZER);
+ }
+
+ /**
+ * Matches any method that is virtual, i.e. non-constructors that are non-static and non-private.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher for virtual methods.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isVirtual() {
+ return new MethodSortMatcher<T>(MethodSortMatcher.Sort.VIRTUAL);
+ }
+
+ /**
+ * Only matches Java 8 default methods.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher that only matches Java 8 default methods.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isDefaultMethod() {
+ return new MethodSortMatcher<T>(MethodSortMatcher.Sort.DEFAULT_METHOD);
+ }
+
+ /**
+ * Matches a default constructor, i.e. a constructor without arguments.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches a default constructor.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isDefaultConstructor() {
+ return isConstructor().and(takesArguments(0));
+ }
+
+ /**
+ * Only matches the {@link Object#finalize()} method if it was not overridden.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher that only matches a non-overridden {@link Object#finalize()} method.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isDefaultFinalizer() {
+ return isFinalizer().and(isDeclaredBy(TypeDescription.OBJECT));
+ }
+
+ /**
+ * Only matches the {@link Object#finalize()} method, even if it was overridden.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher that only matches the {@link Object#finalize()} method.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isFinalizer() {
+ return named("finalize").and(takesArguments(0)).and(returns(TypeDescription.VOID));
+ }
+
+ /**
+ * Only matches the {@link Object#toString()} method, also if it was overridden.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher that only matches the {@link Object#toString()} method.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isHashCode() {
+ return named("hashCode").and(takesArguments(0)).and(returns(int.class));
+ }
+
+ /**
+ * Only matches the {@link Object#equals(Object)} method, also if it was overridden.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher that only matches the {@link Object#equals(Object)} method.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isEquals() {
+ return named("equals").and(takesArguments(TypeDescription.OBJECT)).and(returns(boolean.class));
+ }
+
+ /**
+ * Only matches the {@link Object#clone()} method, also if it was overridden.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher that only matches the {@link Object#clone()} method.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isClone() {
+ return named("clone").and(takesArguments(0)).and(returns(TypeDescription.OBJECT));
+ }
+
+ /**
+ * Only matches the {@link Object#toString()} method, also if it was overridden.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher that only matches the {@link Object#toString()} method.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isToString() {
+ return named("toString").and(takesArguments(0)).and(returns(TypeDescription.STRING));
+ }
+
+ /**
+ * Matches any Java bean setter method.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches any setter method.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isSetter() {
+ return nameStartsWith("set").and(takesArguments(1)).and(returns(TypeDescription.VOID));
+ }
+
+ /**
+ * An element matcher that matches any setter for the given property. When given an empty string, any setter named {@code set}
+ * is matched despite that such a setter is not fulfilling the Java bean naming conventions.
+ *
+ * @param property The property to match a setter for.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches any setter method for the supplied property.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isSetter(String property) {
+ return isSetter().and(property.isEmpty()
+ ? named("set")
+ : named("set" + Character.toUpperCase(property.charAt(0)) + property.substring(1)));
+ }
+
+ /**
+ * Matches any Java bean setter method which takes an argument the given type.
+ *
+ * @param type The required setter type.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches any setter method.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isSetter(Class<?> type) {
+ return isSetter(new TypeDescription.ForLoadedType(type));
+ }
+
+ /**
+ * Matches any Java bean setter method which takes an argument the given type.
+ *
+ * @param type The required setter type.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches any setter method.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isGenericSetter(Type type) {
+ return isGenericSetter(TypeDefinition.Sort.describe(type));
+ }
+
+ /**
+ * Matches any Java bean setter method which takes an argument the given type.
+ *
+ * @param type The required setter type.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches a setter method with the specified argument type.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isSetter(TypeDescription type) {
+ return isSetter(is(type));
+ }
+
+ /**
+ * Matches any Java bean setter method which takes an argument the given type.
+ *
+ * @param type The required setter type.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches a setter method with the specified argument type.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isGenericSetter(TypeDescription.Generic type) {
+ return isGenericSetter(is(type));
+ }
+
+ /**
+ * Matches any Java bean setter method which takes an argument that matches the supplied matcher.
+ *
+ * @param matcher A matcher to be allied to a setter method's argument type.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches a setter method with an argument type that matches the supplied matcher.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isSetter(ElementMatcher<? super TypeDescription> matcher) {
+ return isGenericSetter(erasure(matcher));
+ }
+
+ /**
+ * Matches any Java bean setter method which takes an argument that matches the supplied matcher.
+ *
+ * @param matcher A matcher to be allied to a setter method's argument type.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches a setter method with an argument type that matches the supplied matcher.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isGenericSetter(ElementMatcher<? super TypeDescription.Generic> matcher) {
+ return isSetter().and(takesGenericArguments(new CollectionOneToOneMatcher<TypeDescription.Generic>(Collections.singletonList(matcher))));
+ }
+
+ /**
+ * Matches any Java bean getter method.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches any getter method.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isGetter() {
+ return takesArguments(0).and(not(returns(TypeDescription.VOID))).and(nameStartsWith("get").or(nameStartsWith("is").and(returnsGeneric(anyOf(boolean.class, Boolean.class)))));
+ }
+
+ /**
+ * An element matcher that matches any getter for the given property. When given an empty string, any getter named {@code get}
+ * is matched despite that such a getter is not fulfilling the Java bean naming conventions. If a getter's type is {@code boolean}
+ * or {@link Boolean}, {@code is} is also accepted as a prefix.
+ *
+ * @param property The property to match a getter for.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches any getter method for the supplied property.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isGetter(String property) {
+ return isGetter().and(property.isEmpty()
+ ? named("get").or(named("is"))
+ : named("get" + Character.toUpperCase(property.charAt(0)) + property.substring(1)).or(named("is" + Character.toUpperCase(property.charAt(0)) + property.substring(1))));
+ }
+
+ /**
+ * Matches any Java bean getter method which returns the given type.
+ *
+ * @param type The required getter type.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches a getter method with the given type.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isGetter(Class<?> type) {
+ return isGetter(new TypeDescription.ForLoadedType(type));
+ }
+
+ /**
+ * Matches any Java bean getter method which returns the given type.
+ *
+ * @param type The required getter type.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches a getter method with the given type.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isGenericGetter(Type type) {
+ return isGenericGetter(TypeDefinition.Sort.describe(type));
+ }
+
+ /**
+ * Matches any Java bean getter method which returns the given type.
+ *
+ * @param type The required getter type.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches a getter method with the given type.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isGetter(TypeDescription type) {
+ return isGetter(is(type));
+ }
+
+ /**
+ * Matches any Java bean getter method which returns the given type.
+ *
+ * @param type The required getter type.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches a getter method with the given type.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isGenericGetter(TypeDescription.Generic type) {
+ return isGenericGetter(is(type));
+ }
+
+ /**
+ * Matches any Java bean getter method which returns an value with a type matches the supplied matcher.
+ *
+ * @param matcher A matcher to be allied to a getter method's argument type.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches a getter method with a return type that matches the supplied matcher.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isGetter(ElementMatcher<? super TypeDescription> matcher) {
+ return isGenericGetter(erasure(matcher));
+ }
+
+ /**
+ * Matches any Java bean getter method which returns an value with a type matches the supplied matcher.
+ *
+ * @param matcher A matcher to be allied to a getter method's argument type.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches a getter method with a return type that matches the supplied matcher.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> isGenericGetter(ElementMatcher<? super TypeDescription.Generic> matcher) {
+ return isGetter().and(returnsGeneric(matcher));
+ }
+
+ /**
+ * Matches a method against its internal name such that constructors and type initializers are matched appropriately.
+ *
+ * @param internalName The internal name of the method.
+ * @param <T> The type of the matched object.
+ * @return A matcher for a method with the provided internal name.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> hasMethodName(String internalName) {
+ if (MethodDescription.CONSTRUCTOR_INTERNAL_NAME.equals(internalName)) {
+ return isConstructor();
+ } else if (MethodDescription.TYPE_INITIALIZER_INTERNAL_NAME.equals(internalName)) {
+ return isTypeInitializer();
+ } else {
+ return named(internalName);
+ }
+ }
+
+ /**
+ * Only matches method descriptions that yield the provided signature token.
+ *
+ * @param token The signature token to match against.
+ * @param <T> The type of the matched object.
+ * @return A matcher for a method with the provided signature token.
+ */
+ public static <T extends MethodDescription> ElementMatcher.Junction<T> hasSignature(MethodDescription.SignatureToken token) {
+ return new SignatureTokenMatcher<T>(is(token));
+ }
+
+ /**
+ * Matches any type description that is a subtype of the given type.
+ *
+ * @param type The type to be checked for being a subtype of the matched type.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches any type description that represents a sub type of the given type.
+ */
+ public static <T extends TypeDescription> ElementMatcher.Junction<T> isSubTypeOf(Class<?> type) {
+ return isSubTypeOf(new TypeDescription.ForLoadedType(type));
+ }
+
+ /**
+ * Matches any type description that is a subtype of the given type.
+ *
+ * @param type The type to be checked for being a subtype of the matched type.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches any type description that represents a sub type of the given type.
+ */
+ public static <T extends TypeDescription> ElementMatcher.Junction<T> isSubTypeOf(TypeDescription type) {
+ return new SubTypeMatcher<T>(type);
+ }
+
+ /**
+ * Matches any type description that is a super type of the given type.
+ *
+ * @param type The type to be checked for being a subtype of the matched type.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches any type description that represents a super type of the given type.
+ */
+ public static <T extends TypeDescription> ElementMatcher.Junction<T> isSuperTypeOf(Class<?> type) {
+ return isSuperTypeOf(new TypeDescription.ForLoadedType(type));
+ }
+
+ /**
+ * Matches any type description that is a super type of the given type.
+ *
+ * @param type The type to be checked for being a subtype of the matched type.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches any type description that represents a super type of the given type.
+ */
+ public static <T extends TypeDescription> ElementMatcher.Junction<T> isSuperTypeOf(TypeDescription type) {
+ return new SuperTypeMatcher<T>(type);
+ }
+
+ /**
+ * Matches any type description that declares a super type that matches the provided matcher.
+ *
+ * @param matcher The type to be checked for being a super type of the matched type.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches any type description that declares a super type that matches the provided matcher.
+ */
+ public static <T extends TypeDescription> ElementMatcher.Junction<T> hasSuperType(ElementMatcher<? super TypeDescription> matcher) {
+ return hasGenericSuperType(erasure(matcher));
+ }
+
+ /**
+ * Matches any type description that declares a super type that matches the provided matcher.
+ *
+ * @param matcher The type to be checked for being a super type of the matched type.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches any type description that declares a super type that matches the provided matcher.
+ */
+ public static <T extends TypeDescription> ElementMatcher.Junction<T> hasGenericSuperType(ElementMatcher<? super TypeDescription.Generic> matcher) {
+ return new HasSuperTypeMatcher<T>(matcher);
+ }
+
+ /**
+ * Matches any annotations by their type on a type that declared these annotations or inherited them from its
+ * super classes.
+ *
+ * @param type The annotation type to be matched.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches any inherited annotation by their type.
+ */
+ public static <T extends TypeDescription> ElementMatcher.Junction<T> inheritsAnnotation(Class<?> type) {
+ return inheritsAnnotation(new TypeDescription.ForLoadedType(type));
+ }
+
+ /**
+ * Matches any annotations by their type on a type that declared these annotations or inherited them from its
+ * super classes.
+ *
+ * @param type The annotation type to be matched.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches any inherited annotation by their type.
+ */
+ public static <T extends TypeDescription> ElementMatcher.Junction<T> inheritsAnnotation(TypeDescription type) {
+ return inheritsAnnotation(is(type));
+ }
+
+ /**
+ * Matches any annotations by a given matcher on a type that declared these annotations or inherited them from its
+ * super classes.
+ *
+ * @param matcher A matcher to apply onto the inherited annotations.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches any inherited annotation by a given matcher.
+ */
+ public static <T extends TypeDescription> ElementMatcher.Junction<T> inheritsAnnotation(ElementMatcher<? super TypeDescription> matcher) {
+ return hasAnnotation(annotationType(matcher));
+ }
+
+ /**
+ * Matches a list of annotations by a given matcher on a type that declared these annotations or inherited them
+ * from its super classes.
+ *
+ * @param matcher A matcher to apply onto a list of inherited annotations.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches a list of inherited annotation by a given matcher.
+ */
+ public static <T extends TypeDescription> ElementMatcher.Junction<T> hasAnnotation(ElementMatcher<? super AnnotationDescription> matcher) {
+ return new InheritedAnnotationMatcher<T>(new CollectionItemMatcher<AnnotationDescription>(matcher));
+ }
+
+ /**
+ * Matches a type by a another matcher that is applied on any of its declared fields.
+ *
+ * @param matcher The matcher that is applied onto each declared field.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches any type where another matcher is matched positively on at least on declared field.
+ */
+ public static <T extends TypeDefinition> ElementMatcher.Junction<T> declaresField(ElementMatcher<? super FieldDescription> matcher) {
+ return new DeclaringFieldMatcher<T>(new CollectionItemMatcher<FieldDescription>(matcher));
+ }
+
+ /**
+ * Matches a type by a another matcher that is applied on any of its declared methods.
+ *
+ * @param matcher The matcher that is applied onto each declared method.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches any type where another matcher is matched positively on at least on declared methods.
+ */
+ public static <T extends TypeDefinition> ElementMatcher.Junction<T> declaresMethod(ElementMatcher<? super MethodDescription> matcher) {
+ return new DeclaringMethodMatcher<T>(new CollectionItemMatcher<MethodDescription>(matcher));
+ }
+
+ /**
+ * Matches generic type descriptions of the given sort.
+ *
+ * @param sort The generic type sort to match.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches generic types of the given sort.
+ */
+ public static <T extends TypeDefinition> ElementMatcher.Junction<T> ofSort(TypeDefinition.Sort sort) {
+ return ofSort(is(sort));
+ }
+
+ /**
+ * Matches generic type descriptions of the given sort.
+ *
+ * @param matcher A matcher for a generic type's sort.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches generic types of the given sort.
+ */
+ public static <T extends TypeDefinition> ElementMatcher.Junction<T> ofSort(ElementMatcher<? super TypeDefinition.Sort> matcher) {
+ return new TypeSortMatcher<T>(matcher);
+ }
+
+ /**
+ * Matches a field's generic type against the provided matcher.
+ *
+ * @param fieldType The field type to match.
+ * @param <T> The type of the matched object.
+ * @return A matcher matching the provided field type.
+ */
+ public static <T extends FieldDescription> ElementMatcher.Junction<T> genericFieldType(Type fieldType) {
+ return genericFieldType(TypeDefinition.Sort.describe(fieldType));
+ }
+
+ /**
+ * Matches a field's generic type against the provided matcher.
+ *
+ * @param fieldType The field type to match.
+ * @param <T> The type of the matched object.
+ * @return A matcher matching the provided field type.
+ */
+ public static <T extends FieldDescription> ElementMatcher.Junction<T> genericFieldType(TypeDescription.Generic fieldType) {
+ return genericFieldType(is(fieldType));
+ }
+
+ /**
+ * Matches a field's generic type against the provided matcher.
+ *
+ * @param matcher The matcher to apply to the field's type.
+ * @param <T> The type of the matched object.
+ * @return A matcher matching the provided field type.
+ */
+ public static <T extends FieldDescription> ElementMatcher.Junction<T> genericFieldType(ElementMatcher<? super TypeDescription.Generic> matcher) {
+ return new FieldTypeMatcher<T>(matcher);
+ }
+
+ /**
+ * Matches a field's raw type against the provided matcher.
+ *
+ * @param fieldType The field type to match.
+ * @param <T> The type of the matched object.
+ * @return A matcher matching the provided field type.
+ */
+ public static <T extends FieldDescription> ElementMatcher.Junction<T> fieldType(Class<?> fieldType) {
+ return fieldType(new TypeDescription.ForLoadedType(fieldType));
+ }
+
+ /**
+ * Matches a field's raw type against the provided matcher.
+ *
+ * @param fieldType The field type to match.
+ * @param <T> The type of the matched object.
+ * @return A matcher matching the provided field type.
+ */
+ public static <T extends FieldDescription> ElementMatcher.Junction<T> fieldType(TypeDescription fieldType) {
+ return fieldType(is(fieldType));
+ }
+
+ /**
+ * Matches a field's raw type against the provided matcher.
+ *
+ * @param matcher The matcher to apply to the field's type.
+ * @param <T> The type of the matched object.
+ * @return A matcher matching the provided field type.
+ */
+ public static <T extends FieldDescription> ElementMatcher.Junction<T> fieldType(ElementMatcher<? super TypeDescription> matcher) {
+ return genericFieldType(erasure(matcher));
+ }
+
+ /**
+ * Matches a {@code volatile} field.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher for a {@code volatile} field.
+ */
+ public static <T extends FieldDescription> ElementMatcher.Junction<T> isVolatile() {
+ return new ModifierMatcher<T>(ModifierMatcher.Mode.VOLATILE);
+ }
+
+ /**
+ * Matches a {@code transient} field.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher for a {@code transient} field.
+ */
+ public static <T extends FieldDescription> ElementMatcher.Junction<T> isTransient() {
+ return new ModifierMatcher<T>(ModifierMatcher.Mode.TRANSIENT);
+ }
+
+ /**
+ * Matches if an annotation is of a given type.
+ *
+ * @param type The expected annotation type.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches the annotation's type for being equal to the given type.
+ */
+ public static <T extends AnnotationDescription> ElementMatcher.Junction<T> annotationType(Class<? extends Annotation> type) {
+ return annotationType(new TypeDescription.ForLoadedType(type));
+ }
+
+ /**
+ * Matches if an annotation is of a given type.
+ *
+ * @param type The expected annotation type.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches the annotation's type for being equal to the given type.
+ */
+ public static <T extends AnnotationDescription> ElementMatcher.Junction<T> annotationType(TypeDescription type) {
+ return annotationType(is(type));
+ }
+
+ /**
+ * Matches if an annotation's type matches the supplied matcher.
+ *
+ * @param matcher The matcher to match the annotation's type against.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches the annotation's type.
+ */
+ public static <T extends AnnotationDescription> ElementMatcher.Junction<T> annotationType(ElementMatcher<? super TypeDescription> matcher) {
+ return new AnnotationTypeMatcher<T>(matcher);
+ }
+
+ /**
+ * Matches exactly the bootstrap {@link java.lang.ClassLoader} . The returned matcher is a synonym to
+ * a matcher matching {@code null}.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher that only matches the bootstrap class loader.
+ */
+ public static <T extends ClassLoader> ElementMatcher.Junction<T> isBootstrapClassLoader() {
+ return new NullMatcher<T>();
+ }
+
+ /**
+ * Matches exactly the system {@link java.lang.ClassLoader}. The returned matcher is a synonym to
+ * a matcher matching {@code ClassLoader.gerSystemClassLoader()}.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher that only matches the system class loader.
+ */
+ public static <T extends ClassLoader> ElementMatcher.Junction<T> isSystemClassLoader() {
+ return new EqualityMatcher<T>(ClassLoader.getSystemClassLoader());
+ }
+
+ /**
+ * Matches exactly the extension {@link java.lang.ClassLoader}. The returned matcher is a synonym to
+ * a matcher matching {@code ClassLoader.gerSystemClassLoader().getParent()}.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher that only matches the extension class loader.
+ */
+ public static <T extends ClassLoader> ElementMatcher.Junction<T> isExtensionClassLoader() {
+ ClassLoader classLoader = ClassLoader.getSystemClassLoader().getParent();
+ return classLoader == null // Check if VM supports the extension class loader.
+ ? ElementMatchers.<T>none()
+ : new EqualityMatcher<T>(classLoader);
+ }
+
+ /**
+ * Matches any class loader that is either the given class loader or a child of the given class loader.
+ *
+ * @param classLoader The class loader of which child class loaders are matched.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches the given class loader and any class loader that is a child of the given
+ * class loader.
+ */
+ public static <T extends ClassLoader> ElementMatcher.Junction<T> isChildOf(ClassLoader classLoader) {
+ return classLoader == BOOTSTRAP_CLASSLOADER
+ ? new BooleanMatcher<T>(true)
+ : ElementMatchers.<T>hasChild(is(classLoader));
+ }
+
+ /**
+ * Matches all class loaders in the hierarchy of the matched class loader against a given matcher.
+ *
+ * @param matcher The matcher to apply to all class loaders in the hierarchy of the matched class loader.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches all class loaders in the hierarchy of the matched class loader.
+ */
+ public static <T extends ClassLoader> ElementMatcher.Junction<T> hasChild(ElementMatcher<? super ClassLoader> matcher) {
+ return new ClassLoaderHierarchyMatcher<T>(matcher);
+ }
+
+ /**
+ * Matches any class loader that is either the given class loader or a parent of the given class loader.
+ *
+ * @param classLoader The class loader of which parent class loaders are matched.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches the given class loader and any class loader that is a parent of the given
+ * class loader.
+ */
+ public static <T extends ClassLoader> ElementMatcher.Junction<T> isParentOf(ClassLoader classLoader) {
+ return classLoader == BOOTSTRAP_CLASSLOADER
+ ? ElementMatchers.<T>isBootstrapClassLoader()
+ : new ClassLoaderParentMatcher<T>(classLoader);
+ }
+
+ /**
+ * Matches a class loader's type unless it is the bootstrap class loader which is never matched.
+ *
+ * @param matcher The matcher to apply to the class loader's type.
+ * @param <T> The type of the matched object.
+ * @return A matcher that matches the class loader's type.
+ */
+ public static <T extends ClassLoader> ElementMatcher.Junction<T> ofType(ElementMatcher<? super TypeDescription> matcher) {
+ return new InstanceTypeMatcher<T>(matcher);
+ }
+
+ /**
+ * Matches a module if it exists, i.e. not {@code null}.
+ *
+ * @param <T> The type of the matched object.
+ * @return A matcher that validates a module's existence.
+ */
+ public static <T extends JavaModule> ElementMatcher.Junction<T> supportsModules() {
+ return not(new NullMatcher<T>());
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/EqualityMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/EqualityMatcher.java
new file mode 100644
index 0000000..bbcbf60
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/EqualityMatcher.java
@@ -0,0 +1,36 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+
+/**
+ * An element matcher that checks an object's equality to another object.
+ *
+ * @param <T> The type of the matched entity.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class EqualityMatcher<T> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The object that is checked to be equal to the matched value.
+ */
+ private final Object value;
+
+ /**
+ * Creates an element matcher that tests for equality.
+ *
+ * @param value The object that is checked to be equal to the matched value.
+ */
+ public EqualityMatcher(Object value) {
+ this.value = value;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ return value.equals(target);
+ }
+
+ @Override
+ public String toString() {
+ return "is(" + value + ")";
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/ErasureMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/ErasureMatcher.java
new file mode 100644
index 0000000..ab95599
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/ErasureMatcher.java
@@ -0,0 +1,40 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+
+/**
+ * An element matcher that matches its argument's {@link TypeDescription.Generic} raw type against the
+ * given matcher for a {@link TypeDescription}. As a wildcard does not define an erasure, a runtime
+ * exception is thrown when this matcher is applied to a wildcard.
+ *
+ * @param <T> The type of the matched entity.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class ErasureMatcher<T extends TypeDefinition> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The matcher to apply to the raw type of the matched element.
+ */
+ private final ElementMatcher<? super TypeDescription> matcher;
+
+ /**
+ * Creates a new raw type matcher.
+ *
+ * @param matcher The matcher to apply to the raw type.
+ */
+ public ErasureMatcher(ElementMatcher<? super TypeDescription> matcher) {
+ this.matcher = matcher;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ return matcher.matches(target.asErasure());
+ }
+
+ @Override
+ public String toString() {
+ return "erasure(" + matcher + ")";
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/FailSafeMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/FailSafeMatcher.java
new file mode 100644
index 0000000..a110d34
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/FailSafeMatcher.java
@@ -0,0 +1,47 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+
+/**
+ * A fail-safe matcher catches exceptions that are thrown by a delegate matcher and returns an alternative value.
+ *
+ * @param <T> The type of the matched entity.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class FailSafeMatcher<T> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The delegate matcher that might throw an exception.
+ */
+ private final ElementMatcher<? super T> matcher;
+
+ /**
+ * The fallback value in case of an exception.
+ */
+ private final boolean fallback;
+
+ /**
+ * Creates a new fail-safe element matcher.
+ *
+ * @param matcher The delegate matcher that might throw an exception.
+ * @param fallback The fallback value in case of an exception.
+ */
+ public FailSafeMatcher(ElementMatcher<? super T> matcher, boolean fallback) {
+ this.matcher = matcher;
+ this.fallback = fallback;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ try {
+ return matcher.matches(target);
+ } catch (Exception ignored) {
+ return fallback;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "failSafe(try(" + matcher + ") or " + fallback + ")";
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/FieldTypeMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/FieldTypeMatcher.java
new file mode 100644
index 0000000..002af28
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/FieldTypeMatcher.java
@@ -0,0 +1,38 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.type.TypeDescription;
+
+/**
+ * An element matcher that matches a field's type.
+ *
+ * @param <T> The type of the matched entity.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class FieldTypeMatcher<T extends FieldDescription> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The type matcher to apply to the field's type.
+ */
+ private final ElementMatcher<? super TypeDescription.Generic> matcher;
+
+ /**
+ * Creates a new matcher for a matched field's type.
+ *
+ * @param matcher The type matcher to apply to the matched field's type.
+ */
+ public FieldTypeMatcher(ElementMatcher<? super TypeDescription.Generic> matcher) {
+ this.matcher = matcher;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ return matcher.matches(target.getType());
+ }
+
+ @Override
+ public String toString() {
+ return "ofType(" + matcher + ")";
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/FilterableList.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/FilterableList.java
new file mode 100644
index 0000000..b4476c0
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/FilterableList.java
@@ -0,0 +1,126 @@
+package net.bytebuddy.matcher;
+
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A filterable list allows to use an {@link net.bytebuddy.matcher.ElementMatcher} to reduce a lists to elements
+ * that are matched by this matcher in this list.
+ *
+ * @param <T> The type of the collection's elements.
+ * @param <S> The type of this list.
+ */
+public interface FilterableList<T, S extends FilterableList<T, S>> extends List<T> {
+
+ /**
+ * Filters any elements in this lists by the given {@code elementMatcher} and returns a list that are matched
+ * by the given matcher.
+ *
+ * @param elementMatcher The element matcher to match the elements of this list against.
+ * @return A new list only containing the matched elements.
+ */
+ S filter(ElementMatcher<? super T> elementMatcher);
+
+ /**
+ * Returns the only element of this list. If there is not exactly one element in this list, an
+ * {@link java.lang.IllegalStateException} is thrown.
+ *
+ * @return The only element of this list.
+ */
+ T getOnly();
+
+ @Override
+ S subList(int fromIndex, int toIndex);
+
+ /**
+ * An implementation of an empty {@link net.bytebuddy.matcher.FilterableList}.
+ *
+ * @param <T> The type of the collection's elements.
+ * @param <S> The type of this list.
+ */
+ class Empty<T, S extends FilterableList<T, S>> extends AbstractList<T> implements FilterableList<T, S> {
+
+ @Override
+ public T get(int index) {
+ throw new IndexOutOfBoundsException("index = " + index);
+ }
+
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ @Override
+ public T getOnly() {
+ throw new IllegalStateException("size = 0");
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public S filter(ElementMatcher<? super T> elementMatcher) {
+ return (S) this;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public S subList(int fromIndex, int toIndex) {
+ if (fromIndex == toIndex && toIndex == 0) {
+ return (S) this;
+ } else if (fromIndex > toIndex) {
+ throw new IllegalArgumentException("fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")");
+ } else {
+ throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
+ }
+ }
+ }
+
+ /**
+ * A base implementation of a {@link net.bytebuddy.matcher.FilterableList}.
+ *
+ * @param <T> The type of the collection's elements.
+ * @param <S> The type of this list.
+ */
+ abstract class AbstractBase<T, S extends FilterableList<T, S>> extends AbstractList<T> implements FilterableList<T, S> {
+
+ /**
+ * A convenience variable indicating the index of a list's only variable.
+ */
+ private static final int ONLY = 0;
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public S filter(ElementMatcher<? super T> elementMatcher) {
+ List<T> filteredElements = new ArrayList<T>(size());
+ for (T value : this) {
+ if (elementMatcher.matches(value)) {
+ filteredElements.add(value);
+ }
+ }
+ return filteredElements.size() == size() ?
+ (S) this
+ : wrap(filteredElements);
+ }
+
+ @Override
+ public T getOnly() {
+ if (size() != 1) {
+ throw new IllegalStateException("size = " + size());
+ }
+ return get(ONLY);
+ }
+
+ @Override
+ public S subList(int fromIndex, int toIndex) {
+ return wrap(super.subList(fromIndex, toIndex));
+ }
+
+ /**
+ * Represents a list of values as an instance of this instance's list type.
+ *
+ * @param values The values to wrap in an instance of this list's type.
+ * @return A wrapped instance of the given {@code values}.
+ */
+ protected abstract S wrap(List<T> values);
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/HasSuperTypeMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/HasSuperTypeMatcher.java
new file mode 100644
index 0000000..c6c0908
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/HasSuperTypeMatcher.java
@@ -0,0 +1,63 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * An element matcher that matches a super type.
+ *
+ * @param <T> The type of the matched entity.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class HasSuperTypeMatcher<T extends TypeDescription> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The matcher to apply to any super type of the matched type.
+ */
+ private final ElementMatcher<? super TypeDescription.Generic> matcher;
+
+ /**
+ * Creates a new matcher for a super type.
+ *
+ * @param matcher The matcher to apply to any super type of the matched type.
+ */
+ public HasSuperTypeMatcher(ElementMatcher<? super TypeDescription.Generic> matcher) {
+ this.matcher = matcher;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ Set<TypeDescription> checkedInterfaces = new HashSet<TypeDescription>();
+ for (TypeDefinition typeDefinition : target) {
+ if (matcher.matches(typeDefinition.asGenericType()) || hasInterface(typeDefinition, checkedInterfaces)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Matches a type's interfaces against the provided matcher.
+ *
+ * @param typeDefinition The type for which to check all implemented interfaces.
+ * @param checkedInterfaces The interfaces that have already been checked.
+ * @return {@code true} if any interface matches the supplied matcher.
+ */
+ private boolean hasInterface(TypeDefinition typeDefinition, Set<TypeDescription> checkedInterfaces) {
+ for (TypeDefinition interfaceType : typeDefinition.getInterfaces()) {
+ if (checkedInterfaces.add(interfaceType.asErasure()) && (matcher.matches(interfaceType.asGenericType()) || hasInterface(interfaceType, checkedInterfaces))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "hasSuperType(" + matcher + ")";
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/InheritedAnnotationMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/InheritedAnnotationMatcher.java
new file mode 100644
index 0000000..b4fca2c
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/InheritedAnnotationMatcher.java
@@ -0,0 +1,38 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.type.TypeDescription;
+
+/**
+ * An element matcher that matches the list of inherited annotations of a type description.
+ *
+ * @param <T> The actual matched type of this matcher.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class InheritedAnnotationMatcher<T extends TypeDescription> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The matcher to be applied to the provided annotation list.
+ */
+ private final ElementMatcher<? super AnnotationList> matcher;
+
+ /**
+ * Creates a new matcher for the inherited annotations of a type description.
+ *
+ * @param matcher The matcher to be applied to the provided annotation list.
+ */
+ public InheritedAnnotationMatcher(ElementMatcher<? super AnnotationList> matcher) {
+ this.matcher = matcher;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ return matcher.matches(target.getInheritedAnnotations());
+ }
+
+ @Override
+ public String toString() {
+ return "inheritsAnnotations(" + matcher + ")";
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/InstanceTypeMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/InstanceTypeMatcher.java
new file mode 100644
index 0000000..c9f65b6
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/InstanceTypeMatcher.java
@@ -0,0 +1,37 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.type.TypeDescription;
+
+/**
+ * An element matcher that matches an object's type.
+ *
+ * @param <T> The exact type of the object that is matched.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class InstanceTypeMatcher<T> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The matcher to apply to the object's type.
+ */
+ private final ElementMatcher<? super TypeDescription> matcher;
+
+ /**
+ * Creates a new instance type matcher.
+ *
+ * @param matcher The matcher to apply to the object's type.
+ */
+ public InstanceTypeMatcher(ElementMatcher<? super TypeDescription> matcher) {
+ this.matcher = matcher;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ return target != null && matcher.matches(new TypeDescription.ForLoadedType(target.getClass()));
+ }
+
+ @Override
+ public String toString() {
+ return "ofType(" + matcher + ")";
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/IsNamedMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/IsNamedMatcher.java
new file mode 100644
index 0000000..daabc0b
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/IsNamedMatcher.java
@@ -0,0 +1,23 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.NamedElement;
+
+/**
+ * An element matcher that matches a named element only if is explicitly named.
+ *
+ * @param <T> The type of the matched entity.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class IsNamedMatcher<T extends NamedElement.WithOptionalName> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ @Override
+ public boolean matches(T target) {
+ return target.isNamed();
+ }
+
+ @Override
+ public String toString() {
+ return "isNamed()";
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/LatentMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/LatentMatcher.java
new file mode 100644
index 0000000..8405a1c
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/LatentMatcher.java
@@ -0,0 +1,282 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static net.bytebuddy.matcher.ElementMatchers.*;
+
+/**
+ * A latent matcher that resolves an {@link ElementMatcher} after supplying a type description.
+ *
+ * @param <T> The type of the matched element.
+ */
+public interface LatentMatcher<T> {
+
+ /**
+ * Resolves the element matcher this instance represents for the supplied type description.
+ *
+ * @param typeDescription The type description for which the represented matcher should be resolved.
+ * @return An {@link ElementMatcher} that represents this matcher's resolved form.
+ */
+ ElementMatcher<? super T> resolve(TypeDescription typeDescription);
+
+ /**
+ * A latent matching methods that are declared by the resolved type.
+ */
+ enum ForSelfDeclaredMethod implements LatentMatcher<MethodDescription> {
+
+ /**
+ * Matches any method declared by the resolved type.
+ */
+ DECLARED(false),
+
+ /**
+ * Matches any method not declared by the resolved type.
+ */
+ NOT_DECLARED(true);
+
+ /**
+ * {@code true} if the matcher is inverted.
+ */
+ private final boolean inverted;
+
+ /**
+ * Creates a new latent matcher for a self-declared method.
+ *
+ * @param inverted {@code true} if the matcher is inverted.
+ */
+ ForSelfDeclaredMethod(boolean inverted) {
+ this.inverted = inverted;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public ElementMatcher<? super MethodDescription> resolve(TypeDescription typeDescription) {
+ // Casting is required by some Java 6 compilers.
+ return (ElementMatcher<? super MethodDescription>) (inverted
+ ? not(isDeclaredBy(typeDescription))
+ : isDeclaredBy(typeDescription));
+ }
+ }
+
+ /**
+ * A latent matcher representing an already resolved {@link ElementMatcher}.
+ *
+ * @param <S> The type of the matched element.
+ */
+ @EqualsAndHashCode
+ class Resolved<S> implements LatentMatcher<S> {
+
+ /**
+ * The resolved matcher.
+ */
+ private final ElementMatcher<? super S> matcher;
+
+ /**
+ * Creates a new resolved latent matcher.
+ *
+ * @param matcher The resolved matcher.
+ */
+ public Resolved(ElementMatcher<? super S> matcher) {
+ this.matcher = matcher;
+ }
+
+ @Override
+ public ElementMatcher<? super S> resolve(TypeDescription typeDescription) {
+ return matcher;
+ }
+ }
+
+ /**
+ * A latent matcher where the field token is being attached to the supplied type description before matching.
+ */
+ @EqualsAndHashCode
+ class ForFieldToken implements LatentMatcher<FieldDescription> {
+
+ /**
+ * A token representing the field being matched.
+ */
+ private final FieldDescription.Token token;
+
+ /**
+ * Creates a new latent matcher for a field token.
+ *
+ * @param token A token representing the field being matched.
+ */
+ public ForFieldToken(FieldDescription.Token token) {
+ this.token = token;
+ }
+
+ @Override
+ public ElementMatcher<? super FieldDescription> resolve(TypeDescription typeDescription) {
+ return new ResolvedMatcher(token.asSignatureToken(typeDescription));
+ }
+
+ /**
+ * A resolved matcher of a latent field matcher for a field token.
+ */
+ @EqualsAndHashCode
+ protected static class ResolvedMatcher implements ElementMatcher<FieldDescription> {
+
+ /**
+ * The signature token representing the matched field.
+ */
+ private final FieldDescription.SignatureToken signatureToken;
+
+ /**
+ * Creates a new resolved matcher.
+ *
+ * @param signatureToken The signature token representing the matched field.
+ */
+ protected ResolvedMatcher(FieldDescription.SignatureToken signatureToken) {
+ this.signatureToken = signatureToken;
+ }
+
+ @Override
+ public boolean matches(FieldDescription target) {
+ return target.asSignatureToken().equals(signatureToken);
+ }
+ }
+ }
+
+ /**
+ * A latent matcher where the method token is being attached to the supplied type description before matching.
+ */
+ @EqualsAndHashCode
+ class ForMethodToken implements LatentMatcher<MethodDescription> {
+
+ /**
+ * A token representing the method being matched.
+ */
+ private final MethodDescription.Token token;
+
+ /**
+ * Creates a new latent matcher for a method token.
+ *
+ * @param token A token representing the method being matched.
+ */
+ public ForMethodToken(MethodDescription.Token token) {
+ this.token = token;
+ }
+
+ @Override
+ public ElementMatcher<? super MethodDescription> resolve(TypeDescription typeDescription) {
+ return new ResolvedMatcher(token.asSignatureToken(typeDescription));
+ }
+
+ /**
+ * A resolved matcher of a latent method matcher for a method token.
+ */
+ @EqualsAndHashCode
+ protected static class ResolvedMatcher implements ElementMatcher<MethodDescription> {
+
+ /**
+ * The signature token representing the matched field.
+ */
+ private final MethodDescription.SignatureToken signatureToken;
+
+ /**
+ * Creates a new resolved matcher.
+ *
+ * @param signatureToken The signature token representing the matched field.
+ */
+ protected ResolvedMatcher(MethodDescription.SignatureToken signatureToken) {
+ this.signatureToken = signatureToken;
+ }
+
+ @Override
+ public boolean matches(MethodDescription target) {
+ return target.asSignatureToken().equals(signatureToken);
+ }
+ }
+ }
+
+ /**
+ * A matcher that computes the conjunction of all supplied latent matchers.
+ *
+ * @param <S> The type of the matched element.
+ */
+ @EqualsAndHashCode
+ class Conjunction<S> implements LatentMatcher<S> {
+
+ /**
+ * The matchers this conjunction represents.
+ */
+ private final List<? extends LatentMatcher<? super S>> matchers;
+
+ /**
+ * Creates a new conjunction of latent matchers.
+ *
+ * @param matcher The matchers this conjunction represents.
+ */
+ @SuppressWarnings("unchecked") // In absence of @SafeVarargs for Java 6
+ public Conjunction(LatentMatcher<? super S>... matcher) {
+ this(Arrays.asList(matcher));
+ }
+
+ /**
+ * Creates a new conjunction of latent matchers.
+ *
+ * @param matchers The matchers this conjunction represents.
+ */
+ public Conjunction(List<? extends LatentMatcher<? super S>> matchers) {
+ this.matchers = matchers;
+ }
+
+ @Override
+ public ElementMatcher<? super S> resolve(TypeDescription typeDescription) {
+ ElementMatcher.Junction<S> matcher = any();
+ for (LatentMatcher<? super S> latentMatcher : matchers) {
+ matcher = matcher.and(latentMatcher.resolve(typeDescription));
+ }
+ return matcher;
+ }
+ }
+
+ /**
+ * A matcher that computes the disjunction of all supplied latent matchers.
+ *
+ * @param <S> The type of the matched element.
+ */
+ @EqualsAndHashCode
+ class Disjunction<S> implements LatentMatcher<S> {
+
+ /**
+ * The matchers this disjunction represents.
+ */
+ private final List<? extends LatentMatcher<? super S>> matchers;
+
+ /**
+ * Creates a new disjunction of latent matchers.
+ *
+ * @param matcher The matchers this disjunction represents.
+ */
+ @SuppressWarnings("unchecked") // In absence of @SafeVarargs for Java 6
+ public Disjunction(LatentMatcher<? super S>... matcher) {
+ this(Arrays.asList(matcher));
+ }
+
+ /**
+ * Creates a new disjunction of latent matchers.
+ *
+ * @param matchers The matchers this disjunction represents.
+ */
+ public Disjunction(List<? extends LatentMatcher<? super S>> matchers) {
+ this.matchers = matchers;
+ }
+
+ @Override
+ public ElementMatcher<? super S> resolve(TypeDescription typeDescription) {
+ ElementMatcher.Junction<S> matcher = none();
+ for (LatentMatcher<? super S> latentMatcher : matchers) {
+ matcher = matcher.or(latentMatcher.resolve(typeDescription));
+ }
+ return matcher;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/MethodExceptionTypeMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/MethodExceptionTypeMatcher.java
new file mode 100644
index 0000000..41d3ca7
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/MethodExceptionTypeMatcher.java
@@ -0,0 +1,38 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeList;
+
+/**
+ * An element matcher that matches the exceptions that are declared by a method.
+ *
+ * @param <T> The type of the matched entity.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class MethodExceptionTypeMatcher<T extends MethodDescription> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The matcher to apply to the matched method's exceptions.
+ */
+ private final ElementMatcher<? super TypeList.Generic> matcher;
+
+ /**
+ * Creates a new matcher for a method's exceptions.
+ *
+ * @param matcher The matcher to apply to the matched method's exceptions.
+ */
+ public MethodExceptionTypeMatcher(ElementMatcher<? super TypeList.Generic> matcher) {
+ this.matcher = matcher;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ return matcher.matches(target.getExceptionTypes());
+ }
+
+ @Override
+ public String toString() {
+ return "exceptions(" + matcher + ")";
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/MethodOverrideMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/MethodOverrideMatcher.java
new file mode 100644
index 0000000..417d388
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/MethodOverrideMatcher.java
@@ -0,0 +1,88 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static net.bytebuddy.matcher.ElementMatchers.isVirtual;
+
+/**
+ * A matcher that checks if any super type of a type declares a method with the same shape of a matched method.
+ *
+ * @param <T> The type of the matched entity.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class MethodOverrideMatcher<T extends MethodDescription> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The matcher that is to be applied to the type that declares a method of the same shape.
+ */
+ private final ElementMatcher<? super TypeDescription.Generic> matcher;
+
+ /**
+ * Creates a new method override matcher.
+ *
+ * @param matcher The matcher that is to be applied to the type that declares a method of the same shape.
+ */
+ public MethodOverrideMatcher(ElementMatcher<? super TypeDescription.Generic> matcher) {
+ this.matcher = matcher;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ Set<TypeDescription> duplicates = new HashSet<TypeDescription>();
+ for (TypeDefinition typeDefinition : target.getDeclaringType()) {
+ if (matches(target, typeDefinition) || matches(target, typeDefinition.getInterfaces(), duplicates)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Matches a method against a list of types.
+ *
+ * @param target The method that is matched as a target.
+ * @param typeDefinitions The type definitions to check if they declare a method with the same signature as {@code target}.
+ * @param duplicates A set containing duplicate interfaces that do not need to be revisited.
+ * @return {@code true} if any type defines a method with the same signature as the {@code target} method.
+ */
+ private boolean matches(MethodDescription target, List<? extends TypeDefinition> typeDefinitions, Set<TypeDescription> duplicates) {
+ for (TypeDefinition anInterface : typeDefinitions) {
+ if (duplicates.add(anInterface.asErasure()) && (matches(target, anInterface) || matches(target, anInterface.getInterfaces(), duplicates))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks if a type declares a method with the same signature as {@code target}.
+ *
+ * @param target The method to be checked.
+ * @param typeDefinition The type to check for declaring a method with the same signature as {@code target}.
+ * @return {@code true} if the supplied type declares a compatible method.
+ */
+ private boolean matches(MethodDescription target, TypeDefinition typeDefinition) {
+ for (MethodDescription methodDescription : typeDefinition.getDeclaredMethods().filter(isVirtual())) {
+ if (methodDescription.asSignatureToken().equals(target.asSignatureToken())) {
+ if (matcher.matches(typeDefinition.asGenericType())) {
+ return true;
+ } else {
+ break;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "isOverriddenFrom(" + matcher + ")";
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/MethodParameterTypeMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/MethodParameterTypeMatcher.java
new file mode 100644
index 0000000..91f9112
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/MethodParameterTypeMatcher.java
@@ -0,0 +1,38 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.type.TypeDescription;
+
+/**
+ * An element matcher that matches a method's parameter's type.
+ *
+ * @param <T> The type of the matched entity.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class MethodParameterTypeMatcher<T extends ParameterDescription> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The matcher to apply to the type of the parameter.
+ */
+ private final ElementMatcher<? super TypeDescription.Generic> matcher;
+
+ /**
+ * Creates a new matcher for a method's parameter's type.
+ *
+ * @param matcher The matcher to apply to the type of the parameter.
+ */
+ public MethodParameterTypeMatcher(ElementMatcher<? super TypeDescription.Generic> matcher) {
+ this.matcher = matcher;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ return matcher.matches(target.getType());
+ }
+
+ @Override
+ public String toString() {
+ return "hasType(" + matcher + ")";
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/MethodParameterTypesMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/MethodParameterTypesMatcher.java
new file mode 100644
index 0000000..2609bb2
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/MethodParameterTypesMatcher.java
@@ -0,0 +1,40 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.TypeDescription;
+
+import java.util.List;
+
+/**
+ * An element matcher that matches a method's parameter types.
+ *
+ * @param <T> The type of the matched entity.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class MethodParameterTypesMatcher<T extends ParameterList<?>> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The matcher to apply to the type of the parameter.
+ */
+ private final ElementMatcher<? super List<? extends TypeDescription.Generic>> matcher;
+
+ /**
+ * Creates a new matcher for a method's parameter types.
+ *
+ * @param matcher The matcher to apply to the type of the parameter.
+ */
+ public MethodParameterTypesMatcher(ElementMatcher<? super List<? extends TypeDescription.Generic>> matcher) {
+ this.matcher = matcher;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ return matcher.matches(target.asTypeList());
+ }
+
+ @Override
+ public String toString() {
+ return "hasTypes(" + matcher + ")";
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/MethodParametersMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/MethodParametersMatcher.java
new file mode 100644
index 0000000..0774819
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/MethodParametersMatcher.java
@@ -0,0 +1,39 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+
+/**
+ * An element matcher that matches a method's parameters.
+ *
+ * @param <T> The type of the matched entity.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class MethodParametersMatcher<T extends MethodDescription> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The matcher to apply to the parameters.
+ */
+ private final ElementMatcher<? super ParameterList<?>> matcher;
+
+ /**
+ * Creates a new matcher for a method's parameters.
+ *
+ * @param matcher The matcher to apply to the parameters.
+ */
+ public MethodParametersMatcher(ElementMatcher<? super ParameterList<? extends ParameterDescription>> matcher) {
+ this.matcher = matcher;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ return matcher.matches(target.getParameters());
+ }
+
+ @Override
+ public String toString() {
+ return "hasParameter(" + matcher + ")";
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/MethodReturnTypeMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/MethodReturnTypeMatcher.java
new file mode 100644
index 0000000..e0c4965
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/MethodReturnTypeMatcher.java
@@ -0,0 +1,38 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+
+/**
+ * An element matcher that matches its argument's return type against a given type matcher.
+ *
+ * @param <T> The type of the matched entity.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class MethodReturnTypeMatcher<T extends MethodDescription> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The type matcher to apply to the matched element's return type.
+ */
+ private final ElementMatcher<? super TypeDescription.Generic> matcher;
+
+ /**
+ * Creates a new matcher for a matched element's return type.
+ *
+ * @param matcher The type matcher to apply to the matched element's return type.
+ */
+ public MethodReturnTypeMatcher(ElementMatcher<? super TypeDescription.Generic> matcher) {
+ this.matcher = matcher;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ return matcher.matches(target.getReturnType());
+ }
+
+ @Override
+ public String toString() {
+ return "returns(" + matcher + ")";
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/MethodSortMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/MethodSortMatcher.java
new file mode 100644
index 0000000..03bcfce
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/MethodSortMatcher.java
@@ -0,0 +1,131 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.method.MethodDescription;
+
+/**
+ * Matches a method description by its general characteristics which are represented as a
+ * {@link net.bytebuddy.matcher.MethodSortMatcher.Sort}.
+ *
+ * @param <T> The type of the matched entity.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class MethodSortMatcher<T extends MethodDescription> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The sort of method description to be matched by this element matcher.
+ */
+ private final Sort sort;
+
+ /**
+ * Creates a new element matcher that matches a specific sort of method description.
+ *
+ * @param sort The sort of method description to be matched by this element matcher.
+ */
+ public MethodSortMatcher(Sort sort) {
+ this.sort = sort;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ return sort.isSort(target);
+ }
+
+ @Override
+ public String toString() {
+ return sort.getDescription();
+ }
+
+ /**
+ * Represents a specific characteristic of a method description.
+ */
+ public enum Sort {
+
+ /**
+ * Matches method descriptions that represent methods, not constructors or the type initializer.
+ */
+ METHOD("isMethod()") {
+ @Override
+ protected boolean isSort(MethodDescription target) {
+ return target.isMethod();
+ }
+ },
+
+
+ /**
+ * Matches method descriptions that represent constructors, not methods or the type initializer.
+ */
+ CONSTRUCTOR("isConstructor()") {
+ @Override
+ protected boolean isSort(MethodDescription target) {
+ return target.isConstructor();
+ }
+ },
+
+ /**
+ * Matches method descriptions that represent the type initializers.
+ */
+ TYPE_INITIALIZER("isTypeInitializer()") {
+ @Override
+ protected boolean isSort(MethodDescription target) {
+ return target.isTypeInitializer();
+ }
+ },
+
+ /**
+ * Matches method descriptions that are overridable.
+ */
+ VIRTUAL("isVirtual()") {
+ @Override
+ protected boolean isSort(MethodDescription target) {
+ return target.isVirtual();
+ }
+ },
+
+ /**
+ * Matches method descriptions that represent Java 8 default methods.
+ */
+ DEFAULT_METHOD("isDefaultMethod()") {
+ @Override
+ protected boolean isSort(MethodDescription target) {
+ return target.isDefaultMethod();
+ }
+ };
+
+ /**
+ * A textual representation of the method sort that is represented by this instance.
+ */
+ private final String description;
+
+ /**
+ * Creates a new method sort representation.
+ *
+ * @param description A textual representation of the method sort that is represented by this instance.
+ */
+ Sort(String description) {
+ this.description = description;
+ }
+
+ /**
+ * Determines if a method description is of the represented method sort.
+ *
+ * @param target A textual representation of the method sort that is represented by this instance.
+ * @return {@code true} if the given method if of the method sort that is represented by this instance.
+ */
+ protected abstract boolean isSort(MethodDescription target);
+
+ /**
+ * Returns a textual representation of this instance's method sort.
+ *
+ * @return A textual representation of this instance's method sort.
+ */
+ protected String getDescription() {
+ return description;
+ }
+
+ @Override
+ public String toString() {
+ return "MethodSortMatcher.Sort." + name();
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/ModifierMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/ModifierMatcher.java
new file mode 100644
index 0000000..636a554
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/ModifierMatcher.java
@@ -0,0 +1,178 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.ModifierReviewable;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * An element matcher that matches a byte code element by its modifiers.
+ *
+ * @param <T> The type of the matched entity.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class ModifierMatcher<T extends ModifierReviewable> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The matching mode to apply by this modifier matcher.
+ */
+ private final Mode mode;
+
+ /**
+ * Creates a new element matcher that matches an element by its modifier.
+ *
+ * @param mode The match mode to apply to the matched element's modifier.
+ */
+ public ModifierMatcher(Mode mode) {
+ this.mode = mode;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ return (mode.getModifiers() & target.getModifiers()) != 0;
+ }
+
+ @Override
+ public String toString() {
+ return mode.getDescription();
+ }
+
+ /**
+ * Determines the type of modifier to be matched by a {@link net.bytebuddy.matcher.ModifierMatcher}.
+ */
+ public enum Mode {
+
+ /**
+ * Matches an element that is considered {@code public}.
+ */
+ PUBLIC(Opcodes.ACC_PUBLIC, "isPublic()"),
+
+ /**
+ * Matches an element that is considered {@code protected}.
+ */
+ PROTECTED(Opcodes.ACC_PROTECTED, "isProtected()"),
+
+ /**
+ * Matches an element that is considered {@code private}.
+ */
+ PRIVATE(Opcodes.ACC_PRIVATE, "isPrivate()"),
+
+ /**
+ * Matches an element that is considered {@code final}.
+ */
+ FINAL(Opcodes.ACC_FINAL, "isFinal()"),
+
+ /**
+ * Matches an element that is considered {@code static}.
+ */
+ STATIC(Opcodes.ACC_STATIC, "isStatic()"),
+
+ /**
+ * Matches an element that is considered {@code synchronized}.
+ */
+ SYNCHRONIZED(Opcodes.ACC_SYNCHRONIZED, "isSynchronized()"),
+
+ /**
+ * Matches an element that is considered {@code native}.
+ */
+ NATIVE(Opcodes.ACC_NATIVE, "isNative()"),
+
+ /**
+ * Matches an element that is considered {@code strict}.
+ */
+ STRICT(Opcodes.ACC_STRICT, "isStrict()"),
+
+ /**
+ * Matches an element that is considered to be varargs.
+ */
+ VAR_ARGS(Opcodes.ACC_VARARGS, "isVarArgs()"),
+
+ /**
+ * Matches an element that is considered {@code synthetic}.
+ */
+ SYNTHETIC(Opcodes.ACC_SYNTHETIC, "isSynthetic()"),
+
+ /**
+ * Matches an element that is considered a bridge method.
+ */
+ BRIDGE(Opcodes.ACC_BRIDGE, "isBridge()"),
+
+ /**
+ * Matches an element that is considered {@code abstract}.
+ */
+ ABSTRACT(Opcodes.ACC_ABSTRACT, "isAbstract()"),
+
+ /**
+ * Matches a type that is considered an interface.
+ */
+ INTERFACE(Opcodes.ACC_INTERFACE, "isInterface()"),
+
+ /**
+ * Matches a type that is considered an annotation.
+ */
+ ANNOTATION(Opcodes.ACC_ANNOTATION, "isAnnotation()"),
+
+ /**
+ * Matches a volatile field.
+ */
+ VOLATILE(Opcodes.ACC_VOLATILE, "isVolatile()"),
+
+ /**
+ * Matches a transient field.
+ */
+ TRANSIENT(Opcodes.ACC_TRANSIENT, "isTransient()"),
+
+ /**
+ * Matches a mandated parameter.
+ */
+ MANDATED(Opcodes.ACC_MANDATED, "isMandated()"),
+
+ /**
+ * Matches a type or field for describing an enumeration.
+ */
+ ENUMERATION(Opcodes.ACC_ENUM, "isEnum()");
+
+ /**
+ * The mask of the modifier to match.
+ */
+ private final int modifiers;
+
+ /**
+ * The textual representation of this instance's matching mode.
+ */
+ private final String description;
+
+ /**
+ * Creates a new modifier matcher mode.
+ *
+ * @param modifiers The mask of the modifier to match.
+ * @param description The textual representation of this instance's matching mode.
+ */
+ Mode(int modifiers, String description) {
+ this.modifiers = modifiers;
+ this.description = description;
+ }
+
+ /**
+ * Returns the textual description of this mode.
+ *
+ * @return The textual description of this mode.
+ */
+ protected String getDescription() {
+ return description;
+ }
+
+ /**
+ * Returns the modifiers to match by this mode.
+ *
+ * @return The modifiers to match by this mode.
+ */
+ protected int getModifiers() {
+ return modifiers;
+ }
+
+ @Override
+ public String toString() {
+ return "ModifierMatcher.Mode." + name();
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/NameMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/NameMatcher.java
new file mode 100644
index 0000000..004bf4b
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/NameMatcher.java
@@ -0,0 +1,44 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.NamedElement;
+
+/**
+ * A method matcher that matches a byte code element's source code name:
+ * <ul>
+ * <li>The source code name of types is equal to their binary name where arrays are appended a {@code []} by
+ * their arity and where inner classes are appended by dots to their outer class's source name.</li>
+ * <li>Constructors and the type initializer methods are represented by the empty string as they do not
+ * represent a source code name.</li>
+ * <li>Fields are named as in the source code.</li>
+ * </ul>
+ *
+ * @param <T> The type of the matched entity.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class NameMatcher<T extends NamedElement> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The matcher that is applied to a byte code element's source code name.
+ */
+ private final ElementMatcher<String> matcher;
+
+ /**
+ * Creates a new matcher for a byte code element's source name.
+ *
+ * @param matcher The matcher that is applied to a byte code element's source code name.
+ */
+ public NameMatcher(ElementMatcher<String> matcher) {
+ this.matcher = matcher;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ return matcher.matches(target.getActualName());
+ }
+
+ @Override
+ public String toString() {
+ return "name(" + matcher + ")";
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/NegatingMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/NegatingMatcher.java
new file mode 100644
index 0000000..7912b7c
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/NegatingMatcher.java
@@ -0,0 +1,36 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+
+/**
+ * An element matcher that reverses the matching result of another matcher.
+ *
+ * @param <T> The type of the matched entity.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class NegatingMatcher<T> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The element matcher to be negated.
+ */
+ private final ElementMatcher<? super T> matcher;
+
+ /**
+ * Creates a new negating element matcher.
+ *
+ * @param matcher The element matcher to be negated.
+ */
+ public NegatingMatcher(ElementMatcher<? super T> matcher) {
+ this.matcher = matcher;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ return !matcher.matches(target);
+ }
+
+ @Override
+ public String toString() {
+ return "not(" + matcher + ')';
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/NullMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/NullMatcher.java
new file mode 100644
index 0000000..f91b6d4
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/NullMatcher.java
@@ -0,0 +1,22 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+
+/**
+ * An element matcher that matches the {@code null} value.
+ *
+ * @param <T> The type of the matched entity.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class NullMatcher<T> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ @Override
+ public boolean matches(T target) {
+ return target == null;
+ }
+
+ @Override
+ public String toString() {
+ return "isNull()";
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/SignatureTokenMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/SignatureTokenMatcher.java
new file mode 100644
index 0000000..f3f7370
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/SignatureTokenMatcher.java
@@ -0,0 +1,37 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.method.MethodDescription;
+
+/**
+ * Matches a method description's signature token against another matcher.
+ *
+ * @param <T> The type of the matched entity.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class SignatureTokenMatcher<T extends MethodDescription> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The matcher to match the signature token against.
+ */
+ private final ElementMatcher<? super MethodDescription.SignatureToken> matcher;
+
+ /**
+ * Creates a new signature token matcher.
+ *
+ * @param matcher The matcher to match the signature token against.
+ */
+ public SignatureTokenMatcher(ElementMatcher<? super MethodDescription.SignatureToken> matcher) {
+ this.matcher = matcher;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ return matcher.matches(target.asSignatureToken());
+ }
+
+ @Override
+ public String toString() {
+ return "signature(" + matcher + ")";
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/StringMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/StringMatcher.java
new file mode 100644
index 0000000..538d6f2
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/StringMatcher.java
@@ -0,0 +1,176 @@
+package net.bytebuddy.matcher;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+
+/**
+ * An element matcher that compares two strings by a given pattern which is characterized by a
+ * {@link net.bytebuddy.matcher.StringMatcher.Mode}.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class StringMatcher extends ElementMatcher.Junction.AbstractBase<String> {
+
+ /**
+ * The text value to match against.
+ */
+ private final String value;
+
+ /**
+ * The mode to apply for matching the given value against the matcher's input.
+ */
+ private final Mode mode;
+
+ /**
+ * Creates a new string matcher.
+ *
+ * @param value The value that is the base of the matching.
+ * @param mode The mode to apply for matching the given value against the matcher's input
+ */
+ public StringMatcher(String value, Mode mode) {
+ this.value = value;
+ this.mode = mode;
+ }
+
+ @Override
+ public boolean matches(String target) {
+ return mode.matches(value, target);
+ }
+
+ @Override
+ public String toString() {
+ return mode.getDescription() + '(' + value + ')';
+ }
+
+ /**
+ * Defines the mode a {@link net.bytebuddy.matcher.StringMatcher} compares to strings with.
+ */
+ public enum Mode {
+
+ /**
+ * Checks if two strings equal and respects casing differences.
+ */
+ EQUALS_FULLY("equals") {
+ @Override
+ protected boolean matches(String expected, String actual) {
+ return actual.equals(expected);
+ }
+ },
+
+ /**
+ * Checks if two strings equal without respecting casing differences.
+ */
+ EQUALS_FULLY_IGNORE_CASE("equalsIgnoreCase") {
+ @Override
+ protected boolean matches(String expected, String actual) {
+ return actual.equalsIgnoreCase(expected);
+ }
+ },
+
+ /**
+ * Checks if a string starts with the a second string with respecting casing differences.
+ */
+ STARTS_WITH("startsWith") {
+ @Override
+ protected boolean matches(String expected, String actual) {
+ return actual.startsWith(expected);
+ }
+ },
+
+ /**
+ * Checks if a string starts with a second string without respecting casing differences.
+ */
+ STARTS_WITH_IGNORE_CASE("startsWithIgnoreCase") {
+ @Override
+ @SuppressFBWarnings(value = "DM_CONVERT_CASE", justification = "Both strings are transformed by the default locale")
+ protected boolean matches(String expected, String actual) {
+ return actual.toLowerCase().startsWith(expected.toLowerCase());
+ }
+ },
+
+ /**
+ * Checks if a string ends with a second string with respecting casing differences.
+ */
+ ENDS_WITH("endsWith") {
+ @Override
+ protected boolean matches(String expected, String actual) {
+ return actual.endsWith(expected);
+ }
+ },
+
+ /**
+ * Checks if a string ends with a second string without respecting casing differences.
+ */
+ ENDS_WITH_IGNORE_CASE("endsWithIgnoreCase") {
+ @Override
+ @SuppressFBWarnings(value = "DM_CONVERT_CASE", justification = "Both strings are transformed by the default locale")
+ protected boolean matches(String expected, String actual) {
+ return actual.toLowerCase().endsWith(expected.toLowerCase());
+ }
+ },
+
+ /**
+ * Checks if a string contains another string with respecting casing differences.
+ */
+ CONTAINS("contains") {
+ @Override
+ protected boolean matches(String expected, String actual) {
+ return actual.contains(expected);
+ }
+ },
+
+ /**
+ * Checks if a string contains another string without respecting casing differences.
+ */
+ CONTAINS_IGNORE_CASE("containsIgnoreCase") {
+ @Override
+ @SuppressFBWarnings(value = "DM_CONVERT_CASE", justification = "Both strings are transformed by the default locale")
+ protected boolean matches(String expected, String actual) {
+ return actual.toLowerCase().contains(expected.toLowerCase());
+ }
+ },
+
+ /**
+ * Checks if a string can be matched by a regular expression.
+ */
+ MATCHES("matches") {
+ @Override
+ protected boolean matches(String expected, String actual) {
+ return actual.matches(expected);
+ }
+ };
+
+ /**
+ * A description of the string for providing meaningful {@link Object#toString()} implementations for
+ * method matchers that rely on a match mode.
+ */
+ private final String description;
+
+ /**
+ * Creates a new match mode.
+ *
+ * @param description The description of this mode for providing meaningful {@link Object#toString()}
+ * implementations.
+ */
+ Mode(String description) {
+ this.description = description;
+ }
+
+ /**
+ * Returns the description of this match mode.
+ *
+ * @return The description of this match mode.
+ */
+ protected String getDescription() {
+ return description;
+ }
+
+ /**
+ * Matches a string against another string.
+ *
+ * @param expected The target of the comparison against which the {@code actual} string is compared.
+ * @param actual The source which is subject of the comparison to the {@code expected} value.
+ * @return {@code true} if the source matches the target.
+ */
+ protected abstract boolean matches(String expected, String actual);
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/SubTypeMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/SubTypeMatcher.java
new file mode 100644
index 0000000..a3a3dd0
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/SubTypeMatcher.java
@@ -0,0 +1,38 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.type.TypeDescription;
+
+/**
+ * An element matcher that matches its argument for being another type's subtype.
+ *
+ * @param <T> The type of the matched entity.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class SubTypeMatcher<T extends TypeDescription> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The type to be matched being a super type of the matched type.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new matcher for matching its input for being a sub type of the given {@code typeDescription}.
+ *
+ * @param typeDescription The type to be matched being a super type of the matched type.
+ */
+ public SubTypeMatcher(TypeDescription typeDescription) {
+ this.typeDescription = typeDescription;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ return target.isAssignableTo(typeDescription);
+ }
+
+ @Override
+ public String toString() {
+ return "isSubTypeOf(" + typeDescription + ')';
+ }
+}
+
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/SuperTypeMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/SuperTypeMatcher.java
new file mode 100644
index 0000000..384d6b7
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/SuperTypeMatcher.java
@@ -0,0 +1,37 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.type.TypeDescription;
+
+/**
+ * An element matcher that matches its argument for being another type's super type.
+ *
+ * @param <T> The type of the matched entity.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class SuperTypeMatcher<T extends TypeDescription> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The type to be matched being a sub type of the matched type.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new matcher for matching its input for being a super type of the given {@code typeDescription}.
+ *
+ * @param typeDescription The type to be matched being a sub type of the matched type.
+ */
+ public SuperTypeMatcher(TypeDescription typeDescription) {
+ this.typeDescription = typeDescription;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ return target.isAssignableFrom(typeDescription);
+ }
+
+ @Override
+ public String toString() {
+ return "isSuperTypeOf(" + typeDescription + ')';
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/TypeSortMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/TypeSortMatcher.java
new file mode 100644
index 0000000..31c5698
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/TypeSortMatcher.java
@@ -0,0 +1,37 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.type.TypeDefinition;
+
+/**
+ * An element matcher that validates that a given generic type description represents a type of a given name.
+ *
+ * @param <T> The type of the matched entity.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class TypeSortMatcher<T extends TypeDefinition> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * An element matcher to be applied to the type's sort.
+ */
+ private final ElementMatcher<? super TypeDefinition.Sort> matcher;
+
+ /**
+ * Creates a new type sort matcher.
+ *
+ * @param matcher An element matcher to be applied to the type's sort.
+ */
+ public TypeSortMatcher(ElementMatcher<? super TypeDefinition.Sort> matcher) {
+ this.matcher = matcher;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ return matcher.matches(target.getSort());
+ }
+
+ @Override
+ public String toString() {
+ return "ofSort(" + matcher + ')';
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/VisibilityMatcher.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/VisibilityMatcher.java
new file mode 100644
index 0000000..1659376
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/VisibilityMatcher.java
@@ -0,0 +1,38 @@
+package net.bytebuddy.matcher;
+
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.ByteCodeElement;
+import net.bytebuddy.description.type.TypeDescription;
+
+/**
+ * An element matcher that validates that a given byte code element is visible to a given type.
+ *
+ * @param <T>The type of the matched entity.
+ */
+ at EqualsAndHashCode(callSuper = false)
+public class VisibilityMatcher<T extends ByteCodeElement> extends ElementMatcher.Junction.AbstractBase<T> {
+
+ /**
+ * The type that is to be checked for its viewing rights.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a matcher that validates that a byte code element can be seen by a given type.
+ *
+ * @param typeDescription The type that is to be checked for its viewing rights.
+ */
+ public VisibilityMatcher(TypeDescription typeDescription) {
+ this.typeDescription = typeDescription;
+ }
+
+ @Override
+ public boolean matches(T target) {
+ return target.isVisibleTo(typeDescription);
+ }
+
+ @Override
+ public String toString() {
+ return "isVisibleTo(" + typeDescription + ")";
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/package-info.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/package-info.java
new file mode 100644
index 0000000..0422929
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Contains an API for matching Java byte code entities.
+ */
+package net.bytebuddy.matcher;
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/package-info.java b/byte-buddy-dep/src/main/java/net/bytebuddy/package-info.java
new file mode 100644
index 0000000..7530088
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/package-info.java
@@ -0,0 +1,16 @@
+/**
+ * Byte Buddy is a library for creating Java classes at runtime of a Java program. For this purpose, the
+ * {@link net.bytebuddy.ByteBuddy} class serves as an entry point. The following example
+ * <pre>
+ * Class<?> dynamicType = new ByteBuddy()
+ * .subclass(Object.class)
+ * .implement(Serializable.class)
+ * .intercept(named("toString"), FixedValue.value("Hello World!"))
+ * .make()
+ * .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ * .getLoaded();
+ * dynamicType.newInstance().toString; // returns "Hello World!"</pre>
+ * creates a subclass of the {@link java.lang.Object} class which implements the {@link java.io.Serializable}
+ * interface. The {@link java.lang.Object#toString()} method is overriden to return {@code Hello World!}.
+ */
+package net.bytebuddy;
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/pool/TypePool.java b/byte-buddy-dep/src/main/java/net/bytebuddy/pool/TypePool.java
new file mode 100644
index 0000000..3fdb16a
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/pool/TypePool.java
@@ -0,0 +1,7785 @@
+package net.bytebuddy.pool;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.TypeVariableSource;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.annotation.AnnotationValue;
+import net.bytebuddy.description.enumeration.EnumerationDescription;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.PackageDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import org.objectweb.asm.*;
+import org.objectweb.asm.signature.SignatureReader;
+import org.objectweb.asm.signature.SignatureVisitor;
+
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
+import java.lang.reflect.GenericSignatureFormatError;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import static net.bytebuddy.matcher.ElementMatchers.*;
+
+/**
+ * A type pool allows the retrieval of {@link TypeDescription} by its name.
+ */
+public interface TypePool {
+
+ /**
+ * Locates and describes the given type by its name.
+ *
+ * @param name The name of the type to describe. The name is to be written as when calling {@link Object#toString()}
+ * on a loaded {@link java.lang.Class}.
+ * @return A resolution of the type to describe. If the type to be described was found, the returned
+ * {@link net.bytebuddy.pool.TypePool.Resolution} represents this type. Otherwise, an illegal resolution is returned.
+ */
+ Resolution describe(String name);
+
+ /**
+ * Clears this type pool's cache.
+ */
+ void clear();
+
+ /**
+ * A resolution of a {@link net.bytebuddy.pool.TypePool} which was queried for a description.
+ */
+ interface Resolution {
+
+ /**
+ * Determines if this resolution represents a fully-resolved {@link TypeDescription}.
+ *
+ * @return {@code true} if the queried type could be resolved.
+ */
+ boolean isResolved();
+
+ /**
+ * Resolves this resolution to a {@link TypeDescription}. If this resolution is unresolved, this
+ * method throws an exception either upon invoking this method or upon invoking at least one method
+ * of the returned type description.
+ *
+ * @return The type description that is represented by this resolution.
+ */
+ TypeDescription resolve();
+
+ /**
+ * A simple resolution that represents a given {@link TypeDescription}.
+ */
+ @EqualsAndHashCode
+ class Simple implements Resolution {
+
+ /**
+ * The represented type description.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new successful resolution of a given type description.
+ *
+ * @param typeDescription The represented type description.
+ */
+ public Simple(TypeDescription typeDescription) {
+ this.typeDescription = typeDescription;
+ }
+
+ @Override
+ public boolean isResolved() {
+ return true;
+ }
+
+ @Override
+ public TypeDescription resolve() {
+ return typeDescription;
+ }
+ }
+
+ /**
+ * A canonical representation of a non-successful resolution of a {@link net.bytebuddy.pool.TypePool}.
+ */
+ @EqualsAndHashCode
+ class Illegal implements Resolution {
+
+ /**
+ * The name of the unresolved type.
+ */
+ private final String name;
+
+ /**
+ * Creates a new illegal resolution.
+ *
+ * @param name The name of the unresolved type.
+ */
+ public Illegal(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public boolean isResolved() {
+ return false;
+ }
+
+ @Override
+ public TypeDescription resolve() {
+ throw new IllegalStateException("Cannot resolve type description for " + name);
+ }
+ }
+ }
+
+ /**
+ * A cache provider for a {@link net.bytebuddy.pool.TypePool}.
+ */
+ interface CacheProvider {
+
+ /**
+ * The value that is returned on a cache-miss.
+ */
+ Resolution UNRESOLVED = null;
+
+ /**
+ * Attempts to find a resolution in this cache.
+ *
+ * @param name The name of the type to describe.
+ * @return A resolution of the type or {@code null} if no such resolution can be found in the cache..
+ */
+ Resolution find(String name);
+
+ /**
+ * Registers a resolution in this cache. If a resolution to the given name already exists in the
+ * cache, it should be discarded.
+ *
+ * @param name The name of the type that is to be registered.
+ * @param resolution The resolution to register.
+ * @return The oldest version of a resolution that is currently registered in the cache which might
+ * be the given resolution or another resolution that was previously registered.
+ */
+ Resolution register(String name, Resolution resolution);
+
+ /**
+ * Clears this cache.
+ */
+ void clear();
+
+ /**
+ * A non-operational cache that does not store any type descriptions.
+ */
+ enum NoOp implements CacheProvider {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Resolution find(String name) {
+ return UNRESOLVED;
+ }
+
+ @Override
+ public Resolution register(String name, Resolution resolution) {
+ return resolution;
+ }
+
+ @Override
+ public void clear() {
+ /* do nothing */
+ }
+ }
+
+ /**
+ * A simple, thread-safe type cache based on a {@link java.util.concurrent.ConcurrentHashMap}.
+ */
+ class Simple implements CacheProvider {
+
+ /**
+ * A map containing all cached resolutions by their names.
+ */
+ private final ConcurrentMap<String, Resolution> cache;
+
+ /**
+ * Creates a new simple cache.
+ */
+ public Simple() {
+ cache = new ConcurrentHashMap<String, Resolution>();
+ }
+
+ /**
+ * Returns a simple cache provider that is prepopulated with the {@link Object} type.
+ *
+ * @return A simple cache provider that is prepopulated with the {@link Object} type.
+ */
+ public static CacheProvider withObjectType() {
+ CacheProvider cacheProvider = new Simple();
+ cacheProvider.register(Object.class.getName(), new Resolution.Simple(TypeDescription.OBJECT));
+ return cacheProvider;
+ }
+
+ @Override
+ public Resolution find(String name) {
+ return cache.get(name);
+ }
+
+ @Override
+ public Resolution register(String name, Resolution resolution) {
+ Resolution cached = cache.putIfAbsent(name, resolution);
+ return cached == null
+ ? resolution
+ : cached;
+ }
+
+ @Override
+ public void clear() {
+ cache.clear();
+ }
+ }
+ }
+
+ /**
+ * An empty type pool that cannot describe any type.
+ */
+ enum Empty implements TypePool {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Resolution describe(String name) {
+ return new Resolution.Illegal(name);
+ }
+
+ @Override
+ public void clear() {
+ /* do nothing */
+ }
+ }
+
+ /**
+ * A base implementation of a {@link net.bytebuddy.pool.TypePool} that is managing a cache provider and
+ * that handles the description of array and primitive types.
+ */
+ @EqualsAndHashCode
+ abstract class AbstractBase implements TypePool {
+
+ /**
+ * A map of primitive types by their name.
+ */
+ protected static final Map<String, TypeDescription> PRIMITIVE_TYPES;
+
+ /**
+ * A map of primitive types by their descriptor.
+ */
+ protected static final Map<String, String> PRIMITIVE_DESCRIPTORS;
+
+ /**
+ * The array symbol as used by Java descriptors.
+ */
+ private static final String ARRAY_SYMBOL = "[";
+
+ /*
+ * Initializes the maps of primitive type names and descriptors.
+ */
+ static {
+ Map<String, TypeDescription> primitiveTypes = new HashMap<String, TypeDescription>();
+ Map<String, String> primitiveDescriptors = new HashMap<String, String>();
+ for (Class<?> primitiveType : new Class<?>[]{boolean.class,
+ byte.class,
+ short.class,
+ char.class,
+ int.class,
+ long.class,
+ float.class,
+ double.class,
+ void.class}) {
+ primitiveTypes.put(primitiveType.getName(), new TypeDescription.ForLoadedType(primitiveType));
+ primitiveDescriptors.put(Type.getDescriptor(primitiveType), primitiveType.getName());
+ }
+ PRIMITIVE_TYPES = Collections.unmodifiableMap(primitiveTypes);
+ PRIMITIVE_DESCRIPTORS = Collections.unmodifiableMap(primitiveDescriptors);
+ }
+
+ /**
+ * The cache provider of this instance.
+ */
+ protected final CacheProvider cacheProvider;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param cacheProvider The cache provider to be used.
+ */
+ protected AbstractBase(CacheProvider cacheProvider) {
+ this.cacheProvider = cacheProvider;
+ }
+
+ @Override
+ public Resolution describe(String name) {
+ if (name.contains("/")) {
+ throw new IllegalArgumentException(name + " contains the illegal character '/'");
+ }
+ int arity = 0;
+ while (name.startsWith(ARRAY_SYMBOL)) {
+ arity++;
+ name = name.substring(1);
+ }
+ if (arity > 0) {
+ String primitiveName = PRIMITIVE_DESCRIPTORS.get(name);
+ name = primitiveName == null
+ ? name.substring(1, name.length() - 1)
+ : primitiveName;
+ }
+ TypeDescription typeDescription = PRIMITIVE_TYPES.get(name);
+ Resolution resolution = typeDescription == null
+ ? cacheProvider.find(name)
+ : new Resolution.Simple(typeDescription);
+ if (resolution == null) {
+ resolution = doCache(name, doDescribe(name));
+ }
+ return ArrayTypeResolution.of(resolution, arity);
+ }
+
+ /**
+ * Writes the resolution to the cache. This method should be overridden if the directly
+ * resolved instance should not be added to the cache.
+ *
+ * @param name The name of the type.
+ * @param resolution The resolution for this type.
+ * @return The actual resolution for the type of this name that is stored in the cache.
+ */
+ protected Resolution doCache(String name, Resolution resolution) {
+ return cacheProvider.register(name, resolution);
+ }
+
+ @Override
+ public void clear() {
+ cacheProvider.clear();
+ }
+
+ /**
+ * Determines a resolution to a non-primitive, non-array type.
+ *
+ * @param name The name of the type to describe.
+ * @return A resolution to the type to describe.
+ */
+ protected abstract Resolution doDescribe(String name);
+
+ /**
+ * Implements a hierarchical view of type pools, similarly to class loader hierarchies. For every lookup, the parent type pool
+ * is asked first if it can resolve a type. Only if the parent (and potentially its parents) are unable to resolve a type,
+ * this instance is queried for a type description.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ public abstract static class Hierarchical extends AbstractBase {
+
+ /**
+ * The parent type pool.
+ */
+ private final TypePool parent;
+
+ /**
+ * Creates a hierarchical type pool.
+ *
+ * @param cacheProvider The cache provider to be used.
+ * @param parent The parent type pool to be used.
+ */
+ protected Hierarchical(CacheProvider cacheProvider, TypePool parent) {
+ super(cacheProvider);
+ this.parent = parent;
+ }
+
+ @Override
+ public Resolution describe(String name) {
+ Resolution resolution = parent.describe(name);
+ return resolution.isResolved()
+ ? resolution
+ : super.describe(name);
+ }
+
+ @Override
+ public void clear() {
+ try {
+ parent.clear();
+ } finally {
+ super.clear();
+ }
+ }
+ }
+
+ /**
+ * A resolution for a type that, if resolved, represents an array type.
+ */
+ @EqualsAndHashCode
+ protected static class ArrayTypeResolution implements Resolution {
+
+ /**
+ * The underlying resolution that is represented by this instance.
+ */
+ private final Resolution resolution;
+
+ /**
+ * The arity of the represented array.
+ */
+ private final int arity;
+
+ /**
+ * Creates a wrapper for another resolution that, if resolved, represents an array type.
+ *
+ * @param resolution The underlying resolution that is represented by this instance.
+ * @param arity The arity of the represented array.
+ */
+ protected ArrayTypeResolution(Resolution resolution, int arity) {
+ this.resolution = resolution;
+ this.arity = arity;
+ }
+
+ /**
+ * Creates a wrapper for another resolution that, if resolved, represents an array type. The wrapper
+ * is only created if the arity is not zero. If the arity is zero, the given resolution is simply
+ * returned instead.
+ *
+ * @param resolution The underlying resolution that is represented by this instance.
+ * @param arity The arity of the represented array.
+ * @return A wrapper for another resolution that, if resolved, represents an array type or the
+ * given resolution if the given arity is zero.
+ */
+ protected static Resolution of(Resolution resolution, int arity) {
+ return arity == 0
+ ? resolution
+ : new ArrayTypeResolution(resolution, arity);
+ }
+
+ @Override
+ public boolean isResolved() {
+ return resolution.isResolved();
+ }
+
+ @Override
+ public TypeDescription resolve() {
+ return TypeDescription.ArrayProjection.of(resolution.resolve(), arity);
+ }
+ }
+
+ /**
+ * Represents a nested annotation value.
+ */
+ protected static class RawAnnotationValue extends AnnotationValue.AbstractBase<AnnotationDescription, Annotation> {
+
+ /**
+ * The type pool to use for looking up types.
+ */
+ private final TypePool typePool;
+
+ /**
+ * The annotation token that represents the nested invocation.
+ */
+ private final Default.LazyTypeDescription.AnnotationToken annotationToken;
+
+ /**
+ * Creates a new annotation value for a nested annotation.
+ *
+ * @param typePool The type pool to use for looking up types.
+ * @param annotationToken The token that represents the annotation.
+ */
+ public RawAnnotationValue(TypePool typePool, Default.LazyTypeDescription.AnnotationToken annotationToken) {
+ this.typePool = typePool;
+ this.annotationToken = annotationToken;
+ }
+
+ @Override
+ public AnnotationDescription resolve() {
+ return annotationToken.toAnnotationDescription(typePool).resolve();
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Loaded<Annotation> load(ClassLoader classLoader) throws ClassNotFoundException {
+ Class<?> type = Class.forName(annotationToken.getBinaryName(), false, classLoader);
+ if (type.isAnnotation()) {
+ return new ForAnnotationDescription.Loaded<Annotation>(AnnotationDescription.AnnotationInvocationHandler.of(classLoader,
+ (Class<? extends Annotation>) type,
+ annotationToken.getValues()));
+ } else {
+ return new ForAnnotationDescription.IncompatibleRuntimeType(type);
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return this == other || (other instanceof AnnotationValue<?, ?> && resolve().equals(((AnnotationValue<?, ?>) other).resolve()));
+ }
+
+ @Override
+ public int hashCode() {
+ return resolve().hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return resolve().toString();
+ }
+ }
+
+ /**
+ * Represents an enumeration value of an annotation.
+ */
+ protected static class RawEnumerationValue extends AnnotationValue.AbstractBase<EnumerationDescription, Enum<?>> {
+
+ /**
+ * The type pool to use for looking up types.
+ */
+ private final TypePool typePool;
+
+ /**
+ * The descriptor of the enumeration type.
+ */
+ private final String descriptor;
+
+ /**
+ * The name of the enumeration.
+ */
+ private final String value;
+
+ /**
+ * Creates a new enumeration value representation.
+ *
+ * @param typePool The type pool to use for looking up types.
+ * @param descriptor The descriptor of the enumeration type.
+ * @param value The name of the enumeration.
+ */
+ public RawEnumerationValue(TypePool typePool, String descriptor, String value) {
+ this.typePool = typePool;
+ this.descriptor = descriptor;
+ this.value = value;
+ }
+
+ @Override
+ public EnumerationDescription resolve() {
+ return new LazyEnumerationDescription();
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Loaded<Enum<?>> load(ClassLoader classLoader) throws ClassNotFoundException {
+ Class<?> type = Class.forName(descriptor.substring(1, descriptor.length() - 1).replace('/', '.'), false, classLoader);
+ try {
+ return type.isEnum()
+ ? new ForEnumerationDescription.Loaded(Enum.valueOf((Class) type, value))
+ : new ForEnumerationDescription.IncompatibleRuntimeType(type);
+ } catch (IllegalArgumentException ignored) {
+ return new ForEnumerationDescription.UnknownRuntimeEnumeration((Class) type, value);
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return this == other || (other instanceof AnnotationValue<?, ?> && resolve().equals(((AnnotationValue<?, ?>) other).resolve()));
+ }
+
+ @Override
+ public int hashCode() {
+ return resolve().hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return resolve().toString();
+ }
+
+ /**
+ * An enumeration description where any type references are only resolved on demand.
+ */
+ protected class LazyEnumerationDescription extends EnumerationDescription.AbstractBase {
+
+ @Override
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public TypeDescription getEnumerationType() {
+ return typePool.describe(descriptor.substring(1, descriptor.length() - 1).replace('/', '.')).resolve();
+ }
+
+ @Override
+ public <T extends Enum<T>> T load(Class<T> type) {
+ return Enum.valueOf(type, value);
+ }
+ }
+ }
+
+ /**
+ * Represents a type value of an annotation.
+ */
+ protected static class RawTypeValue extends AnnotationValue.AbstractBase<TypeDescription, Class<?>> {
+
+ /**
+ * A convenience reference indicating that a loaded type should not be initialized.
+ */
+ private static final boolean NO_INITIALIZATION = false;
+
+ /**
+ * The type pool to use for looking up types.
+ */
+ private final TypePool typePool;
+
+ /**
+ * The binary name of the type.
+ */
+ private final String name;
+
+ /**
+ * Represents a type value of an annotation.
+ *
+ * @param typePool The type pool to use for looking up types.
+ * @param type A type representation of the type that is referenced by the annotation..
+ */
+ protected RawTypeValue(TypePool typePool, Type type) {
+ this.typePool = typePool;
+ name = type.getSort() == Type.ARRAY
+ ? type.getInternalName().replace('/', '.')
+ : type.getClassName();
+ }
+
+ @Override
+ public TypeDescription resolve() {
+ return typePool.describe(name).resolve();
+ }
+
+ @Override
+ public AnnotationValue.Loaded<Class<?>> load(ClassLoader classLoader) throws ClassNotFoundException {
+ return new Loaded(Class.forName(name, NO_INITIALIZATION, classLoader));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return this == other || (other instanceof AnnotationValue<?, ?> && resolve().equals(((AnnotationValue<?, ?>) other).resolve()));
+ }
+
+ @Override
+ public int hashCode() {
+ return resolve().hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return RenderingDispatcher.CURRENT.toSourceString(resolve());
+ }
+
+ /**
+ * Represents a loaded annotation property that represents a type.
+ */
+ protected static class Loaded extends AnnotationValue.Loaded.AbstractBase<Class<?>> {
+
+ /**
+ * The type that is represented by an annotation property.
+ */
+ private final Class<?> type;
+
+ /**
+ * Creates a new representation for an annotation property referencing a type.
+ *
+ * @param type The type that is represented by an annotation property.
+ */
+ public Loaded(Class<?> type) {
+ this.type = type;
+ }
+
+ @Override
+ public State getState() {
+ return State.RESOLVED;
+ }
+
+ @Override
+ public Class<?> resolve() {
+ return type;
+ }
+
+ @Override
+ public boolean represents(Object value) {
+ return type.equals(value);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (!(other instanceof AnnotationValue.Loaded<?>)) return false;
+ AnnotationValue.Loaded<?> loadedOther = (AnnotationValue.Loaded<?>) other;
+ return loadedOther.getState().isResolved() && type.equals(loadedOther.resolve());
+ }
+
+ @Override
+ public int hashCode() {
+ return type.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return RenderingDispatcher.CURRENT.toSourceString(new TypeDescription.ForLoadedType(type));
+ }
+ }
+ }
+
+ /**
+ * Represents an array that is referenced by an annotation which does not contain primitive values or {@link String}s.
+ */
+ protected static class RawDescriptionArray extends AnnotationValue.AbstractBase<Object[], Object[]> {
+
+ /**
+ * The type pool to use for looking up types.
+ */
+ private final TypePool typePool;
+
+ /**
+ * A reference to the component type.
+ */
+ private final ComponentTypeReference componentTypeReference;
+
+ /**
+ * A list of all values of this array value in their order.
+ */
+ private List<AnnotationValue<?, ?>> values;
+
+ /**
+ * Creates a new array value representation of a complex array.
+ *
+ * @param typePool The type pool to use for looking up types.
+ * @param componentTypeReference A lazy reference to the component type of this array.
+ * @param values A list of all values of this annotation.
+ */
+ public RawDescriptionArray(TypePool typePool,
+ ComponentTypeReference componentTypeReference,
+ List<AnnotationValue<?, ?>> values) {
+ this.typePool = typePool;
+ this.values = values;
+ this.componentTypeReference = componentTypeReference;
+ }
+
+ @Override
+ public Object[] resolve() {
+ TypeDescription componentTypeDescription = typePool.describe(componentTypeReference.lookup()).resolve();
+ Class<?> componentType;
+ if (componentTypeDescription.represents(Class.class)) {
+ componentType = TypeDescription.class;
+ } else if (componentTypeDescription.isAssignableTo(Enum.class)) { // Enums can implement annotation interfaces, check this first.
+ componentType = EnumerationDescription.class;
+ } else if (componentTypeDescription.isAssignableTo(Annotation.class)) {
+ componentType = AnnotationDescription.class;
+ } else if (componentTypeDescription.represents(String.class)) {
+ componentType = String.class;
+ } else {
+ throw new IllegalStateException("Unexpected complex array component type " + componentTypeDescription);
+ }
+ Object[] array = (Object[]) Array.newInstance(componentType, values.size());
+ int index = 0;
+ for (AnnotationValue<?, ?> annotationValue : values) {
+ Array.set(array, index++, annotationValue.resolve());
+ }
+ return array;
+ }
+
+ @Override
+ public AnnotationValue.Loaded<Object[]> load(ClassLoader classLoader) throws ClassNotFoundException {
+ List<AnnotationValue.Loaded<?>> loadedValues = new ArrayList<AnnotationValue.Loaded<?>>(values.size());
+ for (AnnotationValue<?, ?> value : values) {
+ loadedValues.add(value.load(classLoader));
+ }
+ return new Loaded(Class.forName(componentTypeReference.lookup(), false, classLoader), loadedValues);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (!(other instanceof AnnotationValue<?, ?>)) return false;
+ Object value = ((AnnotationValue<?, ?>) other).resolve();
+ return value instanceof Object[] && Arrays.equals(resolve(), (Object[]) value);
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(resolve());
+ }
+
+ @Override
+ public String toString() {
+ return RenderingDispatcher.CURRENT.toSourceString(values);
+ }
+
+ /**
+ * A lazy representation of the component type of an array.
+ */
+ public interface ComponentTypeReference {
+
+ /**
+ * Lazily returns the binary name of the array component type of an annotation value.
+ *
+ * @return The binary name of the component type.
+ */
+ String lookup();
+ }
+
+ /**
+ * Represents a loaded annotation property representing a complex array.
+ */
+ protected static class Loaded extends AnnotationValue.Loaded.AbstractBase<Object[]> {
+
+ /**
+ * The array's loaded component type.
+ */
+ private final Class<?> componentType;
+
+ /**
+ * A list of loaded values of the represented complex array.
+ */
+ private final List<AnnotationValue.Loaded<?>> values;
+
+ /**
+ * Creates a new representation of an annotation property representing an array of
+ * non-trivial values.
+ *
+ * @param componentType The array's loaded component type.
+ * @param values A list of loaded values of the represented complex array.
+ */
+ public Loaded(Class<?> componentType, List<AnnotationValue.Loaded<?>> values) {
+ this.componentType = componentType;
+ this.values = values;
+ }
+
+ @Override
+ public State getState() {
+ for (AnnotationValue.Loaded<?> value : values) {
+ if (!value.getState().isResolved()) {
+ return State.UNRESOLVED;
+ }
+ }
+ return State.RESOLVED;
+ }
+
+ @Override
+ public Object[] resolve() {
+ Object[] array = (Object[]) Array.newInstance(componentType, values.size());
+ int index = 0;
+ for (AnnotationValue.Loaded<?> annotationValue : values) {
+ Array.set(array, index++, annotationValue.resolve());
+ }
+ return array;
+ }
+
+ @Override
+ public boolean represents(Object value) {
+ if (!(value instanceof Object[])) return false;
+ if (value.getClass().getComponentType() != componentType) return false;
+ Object[] array = (Object[]) value;
+ if (values.size() != array.length) return false;
+ Iterator<AnnotationValue.Loaded<?>> iterator = values.iterator();
+ for (Object aValue : array) {
+ AnnotationValue.Loaded<?> self = iterator.next();
+ if (!self.getState().isResolved() || !self.represents(aValue)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (!(other instanceof AnnotationValue.Loaded<?>)) return false;
+ AnnotationValue.Loaded<?> loadedOther = (AnnotationValue.Loaded<?>) other;
+ if (!loadedOther.getState().isResolved()) return false;
+ Object otherValue = loadedOther.resolve();
+ if (!(otherValue instanceof Object[])) return false;
+ Object[] otherArrayValue = (Object[]) otherValue;
+ if (values.size() != otherArrayValue.length) return false;
+ Iterator<AnnotationValue.Loaded<?>> iterator = values.iterator();
+ for (Object value : otherArrayValue) {
+ AnnotationValue.Loaded<?> self = iterator.next();
+ if (!self.resolve().equals(value)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 1;
+ for (AnnotationValue.Loaded<?> value : values) {
+ result = 31 * result + value.hashCode();
+ }
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return RenderingDispatcher.CURRENT.toSourceString(values);
+ }
+ }
+ }
+ }
+
+ /**
+ * <p>
+ * A default implementation of a {@link net.bytebuddy.pool.TypePool} that models binary data in the Java byte code format
+ * into a {@link TypeDescription}. The data lookup is delegated to a {@link net.bytebuddy.dynamic.ClassFileLocator}.
+ * </p>
+ * <p>
+ * {@link Resolution}s that are produced by this type pool are either fully resolved or not resolved at all.
+ * </p>
+ */
+ @EqualsAndHashCode(callSuper = true)
+ class Default extends AbstractBase.Hierarchical {
+
+ /**
+ * Indicates that a visited method should be ignored.
+ */
+ private static final MethodVisitor IGNORE_METHOD = null;
+
+ /**
+ * The locator to query for finding binary data of a type.
+ */
+ protected final ClassFileLocator classFileLocator;
+
+ /**
+ * The reader mode to apply by this default type pool.
+ */
+ protected final ReaderMode readerMode;
+
+ /**
+ * Creates a new default type pool without a parent pool.
+ *
+ * @param cacheProvider The cache provider to be used.
+ * @param classFileLocator The class file locator to be used.
+ * @param readerMode The reader mode to apply by this default type pool.
+ */
+ public Default(CacheProvider cacheProvider, ClassFileLocator classFileLocator, ReaderMode readerMode) {
+ this(cacheProvider, classFileLocator, readerMode, Empty.INSTANCE);
+ }
+
+ /**
+ * Creates a new default type pool.
+ *
+ * @param cacheProvider The cache provider to be used.
+ * @param classFileLocator The class file locator to be used.
+ * @param readerMode The reader mode to apply by this default type pool.
+ * @param parentPool The parent type pool.
+ */
+ public Default(CacheProvider cacheProvider, ClassFileLocator classFileLocator, ReaderMode readerMode, TypePool parentPool) {
+ super(cacheProvider, parentPool);
+ this.classFileLocator = classFileLocator;
+ this.readerMode = readerMode;
+ }
+
+ /**
+ * Creates a default {@link net.bytebuddy.pool.TypePool} that looks up data by querying the system class
+ * loader. The returned instance is configured to use a fast reading mode and a simple cache.
+ *
+ * @return A type pool that reads its data from the system class path.
+ */
+ public static TypePool ofClassPath() {
+ return of(ClassFileLocator.ForClassLoader.ofClassPath());
+ }
+
+ /**
+ * Returns a type pool for the provided class loader.
+ *
+ * @param classLoader The class loader for which this class pool is representing types.
+ * @return An appropriate type pool.
+ */
+ public static TypePool of(ClassLoader classLoader) {
+ return of(ClassFileLocator.ForClassLoader.of(classLoader));
+ }
+
+ /**
+ * Creates a default {@link net.bytebuddy.pool.TypePool} that looks up data by querying the supplied class
+ * file locator. The returned instance is configured to use a fast reading mode and a simple cache.
+ *
+ * @param classFileLocator The class file locator to use.
+ * @return A type pool that reads its data from the system class path.
+ */
+ public static TypePool of(ClassFileLocator classFileLocator) {
+ return new Default(new CacheProvider.Simple(), classFileLocator, ReaderMode.FAST);
+ }
+
+ @Override
+ protected Resolution doDescribe(String name) {
+ try {
+ ClassFileLocator.Resolution resolution = classFileLocator.locate(name);
+ return resolution.isResolved()
+ ? new Resolution.Simple(parse(resolution.resolve()))
+ : new Resolution.Illegal(name);
+ } catch (IOException exception) {
+ throw new IllegalStateException("Error while reading class file", exception);
+ }
+ }
+
+ /**
+ * Parses a binary representation and transforms it into a type description.
+ *
+ * @param binaryRepresentation The binary data to be parsed.
+ * @return A type description of the binary data.
+ */
+ private TypeDescription parse(byte[] binaryRepresentation) {
+ ClassReader classReader = new ClassReader(binaryRepresentation);
+ TypeExtractor typeExtractor = new TypeExtractor();
+ classReader.accept(typeExtractor, readerMode.getFlags());
+ return typeExtractor.toTypeDescription();
+ }
+
+ /**
+ * Determines the granularity of the class file parsing that is conducted by a {@link net.bytebuddy.pool.TypePool.Default}.
+ */
+ public enum ReaderMode {
+
+ /**
+ * The extended reader mode parses the code segment of each method in order to detect parameter names
+ * that are only stored in a method's debugging information but are not explicitly included.
+ */
+ EXTENDED(ClassReader.SKIP_FRAMES),
+
+ /**
+ * The fast reader mode skips the code segment of each method and cannot detect parameter names that are
+ * only contained within the debugging information. This mode still detects explicitly included method
+ * parameter names.
+ */
+ FAST(ClassReader.SKIP_CODE);
+
+ /**
+ * The flags to provide to a {@link ClassReader} for parsing a file.
+ */
+ private final int flags;
+
+ /**
+ * Creates a new reader mode constant.
+ *
+ * @param flags The flags to provide to a {@link ClassReader} for parsing a file.
+ */
+ ReaderMode(int flags) {
+ this.flags = flags;
+ }
+
+ /**
+ * Returns the flags to provide to a {@link ClassReader} for parsing a file.
+ *
+ * @return The flags to provide to a {@link ClassReader} for parsing a file.
+ */
+ protected int getFlags() {
+ return flags;
+ }
+
+ /**
+ * Determines if this reader mode represents extended reading.
+ *
+ * @return {@code true} if this reader mode represents extended reading.
+ */
+ public boolean isExtended() {
+ return this == EXTENDED;
+ }
+ }
+
+ /**
+ * <p>
+ * A variant of {@link TypePool.Default} that resolves type descriptions lazily. A lazy resolution respects this type
+ * pool's {@link CacheProvider} but requeries this cache pool for every access of a property of a {@link TypeDescription}.
+ * </p>
+ * <p>
+ * {@link Resolution}s of this type pool are only fully resolved if a property that is not the type's name is required.
+ * </p>
+ */
+ public static class WithLazyResolution extends Default {
+
+ /**
+ * Creates a new default type pool with lazy resolution and without a parent pool.
+ *
+ * @param cacheProvider The cache provider to be used.
+ * @param classFileLocator The class file locator to be used.
+ * @param readerMode The reader mode to apply by this default type pool.
+ */
+ public WithLazyResolution(CacheProvider cacheProvider, ClassFileLocator classFileLocator, ReaderMode readerMode) {
+ this(cacheProvider, classFileLocator, readerMode, Empty.INSTANCE);
+ }
+
+ /**
+ * Creates a new default type pool with lazy resolution.
+ *
+ * @param cacheProvider The cache provider to be used.
+ * @param classFileLocator The class file locator to be used.
+ * @param readerMode The reader mode to apply by this default type pool.
+ * @param parentPool The parent type pool.
+ */
+ public WithLazyResolution(CacheProvider cacheProvider, ClassFileLocator classFileLocator, ReaderMode readerMode, TypePool parentPool) {
+ super(cacheProvider, classFileLocator, readerMode, parentPool);
+ }
+
+ /**
+ * Creates a default {@link net.bytebuddy.pool.TypePool} with lazy resolution that looks up data by querying the system class
+ * loader. The returned instance is configured to use a fast reading mode and a simple cache.
+ *
+ * @return A type pool that reads its data from the system class path.
+ */
+ public static TypePool ofClassPath() {
+ return of(ClassFileLocator.ForClassLoader.ofClassPath());
+ }
+
+ /**
+ * Returns a default {@link TypePool} with lazy resolution for the provided class loader.
+ *
+ * @param classLoader The class loader for which this class pool is representing types.
+ * @return An appropriate type pool.
+ */
+ public static TypePool of(ClassLoader classLoader) {
+ return of(ClassFileLocator.ForClassLoader.of(classLoader));
+ }
+
+ /**
+ * Creates a default {@link net.bytebuddy.pool.TypePool} with lazy resolution that looks up data by querying the supplied class
+ * file locator. The returned instance is configured to use a fast reading mode and a simple cache.
+ *
+ * @param classFileLocator The class file locator to use.
+ * @return A type pool that reads its data from the system class path.
+ */
+ public static TypePool of(ClassFileLocator classFileLocator) {
+ return new WithLazyResolution(new CacheProvider.Simple(), classFileLocator, ReaderMode.FAST);
+ }
+
+ @Override
+ protected Resolution doDescribe(String name) {
+ return new LazyResolution(name);
+ }
+
+ @Override
+ protected Resolution doCache(String name, Resolution resolution) {
+ return resolution;
+ }
+
+ /**
+ * Non-lazily resolves a type name.
+ *
+ * @param name The name of the type to resolve.
+ * @return The resolution for the type of this name.
+ */
+ protected Resolution doResolve(String name) {
+ Resolution resolution = cacheProvider.find(name);
+ if (resolution == null) {
+ resolution = cacheProvider.register(name, WithLazyResolution.super.doDescribe(name));
+ }
+ return resolution;
+ }
+
+ /**
+ * A lazy resolution of a type that the enclosing type pool attempts to resolve.
+ */
+ protected class LazyResolution implements Resolution {
+
+ /**
+ * The type's name.
+ */
+ private final String name;
+
+ /**
+ * Creates a new lazy resolution.
+ *
+ * @param name The type's name.
+ */
+ protected LazyResolution(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public boolean isResolved() {
+ return doResolve(name).isResolved();
+ }
+
+ @Override
+ public TypeDescription resolve() {
+ return new LazyTypeDescription(name);
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private WithLazyResolution getOuter() {
+ return WithLazyResolution.this;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object object) {
+ if (this == object) return true;
+ if (object == null || getClass() != object.getClass()) return false;
+ LazyResolution that = (LazyResolution) object;
+ return name.equals(that.name) && getOuter().equals(that.getOuter());
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return name.hashCode() + 31 * getOuter().hashCode();
+ }
+ }
+
+ /**
+ * A lazy type description that resolves any property that is not the name only when requested.
+ */
+ protected class LazyTypeDescription extends TypeDescription.AbstractBase.OfSimpleType.WithDelegation {
+
+ /**
+ * The type's name.
+ */
+ private final String name;
+
+ /**
+ * Creates a new lazy type description.
+ *
+ * @param name The type's name.
+ */
+ protected LazyTypeDescription(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ protected TypeDescription delegate() {
+ return doResolve(name).resolve();
+ }
+ }
+ }
+
+ /**
+ * An annotation registrant implements a visitor pattern for reading an unknown amount of values of annotations.
+ */
+ protected interface AnnotationRegistrant {
+
+ /**
+ * Registers an annotation value.
+ *
+ * @param name The name of the annotation value.
+ * @param annotationValue The value of the annotation.
+ */
+ void register(String name, AnnotationValue<?, ?> annotationValue);
+
+ /**
+ * Called once all annotation values are visited.
+ */
+ void onComplete();
+
+ /**
+ * An abstract base implementation of an annotation registrant.
+ */
+ abstract class AbstractBase implements AnnotationRegistrant {
+
+ /**
+ * The annotation descriptor.
+ */
+ private final String descriptor;
+
+ /**
+ * The values that were collected so far.
+ */
+ private final Map<String, AnnotationValue<?, ?>> values;
+
+ /**
+ * Creates a new annotation registrant.
+ *
+ * @param descriptor The annotation descriptor.
+ */
+ protected AbstractBase(String descriptor) {
+ this.descriptor = descriptor;
+ values = new HashMap<String, AnnotationValue<?, ?>>();
+ }
+
+ @Override
+ public void register(String name, AnnotationValue<?, ?> annotationValue) {
+ values.put(name, annotationValue);
+ }
+
+ @Override
+ public void onComplete() {
+ getTokens().add(new LazyTypeDescription.AnnotationToken(descriptor, values));
+ }
+
+ /**
+ * Returns the token list for this collector.
+ *
+ * @return The token list for this collector.
+ */
+ protected abstract List<LazyTypeDescription.AnnotationToken> getTokens();
+
+ /**
+ * A base implementation for a collector for a type variable.
+ */
+ protected abstract static class ForTypeVariable extends AbstractBase {
+
+ /**
+ * The type variable's type path.
+ */
+ private final String typePath;
+
+ /**
+ * Creates a new annotation collector.
+ *
+ * @param descriptor The annotation descriptor.
+ * @param typePath The type variable's type path.
+ */
+ protected ForTypeVariable(String descriptor, TypePath typePath) {
+ super(descriptor);
+ this.typePath = typePath == null
+ ? LazyTypeDescription.GenericTypeToken.EMPTY_TYPE_PATH
+ : typePath.toString();
+ }
+
+ @Override
+ protected List<LazyTypeDescription.AnnotationToken> getTokens() {
+ Map<String, List<LazyTypeDescription.AnnotationToken>> pathMap = getPathMap();
+ List<LazyTypeDescription.AnnotationToken> tokens = pathMap.get(typePath);
+ if (tokens == null) {
+ tokens = new ArrayList<LazyTypeDescription.AnnotationToken>();
+ pathMap.put(typePath, tokens);
+ }
+ return tokens;
+ }
+
+ /**
+ * Returns this collector's path map.
+ *
+ * @return This collector's path map.
+ */
+ protected abstract Map<String, List<LazyTypeDescription.AnnotationToken>> getPathMap();
+
+ /**
+ * A base implementation for a collector for a type variable with an index.
+ */
+ protected abstract static class WithIndex extends AbstractBase.ForTypeVariable {
+
+ /**
+ * The type variable's index.
+ */
+ private final int index;
+
+ /**
+ * Creates a new annotation collector.
+ *
+ * @param descriptor The annotation descriptor.
+ * @param typePath The type variable's type path.
+ * @param index The type variable's index.
+ */
+ protected WithIndex(String descriptor, TypePath typePath, int index) {
+ super(descriptor, typePath);
+ this.index = index;
+ }
+
+ @Override
+ protected Map<String, List<LazyTypeDescription.AnnotationToken>> getPathMap() {
+ Map<Integer, Map<String, List<LazyTypeDescription.AnnotationToken>>> indexedPathMap = getIndexedPathMap();
+ Map<String, List<LazyTypeDescription.AnnotationToken>> pathMap = indexedPathMap.get(index);
+ if (pathMap == null) {
+ pathMap = new HashMap<String, List<LazyTypeDescription.AnnotationToken>>();
+ indexedPathMap.put(index, pathMap);
+ }
+ return pathMap;
+ }
+
+ /**
+ * Returns this collector's indexed path map.
+ *
+ * @return This collector's indexed path map.
+ */
+ protected abstract Map<Integer, Map<String, List<LazyTypeDescription.AnnotationToken>>> getIndexedPathMap();
+
+ /**
+ * A base implementation for a collector for a type variable with two indices.
+ */
+ protected abstract static class DoubleIndexed extends WithIndex {
+
+ /**
+ * The type variable's first index.
+ */
+ private final int preIndex;
+
+ /**
+ * Creates a new annotation collector.
+ *
+ * @param descriptor The annotation descriptor.
+ * @param typePath The type variable's type path.
+ * @param index The type variable's index.
+ * @param preIndex The type variable's first index.
+ */
+ protected DoubleIndexed(String descriptor, TypePath typePath, int index, int preIndex) {
+ super(descriptor, typePath, index);
+ this.preIndex = preIndex;
+ }
+
+ @Override
+ protected Map<Integer, Map<String, List<LazyTypeDescription.AnnotationToken>>> getIndexedPathMap() {
+ Map<Integer, Map<Integer, Map<String, List<LazyTypeDescription.AnnotationToken>>>> doubleIndexPathMap = getDoubleIndexedPathMap();
+ Map<Integer, Map<String, List<LazyTypeDescription.AnnotationToken>>> indexedPathMap = doubleIndexPathMap.get(preIndex);
+ if (indexedPathMap == null) {
+ indexedPathMap = new HashMap<Integer, Map<String, List<LazyTypeDescription.AnnotationToken>>>();
+ doubleIndexPathMap.put(preIndex, indexedPathMap);
+ }
+ return indexedPathMap;
+ }
+
+ /**
+ * Returns this collector's double indexed path map.
+ *
+ * @return This collector's double indexed path map.
+ */
+ protected abstract Map<Integer, Map<Integer, Map<String, List<LazyTypeDescription.AnnotationToken>>>> getDoubleIndexedPathMap();
+ }
+ }
+ }
+ }
+
+ /**
+ * An annotation collector for a byte code element.
+ */
+ class ForByteCodeElement extends AbstractBase {
+
+ /**
+ * The target collection.
+ */
+ private final List<LazyTypeDescription.AnnotationToken> annotationTokens;
+
+ /**
+ * Creates a new annotation collector for a byte code element.
+ *
+ * @param descriptor The annotation descriptor.
+ * @param annotationTokens The target collection.
+ */
+ protected ForByteCodeElement(String descriptor, List<LazyTypeDescription.AnnotationToken> annotationTokens) {
+ super(descriptor);
+ this.annotationTokens = annotationTokens;
+ }
+
+ @Override
+ protected List<LazyTypeDescription.AnnotationToken> getTokens() {
+ return annotationTokens;
+ }
+
+ /**
+ * An annotation collector for a byte code element with an index.
+ */
+ public static class WithIndex extends AbstractBase {
+
+ /**
+ * The byte code element's index.
+ */
+ private final int index;
+
+ /**
+ * The target collection.
+ */
+ private final Map<Integer, List<LazyTypeDescription.AnnotationToken>> annotationTokens;
+
+ /**
+ * Creates a new annotation collector for a byte code element with an index.
+ *
+ * @param descriptor The annotation descriptor.
+ * @param index The byte code element's index.
+ * @param annotationTokens The target collection.
+ */
+ protected WithIndex(String descriptor, int index, Map<Integer, List<LazyTypeDescription.AnnotationToken>> annotationTokens) {
+ super(descriptor);
+ this.index = index;
+ this.annotationTokens = annotationTokens;
+ }
+
+ @Override
+ protected List<LazyTypeDescription.AnnotationToken> getTokens() {
+ List<LazyTypeDescription.AnnotationToken> annotationTokens = this.annotationTokens.get(index);
+ if (annotationTokens == null) {
+ annotationTokens = new ArrayList<LazyTypeDescription.AnnotationToken>();
+ this.annotationTokens.put(index, annotationTokens);
+ }
+ return annotationTokens;
+ }
+ }
+ }
+
+ /**
+ * An annotation collector for a type variable.
+ */
+ class ForTypeVariable extends AbstractBase.ForTypeVariable {
+
+ /**
+ * The target collection.
+ */
+ private final Map<String, List<LazyTypeDescription.AnnotationToken>> pathMap;
+
+ /**
+ * Creates a new annotation collector.
+ *
+ * @param descriptor The annotation descriptor.
+ * @param typePath The type variable's type path.
+ * @param pathMap The target collection.
+ */
+ protected ForTypeVariable(String descriptor, TypePath typePath, Map<String, List<LazyTypeDescription.AnnotationToken>> pathMap) {
+ super(descriptor, typePath);
+ this.pathMap = pathMap;
+ }
+
+ @Override
+ protected Map<String, List<LazyTypeDescription.AnnotationToken>> getPathMap() {
+ return pathMap;
+ }
+
+ /**
+ * An annotation collector for a type variable with an index.
+ */
+ public static class WithIndex extends AbstractBase.ForTypeVariable.WithIndex {
+
+ /**
+ * The target collection.
+ */
+ private final Map<Integer, Map<String, List<LazyTypeDescription.AnnotationToken>>> indexedPathMap;
+
+ /**
+ * Creates a new annotation collector.
+ *
+ * @param descriptor The annotation descriptor.
+ * @param typePath The type variable's type path.
+ * @param index The target index.
+ * @param indexedPathMap The target collection.
+ */
+ protected WithIndex(String descriptor,
+ TypePath typePath,
+ int index,
+ Map<Integer, Map<String, List<LazyTypeDescription.AnnotationToken>>> indexedPathMap) {
+ super(descriptor, typePath, index);
+ this.indexedPathMap = indexedPathMap;
+ }
+
+ @Override
+ protected Map<Integer, Map<String, List<LazyTypeDescription.AnnotationToken>>> getIndexedPathMap() {
+ return indexedPathMap;
+ }
+
+ /**
+ * An annotation collector for a type variable with two indices.
+ */
+ public static class DoubleIndexed extends AbstractBase.ForTypeVariable.WithIndex.DoubleIndexed {
+
+ /**
+ * The target collection.
+ */
+ private final Map<Integer, Map<Integer, Map<String, List<LazyTypeDescription.AnnotationToken>>>> doubleIndexedPathMap;
+
+ /**
+ * Creates a new annotation collector.
+ *
+ * @param descriptor The annotation descriptor.
+ * @param typePath The type variable's type path.
+ * @param index The target index.
+ * @param preIndex The initial target index.
+ * @param doubleIndexedPathMap The target collection.
+ */
+ protected DoubleIndexed(String descriptor,
+ TypePath typePath,
+ int index,
+ int preIndex,
+ Map<Integer, Map<Integer, Map<String, List<LazyTypeDescription.AnnotationToken>>>> doubleIndexedPathMap) {
+ super(descriptor, typePath, index, preIndex);
+ this.doubleIndexedPathMap = doubleIndexedPathMap;
+ }
+
+ @Override
+ protected Map<Integer, Map<Integer, Map<String, List<LazyTypeDescription.AnnotationToken>>>> getDoubleIndexedPathMap() {
+ return doubleIndexedPathMap;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * A component type locator allows for the lazy location of an array's component type.
+ */
+ protected interface ComponentTypeLocator {
+
+ /**
+ * Binds this component type to a given property name of an annotation.
+ *
+ * @param name The name of an annotation property which the returned component type reference should
+ * query for resolving an array's component type.
+ * @return A component type reference to an annotation value's component type.
+ */
+ RawDescriptionArray.ComponentTypeReference bind(String name);
+
+ /**
+ * A component type locator which cannot legally resolve an array's component type.
+ */
+ enum Illegal implements ComponentTypeLocator {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public RawDescriptionArray.ComponentTypeReference bind(String name) {
+ throw new IllegalStateException("Unexpected lookup of component type for " + name);
+ }
+ }
+
+ /**
+ * A component type locator that lazily analyses an annotation for resolving an annotation property's
+ * array value's component type.
+ */
+ @EqualsAndHashCode
+ class ForAnnotationProperty implements ComponentTypeLocator {
+
+ /**
+ * The type pool to query for type descriptions.
+ */
+ private final TypePool typePool;
+
+ /**
+ * The name of the annotation to analyze.
+ */
+ private final String annotationName;
+
+ /**
+ * Creates a new component type locator for an array value.
+ *
+ * @param typePool The type pool to be used for looking up linked types.
+ * @param annotationDescriptor A descriptor of the annotation to analyze.
+ */
+ public ForAnnotationProperty(TypePool typePool, String annotationDescriptor) {
+ this.typePool = typePool;
+ annotationName = annotationDescriptor.substring(1, annotationDescriptor.length() - 1).replace('/', '.');
+ }
+
+ @Override
+ public RawDescriptionArray.ComponentTypeReference bind(String name) {
+ return new Bound(name);
+ }
+
+ /**
+ * A bound representation of a
+ * {@link net.bytebuddy.pool.TypePool.Default.ComponentTypeLocator.ForAnnotationProperty}.
+ */
+ protected class Bound implements RawDescriptionArray.ComponentTypeReference {
+
+ /**
+ * The name of the annotation property.
+ */
+ private final String name;
+
+ /**
+ * Creates a new bound component type locator for an annotation property.
+ *
+ * @param name The name of the annotation property.
+ */
+ protected Bound(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String lookup() {
+ return typePool.describe(annotationName)
+ .resolve()
+ .getDeclaredMethods()
+ .filter(named(name))
+ .getOnly()
+ .getReturnType()
+ .asErasure()
+ .getComponentType()
+ .getName();
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && name.equals(((Bound) other).name)
+ && ForAnnotationProperty.this.equals(((Bound) other).getOuter());
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return name.hashCode() + 31 * ForAnnotationProperty.this.hashCode();
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private ForAnnotationProperty getOuter() {
+ return ForAnnotationProperty.this;
+ }
+ }
+ }
+
+ /**
+ * A component type locator that locates an array type by a method's return value from its method descriptor.
+ */
+ @EqualsAndHashCode
+ class ForArrayType implements ComponentTypeLocator, RawDescriptionArray.ComponentTypeReference {
+
+ /**
+ * The resolved component type's binary name.
+ */
+ private final String componentType;
+
+ /**
+ * Creates a new component type locator for an array type.
+ *
+ * @param methodDescriptor The method descriptor to resolve.
+ */
+ public ForArrayType(String methodDescriptor) {
+ String arrayType = Type.getMethodType(methodDescriptor).getReturnType().getClassName();
+ componentType = arrayType.substring(0, arrayType.length() - 2);
+ }
+
+ @Override
+ public RawDescriptionArray.ComponentTypeReference bind(String name) {
+ return this;
+ }
+
+ @Override
+ public String lookup() {
+ return componentType;
+ }
+ }
+ }
+
+ /**
+ * A type registrant allows to register a generic type token.
+ */
+ protected interface GenericTypeRegistrant {
+
+ /**
+ * Registers a discovered generic type token.
+ *
+ * @param token The token to be registered.
+ */
+ void register(LazyTypeDescription.GenericTypeToken token);
+
+ /**
+ * A signature visitor that rejects any discovered generic type.
+ */
+ class RejectingSignatureVisitor extends SignatureVisitor {
+
+ /**
+ * The message of the error message.
+ */
+ private static final String MESSAGE = "Unexpected token in generic signature";
+
+ /**
+ * Creates a new rejecting signature visitor.
+ */
+ public RejectingSignatureVisitor() {
+ super(Opcodes.ASM5);
+ }
+
+ @Override
+ public void visitFormalTypeParameter(String name) {
+ throw new IllegalStateException(MESSAGE);
+ }
+
+ @Override
+ public SignatureVisitor visitClassBound() {
+ throw new IllegalStateException(MESSAGE);
+ }
+
+ @Override
+ public SignatureVisitor visitInterfaceBound() {
+ throw new IllegalStateException(MESSAGE);
+ }
+
+ @Override
+ public SignatureVisitor visitSuperclass() {
+ throw new IllegalStateException(MESSAGE);
+ }
+
+ @Override
+ public SignatureVisitor visitInterface() {
+ throw new IllegalStateException(MESSAGE);
+ }
+
+ @Override
+ public SignatureVisitor visitParameterType() {
+ throw new IllegalStateException(MESSAGE);
+ }
+
+ @Override
+ public SignatureVisitor visitReturnType() {
+ throw new IllegalStateException(MESSAGE);
+ }
+
+ @Override
+ public SignatureVisitor visitExceptionType() {
+ throw new IllegalStateException(MESSAGE);
+ }
+
+ @Override
+ public void visitBaseType(char descriptor) {
+ throw new IllegalStateException(MESSAGE);
+ }
+
+ @Override
+ public void visitTypeVariable(String name) {
+ throw new IllegalStateException(MESSAGE);
+ }
+
+ @Override
+ public SignatureVisitor visitArrayType() {
+ throw new IllegalStateException(MESSAGE);
+ }
+
+ @Override
+ public void visitClassType(String name) {
+ throw new IllegalStateException(MESSAGE);
+ }
+
+ @Override
+ public void visitInnerClassType(String name) {
+ throw new IllegalStateException(MESSAGE);
+ }
+
+ @Override
+ public void visitTypeArgument() {
+ throw new IllegalStateException(MESSAGE);
+ }
+
+ @Override
+ public SignatureVisitor visitTypeArgument(char wildcard) {
+ throw new IllegalStateException(MESSAGE);
+ }
+
+ @Override
+ public void visitEnd() {
+ throw new IllegalStateException(MESSAGE);
+ }
+ }
+ }
+
+ /**
+ * A bag for collecting parameter meta information that is stored as debug information for implemented
+ * methods.
+ */
+ protected static class ParameterBag {
+
+ /**
+ * An array of the method's parameter types.
+ */
+ private final Type[] parameterType;
+
+ /**
+ * A map containing the tokens that were collected until now.
+ */
+ private final Map<Integer, String> parameterRegistry;
+
+ /**
+ * Creates a new bag.
+ *
+ * @param parameterType An array of parameter types for the method on which this parameter bag
+ * is used.
+ */
+ protected ParameterBag(Type[] parameterType) {
+ this.parameterType = parameterType;
+ parameterRegistry = new HashMap<Integer, String>();
+ }
+
+ /**
+ * Registers a new parameter.
+ *
+ * @param offset The offset of the registered entry on the local variable array of the method.
+ * @param name The name of the parameter.
+ */
+ protected void register(int offset, String name) {
+ parameterRegistry.put(offset, name);
+ }
+
+ /**
+ * Resolves the collected parameters as a list of parameter tokens.
+ *
+ * @param isStatic {@code true} if the analyzed method is static.
+ * @return A list of parameter tokens based on the collected information.
+ */
+ protected List<LazyTypeDescription.MethodToken.ParameterToken> resolve(boolean isStatic) {
+ List<LazyTypeDescription.MethodToken.ParameterToken> parameterTokens = new ArrayList<LazyTypeDescription.MethodToken.ParameterToken>(parameterType.length);
+ int offset = isStatic
+ ? StackSize.ZERO.getSize()
+ : StackSize.SINGLE.getSize();
+ for (Type aParameterType : parameterType) {
+ String name = this.parameterRegistry.get(offset);
+ parameterTokens.add(name == null
+ ? new LazyTypeDescription.MethodToken.ParameterToken()
+ : new LazyTypeDescription.MethodToken.ParameterToken(name));
+ offset += aParameterType.getSize();
+ }
+ return parameterTokens;
+ }
+ }
+
+ /**
+ * A generic type extractor allows for an iterative extraction of generic type information.
+ */
+ protected static class GenericTypeExtractor extends GenericTypeRegistrant.RejectingSignatureVisitor implements GenericTypeRegistrant {
+
+ /**
+ * A registrant that receives any discovered type.
+ */
+ private final GenericTypeRegistrant genericTypeRegistrant;
+
+ /**
+ * The current token that is in the process of creation.
+ */
+ private IncompleteToken incompleteToken;
+
+ /**
+ * Creates a new generic type extractor.
+ *
+ * @param genericTypeRegistrant The target to receive the complete type.
+ */
+ protected GenericTypeExtractor(GenericTypeRegistrant genericTypeRegistrant) {
+ this.genericTypeRegistrant = genericTypeRegistrant;
+ }
+
+ @Override
+ public void visitBaseType(char descriptor) {
+ genericTypeRegistrant.register(LazyTypeDescription.GenericTypeToken.ForPrimitiveType.of(descriptor));
+ }
+
+ @Override
+ public void visitTypeVariable(String name) {
+ genericTypeRegistrant.register(new LazyTypeDescription.GenericTypeToken.ForTypeVariable(name));
+ }
+
+ @Override
+ public SignatureVisitor visitArrayType() {
+ return new GenericTypeExtractor(this);
+ }
+
+ @Override
+ public void register(LazyTypeDescription.GenericTypeToken componentTypeToken) {
+ genericTypeRegistrant.register(new LazyTypeDescription.GenericTypeToken.ForGenericArray(componentTypeToken));
+ }
+
+ @Override
+ public void visitClassType(String name) {
+ incompleteToken = new IncompleteToken.ForTopLevelType(name);
+ }
+
+ @Override
+ public void visitInnerClassType(String name) {
+ incompleteToken = new IncompleteToken.ForInnerClass(name, incompleteToken);
+ }
+
+ @Override
+ public void visitTypeArgument() {
+ incompleteToken.appendPlaceholder();
+ }
+
+ @Override
+ public SignatureVisitor visitTypeArgument(char wildcard) {
+ switch (wildcard) {
+ case SignatureVisitor.SUPER:
+ return incompleteToken.appendLowerBound();
+ case SignatureVisitor.EXTENDS:
+ return incompleteToken.appendUpperBound();
+ case SignatureVisitor.INSTANCEOF:
+ return incompleteToken.appendDirectBound();
+ default:
+ throw new IllegalArgumentException("Unknown wildcard: " + wildcard);
+ }
+ }
+
+ @Override
+ public void visitEnd() {
+ genericTypeRegistrant.register(incompleteToken.toToken());
+ }
+
+ /**
+ * An incomplete {@link LazyTypeDescription.GenericTypeToken}.
+ */
+ protected interface IncompleteToken {
+
+ /**
+ * Appends a lower bound to this token.
+ *
+ * @return A signature visitor for visiting the lower bound's type.
+ */
+ SignatureVisitor appendLowerBound();
+
+ /**
+ * Appends an upper bound to this token.
+ *
+ * @return A signature visitor for visiting the upper bound's type.
+ */
+ SignatureVisitor appendUpperBound();
+
+ /**
+ * Appends a direct bound to this token.
+ *
+ * @return A signature visitor for visiting the direct bound's type.
+ */
+ SignatureVisitor appendDirectBound();
+
+ /**
+ * Appends a placeholder to this token.
+ */
+ void appendPlaceholder();
+
+ /**
+ * Returns {@code true} if this token describes a type with parameters.
+ *
+ * @return {@code true} if this token describes a type with parameters.
+ */
+ boolean isParameterized();
+
+ /**
+ * Returns the name of this token.
+ *
+ * @return The name of this token.
+ */
+ String getName();
+
+ /**
+ * Converts this incomplete token to a completed token.
+ *
+ * @return The finalized token.
+ */
+ LazyTypeDescription.GenericTypeToken toToken();
+
+ /**
+ * An abstract base implementation of an incomplete token.
+ */
+ abstract class AbstractBase implements IncompleteToken {
+
+ /**
+ * The parameters of this token.
+ */
+ protected final List<LazyTypeDescription.GenericTypeToken> parameters;
+
+ /**
+ * Creates a new base implementation of an incomplete token.
+ */
+ public AbstractBase() {
+ parameters = new ArrayList<LazyTypeDescription.GenericTypeToken>();
+ }
+
+ @Override
+ public SignatureVisitor appendDirectBound() {
+ return new GenericTypeExtractor(new ForDirectBound());
+ }
+
+ @Override
+ public SignatureVisitor appendUpperBound() {
+ return new GenericTypeExtractor(new ForUpperBound());
+ }
+
+ @Override
+ public SignatureVisitor appendLowerBound() {
+ return new GenericTypeExtractor(new ForLowerBound());
+ }
+
+ @Override
+ public void appendPlaceholder() {
+ parameters.add(LazyTypeDescription.GenericTypeToken.ForUnboundWildcard.INSTANCE);
+ }
+
+ /**
+ * A token for registering a direct bound.
+ */
+ protected class ForDirectBound implements GenericTypeRegistrant {
+
+ @Override
+ public void register(LazyTypeDescription.GenericTypeToken token) {
+ parameters.add(token);
+ }
+ }
+
+ /**
+ * A token for registering a wildcard with an upper bound.
+ */
+ protected class ForUpperBound implements GenericTypeRegistrant {
+
+ @Override
+ public void register(LazyTypeDescription.GenericTypeToken token) {
+ parameters.add(new LazyTypeDescription.GenericTypeToken.ForUpperBoundWildcard(token));
+ }
+ }
+
+ /**
+ * A token for registering a wildcard with a lower bound.
+ */
+ protected class ForLowerBound implements GenericTypeRegistrant {
+
+ @Override
+ public void register(LazyTypeDescription.GenericTypeToken token) {
+ parameters.add(new LazyTypeDescription.GenericTypeToken.ForLowerBoundWildcard(token));
+ }
+ }
+ }
+
+ /**
+ * An incomplete token representing a generic type without an outer type.
+ */
+ @EqualsAndHashCode(callSuper = false)
+ class ForTopLevelType extends AbstractBase {
+
+ /**
+ * The internal name of the type.
+ */
+ private final String internalName;
+
+ /**
+ * Creates a new incomplete token representing a type without an outer type.
+ *
+ * @param internalName The internal name of the type.
+ */
+ public ForTopLevelType(String internalName) {
+ this.internalName = internalName;
+ }
+
+ @Override
+ public LazyTypeDescription.GenericTypeToken toToken() {
+ return isParameterized()
+ ? new LazyTypeDescription.GenericTypeToken.ForParameterizedType(getName(), parameters)
+ : new LazyTypeDescription.GenericTypeToken.ForRawType(getName());
+ }
+
+ @Override
+ public boolean isParameterized() {
+ return !parameters.isEmpty();
+ }
+
+ @Override
+ public String getName() {
+ return internalName.replace('/', '.');
+ }
+ }
+
+ /**
+ * An incomplete generic type token representing a type with an outer type.
+ */
+ @EqualsAndHashCode(callSuper = false)
+ class ForInnerClass extends AbstractBase {
+
+ /**
+ * The separator that indicates an inner type.
+ */
+ private static final char INNER_CLASS_SEPARATOR = '$';
+
+ /**
+ * The internal name of the type.
+ */
+ private final String internalName;
+
+ /**
+ * The token representing the outer type.
+ */
+ private final IncompleteToken outerTypeToken;
+
+ /**
+ * Creates a new incomplete token representing a type without an outer type.
+ *
+ * @param internalName The internal name of the type.
+ * @param outerTypeToken The incomplete token representing the outer type.
+ */
+ public ForInnerClass(String internalName, IncompleteToken outerTypeToken) {
+ this.internalName = internalName;
+ this.outerTypeToken = outerTypeToken;
+ }
+
+ @Override
+ public LazyTypeDescription.GenericTypeToken toToken() {
+ return isParameterized() || outerTypeToken.isParameterized()
+ ? new LazyTypeDescription.GenericTypeToken.ForParameterizedType.Nested(getName(), parameters, outerTypeToken.toToken())
+ : new LazyTypeDescription.GenericTypeToken.ForRawType(getName());
+ }
+
+ @Override
+ public boolean isParameterized() {
+ return !parameters.isEmpty() || !outerTypeToken.isParameterized();
+ }
+
+ @Override
+ public String getName() {
+ return outerTypeToken.getName() + INNER_CLASS_SEPARATOR + internalName.replace('/', '.');
+ }
+ }
+ }
+
+ /**
+ * A signature visitor for extracting a generic type resolution.
+ *
+ * @param <T> The type of the resolution this visitor extracts.
+ */
+ protected abstract static class ForSignature<T extends LazyTypeDescription.GenericTypeToken.Resolution>
+ extends RejectingSignatureVisitor
+ implements GenericTypeRegistrant {
+
+ /**
+ * The resolved type variable tokens.
+ */
+ protected final List<LazyTypeDescription.GenericTypeToken.OfFormalTypeVariable> typeVariableTokens;
+
+ /**
+ * The name of the currently constructed type.
+ */
+ protected String currentTypeParameter;
+
+ /**
+ * The bounds of the currently constructed type.
+ */
+ protected List<LazyTypeDescription.GenericTypeToken> currentBounds;
+
+ /**
+ * Creates a new signature visitor.
+ */
+ public ForSignature() {
+ typeVariableTokens = new ArrayList<LazyTypeDescription.GenericTypeToken.OfFormalTypeVariable>();
+ }
+
+ /**
+ * Applies an extraction of a generic signature given the supplied visitor.
+ *
+ * @param genericSignature The generic signature to interpret.
+ * @param visitor The visitor to apply.
+ * @param <S> The type of the generated resolution.
+ * @return The resolution of the supplied signature.
+ */
+ protected static <S extends LazyTypeDescription.GenericTypeToken.Resolution> S extract(String genericSignature, ForSignature<S> visitor) {
+ SignatureReader signatureReader = new SignatureReader(genericSignature);
+ signatureReader.accept(visitor);
+ return visitor.resolve();
+ }
+
+ @Override
+ public void visitFormalTypeParameter(String name) {
+ collectTypeParameter();
+ currentTypeParameter = name;
+ currentBounds = new ArrayList<LazyTypeDescription.GenericTypeToken>();
+ }
+
+ @Override
+ public SignatureVisitor visitClassBound() {
+ return new GenericTypeExtractor(this);
+ }
+
+ @Override
+ public SignatureVisitor visitInterfaceBound() {
+ return new GenericTypeExtractor(this);
+ }
+
+ @Override
+ public void register(LazyTypeDescription.GenericTypeToken token) {
+ if (currentBounds == null) {
+ throw new IllegalStateException("Did not expect " + token + " before finding formal parameter");
+ }
+ currentBounds.add(token);
+ }
+
+ /**
+ * Collects the currently constructed type.
+ */
+ protected void collectTypeParameter() {
+ if (currentTypeParameter != null) {
+ typeVariableTokens.add(new LazyTypeDescription.GenericTypeToken.ForTypeVariable.Formal(currentTypeParameter, currentBounds));
+ }
+ }
+
+ /**
+ * Completes the current resolution.
+ *
+ * @return The resolved generic signature.
+ */
+ public abstract T resolve();
+
+ /**
+ * A parser for a generic type signature.
+ */
+ protected static class OfType extends ForSignature<LazyTypeDescription.GenericTypeToken.Resolution.ForType> {
+
+ /**
+ * The interface type's generic signatures.
+ */
+ private final List<LazyTypeDescription.GenericTypeToken> interfaceTypeTokens;
+
+ /**
+ * The super type's generic signature.
+ */
+ private LazyTypeDescription.GenericTypeToken superClassToken;
+
+ /**
+ * Creates a new parser for a type signature.
+ */
+ protected OfType() {
+ interfaceTypeTokens = new ArrayList<LazyTypeDescription.GenericTypeToken>();
+ }
+
+ /**
+ * Extracts a generic type resolution of a type signature.
+ *
+ * @param genericSignature The signature to interpret.
+ * @return The interpreted type signature.
+ */
+ public static LazyTypeDescription.GenericTypeToken.Resolution.ForType extract(String genericSignature) {
+ try {
+ return genericSignature == null
+ ? LazyTypeDescription.GenericTypeToken.Resolution.Raw.INSTANCE
+ : ForSignature.extract(genericSignature, new OfType());
+ } catch (RuntimeException ignored) {
+ return LazyTypeDescription.GenericTypeToken.Resolution.Malformed.INSTANCE;
+ }
+ }
+
+ @Override
+ public SignatureVisitor visitSuperclass() {
+ collectTypeParameter();
+ return new GenericTypeExtractor(new SuperClassRegistrant());
+ }
+
+ @Override
+ public SignatureVisitor visitInterface() {
+ return new GenericTypeExtractor(new InterfaceTypeRegistrant());
+ }
+
+ @Override
+ public LazyTypeDescription.GenericTypeToken.Resolution.ForType resolve() {
+ return new LazyTypeDescription.GenericTypeToken.Resolution.ForType.Tokenized(superClassToken, interfaceTypeTokens, typeVariableTokens);
+ }
+
+ /**
+ * A registrant for the super type.
+ */
+ protected class SuperClassRegistrant implements GenericTypeRegistrant {
+
+ @Override
+ public void register(LazyTypeDescription.GenericTypeToken token) {
+ superClassToken = token;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return OfType.this.hashCode();
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ return other != null
+ && getClass() == other.getClass()
+ && OfType.this.equals(((SuperClassRegistrant) other).getOuter());
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private OfType getOuter() {
+ return OfType.this;
+ }
+ }
+
+ /**
+ * A registrant for the interface types.
+ */
+ protected class InterfaceTypeRegistrant implements GenericTypeRegistrant {
+
+ @Override
+ public void register(LazyTypeDescription.GenericTypeToken token) {
+ interfaceTypeTokens.add(token);
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return OfType.this.hashCode();
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ return other != null
+ && getClass() == other.getClass()
+ && OfType.this.equals(((InterfaceTypeRegistrant) other).getOuter());
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private OfType getOuter() {
+ return OfType.this;
+ }
+ }
+ }
+
+ /**
+ * A parser for a generic method signature.
+ */
+ protected static class OfMethod extends ForSignature<LazyTypeDescription.GenericTypeToken.Resolution.ForMethod> {
+
+ /**
+ * The generic parameter types.
+ */
+ private final List<LazyTypeDescription.GenericTypeToken> parameterTypeTokens;
+
+ /**
+ * The generic exception types.
+ */
+ private final List<LazyTypeDescription.GenericTypeToken> exceptionTypeTokens;
+
+ /**
+ * The generic return type.
+ */
+ private LazyTypeDescription.GenericTypeToken returnTypeToken;
+
+ /**
+ * Creates a parser for a generic method signature.
+ */
+ public OfMethod() {
+ parameterTypeTokens = new ArrayList<LazyTypeDescription.GenericTypeToken>();
+ exceptionTypeTokens = new ArrayList<LazyTypeDescription.GenericTypeToken>();
+ }
+
+ /**
+ * Extracts a generic method resolution of a method signature.
+ *
+ * @param genericSignature The signature to interpret.
+ * @return The interpreted method signature.
+ */
+ public static LazyTypeDescription.GenericTypeToken.Resolution.ForMethod extract(String genericSignature) {
+ try {
+ return genericSignature == null
+ ? LazyTypeDescription.GenericTypeToken.Resolution.Raw.INSTANCE
+ : ForSignature.extract(genericSignature, new OfMethod());
+ } catch (RuntimeException ignored) {
+ return LazyTypeDescription.GenericTypeToken.Resolution.Malformed.INSTANCE;
+ }
+ }
+
+ @Override
+ public SignatureVisitor visitParameterType() {
+ return new GenericTypeExtractor(new ParameterTypeRegistrant());
+ }
+
+ @Override
+ public SignatureVisitor visitReturnType() {
+ collectTypeParameter();
+ return new GenericTypeExtractor(new ReturnTypeTypeRegistrant());
+ }
+
+ @Override
+ public SignatureVisitor visitExceptionType() {
+ return new GenericTypeExtractor(new ExceptionTypeRegistrant());
+ }
+
+ @Override
+ public LazyTypeDescription.GenericTypeToken.Resolution.ForMethod resolve() {
+ return new LazyTypeDescription.GenericTypeToken.Resolution.ForMethod.Tokenized(returnTypeToken,
+ parameterTypeTokens,
+ exceptionTypeTokens,
+ typeVariableTokens);
+ }
+
+ /**
+ * A registrant for a parameter type.
+ */
+ protected class ParameterTypeRegistrant implements GenericTypeRegistrant {
+
+ @Override
+ public void register(LazyTypeDescription.GenericTypeToken token) {
+ parameterTypeTokens.add(token);
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return OfMethod.this.hashCode();
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ return other != null
+ && getClass() == other.getClass()
+ && OfMethod.this.equals(((ParameterTypeRegistrant) other).getOuter());
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private OfMethod getOuter() {
+ return OfMethod.this;
+ }
+ }
+
+ /**
+ * A registrant for a return type.
+ */
+ protected class ReturnTypeTypeRegistrant implements GenericTypeRegistrant {
+
+ @Override
+ public void register(LazyTypeDescription.GenericTypeToken token) {
+ returnTypeToken = token;
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return OfMethod.this.hashCode();
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ return other != null
+ && getClass() == other.getClass()
+ && OfMethod.this.equals(((ReturnTypeTypeRegistrant) other).getOuter());
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private OfMethod getOuter() {
+ return OfMethod.this;
+ }
+ }
+
+ /**
+ * A registrant for an exception type.
+ */
+ protected class ExceptionTypeRegistrant implements GenericTypeRegistrant {
+
+ @Override
+ public void register(LazyTypeDescription.GenericTypeToken token) {
+ exceptionTypeTokens.add(token);
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public int hashCode() {
+ return OfMethod.this.hashCode();
+ }
+
+ @Override // HE: Remove when Lombok support for getOuter is added.
+ public boolean equals(Object other) {
+ return other != null
+ && getClass() == other.getClass()
+ && OfMethod.this.equals(((ExceptionTypeRegistrant) other).getOuter());
+ }
+
+ /**
+ * Returns the outer instance.
+ *
+ * @return The outer instance.
+ */
+ private OfMethod getOuter() {
+ return OfMethod.this;
+ }
+ }
+ }
+
+ /**
+ * A parser for a generic field signature.
+ */
+ protected static class OfField implements GenericTypeRegistrant {
+
+ /**
+ * The generic field type.
+ */
+ private LazyTypeDescription.GenericTypeToken fieldTypeToken;
+
+ /**
+ * Extracts a generic field resolution of a field signature.
+ *
+ * @param genericSignature The signature to interpret.
+ * @return The interpreted field signature.
+ */
+ public static LazyTypeDescription.GenericTypeToken.Resolution.ForField extract(String genericSignature) {
+ if (genericSignature == null) {
+ return LazyTypeDescription.GenericTypeToken.Resolution.Raw.INSTANCE;
+ } else {
+ SignatureReader signatureReader = new SignatureReader(genericSignature);
+ OfField visitor = new OfField();
+ try {
+ signatureReader.acceptType(new GenericTypeExtractor(visitor));
+ return visitor.resolve();
+ } catch (RuntimeException ignored) {
+ return LazyTypeDescription.GenericTypeToken.Resolution.Malformed.INSTANCE;
+ }
+ }
+ }
+
+ @Override
+ public void register(LazyTypeDescription.GenericTypeToken token) {
+ fieldTypeToken = token;
+ }
+
+ /**
+ * Completes the current resolution.
+ *
+ * @return The resolved generic signature.
+ */
+ protected LazyTypeDescription.GenericTypeToken.Resolution.ForField resolve() {
+ return new LazyTypeDescription.GenericTypeToken.Resolution.ForField.Tokenized(fieldTypeToken);
+ }
+ }
+ }
+ }
+
+ /**
+ * A type description that looks up any referenced {@link net.bytebuddy.description.ByteCodeElement} or
+ * {@link AnnotationDescription} by querying a type pool at lookup time.
+ */
+ protected static class LazyTypeDescription extends TypeDescription.AbstractBase.OfSimpleType {
+
+ /**
+ * The index of a super class's type annotations.
+ */
+ private static final int SUPER_CLASS_INDEX = -1;
+
+ /**
+ * Indicates that a type does not exist and does therefore not have a name.
+ */
+ private static final String NO_TYPE = null;
+
+ /**
+ * The type pool to be used for looking up linked types.
+ */
+ private final TypePool typePool;
+
+ /**
+ * The actual modifiers of this type.
+ */
+ private final int actualModifiers;
+
+ /**
+ * The modifiers of this type.
+ */
+ private final int modifiers;
+
+ /**
+ * The binary name of this type.
+ */
+ private final String name;
+
+ /**
+ * The type's super type's descriptor or {@code null} if this type does not define a super type.
+ */
+ private final String superClassDescriptor;
+
+ /**
+ * The type's generic signature as found in the class file or {@code null} if the type is not generic.
+ */
+ private final String genericSignature;
+
+ /**
+ * The resolution of this type's generic type.
+ */
+ private final GenericTypeToken.Resolution.ForType signatureResolution;
+
+ /**
+ * The descriptor of this type's interfaces.
+ */
+ private final List<String> interfaceTypeDescriptors;
+
+ /**
+ * A definition of this type's containment within another type or method.
+ */
+ private final TypeContainment typeContainment;
+
+ /**
+ * The binary name of this type's declaring type or {@code null} if no such type exists.
+ */
+ private final String declaringTypeName;
+
+ /**
+ * A list of descriptors representing the types that are declared by this type.
+ */
+ private final List<String> declaredTypes;
+
+ /**
+ * {@code true} if this type is an anonymous type.
+ */
+ private final boolean anonymousType;
+
+ /**
+ * A mapping of type annotations for this type's super type and interface types by their indices.
+ */
+ private final Map<Integer, Map<String, List<AnnotationToken>>> superTypeAnnotationTokens;
+
+ /**
+ * A mapping of type annotations of the type variables' type annotations by their indices.
+ */
+ private final Map<Integer, Map<String, List<AnnotationToken>>> typeVariableAnnotationTokens;
+
+ /**
+ * A mapping of type annotations of the type variables' bounds' type annotations by their indices and each variable's index.
+ */
+ private final Map<Integer, Map<Integer, Map<String, List<AnnotationToken>>>> typeVariableBoundsAnnotationTokens;
+
+ /**
+ * A list of tokens that represent the annotations of this type.
+ */
+ private final List<AnnotationToken> annotationTokens;
+
+ /**
+ * A list of field tokens describing the field's of this type.
+ */
+ private final List<FieldToken> fieldTokens;
+
+ /**
+ * A list of method tokens describing the method's of this type.
+ */
+ private final List<MethodToken> methodTokens;
+
+ /**
+ * Creates a new lazy type description.
+ *
+ * @param typePool The type pool to be used for looking up linked types.
+ * @param actualModifiers The actual modifiers of this type.
+ * @param modifiers The modifiers of this type.
+ * @param name The binary name of this type.
+ * @param superClassInternalName The internal name of this type's super type or {@code null} if no such super type is defined.
+ * @param interfaceInternalName An array of this type's interfaces or {@code null} if this type does not define any interfaces.
+ * @param genericSignature The type's generic signature as found in the class file or {@code null} if the type is not generic.
+ * @param typeContainment A definition of this type's containment within another type or method.
+ * @param declaringTypeInternalName The internal name of this type's declaring type or {@code null} if no such type exists.
+ * @param declaredTypes A list of descriptors representing the types that are declared by this type.
+ * @param anonymousType {@code true} if this type is an anonymous type.
+ * @param superTypeAnnotationTokens A mapping of type annotations for this type's super type and interface types by their indices.
+ * @param typeVariableAnnotationTokens A mapping of type annotations of the type variables' type annotations by their indices.
+ * @param typeVariableBoundsAnnotationTokens A mapping of type annotations of the type variables' bounds' type annotations by their indices
+ * and each variable's index.
+ * @param annotationTokens A list of tokens that represent the annotations of this type.
+ * @param fieldTokens A list of field tokens describing the field's of this type.
+ * @param methodTokens A list of method tokens describing the method's of this type.
+ */
+ protected LazyTypeDescription(TypePool typePool,
+ int actualModifiers,
+ int modifiers,
+ String name,
+ String superClassInternalName,
+ String[] interfaceInternalName,
+ String genericSignature,
+ TypeContainment typeContainment,
+ String declaringTypeInternalName,
+ List<String> declaredTypes,
+ boolean anonymousType,
+ Map<Integer, Map<String, List<AnnotationToken>>> superTypeAnnotationTokens,
+ Map<Integer, Map<String, List<AnnotationToken>>> typeVariableAnnotationTokens,
+ Map<Integer, Map<Integer, Map<String, List<AnnotationToken>>>> typeVariableBoundsAnnotationTokens,
+ List<AnnotationToken> annotationTokens,
+ List<FieldToken> fieldTokens,
+ List<MethodToken> methodTokens) {
+ this.typePool = typePool;
+ this.actualModifiers = actualModifiers & ~Opcodes.ACC_SUPER;
+ this.modifiers = modifiers & ~(Opcodes.ACC_SUPER | Opcodes.ACC_DEPRECATED);
+ this.name = Type.getObjectType(name).getClassName();
+ this.superClassDescriptor = superClassInternalName == null
+ ? NO_TYPE
+ : Type.getObjectType(superClassInternalName).getDescriptor();
+ this.genericSignature = genericSignature;
+ signatureResolution = GenericTypeExtractor.ForSignature.OfType.extract(genericSignature);
+ if (interfaceInternalName == null) {
+ interfaceTypeDescriptors = Collections.emptyList();
+ } else {
+ interfaceTypeDescriptors = new ArrayList<String>(interfaceInternalName.length);
+ for (String internalName : interfaceInternalName) {
+ interfaceTypeDescriptors.add(Type.getObjectType(internalName).getDescriptor());
+ }
+ }
+ this.typeContainment = typeContainment;
+ declaringTypeName = declaringTypeInternalName == null
+ ? NO_TYPE
+ : declaringTypeInternalName.replace('/', '.');
+ this.declaredTypes = declaredTypes;
+ this.anonymousType = anonymousType;
+ this.superTypeAnnotationTokens = superTypeAnnotationTokens;
+ this.typeVariableAnnotationTokens = typeVariableAnnotationTokens;
+ this.typeVariableBoundsAnnotationTokens = typeVariableBoundsAnnotationTokens;
+ this.annotationTokens = annotationTokens;
+ this.fieldTokens = fieldTokens;
+ this.methodTokens = methodTokens;
+ }
+
+ @Override
+ public Generic getSuperClass() {
+ return superClassDescriptor == null || isInterface()
+ ? Generic.UNDEFINED
+ : signatureResolution.resolveSuperClass(superClassDescriptor, typePool, superTypeAnnotationTokens.get(SUPER_CLASS_INDEX), this);
+ }
+
+ @Override
+ public TypeList.Generic getInterfaces() {
+ return signatureResolution.resolveInterfaceTypes(interfaceTypeDescriptors, typePool, superTypeAnnotationTokens, this);
+ }
+
+ @Override
+ public MethodDescription getEnclosingMethod() {
+ return typeContainment.getEnclosingMethod(typePool);
+ }
+
+ @Override
+ public TypeDescription getEnclosingType() {
+ return typeContainment.getEnclosingType(typePool);
+ }
+
+ @Override
+ public TypeList getDeclaredTypes() {
+ return new LazyTypeList(typePool, declaredTypes);
+ }
+
+ @Override
+ public boolean isAnonymousClass() {
+ return anonymousType;
+ }
+
+ @Override
+ public boolean isLocalClass() {
+ return !anonymousType && typeContainment.isLocalType();
+ }
+
+ @Override
+ public boolean isMemberClass() {
+ return typeContainment.isMemberClass();
+ }
+
+ @Override
+ public FieldList<FieldDescription.InDefinedShape> getDeclaredFields() {
+ return new FieldTokenList();
+ }
+
+ @Override
+ public MethodList<MethodDescription.InDefinedShape> getDeclaredMethods() {
+ return new MethodTokenList();
+ }
+
+ @Override
+ public PackageDescription getPackage() {
+ String name = getName();
+ int index = name.lastIndexOf('.');
+ return index == -1
+ ? PackageDescription.UNDEFINED
+ : new LazyPackageDescription(typePool, name.substring(0, index));
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public TypeDescription getDeclaringType() {
+ return declaringTypeName == null
+ ? TypeDescription.UNDEFINED
+ : typePool.describe(declaringTypeName).resolve();
+ }
+
+ @Override
+ public int getModifiers() {
+ return modifiers;
+ }
+
+ @Override
+ public int getActualModifiers(boolean superFlag) {
+ return superFlag ? (actualModifiers | Opcodes.ACC_SUPER) : actualModifiers;
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return LazyAnnotationDescription.asList(typePool, annotationTokens);
+ }
+
+ @Override
+ public TypeList.Generic getTypeVariables() {
+ return signatureResolution.resolveTypeVariables(typePool, this, typeVariableAnnotationTokens, typeVariableBoundsAnnotationTokens);
+ }
+
+ @Override
+ public String getGenericSignature() {
+ return genericSignature;
+ }
+
+ /**
+ * A list of field tokens representing each entry as a field description.
+ */
+ protected class FieldTokenList extends FieldList.AbstractBase<FieldDescription.InDefinedShape> {
+
+ @Override
+ public FieldDescription.InDefinedShape get(int index) {
+ return fieldTokens.get(index).toFieldDescription(LazyTypeDescription.this);
+ }
+
+ @Override
+ public int size() {
+ return fieldTokens.size();
+ }
+ }
+
+ /**
+ * A list of method tokens representing each entry as a method description.
+ */
+ protected class MethodTokenList extends MethodList.AbstractBase<MethodDescription.InDefinedShape> {
+
+ @Override
+ public MethodDescription.InDefinedShape get(int index) {
+ return methodTokens.get(index).toMethodDescription(LazyTypeDescription.this);
+ }
+
+ @Override
+ public int size() {
+ return methodTokens.size();
+ }
+ }
+
+ /**
+ * A declaration context encapsulates information about whether a type was declared within another type
+ * or within a method of another type.
+ */
+ protected interface TypeContainment {
+
+ /**
+ * Returns the enclosing method or {@code null} if no such method exists.
+ *
+ * @param typePool The type pool to be used for looking up linked types.
+ * @return A method description describing the linked type or {@code null}.
+ */
+ MethodDescription getEnclosingMethod(TypePool typePool);
+
+ /**
+ * Returns the enclosing type or {@code null} if no such type exists.
+ *
+ * @param typePool The type pool to be used for looking up linked types.
+ * @return A type description describing the linked type or {@code null}.
+ */
+ TypeDescription getEnclosingType(TypePool typePool);
+
+ /**
+ * Returns {@code true} if the type is self-contained.
+ *
+ * @return {@code true} if the type is self-contained.
+ */
+ boolean isSelfContained();
+
+ /**
+ * Returns {@code true} if the type is a member type.
+ *
+ * @return {@code true} if the type is a member type.
+ */
+ boolean isMemberClass();
+
+ /**
+ * Returns {@code true} if the type is a local type unless it is an anonymous type.
+ *
+ * @return {@code true} if the type is a local type unless it is an anonymous type
+ */
+ boolean isLocalType();
+
+ /**
+ * Describes a type that is not contained within another type, a method or a constructor.
+ */
+ enum SelfContained implements TypeContainment {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public MethodDescription getEnclosingMethod(TypePool typePool) {
+ return MethodDescription.UNDEFINED;
+ }
+
+ @Override
+ public TypeDescription getEnclosingType(TypePool typePool) {
+ return TypeDescription.UNDEFINED;
+ }
+
+ @Override
+ public boolean isSelfContained() {
+ return true;
+ }
+
+ @Override
+ public boolean isMemberClass() {
+ return false;
+ }
+
+ @Override
+ public boolean isLocalType() {
+ return false;
+ }
+ }
+
+ /**
+ * Describes a type that is contained within another type.
+ */
+ @EqualsAndHashCode
+ class WithinType implements TypeContainment {
+
+ /**
+ * The type's binary name.
+ */
+ private final String name;
+
+ /**
+ * {@code true} if the type is a local type unless it is an anonymous type.
+ */
+ private final boolean localType;
+
+ /**
+ * Creates a new type containment for a type that is declared within another type.
+ *
+ * @param internalName The type's internal name.
+ * @param localType {@code true} if the type is a local type unless it is an anonymous type.
+ */
+ public WithinType(String internalName, boolean localType) {
+ name = internalName.replace('/', '.');
+ this.localType = localType;
+ }
+
+ @Override
+ public MethodDescription getEnclosingMethod(TypePool typePool) {
+ return MethodDescription.UNDEFINED;
+ }
+
+ @Override
+ public TypeDescription getEnclosingType(TypePool typePool) {
+ return typePool.describe(name).resolve();
+ }
+
+ @Override
+ public boolean isSelfContained() {
+ return false;
+ }
+
+ @Override
+ public boolean isMemberClass() {
+ return !localType;
+ }
+
+ @Override
+ public boolean isLocalType() {
+ return localType;
+ }
+ }
+
+ /**
+ * Describes a type that is contained within a method or constructor.
+ */
+ @EqualsAndHashCode
+ class WithinMethod implements TypeContainment {
+
+ /**
+ * The method's declaring type's internal name.
+ */
+ private final String name;
+
+ /**
+ * The method's internal name.
+ */
+ private final String methodName;
+
+ /**
+ * The method's descriptor.
+ */
+ private final String methodDescriptor;
+
+ /**
+ * Creates a new type containment for a type that is declared within a method.
+ *
+ * @param internalName The method's declaring type's internal name.
+ * @param methodName The method's internal name.
+ * @param methodDescriptor The method's descriptor.
+ */
+ public WithinMethod(String internalName, String methodName, String methodDescriptor) {
+ name = internalName.replace('/', '.');
+ this.methodName = methodName;
+ this.methodDescriptor = methodDescriptor;
+ }
+
+ @Override
+ public MethodDescription getEnclosingMethod(TypePool typePool) {
+ return getEnclosingType(typePool).getDeclaredMethods().filter(hasMethodName(methodName).and(hasDescriptor(methodDescriptor))).getOnly();
+ }
+
+ @Override
+ public TypeDescription getEnclosingType(TypePool typePool) {
+ return typePool.describe(name).resolve();
+ }
+
+ @Override
+ public boolean isSelfContained() {
+ return false;
+ }
+
+ @Override
+ public boolean isMemberClass() {
+ return false;
+ }
+
+ @Override
+ public boolean isLocalType() {
+ return true;
+ }
+ }
+ }
+
+ /**
+ * A token that represents a generic Java type.
+ */
+ protected interface GenericTypeToken {
+
+ /**
+ * Represents an empty type path.
+ */
+ String EMPTY_TYPE_PATH = "";
+
+ /**
+ * Represents a step to a component type within a type path.
+ */
+ char COMPONENT_TYPE_PATH = '[';
+
+ /**
+ * Represents a wildcard type step within a type path.
+ */
+ char WILDCARD_TYPE_PATH = '*';
+
+ /**
+ * Represents a (reversed) step to an inner class within a type path.
+ */
+ char INNER_CLASS_PATH = '.';
+
+ /**
+ * Represents an index type delimiter within a type path.
+ */
+ char INDEXED_TYPE_DELIMITER = ';';
+
+ /**
+ * Transforms this token into a generic type representation.
+ *
+ * @param typePool The type pool to be used for locating non-generic type descriptions.
+ * @param typeVariableSource The type variable source.
+ * @param typePath The type path of the resolved generic type.
+ * @param annotationTokens A mapping of the type's annotation tokens by their type path.
+ * @return A description of the represented generic type.
+ */
+ Generic toGenericType(TypePool typePool, TypeVariableSource typeVariableSource, String typePath, Map<String, List<AnnotationToken>> annotationTokens);
+
+ /**
+ * Determines if a generic type tokens represents a primary bound of a type variable. This method must only be invoked on types
+ * that represent a {@link Sort#NON_GENERIC},
+ * {@link Sort#PARAMETERIZED} or {@link Sort#VARIABLE}.
+ *
+ * @param typePool The type pool to use.
+ * @return {@code true} if this token represents a primary bound.
+ */
+ boolean isPrimaryBound(TypePool typePool);
+
+ /**
+ * Returns the type path prefix that needs to be appended to the existing type path before any further navigation on the parameterized
+ * type. This method must only be called on type tokens that represent parameterized type
+ *
+ * @return A type path segment that needs to be appended to the base type path before any further navigation on the parameterized type.
+ */
+ String getTypePathPrefix();
+
+ /**
+ * Represents a generic type token for a formal type variable.
+ */
+ interface OfFormalTypeVariable {
+
+ /**
+ * Transforms this token into a generic type representation.
+ *
+ * @param typePool The type pool to be used for locating non-generic type descriptions.
+ * @param typeVariableSource The type variable source.
+ * @param annotationTokens A mapping of the type variables' type annotations.
+ * @param boundaryAnnotationTokens A mapping of the type variables' bounds' type annotation by their bound index.
+ * @return A generic type representation of this formal type variable.
+ */
+ Generic toGenericType(TypePool typePool,
+ TypeVariableSource typeVariableSource,
+ Map<String, List<AnnotationToken>> annotationTokens,
+ Map<Integer, Map<String, List<AnnotationToken>>> boundaryAnnotationTokens);
+ }
+
+ /**
+ * A generic type token that represents a primitive type.
+ */
+ enum ForPrimitiveType implements GenericTypeToken {
+
+ /**
+ * The generic type token describing the {@code boolean} type.
+ */
+ BOOLEAN(boolean.class),
+
+ /**
+ * The generic type token describing the {@code byte} type.
+ */
+ BYTE(byte.class),
+
+ /**
+ * The generic type token describing the {@code short} type.
+ */
+ SHORT(short.class),
+
+ /**
+ * The generic type token describing the {@code char} type.
+ */
+ CHAR(char.class),
+
+ /**
+ * The generic type token describing the {@code int} type.
+ */
+ INTEGER(int.class),
+
+ /**
+ * The generic type token describing the {@code long} type.
+ */
+ LONG(long.class),
+
+ /**
+ * The generic type token describing the {@code float} type.
+ */
+ FLOAT(float.class),
+
+ /**
+ * The generic type token describing the {@code double} type.
+ */
+ DOUBLE(double.class),
+
+ /**
+ * The generic type token describing the {@code void} type.
+ */
+ VOID(void.class);
+
+ /**
+ * A description of this primitive type token.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new primitive type token.
+ *
+ * @param type The loaded type representing this primitive.
+ */
+ ForPrimitiveType(Class<?> type) {
+ typeDescription = new ForLoadedType(type);
+ }
+
+ /**
+ * Resolves a generic type token of a primitive type.
+ *
+ * @param descriptor The descriptor of the primitive type.
+ * @return The corresponding generic type token.
+ */
+ public static GenericTypeToken of(char descriptor) {
+ switch (descriptor) {
+ case 'V':
+ return VOID;
+ case 'Z':
+ return BOOLEAN;
+ case 'B':
+ return BYTE;
+ case 'S':
+ return SHORT;
+ case 'C':
+ return CHAR;
+ case 'I':
+ return INTEGER;
+ case 'J':
+ return LONG;
+ case 'F':
+ return FLOAT;
+ case 'D':
+ return DOUBLE;
+ default:
+ throw new IllegalArgumentException("Not a valid primitive type descriptor: " + descriptor);
+ }
+ }
+
+ @Override
+ public Generic toGenericType(TypePool typePool,
+ TypeVariableSource typeVariableSource,
+ String typePath,
+ Map<String, List<AnnotationToken>> annotationTokens) {
+ return new LazyPrimitiveType(typePool,
+ typePath,
+ annotationTokens == null
+ ? Collections.<String, List<AnnotationToken>>emptyMap()
+ : annotationTokens,
+ typeDescription);
+ }
+
+ @Override
+ public boolean isPrimaryBound(TypePool typePool) {
+ throw new IllegalStateException("A primitive type cannot be a type variable bound: " + this);
+ }
+
+ @Override
+ public String getTypePathPrefix() {
+ throw new IllegalStateException("A primitive type cannot be the owner of a nested type: " + this);
+ }
+
+ /**
+ * A representation of a lazy primitive type.
+ */
+ protected static class LazyPrimitiveType extends Generic.OfNonGenericType {
+
+ /**
+ * The type pool to use.
+ */
+ private final TypePool typePool;
+
+ /**
+ * This type's type path.
+ */
+ private final String typePath;
+
+ /**
+ * This type's type annotation tokens.
+ */
+ private final Map<String, List<AnnotationToken>> annotationTokens;
+
+ /**
+ * The represented type's description.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new lazy primitive type.
+ *
+ * @param typePool The type pool to use.
+ * @param typePath This type's type path.
+ * @param annotationTokens This type's type annotation tokens.
+ * @param typeDescription The represented type's description.
+ */
+ protected LazyPrimitiveType(TypePool typePool,
+ String typePath,
+ Map<String, List<AnnotationToken>> annotationTokens,
+ TypeDescription typeDescription) {
+ this.typePool = typePool;
+ this.typePath = typePath;
+ this.annotationTokens = annotationTokens;
+ this.typeDescription = typeDescription;
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ return typeDescription;
+ }
+
+ @Override
+ public Generic getOwnerType() {
+ return Generic.UNDEFINED;
+ }
+
+ @Override
+ public Generic getComponentType() {
+ return Generic.UNDEFINED;
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return LazyAnnotationDescription.asListOfNullable(typePool, annotationTokens.get(typePath));
+ }
+ }
+ }
+
+ /**
+ * A generic type token that represents an unbound wildcard.
+ */
+ enum ForUnboundWildcard implements GenericTypeToken {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Generic toGenericType(TypePool typePool,
+ TypeVariableSource typeVariableSource,
+ String typePath,
+ Map<String, List<AnnotationToken>> annotationTokens) {
+ return new LazyUnboundWildcard(typePool,
+ typePath,
+ annotationTokens == null
+ ? Collections.<String, List<AnnotationToken>>emptyMap()
+ : annotationTokens);
+ }
+
+ @Override
+ public boolean isPrimaryBound(TypePool typePool) {
+ throw new IllegalStateException("A wildcard type cannot be a type variable bound: " + this);
+ }
+
+ @Override
+ public String getTypePathPrefix() {
+ throw new IllegalStateException("An unbound wildcard cannot be the owner of a nested type: " + this);
+ }
+
+ /**
+ * A generic type representation of a generic unbound wildcard.
+ */
+ protected static class LazyUnboundWildcard extends Generic.OfWildcardType {
+
+ /**
+ * The type pool to use.
+ */
+ private final TypePool typePool;
+
+ /**
+ * This type's type path.
+ */
+ private final String typePath;
+
+ /**
+ * The type's type annotations.
+ */
+ private final Map<String, List<AnnotationToken>> annotationTokens;
+
+ /**
+ * Creates a new lazy unbound wildcard.
+ *
+ * @param typePool The type pool to use.
+ * @param typePath This type's type path.
+ * @param annotationTokens The type's type annotations.
+ */
+ protected LazyUnboundWildcard(TypePool typePool, String typePath, Map<String, List<AnnotationToken>> annotationTokens) {
+ this.typePool = typePool;
+ this.typePath = typePath;
+ this.annotationTokens = annotationTokens;
+ }
+
+ @Override
+ public TypeList.Generic getUpperBounds() {
+ return new TypeList.Generic.Explicit(Generic.OBJECT);
+ }
+
+ @Override
+ public TypeList.Generic getLowerBounds() {
+ return new TypeList.Generic.Empty();
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return LazyAnnotationDescription.asListOfNullable(typePool, annotationTokens.get(typePath));
+ }
+ }
+ }
+
+ /**
+ * A resolution of a type's, method's or field's generic types.
+ */
+ interface Resolution {
+
+ /**
+ * Resolves the type variables of the represented element.
+ *
+ * @param typePool The type pool to be used for locating non-generic type descriptions.
+ * @param typeVariableSource The type variable source to use for resolving type variables.
+ * @param annotationTokens A mapping of the type variables' type annotation tokens by their indices.
+ * @param boundAnnotationTokens A mapping of the type variables' bounds' type annotation tokens by their indices
+ * and each type variable's index.
+ * @return A list describing the resolved generic types.
+ */
+ TypeList.Generic resolveTypeVariables(TypePool typePool,
+ TypeVariableSource typeVariableSource,
+ Map<Integer, Map<String, List<AnnotationToken>>> annotationTokens,
+ Map<Integer, Map<Integer, Map<String, List<AnnotationToken>>>> boundAnnotationTokens);
+
+ /**
+ * A resolution of a type's, method's or field's generic types if all of the represented element's are raw.
+ */
+ enum Raw implements ForType, ForMethod, ForField {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Generic resolveFieldType(String fieldTypeDescriptor,
+ TypePool typePool,
+ Map<String, List<AnnotationToken>> annotationTokens,
+ FieldDescription.InDefinedShape definingField) {
+ return RawAnnotatedType.of(typePool, annotationTokens, fieldTypeDescriptor);
+ }
+
+ @Override
+ public Generic resolveReturnType(String returnTypeDescriptor,
+ TypePool typePool,
+ Map<String, List<AnnotationToken>> annotationTokens,
+ MethodDescription.InDefinedShape definingMethod) {
+ return RawAnnotatedType.of(typePool, annotationTokens, returnTypeDescriptor);
+ }
+
+ @Override
+ public TypeList.Generic resolveParameterTypes(List<String> parameterTypeDescriptors,
+ TypePool typePool,
+ Map<Integer, Map<String, List<AnnotationToken>>> annotationTokens,
+ MethodDescription.InDefinedShape definingMethod) {
+ return RawAnnotatedType.LazyRawAnnotatedTypeList.of(typePool, annotationTokens, parameterTypeDescriptors);
+ }
+
+ @Override
+ public TypeList.Generic resolveExceptionTypes(List<String> exceptionTypeDescriptors,
+ TypePool typePool,
+ Map<Integer, Map<String, List<AnnotationToken>>> annotationTokens,
+ MethodDescription.InDefinedShape definingMethod) {
+ return RawAnnotatedType.LazyRawAnnotatedTypeList.of(typePool, annotationTokens, exceptionTypeDescriptors);
+ }
+
+ @Override
+ public Generic resolveSuperClass(String superClassDescriptor,
+ TypePool typePool,
+ Map<String, List<AnnotationToken>> annotationTokens,
+ TypeDescription definingType) {
+ return RawAnnotatedType.of(typePool, annotationTokens, superClassDescriptor);
+ }
+
+ @Override
+ public TypeList.Generic resolveInterfaceTypes(List<String> interfaceTypeDescriptors,
+ TypePool typePool,
+ Map<Integer, Map<String, List<AnnotationToken>>> annotationTokens,
+ TypeDescription definingType) {
+ return RawAnnotatedType.LazyRawAnnotatedTypeList.of(typePool, annotationTokens, interfaceTypeDescriptors);
+ }
+
+ @Override
+ public TypeList.Generic resolveTypeVariables(TypePool typePool,
+ TypeVariableSource typeVariableSource,
+ Map<Integer, Map<String, List<AnnotationToken>>> annotationTokens,
+ Map<Integer, Map<Integer, Map<String, List<AnnotationToken>>>> boundAnnotationTokens) {
+ return new TypeList.Generic.Empty();
+ }
+
+ /**
+ * Represents a non-generic type that defines type annotations.
+ */
+ protected static class RawAnnotatedType extends Generic.OfNonGenericType {
+
+ /**
+ * The type pool to use.
+ */
+ private final TypePool typePool;
+
+ /**
+ * The type's type path.
+ */
+ private final String typePath;
+
+ /**
+ * A mapping of this type's type annotations.
+ */
+ private final Map<String, List<AnnotationToken>> annotationTokens;
+
+ /**
+ * The represented non-generic type.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new raw annotated type.
+ *
+ * @param typePool The type pool to use.
+ * @param typePath The type's type path.
+ * @param annotationTokens A mapping of this type's type annotations.
+ * @param typeDescription The represented non-generic type.
+ */
+ protected RawAnnotatedType(TypePool typePool,
+ String typePath,
+ Map<String, List<AnnotationToken>> annotationTokens,
+ TypeDescription typeDescription) {
+ this.typePool = typePool;
+ this.typePath = typePath;
+ this.annotationTokens = annotationTokens;
+ this.typeDescription = typeDescription;
+ }
+
+ /**
+ * Creates a new raw annotated type.
+ *
+ * @param typePool The type pool to use.
+ * @param annotationTokens A mapping of this type's type annotations.
+ * @param descriptor The descriptor of the represented non-generic type.
+ * @return An annotated non-generic type.
+ */
+ protected static Generic of(TypePool typePool, Map<String, List<AnnotationToken>> annotationTokens, String descriptor) {
+ return new RawAnnotatedType(typePool,
+ EMPTY_TYPE_PATH,
+ annotationTokens == null
+ ? Collections.<String, List<AnnotationToken>>emptyMap()
+ : annotationTokens,
+ TokenizedGenericType.toErasure(typePool, descriptor));
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ return typeDescription;
+ }
+
+ @Override
+ public Generic getOwnerType() {
+ TypeDescription declaringType = typeDescription.getDeclaringType();
+ return declaringType == null
+ ? Generic.UNDEFINED
+ : new RawAnnotatedType(typePool, typePath, annotationTokens, declaringType);
+ }
+
+ @Override
+ public Generic getComponentType() {
+ TypeDescription componentType = typeDescription.getComponentType();
+ return componentType == null
+ ? Generic.UNDEFINED
+ : new RawAnnotatedType(typePool, typePath + COMPONENT_TYPE_PATH, annotationTokens, componentType);
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ StringBuilder typePath = new StringBuilder(this.typePath);
+ for (int index = 0; index < typeDescription.getSegmentCount(); index++) {
+ typePath = typePath.append(INNER_CLASS_PATH);
+ }
+ return LazyAnnotationDescription.asListOfNullable(typePool, annotationTokens.get(typePath.toString()));
+ }
+
+ /**
+ * A generic type list representing raw types.
+ */
+ protected static class LazyRawAnnotatedTypeList extends TypeList.Generic.AbstractBase {
+
+ /**
+ * The type pool to use for locating types.
+ */
+ private final TypePool typePool;
+
+ /**
+ * A mapping of the represented types' type annotation tokens by their indices.
+ */
+ private final Map<Integer, Map<String, List<AnnotationToken>>> annotationTokens;
+
+ /**
+ * A list of type descriptors that this list represents.
+ */
+ private final List<String> descriptors;
+
+ /**
+ * Creates a generic type list only representing raw types.
+ *
+ * @param typePool The type pool to use for locating types.
+ * @param annotationTokens A mapping of the represented types' type annotation tokens by their indices.
+ * @param descriptors A list of type descriptors that this list represents.
+ */
+ protected LazyRawAnnotatedTypeList(TypePool typePool,
+ Map<Integer, Map<String, List<AnnotationToken>>> annotationTokens,
+ List<String> descriptors) {
+ this.typePool = typePool;
+ this.annotationTokens = annotationTokens;
+ this.descriptors = descriptors;
+ }
+
+ /**
+ * Creates generic type list only representing raw types.
+ *
+ * @param typePool The type pool to use for locating types.
+ * @param annotationTokens A mapping of the represented types' type annotation tokens by their indices or
+ * {@code null} if no type annotations are defined for any type.
+ * @param descriptors A list of type descriptors that this list represents.
+ * @return A generic type list representing the raw types this list represents.
+ */
+ protected static TypeList.Generic of(TypePool typePool,
+ Map<Integer, Map<String, List<AnnotationToken>>> annotationTokens,
+ List<String> descriptors) {
+ return new LazyRawAnnotatedTypeList(typePool,
+ annotationTokens == null
+ ? Collections.<Integer, Map<String, List<AnnotationToken>>>emptyMap()
+ : annotationTokens,
+ descriptors);
+ }
+
+ @Override
+ public Generic get(int index) {
+ return RawAnnotatedType.of(typePool, annotationTokens.get(index), descriptors.get(index));
+ }
+
+ @Override
+ public int size() {
+ return descriptors.size();
+ }
+
+ @Override
+ public TypeList asErasures() {
+ return new LazyTypeList(typePool, descriptors);
+ }
+
+ @Override
+ public TypeList.Generic asRawTypes() {
+ return this;
+ }
+
+ @Override
+ public int getStackSize() {
+ int stackSize = 0;
+ for (String descriptor : descriptors) {
+ stackSize += Type.getType(descriptor).getSize();
+ }
+ return stackSize;
+ }
+ }
+ }
+ }
+
+ /**
+ * A resolution of a type's, method's or field's generic types if its generic signature is malformed.
+ */
+ enum Malformed implements ForType, ForMethod, ForField {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Generic resolveFieldType(String fieldTypeDescriptor,
+ TypePool typePool,
+ Map<String, List<AnnotationToken>> annotationTokens,
+ FieldDescription.InDefinedShape definingField) {
+ return new TokenizedGenericType.Malformed(typePool, fieldTypeDescriptor);
+ }
+
+ @Override
+ public Generic resolveReturnType(String returnTypeDescriptor,
+ TypePool typePool,
+ Map<String, List<AnnotationToken>> annotationTokens,
+ MethodDescription.InDefinedShape definingMethod) {
+ return new TokenizedGenericType.Malformed(typePool, returnTypeDescriptor);
+ }
+
+ @Override
+ public TypeList.Generic resolveParameterTypes(List<String> parameterTypeDescriptors,
+ TypePool typePool,
+ Map<Integer, Map<String, List<AnnotationToken>>> annotationTokens,
+ MethodDescription.InDefinedShape definingMethod) {
+ return new TokenizedGenericType.Malformed.TokenList(typePool, parameterTypeDescriptors);
+ }
+
+ @Override
+ public TypeList.Generic resolveExceptionTypes(List<String> exceptionTypeDescriptors,
+ TypePool typePool,
+ Map<Integer, Map<String, List<AnnotationToken>>> annotationTokens,
+ MethodDescription.InDefinedShape definingMethod) {
+ return new TokenizedGenericType.Malformed.TokenList(typePool, exceptionTypeDescriptors);
+ }
+
+ @Override
+ public Generic resolveSuperClass(String superClassDescriptor,
+ TypePool typePool,
+ Map<String, List<AnnotationToken>> annotationTokens,
+ TypeDescription definingType) {
+ return new TokenizedGenericType.Malformed(typePool, superClassDescriptor);
+ }
+
+ @Override
+ public TypeList.Generic resolveInterfaceTypes(List<String> interfaceTypeDescriptors,
+ TypePool typePool,
+ Map<Integer, Map<String, List<AnnotationToken>>> annotationTokens,
+ TypeDescription definingType) {
+ return new TokenizedGenericType.Malformed.TokenList(typePool, interfaceTypeDescriptors);
+ }
+
+ @Override
+ public TypeList.Generic resolveTypeVariables(TypePool typePool,
+ TypeVariableSource typeVariableSource,
+ Map<Integer, Map<String, List<AnnotationToken>>> annotationTokens,
+ Map<Integer, Map<Integer, Map<String, List<AnnotationToken>>>> boundAnnotationTokens) {
+ throw new GenericSignatureFormatError();
+ }
+ }
+
+ /**
+ * A resolution of the generic types of a {@link TypeDescription}.
+ */
+ interface ForType extends Resolution {
+
+ /**
+ * Resolves the generic super type of the represented type.
+ *
+ * @param superClassDescriptor The descriptor of the raw super type.
+ * @param typePool The type pool to be used for locating non-generic type descriptions.
+ * @param annotationTokens A mapping of the super type's type annotation tokens.
+ * @param definingType The type that defines this super type.
+ * @return A description of this type's generic super type.
+ */
+ Generic resolveSuperClass(String superClassDescriptor,
+ TypePool typePool,
+ Map<String, List<AnnotationToken>> annotationTokens,
+ TypeDescription definingType);
+
+ /**
+ * Resolves the generic interface types of the represented type.
+ *
+ * @param interfaceTypeDescriptors The descriptor of the raw interface types.
+ * @param typePool The type pool to be used for locating non-generic type descriptions.
+ * @param annotationTokens A mapping of the interface types' type annotation tokens by their indices.
+ * @param definingType The type that defines these interface type.
+ * @return A description of this type's generic interface types.
+ */
+ TypeList.Generic resolveInterfaceTypes(List<String> interfaceTypeDescriptors,
+ TypePool typePool,
+ Map<Integer, Map<String, List<AnnotationToken>>> annotationTokens,
+ TypeDescription definingType);
+
+ /**
+ * An implementation of a tokenized resolution of generic types of a {@link TypeDescription}.
+ */
+ @EqualsAndHashCode
+ class Tokenized implements ForType {
+
+ /**
+ * The super type's generic type token.
+ */
+ private final GenericTypeToken superClassToken;
+
+ /**
+ * The interface type's generic type tokens.
+ */
+ private final List<GenericTypeToken> interfaceTypeTokens;
+
+ /**
+ * The type variables generic type tokens.
+ */
+ private final List<OfFormalTypeVariable> typeVariableTokens;
+
+ /**
+ * Creates a new tokenized resolution of a {@link TypeDescription}'s generic signatures.
+ *
+ * @param superClassToken The super class's generic type token.
+ * @param interfaceTypeTokens The interface type's generic type tokens.
+ * @param typeVariableTokens The type variables generic type tokens.
+ */
+ public Tokenized(GenericTypeToken superClassToken,
+ List<GenericTypeToken> interfaceTypeTokens,
+ List<OfFormalTypeVariable> typeVariableTokens) {
+ this.superClassToken = superClassToken;
+ this.interfaceTypeTokens = interfaceTypeTokens;
+ this.typeVariableTokens = typeVariableTokens;
+ }
+
+ @Override
+ public Generic resolveSuperClass(String superClassDescriptor,
+ TypePool typePool,
+ Map<String, List<AnnotationToken>> annotationTokens,
+ TypeDescription definingType) {
+ return TokenizedGenericType.of(typePool, superClassToken, superClassDescriptor, annotationTokens, definingType);
+ }
+
+ @Override
+ public TypeList.Generic resolveInterfaceTypes(List<String> interfaceTypeDescriptors,
+ TypePool typePool,
+ Map<Integer, Map<String, List<AnnotationToken>>> annotationTokens,
+ TypeDescription definingType) {
+ return new TokenizedGenericType.TokenList(typePool, interfaceTypeTokens, annotationTokens, interfaceTypeDescriptors, definingType);
+ }
+
+ @Override
+ public TypeList.Generic resolveTypeVariables(TypePool typePool,
+ TypeVariableSource typeVariableSource,
+ Map<Integer, Map<String, List<AnnotationToken>>> annotationTokens,
+ Map<Integer, Map<Integer, Map<String, List<AnnotationToken>>>> boundAnnotationTokens) {
+ return new TokenizedGenericType.TypeVariableList(typePool, typeVariableTokens, typeVariableSource, annotationTokens, boundAnnotationTokens);
+ }
+ }
+ }
+
+ /**
+ * A resolution of the generic types of a {@link MethodDescription}.
+ */
+ interface ForMethod extends Resolution {
+
+ /**
+ * Resolves the return type of the represented method.
+ *
+ * @param returnTypeDescriptor The descriptor of the raw return type.
+ * @param typePool The type pool to be used for locating non-generic type descriptions.
+ * @param annotationTokens A mapping of the return type's type annotation tokens.
+ * @param definingMethod The method that defines this return type.
+ * @return A description of this type's generic return type.
+ */
+ Generic resolveReturnType(String returnTypeDescriptor,
+ TypePool typePool,
+ Map<String, List<AnnotationToken>> annotationTokens,
+ MethodDescription.InDefinedShape definingMethod);
+
+ /**
+ * Resolves the generic parameter types of the represented method.
+ *
+ * @param parameterTypeDescriptors The descriptor of the raw parameter types.
+ * @param typePool The type pool to be used for locating non-generic type descriptions.
+ * @param annotationTokens A mapping of the parameter types' type annotation tokens by their indices.
+ * @param definingMethod The method that defines these parameter types.
+ * @return A description of this type's generic interface types.
+ */
+ TypeList.Generic resolveParameterTypes(List<String> parameterTypeDescriptors,
+ TypePool typePool,
+ Map<Integer, Map<String, List<AnnotationToken>>> annotationTokens,
+ MethodDescription.InDefinedShape definingMethod);
+
+ /**
+ * Resolves the generic parameter types of the represented method.
+ *
+ * @param exceptionTypeDescriptors The descriptor of the raw exception types.
+ * @param typePool The type pool to be used for locating non-generic type descriptions.
+ * @param annotationTokens A mapping of the exception types' type annotation tokens by their indices.
+ * @param definingMethod The method that defines these exception types.
+ * @return A description of this type's generic interface types.
+ */
+ TypeList.Generic resolveExceptionTypes(List<String> exceptionTypeDescriptors,
+ TypePool typePool,
+ Map<Integer, Map<String, List<AnnotationToken>>> annotationTokens,
+ MethodDescription.InDefinedShape definingMethod);
+
+ /**
+ * An implementation of a tokenized resolution of generic types of a {@link MethodDescription}.
+ */
+ @EqualsAndHashCode
+ class Tokenized implements ForMethod {
+
+ /**
+ * A token describing the represented method's return type.
+ */
+ private final GenericTypeToken returnTypeToken;
+
+ /**
+ * A token describing the represented method's parameter types.
+ */
+ private final List<GenericTypeToken> parameterTypeTokens;
+
+ /**
+ * A token describing the represented method's exception types.
+ */
+ private final List<GenericTypeToken> exceptionTypeTokens;
+
+ /**
+ * A token describing the represented method's type variables.
+ */
+ private final List<OfFormalTypeVariable> typeVariableTokens;
+
+ /**
+ * Creates a new tokenized resolution of a {@link MethodDescription}'s generic signatures.
+ *
+ * @param returnTypeToken A token describing the represented method's return type.
+ * @param parameterTypeTokens A token describing the represented method's parameter types.
+ * @param exceptionTypeTokens A token describing the represented method's exception types.
+ * @param typeVariableTokens A token describing the represented method's type variables.
+ */
+ public Tokenized(GenericTypeToken returnTypeToken,
+ List<GenericTypeToken> parameterTypeTokens,
+ List<GenericTypeToken> exceptionTypeTokens,
+ List<OfFormalTypeVariable> typeVariableTokens) {
+ this.returnTypeToken = returnTypeToken;
+ this.parameterTypeTokens = parameterTypeTokens;
+ this.exceptionTypeTokens = exceptionTypeTokens;
+ this.typeVariableTokens = typeVariableTokens;
+ }
+
+ @Override
+ public Generic resolveReturnType(String returnTypeDescriptor,
+ TypePool typePool,
+ Map<String, List<AnnotationToken>> annotationTokens,
+ MethodDescription.InDefinedShape definingMethod) {
+ return TokenizedGenericType.of(typePool, returnTypeToken, returnTypeDescriptor, annotationTokens, definingMethod);
+ }
+
+ @Override
+ public TypeList.Generic resolveParameterTypes(List<String> parameterTypeDescriptors,
+ TypePool typePool,
+ Map<Integer, Map<String, List<AnnotationToken>>> annotationTokens,
+ MethodDescription.InDefinedShape definingMethod) {
+ return new TokenizedGenericType.TokenList(typePool, parameterTypeTokens, annotationTokens, parameterTypeDescriptors, definingMethod);
+ }
+
+ @Override
+ public TypeList.Generic resolveExceptionTypes(List<String> exceptionTypeDescriptors,
+ TypePool typePool,
+ Map<Integer, Map<String, List<AnnotationToken>>> annotationTokens,
+ MethodDescription.InDefinedShape definingMethod) {
+ // Generic signatures of methods are optional.
+ return exceptionTypeTokens.isEmpty()
+ ? Raw.INSTANCE.resolveExceptionTypes(exceptionTypeDescriptors, typePool, annotationTokens, definingMethod)
+ : new TokenizedGenericType.TokenList(typePool, exceptionTypeTokens, annotationTokens, exceptionTypeDescriptors, definingMethod);
+ }
+
+ @Override
+ public TypeList.Generic resolveTypeVariables(TypePool typePool,
+ TypeVariableSource typeVariableSource,
+ Map<Integer, Map<String, List<AnnotationToken>>> annotationTokens,
+ Map<Integer, Map<Integer, Map<String, List<AnnotationToken>>>> boundAnnotationTokens) {
+ return new TokenizedGenericType.TypeVariableList(typePool, typeVariableTokens, typeVariableSource, annotationTokens, boundAnnotationTokens);
+ }
+ }
+ }
+
+ /**
+ * A resolution of the generic types of a {@link FieldDescription}.
+ */
+ interface ForField {
+
+ /**
+ * Resolves the field type of the represented field.
+ *
+ * @param fieldTypeDescriptor The descriptor of the raw field type.
+ * @param annotationTokens A mapping of the represented types' type annotation tokens.
+ * @param typePool The type pool to be used for locating non-generic type descriptions.
+ * @param definingField The field that defines this type. @return A description of this field's type.
+ * @return A generic type representation of the field's type.
+ */
+ Generic resolveFieldType(String fieldTypeDescriptor,
+ TypePool typePool,
+ Map<String, List<AnnotationToken>> annotationTokens,
+ FieldDescription.InDefinedShape definingField);
+
+ /**
+ * An implementation of a tokenized resolution of the generic type of a {@link FieldDescription}.
+ */
+ @EqualsAndHashCode
+ class Tokenized implements ForField {
+
+ /**
+ * The token of the represented field's type.
+ */
+ private final GenericTypeToken fieldTypeToken;
+
+ /**
+ * Creates a new tokenized resolution of a {@link FieldDescription}'s type.
+ *
+ * @param fieldTypeToken The token of the represented field's type.
+ */
+ public Tokenized(GenericTypeToken fieldTypeToken) {
+ this.fieldTypeToken = fieldTypeToken;
+ }
+
+ @Override
+ public Generic resolveFieldType(String fieldTypeDescriptor,
+ TypePool typePool,
+ Map<String, List<AnnotationToken>> annotationTokens,
+ FieldDescription.InDefinedShape definingField) {
+ return TokenizedGenericType.of(typePool, fieldTypeToken, fieldTypeDescriptor, annotationTokens, definingField.getDeclaringType());
+ }
+ }
+ }
+ }
+
+ /**
+ * A generic type token that represents a non-generic type.
+ */
+ @EqualsAndHashCode
+ class ForRawType implements GenericTypeToken {
+
+ /**
+ * The name of the represented type.
+ */
+ private final String name;
+
+ /**
+ * Creates a new type token that represents a non-generic type.
+ *
+ * @param name The name of the represented type.
+ */
+ protected ForRawType(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public Generic toGenericType(TypePool typePool,
+ TypeVariableSource typeVariableSource,
+ String typePath,
+ Map<String, List<AnnotationToken>> annotationTokens) {
+ return new Resolution.Raw.RawAnnotatedType(typePool,
+ typePath,
+ annotationTokens == null
+ ? Collections.<String, List<AnnotationToken>>emptyMap()
+ : annotationTokens,
+ typePool.describe(name).resolve());
+ }
+
+ @Override
+ public boolean isPrimaryBound(TypePool typePool) {
+ return !typePool.describe(name).resolve().isInterface();
+ }
+
+ @Override
+ public String getTypePathPrefix() {
+ throw new IllegalStateException("A non-generic type cannot be the owner of a nested type: " + this);
+ }
+ }
+
+ /**
+ * A generic type token that represents a type variable.
+ */
+ @EqualsAndHashCode
+ class ForTypeVariable implements GenericTypeToken {
+
+ /**
+ * This type variable's nominal symbol.
+ */
+ private final String symbol;
+
+ /**
+ * Creates a generic type token that represents a type variable.
+ *
+ * @param symbol This type variable's nominal symbol.
+ */
+ protected ForTypeVariable(String symbol) {
+ this.symbol = symbol;
+ }
+
+ @Override
+ public Generic toGenericType(TypePool typePool, TypeVariableSource typeVariableSource, String typePath, Map<String, List<AnnotationToken>> annotationTokens) {
+ Generic typeVariable = typeVariableSource.findVariable(symbol);
+ return typeVariable == null
+ ? new UnresolvedTypeVariable(typeVariableSource, typePool, symbol, annotationTokens.get(typePath))
+ : new AnnotatedTypeVariable(typePool, annotationTokens.get(typePath), typeVariable);
+ }
+
+ @Override
+ public boolean isPrimaryBound(TypePool typePool) {
+ return true;
+ }
+
+ @Override
+ public String getTypePathPrefix() {
+ throw new IllegalStateException("A type variable cannot be the owner of a nested type: " + this);
+ }
+
+ /**
+ * An annotated representation of a formal type variable.
+ */
+ protected static class AnnotatedTypeVariable extends Generic.OfTypeVariable {
+
+ /**
+ * The type pool to use.
+ */
+ private final TypePool typePool;
+
+ /**
+ * The represented annotation tokens.
+ */
+ private final List<AnnotationToken> annotationTokens;
+
+ /**
+ * The represented type variable.
+ */
+ private final Generic typeVariable;
+
+ /**
+ * Creates a new annotated type variable.
+ *
+ * @param typePool The type pool to use.
+ * @param annotationTokens The represented annotation tokens.
+ * @param typeVariable The represented type variable.
+ */
+ protected AnnotatedTypeVariable(TypePool typePool, List<AnnotationToken> annotationTokens, Generic typeVariable) {
+ this.typePool = typePool;
+ this.annotationTokens = annotationTokens;
+ this.typeVariable = typeVariable;
+ }
+
+ @Override
+ public TypeList.Generic getUpperBounds() {
+ return typeVariable.getUpperBounds();
+ }
+
+ @Override
+ public TypeVariableSource getTypeVariableSource() {
+ return typeVariable.getTypeVariableSource();
+ }
+
+ @Override
+ public String getSymbol() {
+ return typeVariable.getSymbol();
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return LazyAnnotationDescription.asListOfNullable(typePool, annotationTokens);
+ }
+ }
+
+ /**
+ * Represents a type variable that a type references but that does not exist. Such type variables are only emitted by wrongful
+ * compilation either due to the isolated recompilation of outer classes or due to bugs in compilers.
+ */
+ protected static class UnresolvedTypeVariable extends Generic.OfTypeVariable {
+
+ /**
+ * The undeclared type variable's source.
+ */
+ private final TypeVariableSource typeVariableSource;
+
+ /**
+ * The type pool to use.
+ */
+ private final TypePool typePool;
+
+ /**
+ * The type variable's symbol.
+ */
+ private final String symbol;
+
+ /**
+ * The type variable's annotation tokens.
+ */
+ private final List<AnnotationToken> annotationTokens;
+
+ /**
+ * Creates an unresolved type variable.
+ *
+ * @param typeVariableSource The undeclared type variable's source.
+ * @param typePool The type pool to use.
+ * @param symbol The type variable's symbol.
+ * @param annotationTokens The type variable's annotation tokens.
+ */
+ protected UnresolvedTypeVariable(TypeVariableSource typeVariableSource,
+ TypePool typePool,
+ String symbol,
+ List<AnnotationToken> annotationTokens) {
+ this.typeVariableSource = typeVariableSource;
+ this.typePool = typePool;
+ this.symbol = symbol;
+ this.annotationTokens = annotationTokens;
+ }
+
+ @Override
+ public TypeList.Generic getUpperBounds() {
+ throw new IllegalStateException("Cannot resolve bounds of unresolved type variable " + this + " by " + typeVariableSource);
+ }
+
+ @Override
+ public TypeVariableSource getTypeVariableSource() {
+ return typeVariableSource;
+ }
+
+ @Override
+ public String getSymbol() {
+ return symbol;
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return LazyAnnotationDescription.asListOfNullable(typePool, annotationTokens);
+ }
+ }
+
+ /**
+ * A generic type token that represent a formal type variable, i.e. a type variable including its upper bounds.
+ */
+ @EqualsAndHashCode
+ protected static class Formal implements GenericTypeToken.OfFormalTypeVariable {
+
+ /**
+ * This type variable's nominal symbol.
+ */
+ private final String symbol;
+
+ /**
+ * A list of tokens that represent this type variable's upper bounds.
+ */
+ private final List<GenericTypeToken> boundTypeTokens;
+
+ /**
+ * Creates generic type token that represent a formal type variable.
+ *
+ * @param symbol This type variable's nominal symbol.
+ * @param boundTypeTokens A list of tokens that represent this type variable's upper bounds.
+ */
+ protected Formal(String symbol, List<GenericTypeToken> boundTypeTokens) {
+ this.symbol = symbol;
+ this.boundTypeTokens = boundTypeTokens;
+ }
+
+ @Override
+ public Generic toGenericType(TypePool typePool,
+ TypeVariableSource typeVariableSource,
+ Map<String, List<AnnotationToken>> annotationTokens,
+ Map<Integer, Map<String, List<AnnotationToken>>> boundaryAnnotationTokens) {
+ return new LazyTypeVariable(typePool,
+ typeVariableSource,
+ annotationTokens == null
+ ? Collections.<String, List<AnnotationToken>>emptyMap()
+ : annotationTokens,
+ boundaryAnnotationTokens == null
+ ? Collections.<Integer, Map<String, List<AnnotationToken>>>emptyMap()
+ : boundaryAnnotationTokens,
+ symbol,
+ boundTypeTokens);
+ }
+
+ /**
+ * A type description that represents a type variable with bounds that are resolved lazily.
+ */
+ protected static class LazyTypeVariable extends Generic.OfTypeVariable {
+
+ /**
+ * The type pool to use for locating type descriptions.
+ */
+ private final TypePool typePool;
+
+ /**
+ * The type variable source to use for locating type variables.
+ */
+ private final TypeVariableSource typeVariableSource;
+
+ /**
+ * The type variable's type annotation tokens.
+ */
+ private final Map<String, List<AnnotationToken>> annotationTokens;
+
+ /**
+ * A mapping of the type variable bounds' type annotation tokens by their indices.
+ */
+ private final Map<Integer, Map<String, List<AnnotationToken>>> boundaryAnnotationTokens;
+
+ /**
+ * The type variable's symbol.
+ */
+ private final String symbol;
+
+ /**
+ * Tokenized representations of the type variables bound types.
+ */
+ private final List<GenericTypeToken> boundTypeTokens;
+
+ /**
+ * Creates a lazy type description of a type variables.
+ *
+ * @param typePool The type pool to use for locating type descriptions.
+ * @param typeVariableSource The type variable source to use for locating type variables.
+ * @param annotationTokens The type variable's type annotation tokens.
+ * @param boundaryAnnotationTokens A mapping of the type variable bounds' type annotation tokens by their indices.
+ * @param symbol The type variable's symbol.
+ * @param boundTypeTokens Tokenized representations of the type variables bound types.
+ */
+ protected LazyTypeVariable(TypePool typePool,
+ TypeVariableSource typeVariableSource,
+ Map<String, List<AnnotationToken>> annotationTokens,
+ Map<Integer, Map<String, List<AnnotationToken>>> boundaryAnnotationTokens,
+ String symbol,
+ List<GenericTypeToken> boundTypeTokens) {
+ this.typePool = typePool;
+ this.typeVariableSource = typeVariableSource;
+ this.annotationTokens = annotationTokens;
+ this.boundaryAnnotationTokens = boundaryAnnotationTokens;
+ this.symbol = symbol;
+ this.boundTypeTokens = boundTypeTokens;
+ }
+
+ @Override
+ public TypeList.Generic getUpperBounds() {
+ return new LazyBoundTokenList(typePool, typeVariableSource, boundaryAnnotationTokens, boundTypeTokens);
+ }
+
+ @Override
+ public TypeVariableSource getTypeVariableSource() {
+ return typeVariableSource;
+ }
+
+ @Override
+ public String getSymbol() {
+ return symbol;
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return LazyAnnotationDescription.asListOfNullable(typePool, annotationTokens.get(EMPTY_TYPE_PATH));
+ }
+
+ /**
+ * A list representing a formal type variable's bounds.
+ */
+ protected static class LazyBoundTokenList extends TypeList.Generic.AbstractBase {
+
+ /**
+ * The type pool to use.
+ */
+ private final TypePool typePool;
+
+ /**
+ * The type variable source for locating type variables.
+ */
+ private final TypeVariableSource typeVariableSource;
+
+ /**
+ * A mapping of the bound type's type annotations by their bound index.
+ */
+ private final Map<Integer, Map<String, List<AnnotationToken>>> annotationTokens;
+
+ /**
+ * The bound types in their tokenized form.
+ */
+ private final List<GenericTypeToken> boundTypeTokens;
+
+ /**
+ * Creates a new lazy bound token list for a type variable.
+ *
+ * @param typePool The type pool to use.
+ * @param typeVariableSource The type variable source for locating type variables.
+ * @param annotationTokens A mapping of the bound type's type annotations by their bound index.
+ * @param boundTypeTokens The bound types in their tokenized form.
+ */
+ protected LazyBoundTokenList(TypePool typePool,
+ TypeVariableSource typeVariableSource,
+ Map<Integer, Map<String, List<AnnotationToken>>> annotationTokens,
+ List<GenericTypeToken> boundTypeTokens) {
+ this.typePool = typePool;
+ this.typeVariableSource = typeVariableSource;
+ this.annotationTokens = annotationTokens;
+ this.boundTypeTokens = boundTypeTokens;
+ }
+
+ @Override
+ public Generic get(int index) {
+ // Avoid resolution of interface bound type unless a type annotation can be possibly resolved.
+ Map<String, List<AnnotationToken>> annotationTokens = !this.annotationTokens.containsKey(index) && !this.annotationTokens.containsKey(index + 1)
+ ? Collections.<String, List<AnnotationToken>>emptyMap()
+ : this.annotationTokens.get(index + (boundTypeTokens.get(0).isPrimaryBound(typePool) ? 0 : 1));
+ return boundTypeTokens.get(index).toGenericType(typePool,
+ typeVariableSource,
+ EMPTY_TYPE_PATH,
+ annotationTokens == null
+ ? Collections.<String, List<AnnotationToken>>emptyMap()
+ : annotationTokens);
+ }
+
+ @Override
+ public int size() {
+ return boundTypeTokens.size();
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * A generic type token that represents a generic array.
+ */
+ @EqualsAndHashCode
+ class ForGenericArray implements GenericTypeToken {
+
+ /**
+ * The array's component type.
+ */
+ private final GenericTypeToken componentTypeToken;
+
+ /**
+ * Creates a generic type token that represents a generic array.
+ *
+ * @param componentTypeToken The array's component type.
+ */
+ protected ForGenericArray(GenericTypeToken componentTypeToken) {
+ this.componentTypeToken = componentTypeToken;
+ }
+
+ @Override
+ public Generic toGenericType(TypePool typePool, TypeVariableSource typeVariableSource, String typePath, Map<String, List<AnnotationToken>> annotationTokens) {
+ return new LazyGenericArray(typePool, typeVariableSource, typePath, annotationTokens, componentTypeToken);
+ }
+
+ @Override
+ public boolean isPrimaryBound(TypePool typePool) {
+ throw new IllegalStateException("A generic array type cannot be a type variable bound: " + this);
+ }
+
+ @Override
+ public String getTypePathPrefix() {
+ throw new IllegalStateException("A generic array type cannot be the owner of a nested type: " + this);
+ }
+
+ /**
+ * A generic type representation of a generic array.
+ */
+ protected static class LazyGenericArray extends Generic.OfGenericArray {
+
+ /**
+ * The type pool to use.
+ */
+ private final TypePool typePool;
+
+ /**
+ * The type variable source for locating type variables.
+ */
+ private final TypeVariableSource typeVariableSource;
+
+ /**
+ * This type's type path.
+ */
+ private final String typePath;
+
+ /**
+ * This type's type annotations.
+ */
+ private final Map<String, List<AnnotationToken>> annotationTokens;
+
+ /**
+ * A tokenized representation of this generic arrays's component type.
+ */
+ private final GenericTypeToken componentTypeToken;
+
+ /**
+ * Creates a new lazy generic array.
+ *
+ * @param typePool The type pool to use.
+ * @param typeVariableSource The type variable source for locating type variables.
+ * @param typePath This type's type path.
+ * @param annotationTokens This type's type annotations.
+ * @param componentTypeToken A tokenized representation of this generic arrays's component type.
+ */
+ protected LazyGenericArray(TypePool typePool,
+ TypeVariableSource typeVariableSource,
+ String typePath,
+ Map<String, List<AnnotationToken>> annotationTokens,
+ GenericTypeToken componentTypeToken) {
+ this.typePool = typePool;
+ this.typeVariableSource = typeVariableSource;
+ this.typePath = typePath;
+ this.annotationTokens = annotationTokens;
+ this.componentTypeToken = componentTypeToken;
+ }
+
+ @Override
+ public Generic getComponentType() {
+ return componentTypeToken.toGenericType(typePool, typeVariableSource, typePath + COMPONENT_TYPE_PATH, annotationTokens);
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return LazyAnnotationDescription.asListOfNullable(typePool, annotationTokens.get(typePath));
+ }
+ }
+ }
+
+ /**
+ * A generic type token for a wildcard that is bound below.
+ */
+ @EqualsAndHashCode
+ class ForLowerBoundWildcard implements GenericTypeToken {
+
+ /**
+ * A token that represents the wildcard's lower bound.
+ */
+ private final GenericTypeToken boundTypeToken;
+
+ /**
+ * Creates a generic type token for a wildcard that is bound below.
+ *
+ * @param boundTypeToken A token that represents the wildcard's lower bound.
+ */
+ protected ForLowerBoundWildcard(GenericTypeToken boundTypeToken) {
+ this.boundTypeToken = boundTypeToken;
+ }
+
+ @Override
+ public Generic toGenericType(TypePool typePool, TypeVariableSource typeVariableSource, String typePath, Map<String, List<AnnotationToken>> annotationTokens) {
+ return new LazyLowerBoundWildcard(typePool, typeVariableSource, typePath, annotationTokens, boundTypeToken);
+ }
+
+ @Override
+ public boolean isPrimaryBound(TypePool typePool) {
+ throw new IllegalStateException("A wildcard type cannot be a type variable bound: " + this);
+ }
+
+ @Override
+ public String getTypePathPrefix() {
+ throw new IllegalStateException("A lower bound wildcard cannot be the owner of a nested type: " + this);
+ }
+
+ /**
+ * A generic type representation of a lower bound wildcard.
+ */
+ protected static class LazyLowerBoundWildcard extends Generic.OfWildcardType {
+
+ /**
+ * The type pool to use.
+ */
+ private final TypePool typePool;
+
+ /**
+ * The type variable source for locating type variables.
+ */
+ private final TypeVariableSource typeVariableSource;
+
+ /**
+ * This type's type path.
+ */
+ private final String typePath;
+
+ /**
+ * This type's type annotations.
+ */
+ private final Map<String, List<AnnotationToken>> annotationTokens;
+
+ /**
+ * A tokenized representation of this wildcard's bound.
+ */
+ private final GenericTypeToken boundTypeToken;
+
+ /**
+ * Creates a new lazy lower bound wildcard.
+ *
+ * @param typePool The type pool to use.
+ * @param typeVariableSource The type variable source for locating type variables.
+ * @param typePath This type's type path.
+ * @param annotationTokens This type's type annotations.
+ * @param boundTypeToken A tokenized representation of this wildcard's bound.
+ */
+ protected LazyLowerBoundWildcard(TypePool typePool,
+ TypeVariableSource typeVariableSource,
+ String typePath,
+ Map<String, List<AnnotationToken>> annotationTokens,
+ GenericTypeToken boundTypeToken) {
+ this.typePool = typePool;
+ this.typeVariableSource = typeVariableSource;
+ this.typePath = typePath;
+ this.annotationTokens = annotationTokens;
+ this.boundTypeToken = boundTypeToken;
+ }
+
+ @Override
+ public TypeList.Generic getUpperBounds() {
+ return new TypeList.Generic.Explicit(Generic.OBJECT);
+ }
+
+ @Override
+ public TypeList.Generic getLowerBounds() {
+ return new LazyTokenList.ForWildcardBound(typePool, typeVariableSource, typePath, annotationTokens, boundTypeToken);
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return LazyAnnotationDescription.asListOfNullable(typePool, annotationTokens.get(typePath));
+ }
+ }
+ }
+
+ /**
+ * A generic type token for a wildcard that is bound above.
+ */
+ @EqualsAndHashCode
+ class ForUpperBoundWildcard implements GenericTypeToken {
+
+ /**
+ * A token that represents the wildcard's upper bound.
+ */
+ private final GenericTypeToken boundTypeToken;
+
+ /**
+ * Creates a generic type token for a wildcard that is bound above.
+ *
+ * @param boundTypeToken A token that represents the wildcard's upper bound.
+ */
+ protected ForUpperBoundWildcard(GenericTypeToken boundTypeToken) {
+ this.boundTypeToken = boundTypeToken;
+ }
+
+ @Override
+ public Generic toGenericType(TypePool typePool,
+ TypeVariableSource typeVariableSource,
+ String typePath,
+ Map<String, List<AnnotationToken>> annotationTokens) {
+ return new LazyUpperBoundWildcard(typePool, typeVariableSource, typePath, annotationTokens, boundTypeToken);
+ }
+
+ @Override
+ public boolean isPrimaryBound(TypePool typePool) {
+ throw new IllegalStateException("A wildcard type cannot be a type variable bound: " + this);
+ }
+
+ @Override
+ public String getTypePathPrefix() {
+ throw new IllegalStateException("An upper bound wildcard cannot be the owner of a nested type: " + this);
+ }
+
+ /**
+ * A generic type representation of a tokenized wildcard with an upper bound.
+ */
+ protected static class LazyUpperBoundWildcard extends Generic.OfWildcardType {
+
+ /**
+ * The type pool to use.
+ */
+ private final TypePool typePool;
+
+ /**
+ * The type variable source for locating type variables.
+ */
+ private final TypeVariableSource typeVariableSource;
+
+ /**
+ * This type's type path.
+ */
+ private final String typePath;
+
+ /**
+ * This type's type annotations.
+ */
+ private final Map<String, List<AnnotationToken>> annotationTokens;
+
+ /**
+ * A tokenized representation of this wildcard's bound.
+ */
+ private final GenericTypeToken boundTypeToken;
+
+ /**
+ * Creates a new lazy upper bound wildcard.
+ *
+ * @param typePool The type pool to use.
+ * @param typeVariableSource The type variable source for locating type variables.
+ * @param typePath This type's type path.
+ * @param annotationTokens This type's type annotations.
+ * @param boundTypeToken A tokenized representation of this wildcard's bound.
+ */
+ protected LazyUpperBoundWildcard(TypePool typePool,
+ TypeVariableSource typeVariableSource,
+ String typePath,
+ Map<String, List<AnnotationToken>> annotationTokens,
+ GenericTypeToken boundTypeToken) {
+ this.typePool = typePool;
+ this.typeVariableSource = typeVariableSource;
+ this.typePath = typePath;
+ this.annotationTokens = annotationTokens;
+ this.boundTypeToken = boundTypeToken;
+ }
+
+ @Override
+ public TypeList.Generic getUpperBounds() {
+ return new LazyTokenList.ForWildcardBound(typePool, typeVariableSource, typePath, annotationTokens, boundTypeToken);
+ }
+
+ @Override
+ public TypeList.Generic getLowerBounds() {
+ return new TypeList.Generic.Empty();
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return LazyAnnotationDescription.asListOfNullable(typePool, annotationTokens.get(typePath));
+ }
+ }
+ }
+
+ /**
+ * A generic type token that represents a parameterized type.
+ */
+ @EqualsAndHashCode
+ class ForParameterizedType implements GenericTypeToken {
+
+ /**
+ * The name of the parameterized type's erasure.
+ */
+ private final String name;
+
+ /**
+ * A list of tokens that represent the parameters of the represented type.
+ */
+ private final List<GenericTypeToken> parameterTypeTokens;
+
+ /**
+ * Creates a type token that represents a parameterized type.
+ *
+ * @param name The name of the parameterized type's erasure.
+ * @param parameterTypeTokens A list of tokens that represent the parameters of the represented type.
+ */
+ protected ForParameterizedType(String name, List<GenericTypeToken> parameterTypeTokens) {
+ this.name = name;
+ this.parameterTypeTokens = parameterTypeTokens;
+ }
+
+ @Override
+ public Generic toGenericType(TypePool typePool, TypeVariableSource typeVariableSource, String typePath, Map<String, List<AnnotationToken>> annotationTokens) {
+ return new LazyParameterizedType(typePool, typeVariableSource, typePath, annotationTokens, name, parameterTypeTokens);
+ }
+
+ @Override
+ public boolean isPrimaryBound(TypePool typePool) {
+ return !typePool.describe(name).resolve().isInterface();
+ }
+
+ @Override
+ public String getTypePathPrefix() {
+ return String.valueOf(INNER_CLASS_PATH);
+ }
+
+ /**
+ * A generic type token to describe a parameterized type description with a generic owner type.
+ */
+ @EqualsAndHashCode
+ public static class Nested implements GenericTypeToken {
+
+ /**
+ * The name of the parameterized type's erasure.
+ */
+ private final String name;
+
+ /**
+ * A list of tokens that represent the parameters of the represented type.
+ */
+ private final List<GenericTypeToken> parameterTypeTokens;
+
+ /**
+ * A token that describes the described parameterized type's owner type.
+ */
+ private final GenericTypeToken ownerTypeToken;
+
+ /**
+ * Creates a type token that represents a parameterized type.
+ *
+ * @param name The name of the parameterized type's erasure.
+ * @param parameterTypeTokens A list of tokens that represent the parameters of the represented type.
+ * @param ownerTypeToken A token that describes the described parameterized type's owner type.
+ */
+ protected Nested(String name, List<GenericTypeToken> parameterTypeTokens, GenericTypeToken ownerTypeToken) {
+ this.name = name;
+ this.parameterTypeTokens = parameterTypeTokens;
+ this.ownerTypeToken = ownerTypeToken;
+ }
+
+ @Override
+ public Generic toGenericType(TypePool typePool,
+ TypeVariableSource typeVariableSource,
+ String typePath,
+ Map<String, List<AnnotationToken>> annotationTokens) {
+ return new LazyParameterizedType(typePool, typeVariableSource, typePath, annotationTokens, name, parameterTypeTokens, ownerTypeToken);
+ }
+
+ @Override
+ public String getTypePathPrefix() {
+ return ownerTypeToken.getTypePathPrefix() + INNER_CLASS_PATH;
+ }
+
+ @Override
+ public boolean isPrimaryBound(TypePool typePool) {
+ return !typePool.describe(name).resolve().isInterface();
+ }
+
+ /**
+ * A lazy description of a parameterized type with an owner type.
+ */
+ protected static class LazyParameterizedType extends Generic.OfParameterizedType {
+
+ /**
+ * The type pool that is used for locating a generic type.
+ */
+ private final TypePool typePool;
+
+ /**
+ * The type variable source to use for resolving type variables.
+ */
+ private final TypeVariableSource typeVariableSource;
+
+ /**
+ * This type's type path.
+ */
+ private final String typePath;
+
+ /**
+ * A mapping of type annotations for this type.
+ */
+ private final Map<String, List<AnnotationToken>> annotationTokens;
+
+ /**
+ * The binary name of this parameterized type's raw type.
+ */
+ private final String name;
+
+ /**
+ * Tokens that represent this parameterized type's parameters.
+ */
+ private final List<GenericTypeToken> parameterTypeTokens;
+
+ /**
+ * A token that represents this type's owner type.
+ */
+ private final GenericTypeToken ownerTypeToken;
+
+ /**
+ * Creates a new lazy parameterized type.
+ *
+ * @param typePool The type pool that is used for locating a generic type.
+ * @param typeVariableSource The type variable source to use for resolving type variables.
+ * @param typePath This type's type path.
+ * @param annotationTokens A mapping of type annotations for this type.
+ * @param name The binary name of this parameterized type's raw type.
+ * @param parameterTypeTokens Tokens that represent this parameterized type's parameters.
+ * @param ownerTypeToken A token that represents this type's owner type.
+ */
+ protected LazyParameterizedType(TypePool typePool,
+ TypeVariableSource typeVariableSource,
+ String typePath,
+ Map<String, List<AnnotationToken>> annotationTokens,
+ String name,
+ List<GenericTypeToken> parameterTypeTokens,
+ GenericTypeToken ownerTypeToken) {
+ this.typePool = typePool;
+ this.typeVariableSource = typeVariableSource;
+ this.typePath = typePath;
+ this.annotationTokens = annotationTokens;
+ this.name = name;
+ this.parameterTypeTokens = parameterTypeTokens;
+ this.ownerTypeToken = ownerTypeToken;
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ return typePool.describe(name).resolve();
+ }
+
+ @Override
+ public TypeList.Generic getTypeArguments() {
+ return new LazyTokenList(typePool, typeVariableSource, typePath + ownerTypeToken.getTypePathPrefix(), annotationTokens, parameterTypeTokens);
+ }
+
+ @Override
+ public Generic getOwnerType() {
+ return ownerTypeToken.toGenericType(typePool, typeVariableSource, typePath, annotationTokens);
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return LazyAnnotationDescription.asListOfNullable(typePool, annotationTokens.get(typePath + ownerTypeToken.getTypePathPrefix()));
+ }
+ }
+ }
+
+ /**
+ * A generic type description that represents a parameterized type <b>without</b> an enclosing generic owner type.
+ */
+ protected static class LazyParameterizedType extends Generic.OfParameterizedType {
+
+ /**
+ * The type pool that is used for locating a generic type.
+ */
+ private final TypePool typePool;
+
+ /**
+ * The type variable source to use for resolving type variables.
+ */
+ private final TypeVariableSource typeVariableSource;
+
+ /**
+ * This type's type path.
+ */
+ private final String typePath;
+
+ /**
+ * A mapping of the represent type's annotation tokens.
+ */
+ private final Map<String, List<AnnotationToken>> annotationTokens;
+
+ /**
+ * The binary name of the raw type.
+ */
+ private final String name;
+
+ /**
+ * A list of type tokens representing this type's bounds.
+ */
+ private final List<GenericTypeToken> parameterTypeTokens;
+
+ /**
+ * Creates a new description of a parameterized type.
+ *
+ * @param typePool The type pool that is used for locating a generic type.
+ * @param typeVariableSource The type variable source to use for resolving type variables.
+ * @param typePath This type's type path.
+ * @param annotationTokens A mapping of the represent type's annotation tokens,
+ * @param name The binary name of the raw type.
+ * @param parameterTypeTokens A list of type tokens representing this type's bounds.
+ */
+ protected LazyParameterizedType(TypePool typePool,
+ TypeVariableSource typeVariableSource,
+ String typePath,
+ Map<String, List<AnnotationToken>> annotationTokens,
+ String name,
+ List<GenericTypeToken> parameterTypeTokens) {
+ this.typePool = typePool;
+ this.typeVariableSource = typeVariableSource;
+ this.typePath = typePath;
+ this.annotationTokens = annotationTokens;
+ this.name = name;
+ this.parameterTypeTokens = parameterTypeTokens;
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ return typePool.describe(name).resolve();
+ }
+
+ @Override
+ public TypeList.Generic getTypeArguments() {
+ return new LazyTokenList(typePool, typeVariableSource, typePath, annotationTokens, parameterTypeTokens);
+ }
+
+ @Override
+ public Generic getOwnerType() {
+ TypeDescription ownerType = typePool.describe(name).resolve().getEnclosingType();
+ return ownerType == null
+ ? Generic.UNDEFINED
+ : ownerType.asGenericType();
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return LazyAnnotationDescription.asListOfNullable(typePool, annotationTokens.get(typePath));
+ }
+ }
+ }
+
+ /**
+ * A lazy list of type tokens.
+ */
+ class LazyTokenList extends TypeList.Generic.AbstractBase {
+
+ /**
+ * The type pool that is used for locating a generic type.
+ */
+ private final TypePool typePool;
+
+ /**
+ * The type variable source to use for resolving type variables.
+ */
+ private final TypeVariableSource typeVariableSource;
+
+ /**
+ * The represented types' type path to which an index step is added upon resolution.
+ */
+ private final String typePath;
+
+ /**
+ * A mapping of the represent types' annotation tokens.
+ */
+ private final Map<String, List<AnnotationToken>> annotationTokens;
+
+ /**
+ * A list of type tokens this list represents.
+ */
+ private final List<GenericTypeToken> genericTypeTokens;
+
+ /**
+ * Creates a new type list that represents a list of tokenized types.
+ *
+ * @param typePool The type pool that is used for locating a generic type.
+ * @param typeVariableSource The type variable source to use for resolving type variables.
+ * @param typePath The represented types' type path to which an index step is added upon resolution.
+ * @param annotationTokens A mapping of the represent types' annotation tokens,
+ * @param genericTypeTokens A list of type tokens this list represents.
+ */
+ protected LazyTokenList(TypePool typePool,
+ TypeVariableSource typeVariableSource,
+ String typePath,
+ Map<String, List<AnnotationToken>> annotationTokens,
+ List<GenericTypeToken> genericTypeTokens) {
+ this.typePool = typePool;
+ this.typeVariableSource = typeVariableSource;
+ this.typePath = typePath;
+ this.annotationTokens = annotationTokens;
+ this.genericTypeTokens = genericTypeTokens;
+ }
+
+ @Override
+ public Generic get(int index) {
+ return genericTypeTokens.get(index).toGenericType(typePool, typeVariableSource, typePath + index + INDEXED_TYPE_DELIMITER, annotationTokens);
+ }
+
+ @Override
+ public int size() {
+ return genericTypeTokens.size();
+ }
+
+ /**
+ * A generic type description representing a tokenized wildcard bound.
+ */
+ protected static class ForWildcardBound extends TypeList.Generic.AbstractBase {
+
+ /**
+ * The type pool that is used for locating a generic type.
+ */
+ private final TypePool typePool;
+
+ /**
+ * The type variable source to use for resolving type variables.
+ */
+ private final TypeVariableSource typeVariableSource;
+
+ /**
+ * The represented types' type path to which a wildcard step is added upon resolution.
+ */
+ private final String typePath;
+
+ /**
+ * A mapping of the represent types' annotation tokens.
+ */
+ private final Map<String, List<AnnotationToken>> annotationTokens;
+
+ /**
+ * A token representing the wildcard's bound.
+ */
+ private final GenericTypeToken genericTypeToken;
+
+ /**
+ * @param typePool The type pool that is used for locating a generic type.
+ * @param typeVariableSource The type variable source to use for resolving type variables.
+ * @param typePath The represented types' type path to which a wildcard step is added upon resolution.
+ * @param annotationTokens A mapping of the represent types' annotation tokens,
+ * @param genericTypeToken A token representing the wildcard's bound.
+ */
+ protected ForWildcardBound(TypePool typePool,
+ TypeVariableSource typeVariableSource,
+ String typePath,
+ Map<String, List<AnnotationToken>> annotationTokens,
+ GenericTypeToken genericTypeToken) {
+ this.typePool = typePool;
+ this.typeVariableSource = typeVariableSource;
+ this.typePath = typePath;
+ this.annotationTokens = annotationTokens;
+ this.genericTypeToken = genericTypeToken;
+ }
+
+ @Override
+ public Generic get(int index) {
+ if (index == 0) {
+ return genericTypeToken.toGenericType(typePool, typeVariableSource, typePath + WILDCARD_TYPE_PATH, annotationTokens);
+ } else {
+ throw new IndexOutOfBoundsException("index = " + index);
+ }
+ }
+
+ @Override
+ public int size() {
+ return 1;
+ }
+ }
+ }
+ }
+
+ /**
+ * A token for representing collected data on an annotation.
+ */
+ @EqualsAndHashCode
+ protected static class AnnotationToken {
+
+ /**
+ * The descriptor of the represented annotation.
+ */
+ private final String descriptor;
+
+ /**
+ * A map of annotation value names to their value representations.
+ */
+ private final Map<String, AnnotationValue<?, ?>> values;
+
+ /**
+ * Creates a new annotation token.
+ *
+ * @param descriptor The descriptor of the represented annotation.
+ * @param values A map of annotation value names to their value representations.
+ */
+ protected AnnotationToken(String descriptor, Map<String, AnnotationValue<?, ?>> values) {
+ this.descriptor = descriptor;
+ this.values = values;
+ }
+
+ /**
+ * Returns a map of annotation value names to their value representations.
+ *
+ * @return A map of annotation value names to their value representations.
+ */
+ protected Map<String, AnnotationValue<?, ?>> getValues() {
+ return values;
+ }
+
+ /**
+ * Returns the annotation type's binary name.
+ *
+ * @return The annotation type's binary name.
+ */
+ protected String getBinaryName() {
+ return descriptor.substring(1, descriptor.length() - 1).replace('/', '.');
+ }
+
+ /**
+ * Transforms this token into an annotation description.
+ *
+ * @param typePool The type pool to be used for looking up linked types.
+ * @return An optional description of this annotation's token.
+ */
+ private Resolution toAnnotationDescription(TypePool typePool) {
+ TypePool.Resolution resolution = typePool.describe(getBinaryName());
+ return resolution.isResolved()
+ ? new Resolution.Simple(new LazyAnnotationDescription(typePool, resolution.resolve(), values))
+ : new Resolution.Illegal(getBinaryName());
+ }
+
+ /**
+ * A resolution for an annotation tokens. Any annotation is suppressed if its type is not available.
+ * This conforms to the handling of the Java reflection API.
+ */
+ protected interface Resolution {
+
+ /**
+ * Returns {@code true} if the represented annotation could be resolved.
+ *
+ * @return {@code true} if the represented annotation could be resolved.
+ */
+ boolean isResolved();
+
+ /**
+ * Returns the resolved annotation. This method throws an exception if this instance is not resolved.
+ *
+ * @return The resolved annotation. This method throws an exception if this instance is not resolved.
+ */
+ AnnotationDescription resolve();
+
+ /**
+ * A simple resolved annotation.
+ */
+ @EqualsAndHashCode
+ class Simple implements Resolution {
+
+ /**
+ * The represented annotation description.
+ */
+ private final AnnotationDescription annotationDescription;
+
+ /**
+ * Creates a new simple resolution.
+ *
+ * @param annotationDescription The represented annotation description.
+ */
+ protected Simple(AnnotationDescription annotationDescription) {
+ this.annotationDescription = annotationDescription;
+ }
+
+ @Override
+ public boolean isResolved() {
+ return true;
+ }
+
+ @Override
+ public AnnotationDescription resolve() {
+ return annotationDescription;
+ }
+ }
+
+ /**
+ * An illegal resolution.
+ */
+ @EqualsAndHashCode
+ class Illegal implements Resolution {
+
+ /**
+ * The annotation's binary type name.
+ */
+ private final String annotationType;
+
+ /**
+ * Creates a new illegal resolution.
+ *
+ * @param annotationType The annotation's binary type name.
+ */
+ public Illegal(String annotationType) {
+ this.annotationType = annotationType;
+ }
+
+ @Override
+ public boolean isResolved() {
+ return false;
+ }
+
+ @Override
+ public AnnotationDescription resolve() {
+ throw new IllegalStateException("Annotation type is not available: " + annotationType);
+ }
+ }
+ }
+ }
+
+ /**
+ * A token for representing collected data on a field.
+ */
+ @EqualsAndHashCode
+ protected static class FieldToken {
+
+ /**
+ * The name of the field.
+ */
+ private final String name;
+
+ /**
+ * The modifiers of the represented field.
+ */
+ private final int modifiers;
+
+ /**
+ * The descriptor of the field.
+ */
+ private final String descriptor;
+
+ /**
+ * The field's generic signature as found in the class file or {@code null} if the field is not generic.
+ */
+ private final String genericSignature;
+
+ /**
+ * The resolution of this field's generic type.
+ */
+ private final GenericTypeToken.Resolution.ForField signatureResolution;
+
+ /**
+ * A mapping of the field type's type annotation tokens.
+ */
+ private final Map<String, List<AnnotationToken>> typeAnnotationTokens;
+
+ /**
+ * A list of annotation tokens representing the annotations of the represented field.
+ */
+ private final List<AnnotationToken> annotationTokens;
+
+ /**
+ * Creates a new field token.
+ *
+ * @param name The name of the field.
+ * @param modifiers The modifiers of the represented field.
+ * @param descriptor The descriptor of the field.
+ * @param genericSignature The field's generic signature as found in the class file or {@code null} if the field is not generic.
+ * @param typeAnnotationTokens A mapping of the field type's type annotation tokens.
+ * @param annotationTokens A list of annotation tokens representing the annotations of the represented field.
+ */
+ protected FieldToken(String name,
+ int modifiers,
+ String descriptor,
+ String genericSignature,
+ Map<String, List<AnnotationToken>> typeAnnotationTokens,
+ List<AnnotationToken> annotationTokens) {
+ this.modifiers = modifiers & ~Opcodes.ACC_DEPRECATED;
+ this.name = name;
+ this.descriptor = descriptor;
+ this.genericSignature = genericSignature;
+ signatureResolution = GenericTypeExtractor.ForSignature.OfField.extract(genericSignature);
+ this.typeAnnotationTokens = typeAnnotationTokens;
+ this.annotationTokens = annotationTokens;
+ }
+
+ /**
+ * Transforms this token into a lazy field description.
+ *
+ * @param lazyTypeDescription The lazy type description to attach this field description to.
+ * @return A field description resembling this field token.
+ */
+ private LazyFieldDescription toFieldDescription(LazyTypeDescription lazyTypeDescription) {
+ return lazyTypeDescription.new LazyFieldDescription(name,
+ modifiers,
+ descriptor,
+ genericSignature,
+ signatureResolution,
+ typeAnnotationTokens,
+ annotationTokens);
+ }
+ }
+
+ /**
+ * A token for representing collected data on a method.
+ */
+ @EqualsAndHashCode
+ protected static class MethodToken {
+
+ /**
+ * The internal name of the represented method.
+ */
+ private final String name;
+
+ /**
+ * The modifiers of the represented method.
+ */
+ private final int modifiers;
+
+ /**
+ * The descriptor of the represented method.
+ */
+ private final String descriptor;
+
+ /**
+ * The methods's generic signature as found in the class file or {@code null} if the method is not generic.
+ */
+ private final String genericSignature;
+
+ /**
+ * The generic type resolution of this method.
+ */
+ private final GenericTypeToken.Resolution.ForMethod signatureResolution;
+
+ /**
+ * An array of internal names of the exceptions of the represented method or {@code null} if there
+ * are no such exceptions.
+ */
+ private final String[] exceptionName;
+
+ /**
+ * A mapping of the type variables' type annotation tokens by their indices.
+ */
+ private final Map<Integer, Map<String, List<AnnotationToken>>> typeVariableAnnotationTokens;
+
+ /**
+ * A mapping of the type variables' type bounds' type annotation tokens by their indices and each variable's index.
+ */
+ private final Map<Integer, Map<Integer, Map<String, List<AnnotationToken>>>> typeVariableBoundAnnotationTokens;
+
+ /**
+ * A mapping of the return type's type variable tokens.
+ */
+ private final Map<String, List<AnnotationToken>> returnTypeAnnotationTokens;
+
+ /**
+ * A mapping of the parameter types' type annotation tokens by their indices.
+ */
+ private final Map<Integer, Map<String, List<AnnotationToken>>> parameterTypeAnnotationTokens;
+
+ /**
+ * A mapping of the exception types' type annotation tokens by their indices.
+ */
+ private final Map<Integer, Map<String, List<AnnotationToken>>> exceptionTypeAnnotationTokens;
+
+ /**
+ * A mapping of the receiver type's annotation tokens.
+ */
+ private final Map<String, List<AnnotationToken>> receiverTypeAnnotationTokens;
+
+ /**
+ * A list of annotation tokens that are present on the represented method.
+ */
+ private final List<AnnotationToken> annotationTokens;
+
+ /**
+ * A map of parameter indices to tokens that represent their annotations.
+ */
+ private final Map<Integer, List<AnnotationToken>> parameterAnnotationTokens;
+
+ /**
+ * A list of tokens describing meta data of the method's parameters.
+ */
+ private final List<ParameterToken> parameterTokens;
+
+ /**
+ * The default value of this method or {@code null} if there is no such value.
+ */
+ private final AnnotationValue<?, ?> defaultValue;
+
+ /**
+ * Creates a new method token.
+ *
+ * @param name The name of the method.
+ * @param modifiers The modifiers of the represented method.
+ * @param descriptor The descriptor of the represented method.
+ * @param genericSignature The methods's generic signature as found in the class file or {@code null} if the method is not generic.
+ * @param exceptionName An array of internal names of the exceptions of the represented method or {@code null} if
+ * there are no such exceptions.
+ * @param typeVariableAnnotationTokens A mapping of the type variables' type annotation tokens by their indices.
+ * @param typeVariableBoundAnnotationTokens A mapping of the type variables' type bounds' type annotation tokens by their
+ * index and each variable's index.
+ * @param returnTypeAnnotationTokens A mapping of the return type's type variable tokens.
+ * @param parameterTypeAnnotationTokens A mapping of the parameter types' type annotation tokens by their indices.
+ * @param exceptionTypeAnnotationTokens A mapping of the exception types' type annotation tokens by their indices.
+ * @param receiverTypeAnnotationTokens A mapping of the receiver type's annotation tokens.
+ * @param annotationTokens A list of annotation tokens that are present on the represented method.
+ * @param parameterAnnotationTokens A map of parameter indices to tokens that represent their annotations.
+ * @param parameterTokens A list of tokens describing meta data of the method's parameters.
+ * @param defaultValue The default value of this method or {@code null} if there is no such value.
+ */
+ protected MethodToken(String name,
+ int modifiers,
+ String descriptor,
+ String genericSignature,
+ String[] exceptionName,
+ Map<Integer, Map<String, List<AnnotationToken>>> typeVariableAnnotationTokens,
+ Map<Integer, Map<Integer, Map<String, List<AnnotationToken>>>> typeVariableBoundAnnotationTokens,
+ Map<String, List<AnnotationToken>> returnTypeAnnotationTokens,
+ Map<Integer, Map<String, List<AnnotationToken>>> parameterTypeAnnotationTokens,
+ Map<Integer, Map<String, List<AnnotationToken>>> exceptionTypeAnnotationTokens,
+ Map<String, List<AnnotationToken>> receiverTypeAnnotationTokens,
+ List<AnnotationToken> annotationTokens,
+ Map<Integer, List<AnnotationToken>> parameterAnnotationTokens,
+ List<ParameterToken> parameterTokens,
+ AnnotationValue<?, ?> defaultValue) {
+ this.modifiers = modifiers & ~Opcodes.ACC_DEPRECATED;
+ this.name = name;
+ this.descriptor = descriptor;
+ this.genericSignature = genericSignature;
+ signatureResolution = GenericTypeExtractor.ForSignature.OfMethod.extract(genericSignature);
+ this.exceptionName = exceptionName;
+ this.typeVariableAnnotationTokens = typeVariableAnnotationTokens;
+ this.typeVariableBoundAnnotationTokens = typeVariableBoundAnnotationTokens;
+ this.returnTypeAnnotationTokens = returnTypeAnnotationTokens;
+ this.parameterTypeAnnotationTokens = parameterTypeAnnotationTokens;
+ this.exceptionTypeAnnotationTokens = exceptionTypeAnnotationTokens;
+ this.receiverTypeAnnotationTokens = receiverTypeAnnotationTokens;
+ this.annotationTokens = annotationTokens;
+ this.parameterAnnotationTokens = parameterAnnotationTokens;
+ this.parameterTokens = parameterTokens;
+ this.defaultValue = defaultValue;
+ }
+
+ /**
+ * Transforms this method token to a method description that is attached to a lazy type description.
+ *
+ * @param lazyTypeDescription The lazy type description to attach this method description to.
+ * @return A method description representing this field token.
+ */
+ private MethodDescription.InDefinedShape toMethodDescription(LazyTypeDescription lazyTypeDescription) {
+ return lazyTypeDescription.new LazyMethodDescription(name,
+ modifiers,
+ descriptor,
+ genericSignature,
+ signatureResolution,
+ exceptionName,
+ typeVariableAnnotationTokens,
+ typeVariableBoundAnnotationTokens,
+ returnTypeAnnotationTokens,
+ parameterTypeAnnotationTokens,
+ exceptionTypeAnnotationTokens,
+ receiverTypeAnnotationTokens,
+ annotationTokens,
+ parameterAnnotationTokens,
+ parameterTokens,
+ defaultValue);
+ }
+
+ /**
+ * A token representing a method's parameter.
+ */
+ @EqualsAndHashCode
+ protected static class ParameterToken {
+
+ /**
+ * Donates an unknown name of a parameter.
+ */
+ protected static final String NO_NAME = null;
+
+ /**
+ * Donates an unknown modifier of a parameter.
+ */
+ protected static final Integer NO_MODIFIERS = null;
+
+ /**
+ * The name of the parameter or {@code null} if no explicit name for this parameter is known.
+ */
+ private final String name;
+
+ /**
+ * The modifiers of the parameter or {@code null} if no modifiers are known for this parameter.
+ */
+ private final Integer modifiers;
+
+ /**
+ * Creates a parameter token for a parameter without an explicit name and without specific modifiers.
+ */
+ protected ParameterToken() {
+ this(NO_NAME);
+ }
+
+ /**
+ * Creates a parameter token for a parameter with an explicit name and without specific modifiers.
+ *
+ * @param name The name of the parameter.
+ */
+ protected ParameterToken(String name) {
+ this(name, NO_MODIFIERS);
+ }
+
+ /**
+ * Creates a parameter token for a parameter with an explicit name and with specific modifiers.
+ *
+ * @param name The name of the parameter.
+ * @param modifiers The modifiers of the parameter.
+ */
+ protected ParameterToken(String name, Integer modifiers) {
+ this.name = name;
+ this.modifiers = modifiers;
+ }
+
+ /**
+ * Returns the name of the parameter or {@code null} if there is no such name.
+ *
+ * @return The name of the parameter or {@code null} if there is no such name.
+ */
+ protected String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the modifiers of the parameter or {@code null} if no modifiers are known.
+ *
+ * @return The modifiers of the parameter or {@code null} if no modifiers are known.
+ */
+ protected Integer getModifiers() {
+ return modifiers;
+ }
+ }
+ }
+
+ /**
+ * A lazy description of an annotation that looks up types from a type pool when required.
+ */
+ private static class LazyAnnotationDescription extends AnnotationDescription.AbstractBase {
+
+ /**
+ * The type pool for looking up type references.
+ */
+ protected final TypePool typePool;
+
+ /**
+ * The type of this annotation.
+ */
+ private final TypeDescription annotationType;
+
+ /**
+ * A map of annotation values by their property name.
+ */
+ protected final Map<String, AnnotationValue<?, ?>> values;
+
+ /**
+ * Creates a new lazy annotation description.
+ *
+ * @param typePool The type pool to be used for looking up linked types.
+ * @param annotationType The annotation's type.
+ * @param values A map of annotation value names to their value representations.
+ */
+ private LazyAnnotationDescription(TypePool typePool, TypeDescription annotationType, Map<String, AnnotationValue<?, ?>> values) {
+ this.typePool = typePool;
+ this.annotationType = annotationType;
+ this.values = values;
+ }
+
+ /**
+ * Represents a list of annotation tokens in form of a list of lazy type annotations. Any annotation with
+ * a type that cannot be loaded from the type pool is ignored and not included in the list. If the provided
+ * {@code tokens} are {@code null}, an empty list is returned.
+ *
+ * @param typePool The type pool to be used for looking up linked types.
+ * @param tokens The tokens to represent in the list.
+ * @return A list of the loadable annotations.
+ */
+ protected static AnnotationList asListOfNullable(TypePool typePool, List<? extends AnnotationToken> tokens) {
+ return tokens == null
+ ? new AnnotationList.Empty()
+ : asList(typePool, tokens);
+ }
+
+ /**
+ * Represents a list of annotation tokens in form of a list of lazy type annotations. Any annotation with
+ * a type that cannot be loaded from the type pool is ignored and not included in the list.
+ *
+ * @param typePool The type pool to be used for looking up linked types.
+ * @param tokens The tokens to represent in the list.
+ * @return A list of the loadable annotations.
+ */
+ protected static AnnotationList asList(TypePool typePool, List<? extends AnnotationToken> tokens) {
+ List<AnnotationDescription> annotationDescriptions = new ArrayList<AnnotationDescription>(tokens.size());
+ for (AnnotationToken token : tokens) {
+ AnnotationToken.Resolution resolution = token.toAnnotationDescription(typePool);
+ if (resolution.isResolved()) {
+ annotationDescriptions.add(resolution.resolve());
+ }
+ }
+ return new AnnotationList.Explicit(annotationDescriptions);
+ }
+
+ @Override
+ public AnnotationValue<?, ?> getValue(MethodDescription.InDefinedShape property) {
+ if (!property.getDeclaringType().asErasure().equals(annotationType)) {
+ throw new IllegalArgumentException(property + " is not declared by " + getAnnotationType());
+ }
+ AnnotationValue<?, ?> annotationValue = values.get(property.getName());
+ if (annotationValue == null) {
+ annotationValue = getAnnotationType().getDeclaredMethods().filter(is(property)).getOnly().getDefaultValue();
+ }
+ if (annotationValue != null) {
+ return annotationValue;
+ }
+ throw new IllegalStateException(property + " is not defined on annotation");
+ }
+
+ @Override
+ public TypeDescription getAnnotationType() {
+ return annotationType;
+ }
+
+ @Override
+ public <T extends Annotation> Loadable<T> prepare(Class<T> annotationType) {
+ if (!this.annotationType.represents(annotationType)) {
+ throw new IllegalArgumentException(annotationType + " does not represent " + this.annotationType);
+ }
+ return new Loadable<T>(typePool, annotationType, values);
+ }
+
+ /**
+ * A loadable version of a lazy annotation description.
+ *
+ * @param <S> The annotation type.
+ */
+ private static class Loadable<S extends Annotation> extends LazyAnnotationDescription implements AnnotationDescription.Loadable<S> {
+
+ /**
+ * The loaded annotation type.
+ */
+ private final Class<S> annotationType;
+
+ /**
+ * Creates a new loadable version of a lazy annotation.
+ *
+ * @param typePool The type pool to be used for looking up linked types.
+ * @param annotationType The annotation's loaded type.
+ * @param values A map of annotation value names to their value representations.
+ */
+ private Loadable(TypePool typePool, Class<S> annotationType, Map<String, AnnotationValue<?, ?>> values) {
+ super(typePool, new ForLoadedType(annotationType), values);
+ this.annotationType = annotationType;
+ }
+
+ @Override
+ public S load() throws ClassNotFoundException {
+ return AnnotationInvocationHandler.of(annotationType.getClassLoader(), annotationType, values);
+ }
+
+ @Override
+ public S loadSilent() {
+ try {
+ return load();
+ } catch (ClassNotFoundException exception) {
+ throw new IllegalStateException("Could not load annotation type or referenced type", exception);
+ }
+ }
+ }
+ }
+
+ /**
+ * An implementation of a {@link PackageDescription} that only
+ * loads its annotations on requirement.
+ */
+ private static class LazyPackageDescription extends PackageDescription.AbstractBase {
+
+ /**
+ * The type pool to use for look-ups.
+ */
+ private final TypePool typePool;
+
+ /**
+ * The name of the package.
+ */
+ private final String name;
+
+ /**
+ * Creates a new lazy package description.
+ *
+ * @param typePool The type pool to use for look-ups.
+ * @param name The name of the package.
+ */
+ private LazyPackageDescription(TypePool typePool, String name) {
+ this.typePool = typePool;
+ this.name = name;
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ Resolution resolution = typePool.describe(name + "." + PackageDescription.PACKAGE_CLASS_NAME);
+ return resolution.isResolved()
+ ? resolution.resolve().getDeclaredAnnotations()
+ : new AnnotationList.Empty();
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+ }
+
+ /**
+ * A list that is constructing {@link LazyTypeDescription}s.
+ */
+ private static class LazyTypeList extends TypeList.AbstractBase {
+
+ /**
+ * The type pool to use for locating types.
+ */
+ private final TypePool typePool;
+
+ /**
+ * A list of type descriptors that this list represents.
+ */
+ private final List<String> descriptors;
+
+ /**
+ * Creates a list of lazy type descriptions.
+ *
+ * @param typePool The type pool to use for locating types.
+ * @param descriptors A list of type descriptors that this list represents.
+ */
+ private LazyTypeList(TypePool typePool, List<String> descriptors) {
+ this.typePool = typePool;
+ this.descriptors = descriptors;
+ }
+
+ @Override
+ public TypeDescription get(int index) {
+ return TokenizedGenericType.toErasure(typePool, descriptors.get(index));
+ }
+
+ @Override
+ public int size() {
+ return descriptors.size();
+ }
+
+ @Override
+ public String[] toInternalNames() {
+ String[] internalName = new String[descriptors.size()];
+ int index = 0;
+ for (String descriptor : descriptors) {
+ internalName[index++] = Type.getType(descriptor).getInternalName();
+ }
+ return internalName.length == 0
+ ? NO_INTERFACES
+ : internalName;
+ }
+
+ @Override
+ public int getStackSize() {
+ int stackSize = 0;
+ for (String descriptor : descriptors) {
+ stackSize += Type.getType(descriptor).getSize();
+ }
+ return stackSize;
+ }
+ }
+
+ /**
+ * A representation of a generic type that is described by a {@link GenericTypeToken}.
+ */
+ private static class TokenizedGenericType extends Generic.LazyProjection.WithEagerNavigation {
+
+ /**
+ * The type pool to use for locating referenced types.
+ */
+ private final TypePool typePool;
+
+ /**
+ * The token that describes the represented generic type.
+ */
+ private final GenericTypeToken genericTypeToken;
+
+ /**
+ * A descriptor of the generic type's raw type.
+ */
+ private final String rawTypeDescriptor;
+
+ /**
+ * The tokenized type's type annotation tokens.
+ */
+ private final Map<String, List<AnnotationToken>> annotationTokens;
+
+ /**
+ * The closest type variable source of this generic type's declaration context.
+ */
+ private final TypeVariableSource typeVariableSource;
+
+ /**
+ * Creates a new tokenized generic type.
+ *
+ * @param typePool The type pool to use for locating referenced types.
+ * @param genericTypeToken The token that describes the represented generic type.
+ * @param rawTypeDescriptor A descriptor of the generic type's erasure.
+ * @param annotationTokens The tokenized type's type annotation tokens.
+ * @param typeVariableSource The closest type variable source of this generic type's declaration context.
+ */
+ protected TokenizedGenericType(TypePool typePool,
+ GenericTypeToken genericTypeToken,
+ String rawTypeDescriptor,
+ Map<String, List<AnnotationToken>> annotationTokens,
+ TypeVariableSource typeVariableSource) {
+ this.typePool = typePool;
+ this.genericTypeToken = genericTypeToken;
+ this.rawTypeDescriptor = rawTypeDescriptor;
+ this.annotationTokens = annotationTokens;
+ this.typeVariableSource = typeVariableSource;
+ }
+
+ /**
+ * Creates a new generic type description for a tokenized generic type.
+ *
+ * @param typePool The type pool to use for locating referenced types.
+ * @param genericTypeToken The token that describes the represented generic type.
+ * @param rawTypeDescriptor A descriptor of the generic type's erasure.
+ * @param annotationTokens The tokenized type's type annotation tokens or {@code null} if no such annotations are defined.
+ * @param typeVariableSource The closest type variable source of this generic type's declaration context.
+ * @return A suitable generic type.
+ */
+ protected static Generic of(TypePool typePool,
+ GenericTypeToken genericTypeToken,
+ String rawTypeDescriptor,
+ Map<String, List<AnnotationToken>> annotationTokens,
+ TypeVariableSource typeVariableSource) {
+ return new TokenizedGenericType(typePool,
+ genericTypeToken,
+ rawTypeDescriptor,
+ annotationTokens == null
+ ? Collections.<String, List<AnnotationToken>>emptyMap()
+ : annotationTokens,
+ typeVariableSource);
+ }
+
+ /**
+ * Creates a type description from a descriptor by looking up the corresponding type.
+ *
+ * @param typePool The type pool to use for locating a type.
+ * @param descriptor The descriptor to interpret.
+ * @return A description of the type represented by the descriptor.
+ */
+ protected static TypeDescription toErasure(TypePool typePool, String descriptor) {
+ Type type = Type.getType(descriptor);
+ return typePool.describe(type.getSort() == Type.ARRAY
+ ? type.getInternalName().replace('/', '.')
+ : type.getClassName()).resolve();
+ }
+
+ @Override
+ protected Generic resolve() {
+ return genericTypeToken.toGenericType(typePool, typeVariableSource, GenericTypeToken.EMPTY_TYPE_PATH, annotationTokens);
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ return toErasure(typePool, rawTypeDescriptor);
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return resolve().getDeclaredAnnotations();
+ }
+
+ /**
+ * A tokenized list of generic types.
+ */
+ protected static class TokenList extends TypeList.Generic.AbstractBase {
+
+ /**
+ * The type pool to use for locating types.
+ */
+ private final TypePool typePool;
+
+ /**
+ * Type tokens that describe the represented generic types.
+ */
+ private final List<GenericTypeToken> genericTypeTokens;
+
+ /**
+ * A list of the generic types' erasures.
+ */
+ private final List<String> rawTypeDescriptors;
+
+ /**
+ * The closest type variable source of this generic type's declaration context.
+ */
+ private final TypeVariableSource typeVariableSource;
+
+ /**
+ * A mapping of each type's type annotation tokens by its index.
+ */
+ private final Map<Integer, Map<String, List<AnnotationToken>>> annotationTokens;
+
+ /**
+ * Creates a list of tokenized generic types.
+ *
+ * @param typePool The type pool to use for locating type descriptions.
+ * @param genericTypeTokens A list of tokens describing the represented generic types.
+ * @param annotationTokens A mapping of each type's type annotation tokens by its index.
+ * @param rawTypeDescriptors A list of the generic types' erasures.
+ * @param typeVariableSource The closest type variable source of this generic type's declaration context.
+ */
+ private TokenList(TypePool typePool,
+ List<GenericTypeToken> genericTypeTokens,
+ Map<Integer, Map<String, List<AnnotationToken>>> annotationTokens,
+ List<String> rawTypeDescriptors,
+ TypeVariableSource typeVariableSource) {
+ this.typePool = typePool;
+ this.genericTypeTokens = genericTypeTokens;
+ this.annotationTokens = annotationTokens;
+ this.rawTypeDescriptors = rawTypeDescriptors;
+ this.typeVariableSource = typeVariableSource;
+ }
+
+ @Override
+ public Generic get(int index) {
+ return rawTypeDescriptors.size() == genericTypeTokens.size()
+ ? TokenizedGenericType.of(typePool, genericTypeTokens.get(index), rawTypeDescriptors.get(index), annotationTokens.get(index), typeVariableSource)
+ : TokenizedGenericType.toErasure(typePool, rawTypeDescriptors.get(index)).asGenericType();
+ }
+
+ @Override
+ public int size() {
+ return rawTypeDescriptors.size();
+ }
+
+ @Override
+ public TypeList asErasures() {
+ return new LazyTypeList(typePool, rawTypeDescriptors);
+ }
+ }
+
+ /**
+ * A list of tokenized type variables.
+ */
+ protected static class TypeVariableList extends TypeList.Generic.AbstractBase {
+
+ /**
+ * The type pool to use for locating types.
+ */
+ private final TypePool typePool;
+
+ /**
+ * Type tokens that describe the represented type variables.
+ */
+ private final List<GenericTypeToken.OfFormalTypeVariable> typeVariables;
+
+ /**
+ * The type variable source of the represented type variables.
+ */
+ private final TypeVariableSource typeVariableSource;
+
+ /**
+ * A mapping of the type variables' type annotation tokens by their indices.
+ */
+ private final Map<Integer, Map<String, List<AnnotationToken>>> annotationTokens;
+
+ /**
+ * A mapping of the type variables' bound types' annotation tokens by their indices and each type variable's index..
+ */
+ private final Map<Integer, Map<Integer, Map<String, List<AnnotationToken>>>> boundAnnotationTokens;
+
+ /**
+ * Creates a list of type variables.
+ *
+ * @param typePool The type pool to use for locating types.
+ * @param typeVariables Type tokens that describe the represented generic types.
+ * @param typeVariableSource The type variable source of the represented type variables.
+ * @param annotationTokens A mapping of the type variables' type annotation tokens by their indices.
+ * @param boundAnnotationTokens A mapping of the type variables' bound types' annotation tokens by their indices
+ * and each type variable's index.
+ */
+ protected TypeVariableList(TypePool typePool,
+ List<GenericTypeToken.OfFormalTypeVariable> typeVariables,
+ TypeVariableSource typeVariableSource,
+ Map<Integer, Map<String, List<AnnotationToken>>> annotationTokens,
+ Map<Integer, Map<Integer, Map<String, List<AnnotationToken>>>> boundAnnotationTokens) {
+ this.typePool = typePool;
+ this.typeVariables = typeVariables;
+ this.typeVariableSource = typeVariableSource;
+ this.annotationTokens = annotationTokens;
+ this.boundAnnotationTokens = boundAnnotationTokens;
+ }
+
+ @Override
+ public Generic get(int index) {
+ return typeVariables.get(index).toGenericType(typePool, typeVariableSource, annotationTokens.get(index), boundAnnotationTokens.get(index));
+ }
+
+ @Override
+ public int size() {
+ return typeVariables.size();
+ }
+ }
+
+ /**
+ * A lazy description of a non-well-defined described generic type.
+ */
+ protected static class Malformed extends LazyProjection.WithEagerNavigation {
+
+ /**
+ * The type pool to use for locating types.
+ */
+ private final TypePool typePool;
+
+ /**
+ * The descriptor of the type erasure.
+ */
+ private final String rawTypeDescriptor;
+
+ /**
+ * Creates a lazy description of a non-well-defined described generic type.
+ *
+ * @param typePool The type pool to use for locating types.
+ * @param rawTypeDescriptor The descriptor of the type erasure.
+ */
+ protected Malformed(TypePool typePool, String rawTypeDescriptor) {
+ this.typePool = typePool;
+ this.rawTypeDescriptor = rawTypeDescriptor;
+ }
+
+ @Override
+ protected Generic resolve() {
+ throw new GenericSignatureFormatError();
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ return toErasure(typePool, rawTypeDescriptor);
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ throw new GenericSignatureFormatError();
+ }
+
+ /**
+ * A tokenized list of non-well-defined generic types.
+ */
+ protected static class TokenList extends TypeList.Generic.AbstractBase {
+
+ /**
+ * The type pool to use for locating types.
+ */
+ private final TypePool typePool;
+
+ /**
+ * A list of descriptors of the list's types' erasures.
+ */
+ private final List<String> rawTypeDescriptors;
+
+ /**
+ * Creates a new tokenized list of generic types.
+ *
+ * @param typePool The type pool to use for locating types.
+ * @param rawTypeDescriptors A list of descriptors of the list's types' erasures.
+ */
+ protected TokenList(TypePool typePool, List<String> rawTypeDescriptors) {
+ this.typePool = typePool;
+ this.rawTypeDescriptors = rawTypeDescriptors;
+ }
+
+ @Override
+ public Generic get(int index) {
+ return new Malformed(typePool, rawTypeDescriptors.get(index));
+ }
+
+ @Override
+ public int size() {
+ return rawTypeDescriptors.size();
+ }
+
+ @Override
+ public TypeList asErasures() {
+ return new LazyTypeList(typePool, rawTypeDescriptors);
+ }
+ }
+
+ }
+ }
+
+ /**
+ * A lazy field description that only resolved type references when required.
+ */
+ private class LazyFieldDescription extends FieldDescription.InDefinedShape.AbstractBase {
+
+ /**
+ * The name of the field.
+ */
+ private final String name;
+
+ /**
+ * The modifiers of the field.
+ */
+ private final int modifiers;
+
+ /**
+ * The descriptor of this field's type.
+ */
+ private final String descriptor;
+
+ /**
+ * The field's generic signature as found in the class file or {@code null} if the field is not generic.
+ */
+ private final String genericSignature;
+
+ /**
+ * A resolution of this field's generic type.
+ */
+ private final GenericTypeToken.Resolution.ForField signatureResolution;
+
+ /**
+ * A mapping of the field type's type annotation tokens.
+ */
+ private final Map<String, List<AnnotationToken>> typeAnnotationTokens;
+
+ /**
+ * A list of annotation descriptions of this field.
+ */
+ private final List<AnnotationToken> annotationTokens;
+
+ /**
+ * Creates a new lazy field description.
+ *
+ * @param name The name of the field.
+ * @param modifiers The modifiers of the field.
+ * @param descriptor The descriptor of this field's type.
+ * @param genericSignature The field's generic signature as found in the class file or {@code null} if the field is not generic.
+ * @param signatureResolution A resolution of this field's generic type.
+ * @param typeAnnotationTokens A mapping of the field type's type annotation tokens.
+ * @param annotationTokens A list of annotation descriptions of this field.
+ */
+ private LazyFieldDescription(String name,
+ int modifiers,
+ String descriptor,
+ String genericSignature,
+ GenericTypeToken.Resolution.ForField signatureResolution,
+ Map<String, List<AnnotationToken>> typeAnnotationTokens,
+ List<AnnotationToken> annotationTokens) {
+ this.modifiers = modifiers;
+ this.name = name;
+ this.descriptor = descriptor;
+ this.genericSignature = genericSignature;
+ this.signatureResolution = signatureResolution;
+ this.typeAnnotationTokens = typeAnnotationTokens;
+ this.annotationTokens = annotationTokens;
+ }
+
+ @Override
+ public Generic getType() {
+ return signatureResolution.resolveFieldType(descriptor, typePool, typeAnnotationTokens, this);
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return LazyAnnotationDescription.asListOfNullable(typePool, annotationTokens);
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public TypeDescription getDeclaringType() {
+ return LazyTypeDescription.this;
+ }
+
+ @Override
+ public int getModifiers() {
+ return modifiers;
+ }
+
+ @Override
+ public String getGenericSignature() {
+ return genericSignature;
+ }
+ }
+
+ /**
+ * A lazy representation of a method that resolves references to types only on demand.
+ */
+ private class LazyMethodDescription extends MethodDescription.InDefinedShape.AbstractBase {
+
+ /**
+ * The internal name of this method.
+ */
+ private final String internalName;
+
+ /**
+ * The modifiers of this method.
+ */
+ private final int modifiers;
+
+ /**
+ * The descriptor of the return type.
+ */
+ private final String returnTypeDescriptor;
+
+ /**
+ * The method's generic signature as found in the class file or {@code null} if the method is not generic.
+ */
+ private final String genericSignature;
+
+ /**
+ * The generic type token of this method.
+ */
+ private final GenericTypeToken.Resolution.ForMethod signatureResolution;
+
+ /**
+ * A list of type descriptions of this method's parameters.
+ */
+ private final List<String> parameterTypeDescriptors;
+
+ /**
+ * A list of type descriptions of this method's exception types.
+ */
+ private final List<String> exceptionTypeDescriptors;
+
+ /**
+ * A mapping of the type variables' type annotation tokens by their indices.
+ */
+ private final Map<Integer, Map<String, List<AnnotationToken>>> typeVariableAnnotationTokens;
+
+ /**
+ * A mapping of the type variables' type bounds' type annotation tokens by their indices and each variable's index.
+ */
+ private final Map<Integer, Map<Integer, Map<String, List<AnnotationToken>>>> typeVariableBoundAnnotationTokens;
+
+ /**
+ * A mapping of the return type's type variable tokens.
+ */
+ private final Map<String, List<AnnotationToken>> returnTypeAnnotationTokens;
+
+ /**
+ * A mapping of the parameter types' type annotation tokens by their indices.
+ */
+ private final Map<Integer, Map<String, List<AnnotationToken>>> parameterTypeAnnotationTokens;
+
+ /**
+ * A mapping of the exception types' type annotation tokens by their indices.
+ */
+ private final Map<Integer, Map<String, List<AnnotationToken>>> exceptionTypeAnnotationTokens;
+
+ /**
+ * A mapping of the receiver type's type annotation tokens.
+ */
+ private final Map<String, List<AnnotationToken>> receiverTypeAnnotationTokens;
+
+ /**
+ * The annotation tokens representing the method's annotations.
+ */
+ private final List<AnnotationToken> annotationTokens;
+
+ /**
+ * The annotation tokens representing the parameter's annotation. Every index can
+ * contain {@code null} if a parameter does not define any annotations.
+ */
+ private final Map<Integer, List<AnnotationToken>> parameterAnnotationTokens;
+
+ /**
+ * An array of parameter names which may be {@code null} if no explicit name is known for a parameter.
+ */
+ private final String[] parameterNames;
+
+ /**
+ * An array of parameter modifiers which may be {@code null} if no modifiers is known.
+ */
+ private final Integer[] parameterModifiers;
+
+ /**
+ * The default value of this method or {@code null} if no such value exists.
+ */
+ private final AnnotationValue<?, ?> defaultValue;
+
+ /**
+ * Creates a new lazy method description.
+ *
+ * @param internalName The internal name of this method.
+ * @param modifiers The modifiers of the represented method.
+ * @param descriptor The method descriptor of this method.
+ * @param genericSignature The method's generic signature as found in the class file or {@code null} if the method is not generic.
+ * @param signatureResolution The generic type token of this method.
+ * @param exceptionTypeInternalName The internal names of the exceptions that are declared by this
+ * method or {@code null} if no exceptions are declared by this
+ * method.
+ * @param typeVariableAnnotationTokens A mapping of the type variables' type annotation tokens by their indices.
+ * @param typeVariableBoundAnnotationTokens A mapping of the type variables' type bounds' type annotation tokens by their
+ * index and each variable's index.
+ * @param returnTypeAnnotationTokens A mapping of the return type's type variable tokens.
+ * @param parameterTypeAnnotationTokens A mapping of the parameter types' type annotation tokens by their indices.
+ * @param exceptionTypeAnnotationTokens A mapping of the exception types' type annotation tokens by their indices.
+ * @param receiverTypeAnnotationTokens A mapping of the receiver type's type annotation tokens.
+ * @param annotationTokens The annotation tokens representing the method's annotations.
+ * @param parameterAnnotationTokens The annotation tokens representing the parameter's annotation. Every
+ * index can contain {@code null} if a parameter does not define any annotations.
+ * @param parameterTokens A list of parameter tokens which might be empty or even out of sync
+ * with the actual parameters if the debugging information found in a
+ * class was corrupt.
+ * @param defaultValue The default value of this method or {@code null} if there is no
+ */
+ private LazyMethodDescription(String internalName,
+ int modifiers,
+ String descriptor,
+ String genericSignature,
+ GenericTypeToken.Resolution.ForMethod signatureResolution,
+ String[] exceptionTypeInternalName,
+ Map<Integer, Map<String, List<AnnotationToken>>> typeVariableAnnotationTokens,
+ Map<Integer, Map<Integer, Map<String, List<AnnotationToken>>>> typeVariableBoundAnnotationTokens,
+ Map<String, List<AnnotationToken>> returnTypeAnnotationTokens,
+ Map<Integer, Map<String, List<AnnotationToken>>> parameterTypeAnnotationTokens,
+ Map<Integer, Map<String, List<AnnotationToken>>> exceptionTypeAnnotationTokens,
+ Map<String, List<AnnotationToken>> receiverTypeAnnotationTokens,
+ List<AnnotationToken> annotationTokens,
+ Map<Integer, List<AnnotationToken>> parameterAnnotationTokens,
+ List<MethodToken.ParameterToken> parameterTokens,
+ AnnotationValue<?, ?> defaultValue) {
+ this.modifiers = modifiers;
+ this.internalName = internalName;
+ Type methodType = Type.getMethodType(descriptor);
+ Type returnType = methodType.getReturnType();
+ Type[] parameterType = methodType.getArgumentTypes();
+ returnTypeDescriptor = returnType.getDescriptor();
+ parameterTypeDescriptors = new ArrayList<String>(parameterType.length);
+ for (Type type : parameterType) {
+ parameterTypeDescriptors.add(type.getDescriptor());
+ }
+ this.genericSignature = genericSignature;
+ this.signatureResolution = signatureResolution;
+ if (exceptionTypeInternalName == null) {
+ exceptionTypeDescriptors = Collections.emptyList();
+ } else {
+ exceptionTypeDescriptors = new ArrayList<String>(exceptionTypeInternalName.length);
+ for (String anExceptionTypeInternalName : exceptionTypeInternalName) {
+ exceptionTypeDescriptors.add(Type.getObjectType(anExceptionTypeInternalName).getDescriptor());
+ }
+ }
+ this.typeVariableAnnotationTokens = typeVariableAnnotationTokens;
+ this.typeVariableBoundAnnotationTokens = typeVariableBoundAnnotationTokens;
+ this.returnTypeAnnotationTokens = returnTypeAnnotationTokens;
+ this.parameterTypeAnnotationTokens = parameterTypeAnnotationTokens;
+ this.exceptionTypeAnnotationTokens = exceptionTypeAnnotationTokens;
+ this.receiverTypeAnnotationTokens = receiverTypeAnnotationTokens;
+ this.annotationTokens = annotationTokens;
+ this.parameterAnnotationTokens = parameterAnnotationTokens;
+ parameterNames = new String[parameterType.length];
+ parameterModifiers = new Integer[parameterType.length];
+ if (parameterTokens.size() == parameterType.length) {
+ int index = 0;
+ for (MethodToken.ParameterToken parameterToken : parameterTokens) {
+ parameterNames[index] = parameterToken.getName();
+ parameterModifiers[index] = parameterToken.getModifiers();
+ index++;
+ }
+ }
+ this.defaultValue = defaultValue;
+ }
+
+ @Override
+ public Generic getReturnType() {
+ return signatureResolution.resolveReturnType(returnTypeDescriptor, typePool, returnTypeAnnotationTokens, this);
+ }
+
+ @Override
+ public TypeList.Generic getExceptionTypes() {
+ return signatureResolution.resolveExceptionTypes(exceptionTypeDescriptors, typePool, exceptionTypeAnnotationTokens, this);
+ }
+
+ @Override
+ public ParameterList<ParameterDescription.InDefinedShape> getParameters() {
+ return new LazyParameterList();
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return LazyAnnotationDescription.asList(typePool, annotationTokens);
+ }
+
+ @Override
+ public String getInternalName() {
+ return internalName;
+ }
+
+ @Override
+ public TypeDescription getDeclaringType() {
+ return LazyTypeDescription.this;
+ }
+
+ @Override
+ public int getModifiers() {
+ return modifiers;
+ }
+
+ @Override
+ public TypeList.Generic getTypeVariables() {
+ return signatureResolution.resolveTypeVariables(typePool, this, typeVariableAnnotationTokens, typeVariableBoundAnnotationTokens);
+ }
+
+ @Override
+ public AnnotationValue<?, ?> getDefaultValue() {
+ return defaultValue;
+ }
+
+ @Override
+ public Generic getReceiverType() {
+ if (isStatic()) {
+ return Generic.UNDEFINED;
+ } else if (isConstructor()) {
+ TypeDescription declaringType = getDeclaringType(), enclosingDeclaringType = declaringType.getEnclosingType();
+ if (enclosingDeclaringType == null) {
+ return declaringType.isGenerified()
+ ? new LazyParameterizedReceiverType(declaringType)
+ : new LazyNonGenericReceiverType(declaringType);
+ } else {
+ return !declaringType.isStatic() && declaringType.isGenerified()
+ ? new LazyParameterizedReceiverType(enclosingDeclaringType)
+ : new LazyNonGenericReceiverType(enclosingDeclaringType);
+ }
+ } else {
+ return LazyTypeDescription.this.isGenerified()
+ ? new LazyParameterizedReceiverType()
+ : new LazyNonGenericReceiverType();
+ }
+ }
+
+ @Override
+ public String getGenericSignature() {
+ return genericSignature;
+ }
+
+ /**
+ * A lazy list of parameter descriptions for the enclosing method description.
+ */
+ private class LazyParameterList extends ParameterList.AbstractBase<ParameterDescription.InDefinedShape> {
+
+ @Override
+ public ParameterDescription.InDefinedShape get(int index) {
+ return new LazyParameterDescription(index);
+ }
+
+ @Override
+ public boolean hasExplicitMetaData() {
+ for (int i = 0; i < size(); i++) {
+ if (parameterNames[i] == null || parameterModifiers[i] == null) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public int size() {
+ return parameterTypeDescriptors.size();
+ }
+
+ @Override
+ public TypeList.Generic asTypeList() {
+ return signatureResolution.resolveParameterTypes(parameterTypeDescriptors, typePool, parameterTypeAnnotationTokens, LazyMethodDescription.this);
+ }
+ }
+
+ /**
+ * A lazy description of a parameters of the enclosing method.
+ */
+ private class LazyParameterDescription extends ParameterDescription.InDefinedShape.AbstractBase {
+
+ /**
+ * The index of the described parameter.
+ */
+ private final int index;
+
+ /**
+ * Creates a new description for a given parameter of the enclosing method.
+ *
+ * @param index The index of the described parameter.
+ */
+ protected LazyParameterDescription(int index) {
+ this.index = index;
+ }
+
+ @Override
+ public MethodDescription.InDefinedShape getDeclaringMethod() {
+ return LazyMethodDescription.this;
+ }
+
+ @Override
+ public int getIndex() {
+ return index;
+ }
+
+ @Override
+ public boolean isNamed() {
+ return parameterNames[index] != null;
+ }
+
+ @Override
+ public boolean hasModifiers() {
+ return parameterModifiers[index] != null;
+ }
+
+ @Override
+ public String getName() {
+ return isNamed()
+ ? parameterNames[index]
+ : super.getName();
+ }
+
+ @Override
+ public int getModifiers() {
+ return hasModifiers()
+ ? parameterModifiers[index]
+ : super.getModifiers();
+ }
+
+ @Override
+ public Generic getType() {
+ return signatureResolution.resolveParameterTypes(parameterTypeDescriptors, typePool, parameterTypeAnnotationTokens, LazyMethodDescription.this).get(index);
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return LazyAnnotationDescription.asListOfNullable(typePool, parameterAnnotationTokens.get(index));
+ }
+ }
+
+ /**
+ * A lazy description of a parameterized receiver type.
+ */
+ private class LazyParameterizedReceiverType extends Generic.OfParameterizedType {
+
+ /**
+ * The erasure of the type to be represented as a parameterized receiver type.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new lazy parameterized receiver type of the method's declaring type.
+ */
+ protected LazyParameterizedReceiverType() {
+ this(LazyTypeDescription.this);
+ }
+
+ /**
+ * Creates a new lazy parameterized receiver type of the supplied receiver type.
+ *
+ * @param typeDescription The erasure of the type to be represented as a parameterized receiver type.
+ */
+ protected LazyParameterizedReceiverType(TypeDescription typeDescription) {
+ this.typeDescription = typeDescription;
+ }
+
+ @Override
+ public TypeList.Generic getTypeArguments() {
+ return new TypeArgumentList(typeDescription.getTypeVariables());
+ }
+
+ @Override
+ public Generic getOwnerType() {
+ TypeDescription declaringType = typeDescription.getDeclaringType();
+ if (declaringType == null) {
+ return Generic.UNDEFINED;
+ } else {
+ return !typeDescription.isStatic() && declaringType.isGenerified()
+ ? new LazyParameterizedReceiverType(declaringType)
+ : new LazyNonGenericReceiverType(declaringType);
+ }
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return LazyAnnotationDescription.asListOfNullable(typePool, receiverTypeAnnotationTokens.get(getTypePath()));
+ }
+
+ /**
+ * Returns the type path for this type.
+ *
+ * @return This type's type path.
+ */
+ private String getTypePath() {
+ StringBuilder typePath = new StringBuilder();
+ for (int index = 0; index < typeDescription.getSegmentCount(); index++) {
+ typePath = typePath.append(GenericTypeToken.INNER_CLASS_PATH);
+ }
+ return typePath.toString();
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ return typeDescription;
+ }
+
+ /**
+ * A list of generic types representing the receiver type's type arguments.
+ */
+ protected class TypeArgumentList extends TypeList.Generic.AbstractBase {
+
+ /**
+ * The type variables of the represented receiver type.
+ */
+ private final List<? extends Generic> typeVariables;
+
+ /**
+ * Creates a new type argument list.
+ *
+ * @param typeVariables The type variables of the represented receiver type.
+ */
+ protected TypeArgumentList(List<? extends Generic> typeVariables) {
+ this.typeVariables = typeVariables;
+ }
+
+ @Override
+ public Generic get(int index) {
+ return new AnnotatedTypeVariable(typeVariables.get(index), index);
+ }
+
+ @Override
+ public int size() {
+ return typeVariables.size();
+ }
+
+ /**
+ * Represents a type variable as a type argument with type annotations.
+ */
+ protected class AnnotatedTypeVariable extends OfTypeVariable {
+
+ /**
+ * The type variable's description.
+ */
+ private final Generic typeVariable;
+
+ /**
+ * The type variable's index.
+ */
+ private final int index;
+
+ /**
+ * Creates a new description of an annotated type variable as a type argument.
+ *
+ * @param typeVariable The type variable's description.
+ * @param index The type variable's index.
+ */
+ protected AnnotatedTypeVariable(Generic typeVariable, int index) {
+ this.typeVariable = typeVariable;
+ this.index = index;
+ }
+
+ @Override
+ public TypeList.Generic getUpperBounds() {
+ return typeVariable.getUpperBounds();
+ }
+
+ @Override
+ public TypeVariableSource getTypeVariableSource() {
+ return typeVariable.getTypeVariableSource();
+ }
+
+ @Override
+ public String getSymbol() {
+ return typeVariable.getSymbol();
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ return LazyAnnotationDescription.asListOfNullable(typePool, receiverTypeAnnotationTokens.get(getTypePath()
+ + index
+ + GenericTypeToken.INDEXED_TYPE_DELIMITER));
+ }
+ }
+ }
+ }
+
+ /**
+ * A lazy description of a non-generic receiver type.
+ */
+ protected class LazyNonGenericReceiverType extends Generic.OfNonGenericType {
+
+ /**
+ * The type description of the non-generic receiver type.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new non-generic receiver type of the method's declaring type.
+ */
+ protected LazyNonGenericReceiverType() {
+ this(LazyTypeDescription.this);
+ }
+
+ /**
+ * Creates a new non-generic receiver type of the supplied type.
+ *
+ * @param typeDescription The type to represent as a non-generic receiver type.
+ */
+ protected LazyNonGenericReceiverType(TypeDescription typeDescription) {
+ this.typeDescription = typeDescription;
+ }
+
+ @Override
+ public Generic getOwnerType() {
+ TypeDescription declaringType = typeDescription.getDeclaringType();
+ return declaringType == null
+ ? Generic.UNDEFINED
+ : new LazyNonGenericReceiverType(declaringType);
+ }
+
+ @Override
+ public Generic getComponentType() {
+ return Generic.UNDEFINED;
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ StringBuilder typePath = new StringBuilder();
+ for (int index = 0; index < typeDescription.getSegmentCount(); index++) {
+ typePath = typePath.append(GenericTypeToken.INNER_CLASS_PATH);
+ }
+ return LazyAnnotationDescription.asListOfNullable(typePool, receiverTypeAnnotationTokens.get(typePath.toString()));
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ return typeDescription;
+ }
+ }
+ }
+ }
+
+ /**
+ * A type extractor reads a class file and collects data that is relevant to create a type description.
+ */
+ protected class TypeExtractor extends ClassVisitor {
+
+ /**
+ * A mask that cuts off pseudo flags beyond the second byte that are inserted by ASM.
+ */
+ private static final int REAL_MODIFIER_MASK = 0xFFFF;
+
+ /**
+ * A mapping of the super types' type annotation tokens by their indices.
+ */
+ private final Map<Integer, Map<String, List<LazyTypeDescription.AnnotationToken>>> superTypeAnnotationTokens;
+
+ /**
+ * A mapping of the type variables' type annotation tokens by their indices.
+ */
+ private final Map<Integer, Map<String, List<LazyTypeDescription.AnnotationToken>>> typeVariableAnnotationTokens;
+
+ /**
+ * A mapping of the type variables' bounds' type annotation tokens by their indices and each variables index.
+ */
+ private final Map<Integer, Map<Integer, Map<String, List<LazyTypeDescription.AnnotationToken>>>> typeVariableBoundsAnnotationTokens;
+
+ /**
+ * A list of annotation tokens describing annotations that are found on the visited type.
+ */
+ private final List<LazyTypeDescription.AnnotationToken> annotationTokens;
+
+ /**
+ * A list of field tokens describing fields that are found on the visited type.
+ */
+ private final List<LazyTypeDescription.FieldToken> fieldTokens;
+
+ /**
+ * A list of method tokens describing annotations that are found on the visited type.
+ */
+ private final List<LazyTypeDescription.MethodToken> methodTokens;
+
+ /**
+ * The actual modifiers found for this type.
+ */
+ private int actualModifiers;
+
+ /**
+ * The modifiers found for this type.
+ */
+ private int modifiers;
+
+ /**
+ * The internal name found for this type.
+ */
+ private String internalName;
+
+ /**
+ * The internal name of the super type found for this type or {@code null} if no such type exists.
+ */
+ private String superClassName;
+
+ /**
+ * The generic signature of the type or {@code null} if it is not generic.
+ */
+ private String genericSignature;
+
+ /**
+ * A list of internal names of interfaces implemented by this type or {@code null} if no interfaces
+ * are implemented.
+ */
+ private String[] interfaceName;
+
+ /**
+ * {@code true} if this type was found to represent an anonymous type.
+ */
+ private boolean anonymousType;
+
+ /**
+ * The declaration context found for this type.
+ */
+ private LazyTypeDescription.TypeContainment typeContainment;
+
+ /**
+ * The binary name of this type's declaring type or {@code null} if no such type exists.
+ */
+ private String declaringTypeName;
+
+ /**
+ * A list of descriptors representing the types that are declared by the parsed type.
+ */
+ private final List<String> declaredTypes;
+
+ /**
+ * Creates a new type extractor.
+ */
+ protected TypeExtractor() {
+ super(Opcodes.ASM5);
+ superTypeAnnotationTokens = new HashMap<Integer, Map<String, List<LazyTypeDescription.AnnotationToken>>>();
+ typeVariableAnnotationTokens = new HashMap<Integer, Map<String, List<LazyTypeDescription.AnnotationToken>>>();
+ typeVariableBoundsAnnotationTokens = new HashMap<Integer, Map<Integer, Map<String, List<LazyTypeDescription.AnnotationToken>>>>();
+ annotationTokens = new ArrayList<LazyTypeDescription.AnnotationToken>();
+ fieldTokens = new ArrayList<LazyTypeDescription.FieldToken>();
+ methodTokens = new ArrayList<LazyTypeDescription.MethodToken>();
+ anonymousType = false;
+ typeContainment = LazyTypeDescription.TypeContainment.SelfContained.INSTANCE;
+ declaredTypes = new ArrayList<String>();
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "The array is not to be modified by contract")
+ public void visit(int classFileVersion,
+ int modifiers,
+ String internalName,
+ String genericSignature,
+ String superClassName,
+ String[] interfaceName) {
+ this.modifiers = modifiers & REAL_MODIFIER_MASK;
+ actualModifiers = modifiers;
+ this.internalName = internalName;
+ this.genericSignature = genericSignature;
+ this.superClassName = superClassName;
+ this.interfaceName = interfaceName;
+ }
+
+ @Override
+ public void visitOuterClass(String typeName, String methodName, String methodDescriptor) {
+ if (methodName != null) {
+ typeContainment = new LazyTypeDescription.TypeContainment.WithinMethod(typeName, methodName, methodDescriptor);
+ } else if (typeName != null) {
+ typeContainment = new LazyTypeDescription.TypeContainment.WithinType(typeName, true);
+ }
+ }
+
+ @Override
+ public void visitInnerClass(String internalName, String outerName, String innerName, int modifiers) {
+ if (internalName.equals(this.internalName)) {
+ this.modifiers = modifiers & REAL_MODIFIER_MASK;
+ if (innerName == null) {
+ anonymousType = true;
+ }
+ if (outerName != null) {
+ declaringTypeName = outerName;
+ if (typeContainment.isSelfContained()) {
+ typeContainment = new LazyTypeDescription.TypeContainment.WithinType(outerName, false);
+ }
+ }
+ } else if (outerName != null && innerName != null && internalName.equals(this.internalName + "$" + innerName)) {
+ declaredTypes.add("L" + internalName + ";");
+ }
+ }
+
+ @Override
+ public AnnotationVisitor visitTypeAnnotation(int rawTypeReference, TypePath typePath, String descriptor, boolean visible) {
+ AnnotationRegistrant annotationRegistrant;
+ TypeReference typeReference = new TypeReference(rawTypeReference);
+ switch (typeReference.getSort()) {
+ case TypeReference.CLASS_EXTENDS:
+ annotationRegistrant = new AnnotationRegistrant.ForTypeVariable.WithIndex(descriptor,
+ typePath,
+ typeReference.getSuperTypeIndex(),
+ superTypeAnnotationTokens);
+ break;
+ case TypeReference.CLASS_TYPE_PARAMETER:
+ annotationRegistrant = new AnnotationRegistrant.ForTypeVariable.WithIndex(descriptor,
+ typePath,
+ typeReference.getTypeParameterIndex(),
+ typeVariableAnnotationTokens);
+ break;
+ case TypeReference.CLASS_TYPE_PARAMETER_BOUND:
+ annotationRegistrant = new AnnotationRegistrant.ForTypeVariable.WithIndex.DoubleIndexed(descriptor,
+ typePath,
+ typeReference.getTypeParameterBoundIndex(),
+ typeReference.getTypeParameterIndex(),
+ typeVariableBoundsAnnotationTokens);
+ break;
+ default:
+ throw new IllegalArgumentException("Unexpected type reference: " + typeReference.getSort());
+ }
+ return new AnnotationExtractor(annotationRegistrant, new ComponentTypeLocator.ForAnnotationProperty(Default.this, descriptor));
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+ return new AnnotationExtractor(descriptor, annotationTokens, new ComponentTypeLocator.ForAnnotationProperty(Default.this, descriptor));
+ }
+
+ @Override
+ public FieldVisitor visitField(int modifiers, String internalName, String descriptor, String genericSignature, Object defaultValue) {
+ return new FieldExtractor(modifiers & REAL_MODIFIER_MASK, internalName, descriptor, genericSignature);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int modifiers, String internalName, String descriptor, String genericSignature, String[] exceptionName) {
+ return internalName.equals(MethodDescription.TYPE_INITIALIZER_INTERNAL_NAME)
+ ? IGNORE_METHOD
+ : new MethodExtractor(modifiers & REAL_MODIFIER_MASK, internalName, descriptor, genericSignature, exceptionName);
+ }
+
+ /**
+ * Creates a type description from all data that is currently collected. This method should only be invoked
+ * after a class file was parsed fully.
+ *
+ * @return A type description reflecting the data that was collected by this instance.
+ */
+ protected TypeDescription toTypeDescription() {
+ return new LazyTypeDescription(Default.this,
+ actualModifiers,
+ modifiers,
+ internalName,
+ superClassName,
+ interfaceName,
+ genericSignature,
+ typeContainment,
+ declaringTypeName,
+ declaredTypes,
+ anonymousType,
+ superTypeAnnotationTokens,
+ typeVariableAnnotationTokens,
+ typeVariableBoundsAnnotationTokens,
+ annotationTokens,
+ fieldTokens,
+ methodTokens);
+ }
+
+ /**
+ * An annotation extractor reads an annotation found in a class field an collects data that
+ * is relevant to creating a related annotation description.
+ */
+ protected class AnnotationExtractor extends AnnotationVisitor {
+
+ /**
+ * The annotation registrant to register found annotation values on.
+ */
+ private final AnnotationRegistrant annotationRegistrant;
+
+ /**
+ * A locator for the component type of any found annotation value.
+ */
+ private final ComponentTypeLocator componentTypeLocator;
+
+ /**
+ * Creates a new annotation extractor for a byte code element without an index.
+ *
+ * @param descriptor The annotation descriptor.
+ * @param annotationTokens The collection for storing any discovered annotation tokens.
+ * @param componentTypeLocator The component type locator to use.
+ */
+ protected AnnotationExtractor(String descriptor, List<LazyTypeDescription.AnnotationToken> annotationTokens, ComponentTypeLocator componentTypeLocator) {
+ this(new AnnotationRegistrant.ForByteCodeElement(descriptor, annotationTokens), componentTypeLocator);
+ }
+
+ /**
+ * Creates a new annotation extractor for a byte code element with an index.
+ *
+ * @param descriptor The annotation descriptor.
+ * @param index The index of the element for which the annotations are collected.
+ * @param annotationTokens The collection for storing any discovered annotation tokens.
+ * @param componentTypeLocator The component type locator to use.
+ */
+ protected AnnotationExtractor(String descriptor,
+ int index,
+ Map<Integer, List<LazyTypeDescription.AnnotationToken>> annotationTokens,
+ ComponentTypeLocator componentTypeLocator) {
+ this(new AnnotationRegistrant.ForByteCodeElement.WithIndex(descriptor, index, annotationTokens), componentTypeLocator);
+ }
+
+ /**
+ * Creates a new annotation extractor.
+ *
+ * @param annotationRegistrant The annotation registrant to register found annotation values on.
+ * @param componentTypeLocator A locator for the component type of any found annotation value.
+ */
+ protected AnnotationExtractor(AnnotationRegistrant annotationRegistrant, ComponentTypeLocator componentTypeLocator) {
+ super(Opcodes.ASM5);
+ this.annotationRegistrant = annotationRegistrant;
+ this.componentTypeLocator = componentTypeLocator;
+ }
+
+ @Override
+ public void visit(String name, Object value) {
+ annotationRegistrant.register(name, value instanceof Type
+ ? new RawTypeValue(Default.this, (Type) value)
+ : AnnotationValue.ForConstant.of(value));
+ }
+
+ @Override
+ public void visitEnum(String name, String descriptor, String value) {
+ annotationRegistrant.register(name, new RawEnumerationValue(Default.this, descriptor, value));
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String name, String descriptor) {
+ return new AnnotationExtractor(new AnnotationLookup(descriptor, name),
+ new ComponentTypeLocator.ForAnnotationProperty(TypePool.Default.this, descriptor));
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ return new AnnotationExtractor(new ArrayLookup(name, componentTypeLocator.bind(name)), ComponentTypeLocator.Illegal.INSTANCE);
+ }
+
+ @Override
+ public void visitEnd() {
+ annotationRegistrant.onComplete();
+ }
+
+ /**
+ * An annotation registrant for registering values of an array.
+ */
+ protected class ArrayLookup implements AnnotationRegistrant {
+
+ /**
+ * The name of the annotation property the collected array is representing.
+ */
+ private final String name;
+
+ /**
+ * A lazy reference to resolve the component type of the collected array.
+ */
+ private final RawDescriptionArray.ComponentTypeReference componentTypeReference;
+
+ /**
+ * A list of all annotation values that are found on this array.
+ */
+ private final List<AnnotationValue<?, ?>> values;
+
+ /**
+ * Creates a new annotation registrant for an array lookup.
+ *
+ * @param name The name of the annotation property the collected array is representing.
+ * @param componentTypeReference A lazy reference to resolve the component type of the collected array.
+ */
+ protected ArrayLookup(String name, RawDescriptionArray.ComponentTypeReference componentTypeReference) {
+ this.name = name;
+ this.componentTypeReference = componentTypeReference;
+ values = new ArrayList<AnnotationValue<?, ?>>();
+ }
+
+ @Override
+ public void register(String ignored, AnnotationValue<?, ?> annotationValue) {
+ values.add(annotationValue);
+ }
+
+ @Override
+ public void onComplete() {
+ annotationRegistrant.register(name, new RawDescriptionArray(Default.this, componentTypeReference, values));
+ }
+ }
+
+ /**
+ * An annotation registrant for registering the values on an array that is itself an annotation property.
+ */
+ protected class AnnotationLookup implements AnnotationRegistrant {
+
+ /**
+ * The descriptor of the original annotation for which the annotation values are looked up.
+ */
+ private final String descriptor;
+
+ /**
+ * The name of the original annotation for which the annotation values are looked up.
+ */
+ private final String name;
+
+ /**
+ * This annotation's values mapped by their attribute name.
+ */
+ private final Map<String, AnnotationValue<?, ?>> values;
+
+ /**
+ * Creates a new annotation registrant for a recursive annotation lookup.
+ *
+ * @param name The name of the original annotation for which the annotation values are looked up.
+ * @param descriptor The descriptor of the original annotation for which the annotation values are looked up.
+ */
+ protected AnnotationLookup(String descriptor, String name) {
+ this.descriptor = descriptor;
+ this.name = name;
+ values = new HashMap<String, AnnotationValue<?, ?>>();
+ }
+
+ @Override
+ public void register(String name, AnnotationValue<?, ?> annotationValue) {
+ values.put(name, annotationValue);
+ }
+
+ @Override
+ public void onComplete() {
+ annotationRegistrant.register(name, new RawAnnotationValue(Default.this, new LazyTypeDescription.AnnotationToken(descriptor, values)));
+ }
+ }
+ }
+
+ /**
+ * A field extractor reads a field within a class file and collects data that is relevant
+ * to creating a related field description.
+ */
+ protected class FieldExtractor extends FieldVisitor {
+
+ /**
+ * The modifiers found on the field.
+ */
+ private final int modifiers;
+
+ /**
+ * The name of the field.
+ */
+ private final String internalName;
+
+ /**
+ * The descriptor of the field type.
+ */
+ private final String descriptor;
+
+ /**
+ * The generic signature of the field or {@code null} if it is not generic.
+ */
+ private final String genericSignature;
+
+ /**
+ * A mapping of the field type's type annotations.
+ */
+ private final Map<String, List<LazyTypeDescription.AnnotationToken>> typeAnnotationTokens;
+
+ /**
+ * A list of annotation tokens found for this field.
+ */
+ private final List<LazyTypeDescription.AnnotationToken> annotationTokens;
+
+ /**
+ * Creates a new field extractor.
+ *
+ * @param modifiers The modifiers found for this field.
+ * @param internalName The name of the field.
+ * @param descriptor The descriptor of the field type.
+ * @param genericSignature The generic signature of the field or {@code null} if it is not generic.
+ */
+ protected FieldExtractor(int modifiers,
+ String internalName,
+ String descriptor,
+ String genericSignature) {
+ super(Opcodes.ASM5);
+ this.modifiers = modifiers;
+ this.internalName = internalName;
+ this.descriptor = descriptor;
+ this.genericSignature = genericSignature;
+ typeAnnotationTokens = new HashMap<String, List<LazyTypeDescription.AnnotationToken>>();
+ annotationTokens = new ArrayList<LazyTypeDescription.AnnotationToken>();
+ }
+
+ @Override
+ public AnnotationVisitor visitTypeAnnotation(int rawTypeReference, TypePath typePath, String descriptor, boolean visible) {
+ AnnotationRegistrant annotationRegistrant;
+ TypeReference typeReference = new TypeReference(rawTypeReference);
+ switch (typeReference.getSort()) {
+ case TypeReference.FIELD:
+ annotationRegistrant = new AnnotationRegistrant.ForTypeVariable(descriptor, typePath, typeAnnotationTokens);
+ break;
+ default:
+ throw new IllegalStateException("Unexpected type reference on field: " + typeReference.getSort());
+ }
+ return new AnnotationExtractor(annotationRegistrant, new ComponentTypeLocator.ForAnnotationProperty(Default.this, descriptor));
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+ return new AnnotationExtractor(descriptor, annotationTokens, new ComponentTypeLocator.ForAnnotationProperty(Default.this, descriptor));
+ }
+
+ @Override
+ public void visitEnd() {
+ fieldTokens.add(new LazyTypeDescription.FieldToken(internalName,
+ modifiers,
+ descriptor,
+ genericSignature,
+ typeAnnotationTokens,
+ annotationTokens));
+ }
+ }
+
+ /**
+ * A method extractor reads a method within a class file and collects data that is relevant
+ * to creating a related method description.
+ */
+ protected class MethodExtractor extends MethodVisitor implements AnnotationRegistrant {
+
+ /**
+ * The modifiers found for this method.
+ */
+ private final int modifiers;
+
+ /**
+ * The internal name found for this method.
+ */
+ private final String internalName;
+
+ /**
+ * The descriptor found for this method.
+ */
+ private final String descriptor;
+
+ /**
+ * The generic signature of the method or {@code null} if it is not generic.
+ */
+ private final String genericSignature;
+
+ /**
+ * An array of internal names of the exceptions of the found method
+ * or {@code null} if there are no such exceptions.
+ */
+ private final String[] exceptionName;
+
+ /**
+ * A mapping of the method's type variables' type annotations by their indices.
+ */
+ private final Map<Integer, Map<String, List<LazyTypeDescription.AnnotationToken>>> typeVariableAnnotationTokens;
+
+ /**
+ * A mapping of the method's type variables' bounds' type annotations by their indices and each variable's index.
+ */
+ private final Map<Integer, Map<Integer, Map<String, List<LazyTypeDescription.AnnotationToken>>>> typeVariableBoundAnnotationTokens;
+
+ /**
+ * A mapping of the method's return type's type annotations.
+ */
+ private final Map<String, List<LazyTypeDescription.AnnotationToken>> returnTypeAnnotationTokens;
+
+ /**
+ * A mapping of the parameters' type annotations by their indices.
+ */
+ private final Map<Integer, Map<String, List<LazyTypeDescription.AnnotationToken>>> parameterTypeAnnotationTokens;
+
+ /**
+ * A mapping of the exception types' type annotations by their indices.
+ */
+ private final Map<Integer, Map<String, List<LazyTypeDescription.AnnotationToken>>> exceptionTypeAnnotationTokens;
+
+ /**
+ * A mapping of the receiver type's type annotations.
+ */
+ private final Map<String, List<LazyTypeDescription.AnnotationToken>> receiverTypeAnnotationTokens;
+
+ /**
+ * A list of annotation tokens declared on the found method.
+ */
+ private final List<LazyTypeDescription.AnnotationToken> annotationTokens;
+
+ /**
+ * A mapping of parameter indices to annotation tokens found for the parameters at these indices.
+ */
+ private final Map<Integer, List<LazyTypeDescription.AnnotationToken>> parameterAnnotationTokens;
+
+ /**
+ * A list of tokens representing meta information of a parameter as it is available for method's
+ * that are compiled in the Java 8 version format.
+ */
+ private final List<LazyTypeDescription.MethodToken.ParameterToken> parameterTokens;
+
+ /**
+ * A bag of parameter meta information representing debugging information which allows to extract
+ * a method's parameter names.
+ */
+ private final ParameterBag legacyParameterBag;
+
+ /**
+ * The first label that is found in the method's body, if any, denoting the start of the method.
+ * This label can be used to identify names of local variables that describe the method's parameters.
+ */
+ private Label firstLabel;
+
+ /**
+ * The default value of the found method or {@code null} if no such value exists.
+ */
+ private AnnotationValue<?, ?> defaultValue;
+
+ /**
+ * Creates a method extractor.
+ *
+ * @param modifiers The modifiers found for this method.
+ * @param internalName The internal name found for this method.
+ * @param descriptor The descriptor found for this method.
+ * @param genericSignature The generic signature of the method or {@code null} if it is not generic.
+ * @param exceptionName An array of internal names of the exceptions of the found method
+ * or {@code null} if there are no such exceptions.
+ */
+ protected MethodExtractor(int modifiers,
+ String internalName,
+ String descriptor,
+ String genericSignature,
+ String[] exceptionName) {
+ super(Opcodes.ASM5);
+ this.modifiers = modifiers;
+ this.internalName = internalName;
+ this.descriptor = descriptor;
+ this.genericSignature = genericSignature;
+ this.exceptionName = exceptionName;
+ typeVariableAnnotationTokens = new HashMap<Integer, Map<String, List<LazyTypeDescription.AnnotationToken>>>();
+ typeVariableBoundAnnotationTokens = new HashMap<Integer, Map<Integer, Map<String, List<LazyTypeDescription.AnnotationToken>>>>();
+ returnTypeAnnotationTokens = new HashMap<String, List<LazyTypeDescription.AnnotationToken>>();
+ parameterTypeAnnotationTokens = new HashMap<Integer, Map<String, List<LazyTypeDescription.AnnotationToken>>>();
+ exceptionTypeAnnotationTokens = new HashMap<Integer, Map<String, List<LazyTypeDescription.AnnotationToken>>>();
+ receiverTypeAnnotationTokens = new HashMap<String, List<LazyTypeDescription.AnnotationToken>>();
+ annotationTokens = new ArrayList<LazyTypeDescription.AnnotationToken>();
+ parameterAnnotationTokens = new HashMap<Integer, List<LazyTypeDescription.AnnotationToken>>();
+ parameterTokens = new ArrayList<LazyTypeDescription.MethodToken.ParameterToken>();
+ legacyParameterBag = new ParameterBag(Type.getMethodType(descriptor).getArgumentTypes());
+ }
+
+ @Override
+ public AnnotationVisitor visitTypeAnnotation(int rawTypeReference, TypePath typePath, String descriptor, boolean visible) {
+ AnnotationRegistrant annotationRegistrant;
+ TypeReference typeReference = new TypeReference(rawTypeReference);
+ switch (typeReference.getSort()) {
+ case TypeReference.METHOD_TYPE_PARAMETER:
+ annotationRegistrant = new ForTypeVariable.WithIndex(descriptor,
+ typePath,
+ typeReference.getTypeParameterIndex(),
+ typeVariableAnnotationTokens);
+ break;
+ case TypeReference.METHOD_TYPE_PARAMETER_BOUND:
+ annotationRegistrant = new ForTypeVariable.WithIndex.DoubleIndexed(descriptor,
+ typePath,
+ typeReference.getTypeParameterBoundIndex(),
+ typeReference.getTypeParameterIndex(),
+ typeVariableBoundAnnotationTokens);
+ break;
+ case TypeReference.METHOD_RETURN:
+ annotationRegistrant = new ForTypeVariable(descriptor,
+ typePath,
+ returnTypeAnnotationTokens);
+ break;
+ case TypeReference.METHOD_FORMAL_PARAMETER:
+ annotationRegistrant = new ForTypeVariable.WithIndex(descriptor,
+ typePath,
+ typeReference.getFormalParameterIndex(),
+ parameterTypeAnnotationTokens);
+ break;
+ case TypeReference.THROWS:
+ annotationRegistrant = new ForTypeVariable.WithIndex(descriptor,
+ typePath,
+ typeReference.getExceptionIndex(),
+ exceptionTypeAnnotationTokens);
+ break;
+ case TypeReference.METHOD_RECEIVER:
+ annotationRegistrant = new ForTypeVariable(descriptor,
+ typePath,
+ receiverTypeAnnotationTokens);
+ break;
+ default:
+ throw new IllegalStateException("Unexpected type reference on method: " + typeReference.getSort());
+ }
+ return new AnnotationExtractor(annotationRegistrant, new ComponentTypeLocator.ForAnnotationProperty(Default.this, descriptor));
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+ return new AnnotationExtractor(descriptor, annotationTokens, new ComponentTypeLocator.ForAnnotationProperty(Default.this, descriptor));
+ }
+
+ @Override
+ public AnnotationVisitor visitParameterAnnotation(int index, String descriptor, boolean visible) {
+ return new AnnotationExtractor(descriptor, index, parameterAnnotationTokens, new ComponentTypeLocator.ForAnnotationProperty(Default.this, descriptor));
+ }
+
+ @Override
+ public void visitLabel(Label label) {
+ if (readerMode.isExtended() && firstLabel == null) {
+ firstLabel = label;
+ }
+ }
+
+ @Override
+ public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) {
+ if (readerMode.isExtended() && start == firstLabel) {
+ legacyParameterBag.register(index, name);
+ }
+ }
+
+ @Override
+ public void visitParameter(String name, int modifiers) {
+ parameterTokens.add(new LazyTypeDescription.MethodToken.ParameterToken(name, modifiers));
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotationDefault() {
+ return new AnnotationExtractor(this, new ComponentTypeLocator.ForArrayType(descriptor));
+ }
+
+ @Override
+ public void register(String ignored, AnnotationValue<?, ?> annotationValue) {
+ defaultValue = annotationValue;
+ }
+
+ @Override
+ public void onComplete() {
+ /* do nothing, as the register method is called at most once for default values */
+ }
+
+ @Override
+ public void visitEnd() {
+ methodTokens.add(new LazyTypeDescription.MethodToken(internalName,
+ modifiers,
+ descriptor,
+ genericSignature,
+ exceptionName,
+ typeVariableAnnotationTokens,
+ typeVariableBoundAnnotationTokens,
+ returnTypeAnnotationTokens,
+ parameterTypeAnnotationTokens,
+ exceptionTypeAnnotationTokens,
+ receiverTypeAnnotationTokens,
+ annotationTokens,
+ parameterAnnotationTokens,
+ parameterTokens.isEmpty()
+ ? legacyParameterBag.resolve((modifiers & Opcodes.ACC_STATIC) != 0)
+ : parameterTokens,
+ defaultValue));
+ }
+ }
+ }
+ }
+
+ /**
+ * A lazy facade of a type pool that delegates any lookups to another type pool only if another value than the type's name is looked up.
+ */
+ @EqualsAndHashCode(callSuper = false)
+ class LazyFacade extends AbstractBase {
+
+ /**
+ * The type pool to delegate to.
+ */
+ private final TypePool typePool;
+
+ /**
+ * Creates a lazy facade for a type pool.
+ *
+ * @param typePool The type pool to delegate to.
+ */
+ public LazyFacade(TypePool typePool) {
+ super(CacheProvider.NoOp.INSTANCE);
+ this.typePool = typePool;
+ }
+
+ @Override
+ protected Resolution doDescribe(String name) {
+ return new LazyResolution(typePool, name);
+ }
+
+ @Override
+ public void clear() {
+ typePool.clear();
+ }
+
+ /**
+ * The lazy resolution for a lazy facade for a type pool.
+ */
+ @EqualsAndHashCode
+ protected static class LazyResolution implements Resolution {
+
+ /**
+ * The type pool to delegate to.
+ */
+ private final TypePool typePool;
+
+ /**
+ * The name of the type that is represented by this resolution.
+ */
+ private final String name;
+
+ /**
+ * Creates a lazy resolution for a lazy facade for a type pool.
+ *
+ * @param typePool The type pool to delegate to.
+ * @param name The name of the type that is represented by this resolution.
+ */
+ protected LazyResolution(TypePool typePool, String name) {
+ this.typePool = typePool;
+ this.name = name;
+ }
+
+ @Override
+ public boolean isResolved() {
+ return typePool.describe(name).isResolved();
+ }
+
+ @Override
+ public TypeDescription resolve() {
+ return new LazyTypeDescription(typePool, name);
+ }
+ }
+
+ /**
+ * A description of a type that delegates to another type pool once a property that is not the name is resolved.
+ */
+ protected static class LazyTypeDescription extends TypeDescription.AbstractBase.OfSimpleType.WithDelegation {
+
+ /**
+ * The type pool to delegate to.
+ */
+ private final TypePool typePool;
+
+ /**
+ * The name of the type that is represented by this resolution.
+ */
+ private final String name;
+
+ /**
+ * Creates a new lazy type resolution.
+ *
+ * @param typePool The type pool to delegate to.
+ * @param name The name of the type.
+ */
+ protected LazyTypeDescription(TypePool typePool, String name) {
+ this.typePool = typePool;
+ this.name = name;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ protected TypeDescription delegate() {
+ return typePool.describe(name).resolve();
+ }
+ }
+ }
+
+ /**
+ * A type pool that attempts to load a class.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ class ClassLoading extends AbstractBase.Hierarchical {
+
+ /**
+ * Type-safe representation of the bootstrap class loader which is {@code null}.
+ */
+ private static final ClassLoader BOOTSTRAP_CLASS_LOADER = null;
+
+ /**
+ * The class loader to query.
+ */
+ private final ClassLoader classLoader;
+
+ /**
+ * Creates a class loadings type pool.
+ *
+ * @param cacheProvider The cache provider to use.
+ * @param parent The parent type pool.
+ * @param classLoader The class loader to use for locating files.
+ */
+ public ClassLoading(CacheProvider cacheProvider, TypePool parent, ClassLoader classLoader) {
+ super(cacheProvider, parent);
+ this.classLoader = classLoader;
+ }
+
+ /**
+ * Returns a type pool that attempts type descriptions by loadings types from the given class loader.
+ *
+ * @param classLoader The class loader to use.
+ * @return An class loading type pool.
+ */
+ public static TypePool of(ClassLoader classLoader) {
+ return of(classLoader, Empty.INSTANCE);
+ }
+
+ /**
+ * Returns a type pool that attempts type descriptions by loadings types from the given class loader.
+ *
+ * @param classLoader The class loader to use.
+ * @param parent The parent type pool to use.
+ * @return An class loading type pool.
+ */
+ public static TypePool of(ClassLoader classLoader, TypePool parent) {
+ return new ClassLoading(CacheProvider.NoOp.INSTANCE, parent, classLoader);
+ }
+
+ /**
+ * Returns a type pool that attempts type descriptions by loadings types from the bootstrap class loader.
+ *
+ * @return An class loading type pool for the bootstrap class loader.
+ */
+ public static TypePool ofBootPath() {
+ return of(BOOTSTRAP_CLASS_LOADER);
+ }
+
+ /**
+ * Returns a type pool that attempts type descriptions by loadings types from the system class loader.
+ *
+ * @return An class loading type pool for the system class loader.
+ */
+ public static TypePool ofClassPath() {
+ return of(ClassLoader.getSystemClassLoader());
+ }
+
+ @Override
+ public Resolution doDescribe(String name) {
+ try {
+ return new Resolution.Simple(new TypeDescription.ForLoadedType(Class.forName(name, false, classLoader)));
+ } catch (ClassNotFoundException ignored) {
+ return new Resolution.Illegal(name);
+ }
+ }
+ }
+
+ /**
+ * A type pool that supplies explicitly known type descriptions.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ class Explicit extends AbstractBase.Hierarchical {
+
+ /**
+ * A mapping from type names to type descriptions of that name.
+ */
+ private final Map<String, TypeDescription> types;
+
+ /**
+ * Creates a new explicit type pool without a parent.
+ *
+ * @param types A mapping from type names to type descriptions of that name.
+ */
+ public Explicit(Map<String, TypeDescription> types) {
+ this(Empty.INSTANCE, types);
+ }
+
+ /**
+ * Creates a new explicit type pool.
+ *
+ * @param parent The parent type pool.
+ * @param types A mapping from type names to type descriptions of that name.
+ */
+ public Explicit(TypePool parent, Map<String, TypeDescription> types) {
+ super(CacheProvider.NoOp.INSTANCE, parent);
+ this.types = types;
+ }
+
+ @Override
+ protected Resolution doDescribe(String name) {
+ TypeDescription typeDescription = types.get(name);
+ return typeDescription == null
+ ? new Resolution.Illegal(name)
+ : new Resolution.Simple(typeDescription);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/pool/package-info.java b/byte-buddy-dep/src/main/java/net/bytebuddy/pool/package-info.java
new file mode 100644
index 0000000..e112e60
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/pool/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Classes of this package allow for the creating {@link net.bytebuddy.description.type.TypeDescription}s without
+ * loading any classes.
+ */
+package net.bytebuddy.pool;
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/utility/CompoundList.java b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/CompoundList.java
new file mode 100644
index 0000000..4adcb59
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/CompoundList.java
@@ -0,0 +1,57 @@
+package net.bytebuddy.utility;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Creates a list representation of two lists as a single, compound list.
+ */
+public class CompoundList {
+
+ /**
+ * A compund list cannot be created.
+ */
+ private CompoundList() {
+ throw new UnsupportedOperationException("Cannot create a compound list");
+ }
+
+ /**
+ * Creates a list of a single element and another list.
+ *
+ * @param left The left element.
+ * @param right The right list.
+ * @param <S> The type of the list's elements.
+ * @return A compound list representing the element and the list.
+ */
+ public static <S> List<S> of(S left, List<? extends S> right) {
+ return of(Collections.singletonList(left), right);
+ }
+
+ /**
+ * Creates a list of a list and an element.
+ *
+ * @param left The left left.
+ * @param right The right element.
+ * @param <S> The type of the list's elements.
+ * @return A compound list representing the element and the list.
+ */
+ public static <S> List<S> of(List<? extends S> left, S right) {
+ return of(left, Collections.singletonList(right));
+ }
+
+ /**
+ * Creates a list of a left and right list.
+ *
+ * @param left The left list.
+ * @param right The right list.
+ * @param <S> The type of the list's elements.
+ * @return A compound list representing the element and the list.
+ */
+ public static <S> List<S> of(List<? extends S> left, List<? extends S> right) {
+ List<S> list = new ArrayList<S>(left.size() + right.size());
+ list.addAll(left);
+ list.addAll(right);
+ return list;
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/utility/JavaConstant.java b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/JavaConstant.java
new file mode 100644
index 0000000..5784faf
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/JavaConstant.java
@@ -0,0 +1,1310 @@
+package net.bytebuddy.utility;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.constant.JavaConstantValue;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Returns a Java instance of an object that has a special meaning to the Java virtual machine and that is not
+ * available to Java in versions 6.
+ */
+public interface JavaConstant {
+
+ /**
+ * Returns the represented instance as a constant pool value.
+ *
+ * @return The constant pool value in a format that can be written by ASM.
+ */
+ Object asConstantPoolValue();
+
+ /**
+ * Returns the instance as loadable onto the operand stack.
+ *
+ * @return A stack manipulation that loads the represented value onto the operand stack.
+ */
+ StackManipulation asStackManipulation();
+
+ /**
+ * Returns a description of the type of the represented instance or at least a stub.
+ *
+ * @return A description of the type of the represented instance or at least a stub.
+ */
+ TypeDescription getType();
+
+ /**
+ * Represents a {@code java.lang.invoke.MethodType} object.
+ */
+ class MethodType implements JavaConstant {
+
+ /**
+ * A dispatcher for extracting information from a {@code java.lang.invoke.MethodType} instance.
+ */
+ private static final Dispatcher DISPATCHER = AccessController.doPrivileged(Dispatcher.CreationAction.INSTANCE);
+
+ /**
+ * The return type of this method type.
+ */
+ private final TypeDescription returnType;
+
+ /**
+ * The parameter types of this method type.
+ */
+ private final List<? extends TypeDescription> parameterTypes;
+
+ /**
+ * Creates a method type for the given types.
+ *
+ * @param returnType The return type of the method type.
+ * @param parameterTypes The parameter types of the method type.
+ */
+ protected MethodType(TypeDescription returnType, List<? extends TypeDescription> parameterTypes) {
+ this.returnType = returnType;
+ this.parameterTypes = parameterTypes;
+ }
+
+ /**
+ * Returns a method type representation of a loaded {@code MethodType} object.
+ *
+ * @param methodType A method type object to represent as a {@link JavaConstant}.
+ * @return The method type represented as a {@link MethodType}.
+ */
+ public static MethodType ofLoaded(Object methodType) {
+ if (!JavaType.METHOD_TYPE.getTypeStub().isInstance(methodType)) {
+ throw new IllegalArgumentException("Expected method type object: " + methodType);
+ }
+ return of(DISPATCHER.returnType(methodType), DISPATCHER.parameterArray(methodType));
+ }
+
+ /**
+ * Returns a method type description of the given return type and parameter types.
+ *
+ * @param returnType The return type to represent.
+ * @param parameterType The parameter types to represent.
+ * @return A method type of the given return type and parameter types.
+ */
+ public static MethodType of(Class<?> returnType, Class<?>... parameterType) {
+ return of(new TypeDescription.ForLoadedType(returnType), new TypeList.ForLoadedTypes(parameterType));
+ }
+
+ /**
+ * Returns a method type description of the given return type and parameter types.
+ *
+ * @param returnType The return type to represent.
+ * @param parameterTypes The parameter types to represent.
+ * @return A method type of the given return type and parameter types.
+ */
+ public static MethodType of(TypeDescription returnType, List<? extends TypeDescription> parameterTypes) {
+ return new MethodType(returnType, parameterTypes);
+ }
+
+ /**
+ * Returns a method type description of the given method.
+ *
+ * @param method The method to extract the method type from.
+ * @return The method type of the given method.
+ */
+ public static MethodType of(Method method) {
+ return of(new MethodDescription.ForLoadedMethod(method));
+ }
+
+ /**
+ * Returns a method type description of the given constructor.
+ *
+ * @param constructor The constructor to extract the method type from.
+ * @return The method type of the given constructor.
+ */
+ public static MethodType of(Constructor<?> constructor) {
+ return of(new MethodDescription.ForLoadedConstructor(constructor));
+ }
+
+ /**
+ * Returns a method type description of the given method.
+ *
+ * @param methodDescription The method to extract the method type from.
+ * @return The method type of the given method.
+ */
+ public static MethodType of(MethodDescription methodDescription) {
+ return new MethodType(methodDescription.getReturnType().asErasure(), methodDescription.getParameters().asTypeList().asErasures());
+ }
+
+ /**
+ * Returns a method type for a setter of the given field.
+ *
+ * @param field The field to extract a setter type for.
+ * @return The type of a setter for the given field.
+ */
+ public static MethodType ofSetter(Field field) {
+ return ofSetter(new FieldDescription.ForLoadedField(field));
+ }
+
+ /**
+ * Returns a method type for a setter of the given field.
+ *
+ * @param fieldDescription The field to extract a setter type for.
+ * @return The type of a setter for the given field.
+ */
+ public static MethodType ofSetter(FieldDescription fieldDescription) {
+ return new MethodType(TypeDescription.VOID, Collections.singletonList(fieldDescription.getType().asErasure()));
+ }
+
+ /**
+ * Returns a method type for a getter of the given field.
+ *
+ * @param field The field to extract a getter type for.
+ * @return The type of a getter for the given field.
+ */
+ public static MethodType ofGetter(Field field) {
+ return ofGetter(new FieldDescription.ForLoadedField(field));
+ }
+
+ /**
+ * Returns a method type for a getter of the given field.
+ *
+ * @param fieldDescription The field to extract a getter type for.
+ * @return The type of a getter for the given field.
+ */
+ public static MethodType ofGetter(FieldDescription fieldDescription) {
+ return new MethodType(fieldDescription.getType().asErasure(), Collections.<TypeDescription>emptyList());
+ }
+
+ /**
+ * Returns a method type for the given constant.
+ *
+ * @param instance The constant for which a constant method type should be created.
+ * @return A method type for the given constant.
+ */
+ public static MethodType ofConstant(Object instance) {
+ return ofConstant(instance.getClass());
+ }
+
+ /**
+ * Returns a method type for the given constant type.
+ *
+ * @param type The constant type for which a constant method type should be created.
+ * @return A method type for the given constant type.
+ */
+ public static MethodType ofConstant(Class<?> type) {
+ return ofConstant(new TypeDescription.ForLoadedType(type));
+ }
+
+ /**
+ * Returns a method type for the given constant type.
+ *
+ * @param typeDescription The constant type for which a constant method type should be created.
+ * @return A method type for the given constant type.
+ */
+ public static MethodType ofConstant(TypeDescription typeDescription) {
+ return new MethodType(typeDescription, Collections.<TypeDescription>emptyList());
+ }
+
+ /**
+ * Returns the return type of this method type.
+ *
+ * @return The return type of this method type.
+ */
+ public TypeDescription getReturnType() {
+ return returnType;
+ }
+
+ /**
+ * Returns the parameter types of this method type.
+ *
+ * @return The parameter types of this method type.
+ */
+ public TypeList getParameterTypes() {
+ return new TypeList.Explicit(parameterTypes);
+ }
+
+ /**
+ * Returns the method descriptor of this method type representation.
+ *
+ * @return The method descriptor of this method type representation.
+ */
+ public String getDescriptor() {
+ StringBuilder stringBuilder = new StringBuilder("(");
+ for (TypeDescription parameterType : parameterTypes) {
+ stringBuilder.append(parameterType.getDescriptor());
+ }
+ return stringBuilder.append(')').append(returnType.getDescriptor()).toString();
+ }
+
+ @Override
+ public Object asConstantPoolValue() {
+ StringBuilder stringBuilder = new StringBuilder("(");
+ for (TypeDescription parameterType : getParameterTypes()) {
+ stringBuilder.append(parameterType.getDescriptor());
+ }
+ return Type.getMethodType(stringBuilder.append(")").append(getReturnType().getDescriptor()).toString());
+ }
+
+ @Override
+ public StackManipulation asStackManipulation() {
+ return new JavaConstantValue(this);
+ }
+
+ @Override
+ public TypeDescription getType() {
+ return JavaType.METHOD_TYPE.getTypeStub();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (!(other instanceof MethodType)) return false;
+ MethodType that = (MethodType) other;
+ return parameterTypes.equals(that.parameterTypes) && returnType.equals(that.returnType);
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result = returnType.hashCode();
+ result = 31 * result + parameterTypes.hashCode();
+ return result;
+ }
+
+ /**
+ * A dispatcher for extracting information from a {@code java.lang.invoke.MethodType} instance.
+ */
+ protected interface Dispatcher {
+
+ /**
+ * Extracts the return type of the supplied method type.
+ *
+ * @param methodType An instance of {@code java.lang.invoke.MethodType}.
+ * @return The return type that is described by the supplied instance.
+ */
+ Class<?> returnType(Object methodType);
+
+ /**
+ * Extracts the parameter types of the supplied method type.
+ *
+ * @param methodType An instance of {@code java.lang.invoke.MethodType}.
+ * @return The parameter types that are described by the supplied instance.
+ */
+ Class<?>[] parameterArray(Object methodType);
+
+ /**
+ * A creation action for a dispatcher.
+ */
+ enum CreationAction implements PrivilegedAction<Dispatcher> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback")
+ public Dispatcher run() {
+ try {
+ Class<?> methodType = JavaType.METHOD_TYPE.load();
+ return new Dispatcher.ForJava7CapableVm(methodType.getMethod("returnType"), methodType.getMethod("parameterArray"));
+ } catch (Exception ignored) {
+ return Dispatcher.ForLegacyVm.INSTANCE;
+ }
+ }
+ }
+
+ /**
+ * A dispatcher for virtual machines that are aware of the {@code java.lang.invoke.MethodType} type that was added in Java version 7.
+ */
+ @EqualsAndHashCode
+ class ForJava7CapableVm implements Dispatcher {
+
+ /**
+ * A reference to {@code java.lang.invoke.MethodType#returnType}.
+ */
+ private final Method returnType;
+
+ /**
+ * A reference to {@code java.lang.invoke.MethodType#returnType}.
+ */
+ private final Method parameterArray;
+
+ /**
+ * Creates a new dispatcher for a modern JVM.
+ *
+ * @param returnType A reference to {@code java.lang.invoke.MethodType#returnType}.
+ * @param parameterArray A reference to {@code java.lang.invoke.MethodType#returnType}.
+ */
+ protected ForJava7CapableVm(Method returnType, Method parameterArray) {
+ this.returnType = returnType;
+ this.parameterArray = parameterArray;
+ }
+
+ @Override
+ public Class<?> returnType(Object methodType) {
+ try {
+ return (Class<?>) returnType.invoke(methodType);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.invoke.MethodType#returnType", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.invoke.MethodType#returnType", exception.getCause());
+ }
+ }
+
+ @Override
+ public Class<?>[] parameterArray(Object methodType) {
+ try {
+ return (Class<?>[]) parameterArray.invoke(methodType);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.invoke.MethodType#parameterArray", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.invoke.MethodType#parameterArray", exception.getCause());
+ }
+ }
+ }
+
+ /**
+ * A dispatcher for virtual machines that are <b>not</b> aware of the {@code java.lang.invoke.MethodType} type that was added in Java version 7.
+ */
+ enum ForLegacyVm implements Dispatcher {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Class<?> returnType(Object methodType) {
+ throw new IllegalStateException("Unsupported type for the current JVM: java.lang.invoke.MethodType");
+ }
+
+ @Override
+ public Class<?>[] parameterArray(Object methodType) {
+ throw new IllegalStateException("Unsupported type for the current JVM: java.lang.invoke.MethodType");
+ }
+ }
+ }
+ }
+
+ /**
+ * Represents a {@code java.lang.invoke.MethodHandle} object. Note that constant {@code MethodHandle}s cannot
+ * be represented within the constant pool of a Java class and can therefore not be represented as an instance of
+ * this representation order.
+ */
+ class MethodHandle implements JavaConstant {
+
+ /**
+ * A dispatcher for receiving the type information that is represented by a {@code java.lang.invoke.MethodHandle} instance.
+ */
+ private static final Dispatcher.Initializable DISPATCHER = AccessController.doPrivileged(Dispatcher.CreationAction.INSTANCE);
+
+ /**
+ * The handle type that is represented by this instance.
+ */
+ private final HandleType handleType;
+
+ /**
+ * The owner type that is represented by this instance.
+ */
+ private final TypeDescription ownerType;
+
+ /**
+ * The name that is represented by this instance.
+ */
+ private final String name;
+
+ /**
+ * The return type that is represented by this instance.
+ */
+ private final TypeDescription returnType;
+
+ /**
+ * The parameter types that is represented by this instance.
+ */
+ private final List<? extends TypeDescription> parameterTypes;
+
+ /**
+ * Creates a method handle representation.
+ *
+ * @param handleType The handle type that is represented by this instance.
+ * @param ownerType The owner type that is represented by this instance.
+ * @param name The name that is represented by this instance.
+ * @param returnType The return type that is represented by this instance.
+ * @param parameterTypes The parameter types that is represented by this instance.
+ */
+ protected MethodHandle(HandleType handleType,
+ TypeDescription ownerType,
+ String name,
+ TypeDescription returnType,
+ List<? extends TypeDescription> parameterTypes) {
+ this.handleType = handleType;
+ this.ownerType = ownerType;
+ this.name = name;
+ this.returnType = returnType;
+ this.parameterTypes = parameterTypes;
+ }
+
+ /**
+ * Creates a method handles representation of a loaded method handle which is analyzed using a public {@code MethodHandles.Lookup} object.
+ * A method handle can only be analyzed on virtual machines that support the corresponding API (Java 7+). For virtual machines before Java 8+,
+ * a method handle instance can only be analyzed by taking advantage of private APIs what might require a access context.
+ *
+ * @param methodHandle The loaded method handle to represent.
+ * @return A representation of the loaded method handle
+ */
+ public static MethodHandle ofLoaded(Object methodHandle) {
+ return ofLoaded(methodHandle, DISPATCHER.publicLookup());
+ }
+
+ /**
+ * Creates a method handles representation of a loaded method handle which is analyzed using the given lookup context.
+ * A method handle can only be analyzed on virtual machines that support the corresponding API (Java 7+). For virtual machines before Java 8+,
+ * a method handle instance can only be analyzed by taking advantage of private APIs what might require a access context.
+ *
+ * @param methodHandle The loaded method handle to represent.
+ * @param lookup The lookup object to use for analyzing the method handle.
+ * @return A representation of the loaded method handle
+ */
+ public static MethodHandle ofLoaded(Object methodHandle, Object lookup) {
+ if (!JavaType.METHOD_HANDLE.getTypeStub().isInstance(methodHandle)) {
+ throw new IllegalArgumentException("Expected method handle object: " + methodHandle);
+ } else if (!JavaType.METHOD_HANDLES_LOOKUP.getTypeStub().isInstance(lookup)) {
+ throw new IllegalArgumentException("Expected method handle lookup object: " + lookup);
+ }
+ Dispatcher dispatcher = DISPATCHER.initialize();
+ Object methodHandleInfo = dispatcher.reveal(lookup, methodHandle);
+ Object methodType = dispatcher.getMethodType(methodHandleInfo);
+ return new MethodHandle(HandleType.of(dispatcher.getReferenceKind(methodHandleInfo)),
+ new TypeDescription.ForLoadedType(dispatcher.getDeclaringClass(methodHandleInfo)),
+ dispatcher.getName(methodHandleInfo),
+ new TypeDescription.ForLoadedType(dispatcher.returnType(methodType)),
+ new TypeList.ForLoadedTypes(dispatcher.parameterArray(methodType)));
+ }
+
+ /**
+ * Creates a method handle representation of the given method.
+ *
+ * @param method The method ro represent.
+ * @return A method handle representing the given method.
+ */
+ public static MethodHandle of(Method method) {
+ return of(new MethodDescription.ForLoadedMethod(method));
+ }
+
+ /**
+ * Creates a method handle representation of the given constructor.
+ *
+ * @param constructor The constructor ro represent.
+ * @return A method handle representing the given constructor.
+ */
+ public static MethodHandle of(Constructor<?> constructor) {
+ return of(new MethodDescription.ForLoadedConstructor(constructor));
+ }
+
+ /**
+ * Creates a method handle representation of the given method.
+ *
+ * @param methodDescription The method ro represent.
+ * @return A method handle representing the given method.
+ */
+ public static MethodHandle of(MethodDescription.InDefinedShape methodDescription) {
+ return new MethodHandle(HandleType.of(methodDescription),
+ methodDescription.getDeclaringType().asErasure(),
+ methodDescription.getInternalName(),
+ methodDescription.getReturnType().asErasure(),
+ methodDescription.getParameters().asTypeList().asErasures());
+ }
+
+ /**
+ * Creates a method handle representation of the given method for an explicit special method invocation of an otherwise virtual method.
+ *
+ * @param method The method ro represent.
+ * @param type The type on which the method is to be invoked on as a special method invocation.
+ * @return A method handle representing the given method as special method invocation.
+ */
+ public static MethodHandle ofSpecial(Method method, Class<?> type) {
+ return ofSpecial(new MethodDescription.ForLoadedMethod(method), new TypeDescription.ForLoadedType(type));
+ }
+
+ /**
+ * Creates a method handle representation of the given method for an explicit special method invocation of an otherwise virtual method.
+ *
+ * @param methodDescription The method ro represent.
+ * @param typeDescription The type on which the method is to be invoked on as a special method invocation.
+ * @return A method handle representing the given method as special method invocation.
+ */
+ public static MethodHandle ofSpecial(MethodDescription.InDefinedShape methodDescription, TypeDescription typeDescription) {
+ if (!methodDescription.isSpecializableFor(typeDescription)) {
+ throw new IllegalArgumentException("Cannot specialize " + methodDescription + " for " + typeDescription);
+ }
+ return new MethodHandle(HandleType.ofSpecial(methodDescription),
+ typeDescription,
+ methodDescription.getInternalName(),
+ methodDescription.getReturnType().asErasure(),
+ methodDescription.getParameters().asTypeList().asErasures());
+ }
+
+ /**
+ * Returns a method handle for a setter of the given field.
+ *
+ * @param field The field to represent.
+ * @return A method handle for a setter of the given field.
+ */
+ public static MethodHandle ofGetter(Field field) {
+ return ofGetter(new FieldDescription.ForLoadedField(field));
+ }
+
+ /**
+ * Returns a method handle for a setter of the given field.
+ *
+ * @param fieldDescription The field to represent.
+ * @return A method handle for a setter of the given field.
+ */
+ public static MethodHandle ofGetter(FieldDescription.InDefinedShape fieldDescription) {
+ return new MethodHandle(HandleType.ofGetter(fieldDescription),
+ fieldDescription.getDeclaringType().asErasure(),
+ fieldDescription.getInternalName(),
+ fieldDescription.getType().asErasure(),
+ Collections.<TypeDescription>emptyList());
+ }
+
+ /**
+ * Returns a method handle for a getter of the given field.
+ *
+ * @param field The field to represent.
+ * @return A method handle for a getter of the given field.
+ */
+ public static MethodHandle ofSetter(Field field) {
+ return ofSetter(new FieldDescription.ForLoadedField(field));
+ }
+
+ /**
+ * Returns a method handle for a getter of the given field.
+ *
+ * @param fieldDescription The field to represent.
+ * @return A method handle for a getter of the given field.
+ */
+ public static MethodHandle ofSetter(FieldDescription.InDefinedShape fieldDescription) {
+ return new MethodHandle(HandleType.ofSetter(fieldDescription),
+ fieldDescription.getDeclaringType().asErasure(),
+ fieldDescription.getInternalName(),
+ TypeDescription.VOID,
+ Collections.singletonList(fieldDescription.getType().asErasure()));
+ }
+
+ @Override
+ public Object asConstantPoolValue() {
+ StringBuilder stringBuilder = new StringBuilder("(");
+ for (TypeDescription parameterType : getParameterTypes()) {
+ stringBuilder.append(parameterType.getDescriptor());
+ }
+ String descriptor = stringBuilder.append(")").append(getReturnType().getDescriptor()).toString();
+ return new Handle(getHandleType().getIdentifier(), getOwnerType().getInternalName(), getName(), descriptor, getOwnerType().isInterface());
+ }
+
+ @Override
+ public StackManipulation asStackManipulation() {
+ return new JavaConstantValue(this);
+ }
+
+ @Override
+ public TypeDescription getType() {
+ return JavaType.METHOD_HANDLE.getTypeStub();
+ }
+
+ /**
+ * Returns the handle type represented by this instance.
+ *
+ * @return The handle type represented by this instance.
+ */
+ public HandleType getHandleType() {
+ return handleType;
+ }
+
+ /**
+ * Returns the owner type of this instance.
+ *
+ * @return The owner type of this instance.
+ */
+ public TypeDescription getOwnerType() {
+ return ownerType;
+ }
+
+ /**
+ * Returns the name represented by this instance.
+ *
+ * @return The name represented by this instance.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the return type represented by this instance.
+ *
+ * @return The return type represented by this instance.
+ */
+ public TypeDescription getReturnType() {
+ return returnType;
+ }
+
+ /**
+ * Returns the parameter types represented by this instance.
+ *
+ * @return The parameter types represented by this instance.
+ */
+ public TypeList getParameterTypes() {
+ return new TypeList.Explicit(parameterTypes);
+ }
+
+ /**
+ * Returns the method descriptor of this method handle representation.
+ *
+ * @return The method descriptor of this method handle representation.
+ */
+ public String getDescriptor() {
+ StringBuilder stringBuilder = new StringBuilder().append('(');
+ for (TypeDescription parameterType : parameterTypes) {
+ stringBuilder.append(parameterType.getDescriptor());
+ }
+ return stringBuilder.append(')').append(returnType.getDescriptor()).toString();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (!(other instanceof MethodHandle)) return false;
+ MethodHandle aDefault = (MethodHandle) other;
+ return handleType == aDefault.handleType
+ && name.equals(aDefault.name)
+ && ownerType.equals(aDefault.ownerType)
+ && parameterTypes.equals(aDefault.parameterTypes)
+ && returnType.equals(aDefault.returnType);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = handleType.hashCode();
+ result = 31 * result + ownerType.hashCode();
+ result = 31 * result + name.hashCode();
+ result = 31 * result + returnType.hashCode();
+ result = 31 * result + parameterTypes.hashCode();
+ return result;
+ }
+
+ /**
+ * Returns the lookup type of the provided {@code java.lang.invoke.MethodHandles$Lookup} instance.
+ *
+ * @param callerClassLookup An instance of {@code java.lang.invoke.MethodHandles$Lookup}.
+ * @return The instance's lookup type.
+ */
+ public static Class<?> lookupType(Object callerClassLookup) {
+ return DISPATCHER.lookupType(callerClassLookup);
+ }
+
+ /**
+ * A dispatcher for analyzing a {@code java.lang.invoke.MethodHandle} instance.
+ */
+ protected interface Dispatcher {
+
+ /**
+ * Reveals a method handle's information object.
+ *
+ * @param lookup The lookup to be used for introspecting the instance.
+ * @param methodHandle The method handle to be introspected.
+ * @return The {@code java.lang.invoke.MethodHandleInfo} object that describes the instance.
+ */
+ Object reveal(Object lookup, Object methodHandle);
+
+ /**
+ * Returns a method handle info's method type.
+ *
+ * @param methodHandleInfo The method handle info to introspect.
+ * @return The {@code java.lang.invoke.MethodType} instance representing the method handle's type.
+ */
+ Object getMethodType(Object methodHandleInfo);
+
+ /**
+ * Returns the reference kind of the supplied method handle info.
+ *
+ * @param methodHandleInfo The method handle to be introspected.
+ * @return The method handle info's reference type.
+ */
+ int getReferenceKind(Object methodHandleInfo);
+
+ /**
+ * Returns the declaring class of the supplied method handle info.
+ *
+ * @param methodHandleInfo The method handle to be introspected.
+ * @return The method handle info's declaring class.
+ */
+ Class<?> getDeclaringClass(Object methodHandleInfo);
+
+ /**
+ * Returns the method name of the supplied method handle info.
+ *
+ * @param methodHandleInfo The method handle to be introspected.
+ * @return The method handle info's method name.
+ */
+ String getName(Object methodHandleInfo);
+
+ /**
+ * Returns the return type of the supplied method type.
+ *
+ * @param methodType The method type to be introspected.
+ * @return The method type's return type.
+ */
+ Class<?> returnType(Object methodType);
+
+ /**
+ * Returns the parameter types of the supplied method type.
+ *
+ * @param methodType The method type to be introspected.
+ * @return The method type's parameter types.
+ */
+ List<? extends Class<?>> parameterArray(Object methodType);
+
+ /**
+ * An initializable version of a dispatcher that is not yet made accessible.
+ */
+ interface Initializable {
+
+ /**
+ * Initializes the dispatcher, if required.
+ *
+ * @return The initialized dispatcher.
+ */
+ Dispatcher initialize();
+
+ /**
+ * Returns a public {@code java.lang.invoke.MethodHandles.Lookup} instance.
+ *
+ * @return A public {@code java.lang.invoke.MethodHandles.Lookup} instance.
+ */
+ Object publicLookup();
+
+ /**
+ * Returns the lookup type of a given {@code java.lang.invoke.MethodHandles$Lookup} instance.
+ *
+ * @param lookup A {@code java.lang.invoke.MethodHandles$Lookup} instance.
+ * @return The provided instance's lookup type.
+ */
+ Class<?> lookupType(Object lookup);
+ }
+
+ /**
+ * A creation action for a dispatcher.
+ */
+ enum CreationAction implements PrivilegedAction<Initializable> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback")
+ public Initializable run() {
+ try {
+ try {
+ return new Dispatcher.ForJava8CapableVm(Class.forName("java.lang.invoke.MethodHandles").getMethod("publicLookup"),
+ Class.forName("java.lang.invoke.MethodHandleInfo").getMethod("getName"),
+ Class.forName("java.lang.invoke.MethodHandleInfo").getMethod("getDeclaringClass"),
+ Class.forName("java.lang.invoke.MethodHandleInfo").getMethod("getReferenceKind"),
+ Class.forName("java.lang.invoke.MethodHandleInfo").getMethod("getMethodType"),
+ JavaType.METHOD_TYPE.load().getMethod("returnType"),
+ JavaType.METHOD_TYPE.load().getMethod("parameterArray"),
+ JavaType.METHOD_HANDLES_LOOKUP.load().getMethod("lookupClass"),
+ JavaType.METHOD_HANDLES_LOOKUP.load().getMethod("revealDirect", JavaType.METHOD_HANDLE.load()));
+ } catch (Exception ignored) {
+ return new Dispatcher.ForJava7CapableVm(Class.forName("java.lang.invoke.MethodHandles").getMethod("publicLookup"),
+ Class.forName("java.lang.invoke.MethodHandleInfo").getMethod("getName"),
+ Class.forName("java.lang.invoke.MethodHandleInfo").getMethod("getDeclaringClass"),
+ Class.forName("java.lang.invoke.MethodHandleInfo").getMethod("getReferenceKind"),
+ Class.forName("java.lang.invoke.MethodHandleInfo").getMethod("getMethodType"),
+ JavaType.METHOD_TYPE.load().getMethod("returnType"),
+ JavaType.METHOD_TYPE.load().getMethod("parameterArray"),
+ JavaType.METHOD_HANDLES_LOOKUP.load().getMethod("lookupClass"),
+ Class.forName("java.lang.invoke.MethodHandleInfo").getConstructor(JavaType.METHOD_HANDLE.load()));
+ }
+ } catch (Exception ignored) {
+ return Dispatcher.ForLegacyVm.INSTANCE;
+ }
+ }
+ }
+
+ /**
+ * An abstract base impleementation of a dispatcher.
+ */
+ @EqualsAndHashCode
+ abstract class AbstractBase implements Dispatcher, Initializable {
+
+ /**
+ * A reference to {@code java.lang.invoke.MethodHandles#publicLookup}.
+ */
+ protected final Method publicLookup;
+
+ /**
+ * A reference to {@code java.lang.invoke.MethodHandleInfo#getName}.
+ */
+ protected final Method getName;
+
+ /**
+ * A reference to {@code java.lang.invoke.MethodHandleInfo#getDeclaringClass}.
+ */
+ protected final Method getDeclaringClass;
+
+ /**
+ * A reference to {@code java.lang.invoke.MethodHandleInfo#getReferenceKind}.
+ */
+ protected final Method getReferenceKind;
+
+ /**
+ * A reference to {@code java.lang.invoke.MethodHandleInfo#getMethodType}.
+ */
+ protected final Method getMethodType;
+
+ /**
+ * A reference to {@code java.lang.invoke.MethodType#returnType}.
+ */
+ protected final Method returnType;
+
+ /**
+ * A reference to {@code java.lang.invoke.MethodType#parameterArray}.
+ */
+ protected final Method parameterArray;
+
+ /**
+ * A reference to {@code java.lang.invoke.MethodHandles$Lookup#lookupClass} method.
+ */
+ protected final Method lookupClass;
+
+ /**
+ * Creates a legal dispatcher.
+ *
+ * @param publicLookup A reference to {@code java.lang.invoke.MethodHandles#publicLookup}.
+ * @param getName A reference to {@code java.lang.invoke.MethodHandleInfo#getName}.
+ * @param getDeclaringClass A reference to {@code java.lang.invoke.MethodHandleInfo#getDeclaringClass}.
+ * @param getReferenceKind A reference to {@code java.lang.invoke.MethodHandleInfo#getReferenceKind}.
+ * @param getMethodType A reference to {@code java.lang.invoke.MethodHandleInfo#getMethodType}.
+ * @param returnType A reference to {@code java.lang.invoke.MethodType#returnType}.
+ * @param parameterArray A reference to {@code java.lang.invoke.MethodType#parameterArray}.
+ * @param lookupClass A reference to {@code java.lang.invoke.MethodHandles$Lookup#lookupClass} method.
+ */
+ protected AbstractBase(Method publicLookup,
+ Method getName,
+ Method getDeclaringClass,
+ Method getReferenceKind,
+ Method getMethodType,
+ Method returnType,
+ Method parameterArray,
+ Method lookupClass) {
+ this.publicLookup = publicLookup;
+ this.getName = getName;
+ this.getDeclaringClass = getDeclaringClass;
+ this.getReferenceKind = getReferenceKind;
+ this.getMethodType = getMethodType;
+ this.returnType = returnType;
+ this.parameterArray = parameterArray;
+ this.lookupClass = lookupClass;
+ }
+
+ @Override
+ public Object publicLookup() {
+ try {
+ return publicLookup.invoke(null);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.invoke.MethodHandles#publicLookup", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.invoke.MethodHandles#publicLookup", exception.getCause());
+ }
+ }
+
+ @Override
+ public Object getMethodType(Object methodHandleInfo) {
+ try {
+ return getMethodType.invoke(methodHandleInfo);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.invoke.MethodHandleInfo#getMethodType", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.invoke.MethodHandleInfo#getMethodType", exception.getCause());
+ }
+ }
+
+ @Override
+ public int getReferenceKind(Object methodHandleInfo) {
+ try {
+ return (Integer) getReferenceKind.invoke(methodHandleInfo);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.invoke.MethodHandleInfo#getReferenceKind", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.invoke.MethodHandleInfo#getReferenceKind", exception.getCause());
+ }
+ }
+
+ @Override
+ public Class<?> getDeclaringClass(Object methodHandleInfo) {
+ try {
+ return (Class<?>) getDeclaringClass.invoke(methodHandleInfo);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.invoke.MethodHandleInfo#getDeclaringClass", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.invoke.MethodHandleInfo#getDeclaringClass", exception.getCause());
+ }
+ }
+
+ @Override
+ public String getName(Object methodHandleInfo) {
+ try {
+ return (String) getName.invoke(methodHandleInfo);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.invoke.MethodHandleInfo#getName", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.invoke.MethodHandleInfo#getName", exception.getCause());
+ }
+ }
+
+ @Override
+ public Class<?> returnType(Object methodType) {
+ try {
+ return (Class<?>) returnType.invoke(methodType);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.invoke.MethodType#returnType", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.reflect.MethodType#returnType", exception.getCause());
+ }
+ }
+
+ @Override
+ public List<? extends Class<?>> parameterArray(Object methodType) {
+ try {
+ return Arrays.asList((Class<?>[]) parameterArray.invoke(methodType));
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.reflect.MethodType#parameterArray", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.reflect.MethodType#parameterArray", exception.getCause());
+ }
+ }
+
+ @Override
+ public Class<?> lookupType(Object lookup) {
+ try {
+ return (Class<?>) lookupClass.invoke(lookup);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.reflect.MethodHandles.Lookup#lookupClass", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.reflect.MethodHandles.Lookup#lookupClass", exception.getCause());
+ }
+ }
+ }
+
+ /**
+ * A dispatcher for introspecting a {@code java.lang.invoke.MethodHandle} instance on a virtual machine that officially supports this
+ * introspection, i.e. Java versions 8+.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ class ForJava8CapableVm extends AbstractBase {
+
+ /**
+ * A reference to the {@code java.lang.invoke.MethodHandles.Lookup#revealDirect} method.
+ */
+ private final Method revealDirect;
+
+ /**
+ * Creates a dispatcher for a modern VM.
+ *
+ * @param publicLookup A reference to {@code java.lang.invoke.MethodHandles#publicLookup}.
+ * @param getName A reference to {@code java.lang.invoke.MethodHandleInfo#getName}.
+ * @param getDeclaringClass A reference to {@code java.lang.invoke.MethodHandleInfo#getDeclaringClass}.
+ * @param getReferenceKind A reference to {@code java.lang.invoke.MethodHandleInfo#getReferenceKind}.
+ * @param getMethodType A reference to {@code java.lang.invoke.MethodHandleInfo#getMethodType}.
+ * @param returnType A reference to {@code java.lang.invoke.MethodType#returnType}.
+ * @param parameterArray A reference to {@code java.lang.invoke.MethodType#parameterArray}.
+ * @param lookupClass A reference to {@code java.lang.invoke.MethodHandles$Lookup#lookupClass} method.
+ * @param revealDirect A reference to the {@code java.lang.invoke.MethodHandles.Lookup#revealDirect} method.
+ */
+ protected ForJava8CapableVm(Method publicLookup,
+ Method getName,
+ Method getDeclaringClass,
+ Method getReferenceKind,
+ Method getMethodType,
+ Method returnType,
+ Method parameterArray,
+ Method lookupClass,
+ Method revealDirect) {
+ super(publicLookup, getName, getDeclaringClass, getReferenceKind, getMethodType, returnType, parameterArray, lookupClass);
+ this.revealDirect = revealDirect;
+ }
+
+ @Override
+ public Object reveal(Object lookup, Object methodHandle) {
+ try {
+ return revealDirect.invoke(lookup, methodHandle);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.invoke.MethodHandles.Lookup#revealDirect", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.invoke.MethodHandles.Lookup#revealDirect", exception.getCause());
+ }
+ }
+
+ @Override
+ public Dispatcher initialize() {
+ return this;
+ }
+ }
+
+ /**
+ * A dispatcher that extracts the information of a method handle by using private APIs that are available in Java 7+.
+ */
+ @EqualsAndHashCode(callSuper = true)
+ class ForJava7CapableVm extends AbstractBase implements PrivilegedAction<Dispatcher> {
+
+ /**
+ * A reference to the {@code java.lang.invoke.MethodInfo} constructor.
+ */
+ private final Constructor<?> methodInfo;
+
+ /**
+ * Creates a dispatcher for an intermediate VM.
+ *
+ * @param publicLookup A reference to {@code java.lang.invoke.MethodHandles#publicLookup}.
+ * @param getName A reference to {@code java.lang.invoke.MethodHandleInfo#getName}.
+ * @param getDeclaringClass A reference to {@code java.lang.invoke.MethodHandleInfo#getDeclaringClass}.
+ * @param getReferenceKind A reference to {@code java.lang.invoke.MethodHandleInfo#getReferenceKind}.
+ * @param getMethodType A reference to {@code java.lang.invoke.MethodHandleInfo#getMethodType}.
+ * @param returnType A reference to {@code java.lang.invoke.MethodType#returnType}.
+ * @param parameterArray A reference to {@code java.lang.invoke.MethodType#parameterArray}.
+ * @param lookupClass A reference to {@code java.lang.invoke.MethodHandles$Lookup#lookupClass} method.
+ * @param methodInfo A reference to the {@code java.lang.invoke.MethodInfo} constructor.
+ */
+ protected ForJava7CapableVm(Method publicLookup,
+ Method getName,
+ Method getDeclaringClass,
+ Method getReferenceKind,
+ Method getMethodType,
+ Method returnType,
+ Method parameterArray,
+ Method lookupClass,
+ Constructor<?> methodInfo) {
+ super(publicLookup, getName, getDeclaringClass, getReferenceKind, getMethodType, returnType, parameterArray, lookupClass);
+ this.methodInfo = methodInfo;
+ }
+
+ @Override
+ public Dispatcher initialize() {
+ return AccessController.doPrivileged(this);
+ }
+
+ @Override
+ public Dispatcher run() {
+ // This is safe even in a multi-threaded environment as all threads set the instances accessible before invoking any methods.
+ // By always setting accessibility, the security manager is always triggered if this operation was illegal.
+ methodInfo.setAccessible(true);
+ getName.setAccessible(true);
+ getDeclaringClass.setAccessible(true);
+ getReferenceKind.setAccessible(true);
+ getMethodType.setAccessible(true);
+ return this;
+ }
+
+ @Override
+ public Object reveal(Object lookup, Object methodHandle) {
+ try {
+ return methodInfo.newInstance(methodHandle);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access java.lang.invoke.MethodInfo()", exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Error invoking java.lang.invoke.MethodInfo()", exception.getCause());
+ } catch (InstantiationException exception) {
+ throw new IllegalStateException("Error constructing java.lang.invoke.MethodInfo", exception);
+ }
+ }
+ }
+
+ /**
+ * A dispatcher that does not support method handles at all.
+ */
+ enum ForLegacyVm implements Initializable {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public Dispatcher initialize() {
+ throw new IllegalStateException("Unsupported type on current JVM: java.lang.invoke.MethodHandle");
+ }
+
+ @Override
+ public Object publicLookup() {
+ throw new IllegalStateException("Unsupported type on current JVM: java.lang.invoke.MethodHandle");
+ }
+
+ @Override
+ public Class<?> lookupType(Object lookup) {
+ throw new IllegalStateException("Unsupported type on current JVM: java.lang.invoke.MethodHandle");
+ }
+ }
+ }
+
+ /**
+ * A representation of a method handle's type.
+ */
+ public enum HandleType {
+
+ /**
+ * A handle representing an invokevirtual invocation.
+ */
+ INVOKE_VIRTUAL(Opcodes.H_INVOKEVIRTUAL),
+
+ /**
+ * A handle representing an invokestatic invocation.
+ */
+ INVOKE_STATIC(Opcodes.H_INVOKESTATIC),
+
+ /**
+ * A handle representing an invokespecial invocation for a non-constructor.
+ */
+ INVOKE_SPECIAL(Opcodes.H_INVOKESPECIAL),
+
+ /**
+ * A handle representing an invokeinterface invocation.
+ */
+ INVOKE_INTERFACE(Opcodes.H_INVOKEINTERFACE),
+
+ /**
+ * A handle representing an invokespecial invocation for a constructor.
+ */
+ INVOKE_SPECIAL_CONSTRUCTOR(Opcodes.H_NEWINVOKESPECIAL),
+
+ /**
+ * A handle representing a write of a non-static field invocation.
+ */
+ PUT_FIELD(Opcodes.H_PUTFIELD),
+
+ /**
+ * A handle representing a read of a non-static field invocation.
+ */
+ GET_FIELD(Opcodes.H_GETFIELD),
+
+ /**
+ * A handle representing a write of a static field invocation.
+ */
+ PUT_STATIC_FIELD(Opcodes.H_PUTSTATIC),
+
+ /**
+ * A handle representing a read of a static field invocation.
+ */
+ GET_STATIC_FIELD(Opcodes.H_GETSTATIC);
+
+ /**
+ * The represented identifier.
+ */
+ private final int identifier;
+
+ /**
+ * Creates a new handle type.
+ *
+ * @param identifier The represented identifier.
+ */
+ HandleType(int identifier) {
+ this.identifier = identifier;
+ }
+
+ /**
+ * Extracts a handle type for invoking the given method.
+ *
+ * @param methodDescription The method for which a handle type should be found.
+ * @return The handle type for the given method.
+ */
+ protected static HandleType of(MethodDescription.InDefinedShape methodDescription) {
+ if (methodDescription.isStatic()) {
+ return INVOKE_STATIC;
+ } else if (methodDescription.isPrivate()) {
+ return INVOKE_SPECIAL;
+ } else if (methodDescription.isConstructor()) {
+ return INVOKE_SPECIAL_CONSTRUCTOR;
+ } else if (methodDescription.getDeclaringType().isInterface()) {
+ return INVOKE_INTERFACE;
+ } else {
+ return INVOKE_VIRTUAL;
+ }
+ }
+
+ /**
+ * Extracts a handle type for the given identifier.
+ *
+ * @param identifier The identifier to extract a handle type for.
+ * @return The representing handle type.
+ */
+ protected static HandleType of(int identifier) {
+ for (HandleType handleType : HandleType.values()) {
+ if (handleType.getIdentifier() == identifier) {
+ return handleType;
+ }
+ }
+ throw new IllegalArgumentException("Unknown handle type: " + identifier);
+ }
+
+ /**
+ * Extracts a handle type for invoking the given method via invokespecial.
+ *
+ * @param methodDescription The method for which a handle type should be found.
+ * @return The handle type for the given method.
+ */
+ protected static HandleType ofSpecial(MethodDescription.InDefinedShape methodDescription) {
+ if (methodDescription.isStatic() || methodDescription.isAbstract()) {
+ throw new IllegalArgumentException("Cannot invoke " + methodDescription + " via invokespecial");
+ }
+ return methodDescription.isConstructor()
+ ? INVOKE_SPECIAL_CONSTRUCTOR
+ : INVOKE_SPECIAL;
+ }
+
+ /**
+ * Extracts a handle type for a getter of the given field.
+ *
+ * @param fieldDescription The field for which to create a getter handle.
+ * @return The corresponding handle type.
+ */
+ protected static HandleType ofGetter(FieldDescription.InDefinedShape fieldDescription) {
+ return fieldDescription.isStatic()
+ ? GET_STATIC_FIELD
+ : GET_FIELD;
+ }
+
+ /**
+ * Extracts a handle type for a setter of the given field.
+ *
+ * @param fieldDescription The field for which to create a setter handle.
+ * @return The corresponding handle type.
+ */
+ protected static HandleType ofSetter(FieldDescription.InDefinedShape fieldDescription) {
+ return fieldDescription.isStatic()
+ ? PUT_STATIC_FIELD
+ : PUT_FIELD;
+ }
+
+ /**
+ * Returns the represented identifier.
+ *
+ * @return The represented identifier.
+ */
+ public int getIdentifier() {
+ return identifier;
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/utility/JavaModule.java b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/JavaModule.java
new file mode 100644
index 0000000..418a6ac
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/JavaModule.java
@@ -0,0 +1,479 @@
+package net.bytebuddy.utility;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import lombok.EqualsAndHashCode;
+import net.bytebuddy.description.NamedElement;
+
+import java.io.InputStream;
+import java.lang.instrument.Instrumentation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Type-safe representation of a {@code java.lang.Module}. On platforms that do not support the module API, modules are represented by {@code null}.
+ */
+public class JavaModule implements NamedElement.WithOptionalName {
+
+ /**
+ * Canonical representation of a Java module on a JVM that does not support the module API.
+ */
+ public static final JavaModule UNSUPPORTED = null;
+
+ /**
+ * The dispatcher to use for accessing Java modules, if available.
+ */
+ private static final Dispatcher DISPATCHER = AccessController.doPrivileged(Dispatcher.CreationAction.INSTANCE);
+
+ /**
+ * The {@code java.lang.Module} instance this wrapper represents.
+ */
+ private final Object module;
+
+ /**
+ * Creates a new Java module representation.
+ *
+ * @param module The {@code java.lang.Module} instance this wrapper represents.
+ */
+ protected JavaModule(Object module) {
+ this.module = module;
+ }
+
+ /**
+ * Returns a representation of the supplied type's {@code java.lang.Module} or {@code null} if the current VM does not support modules.
+ *
+ * @param type The type for which to describe the module.
+ * @return A representation of the type's module or {@code null} if the current VM does not support modules.
+ */
+ public static JavaModule ofType(Class<?> type) {
+ return DISPATCHER.moduleOf(type);
+ }
+
+ /**
+ * Represents the supplied {@code java.lang.Module} as an instance of this class and validates that the
+ * supplied instance really represents a Java {@code Module}.
+ *
+ * @param module The module to represent.
+ * @return A representation of the supplied Java module.
+ */
+ public static JavaModule of(Object module) {
+ if (!JavaType.MODULE.getTypeStub().isInstance(module)) {
+ throw new IllegalArgumentException("Not a Java module: " + module);
+ }
+ return new JavaModule(module);
+ }
+
+ /**
+ * Checks if the current VM supports the {@code java.lang.Module} API.
+ *
+ * @return {@code true} if the current VM supports modules.
+ */
+ public static boolean isSupported() {
+ return DISPATCHER.isAlive();
+ }
+
+ @Override
+ public boolean isNamed() {
+ return DISPATCHER.isNamed(module);
+ }
+
+ @Override
+ public String getActualName() {
+ return DISPATCHER.getName(module);
+ }
+
+ /**
+ * Returns a resource stream for this module for a resource of the given name or {@code null} if such a resource does not exist.
+ *
+ * @param name The name of the resource.
+ * @return An input stream for the resource or {@code null} if it does not exist.
+ */
+ public InputStream getResourceAsStream(String name) {
+ return DISPATCHER.getResourceAsStream(module, name);
+ }
+
+ /**
+ * Returns the class loader of this module.
+ *
+ * @return The class loader of the represented module.
+ */
+ public ClassLoader getClassLoader() {
+ return DISPATCHER.getClassLoader(module);
+ }
+
+ /**
+ * Unwraps this instance to a {@code java.lang.Module}.
+ *
+ * @return The represented {@code java.lang.Module}.
+ */
+ public Object unwrap() {
+ return module;
+ }
+
+ /**
+ * Checks if this module can read the exported packages of the supplied module.
+ *
+ * @param module The module to check for its readability by this module.
+ * @return {@code true} if this module can read the supplied module.
+ */
+ public boolean canRead(JavaModule module) {
+ return DISPATCHER.canRead(this.module, module.unwrap());
+ }
+
+ /**
+ * Adds a read-edge to this module to the supplied module using the instrumentation API.
+ *
+ * @param instrumentation The instrumentation instance to use for adding the edge.
+ * @param module The module to add as a read dependency to this module.
+ */
+ public void addReads(Instrumentation instrumentation, JavaModule module) {
+ DISPATCHER.addReads(instrumentation, this.module, module.unwrap());
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (!(other instanceof JavaModule)) return false;
+ JavaModule that = (JavaModule) other;
+ return module.equals(that.module);
+ }
+
+ @Override
+ public int hashCode() {
+ return module.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return module.toString();
+ }
+
+ /**
+ * A dispatcher for accessing the {@code java.lang.Module} API if it is available on the current VM.
+ */
+ protected interface Dispatcher {
+
+ /**
+ * Checks if this dispatcher is alive, i.e. supports modules.
+ *
+ * @return {@code true} if modules are supported on the current VM.
+ */
+ boolean isAlive();
+
+ /**
+ * Extracts the Java {@code Module} for the provided class or returns {@code null} if the current VM does not support modules.
+ *
+ * @param type The type for which to extract the module.
+ * @return The class's {@code Module} or {@code null} if the current VM does not support modules.
+ */
+ JavaModule moduleOf(Class<?> type);
+
+ /**
+ * Returns {@code true} if the supplied module is named.
+ *
+ * @param module The {@code java.lang.Module} to check for the existence of a name.
+ * @return {@code true} if the supplied module is named.
+ */
+ boolean isNamed(Object module);
+
+ /**
+ * Returns the module's name.
+ *
+ * @param module The {@code java.lang.Module} to check for its name.
+ * @return The module's (implicit or explicit) name.
+ */
+ String getName(Object module);
+
+ /**
+ * Returns a resource stream for this module for a resource of the given name or {@code null} if such a resource does not exist.
+ *
+ * @param module The {@code java.lang.Module} instance to apply this method upon.
+ * @param name The name of the resource.
+ * @return An input stream for the resource or {@code null} if it does not exist.
+ */
+ InputStream getResourceAsStream(Object module, String name);
+
+ /**
+ * Returns the module's class loader.
+ *
+ * @param module The {@code java.lang.Module}
+ * @return The module's class loader.
+ */
+ ClassLoader getClassLoader(Object module);
+
+ /**
+ * Checks if the source module can read the target module.
+ *
+ * @param source The source module.
+ * @param target The target module.
+ * @return {@code true} if the source module can read the target module.
+ */
+ boolean canRead(Object source, Object target);
+
+ /**
+ * Adds a read-edge from the source to the target module.
+ *
+ * @param instrumentation The instrumentation instance to use for adding the edge.
+ * @param source The source module.
+ * @param target The target module.
+ */
+ void addReads(Instrumentation instrumentation, Object source, Object target);
+
+ /**
+ * A creation action for a dispatcher.
+ */
+ enum CreationAction implements PrivilegedAction<Dispatcher> {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback")
+ public Dispatcher run() {
+ try {
+ Class<?> module = Class.forName("java.lang.Module");
+ return new Dispatcher.Enabled(Class.class.getMethod("getModule"),
+ module.getMethod("getClassLoader"),
+ module.getMethod("isNamed"),
+ module.getMethod("getName"),
+ module.getMethod("getResourceAsStream", String.class),
+ module.getMethod("canRead", module),
+ module.getMethod("isModifiableModule", module),
+ Instrumentation.class.getMethod("redefineModule", module, Set.class, Map.class, Map.class, Set.class, Map.class));
+ } catch (Exception ignored) {
+ return Dispatcher.Disabled.INSTANCE;
+ }
+ }
+ }
+
+ /**
+ * A dispatcher for a VM that does support the {@code java.lang.Module} API.
+ */
+ @EqualsAndHashCode
+ class Enabled implements Dispatcher {
+
+ /**
+ * The {@code java.lang.Class#getModule()} method.
+ */
+ private final Method getModule;
+
+ /**
+ * The {@code java.lang.Module#getClassLoader()} method.
+ */
+ private final Method getClassLoader;
+
+ /**
+ * The {@code java.lang.Module#isNamed()} method.
+ */
+ private final Method isNamed;
+
+ /**
+ * The {@code java.lang.Module#getName()} method.
+ */
+ private final Method getName;
+
+ /**
+ * The {@code java.lang.Module#getResourceAsStream(String)} method.
+ */
+ private final Method getResourceAsStream;
+
+ /**
+ * The {@code java.lang.Module#canRead(Module)} method.
+ */
+ private final Method canRead;
+
+ /**
+ * The {@code java.lang.instrument.Instrumentation#isModifiableModule} method.
+ */
+ private final Method isModifiableModule;
+
+ /**
+ * The {@code java.lang.instrument.Instrumentation#redefineModule} method.
+ */
+ private final Method redefineModule;
+
+ /**
+ * Creates an enabled dispatcher.
+ *
+ * @param getModule The {@code java.lang.Class#getModule()} method.
+ * @param getClassLoader The {@code java.lang.Module#getClassLoader()} method.
+ * @param isNamed The {@code java.lang.Module#isNamed()} method.
+ * @param getName The {@code java.lang.Module#getName()} method.
+ * @param getResourceAsStream The {@code java.lang.Module#getResourceAsStream(String)} method.
+ * @param canRead The {@code java.lang.Module#canRead(Module)} method.
+ * @param isModifiableModule The {@code java.lang.instrument.Instrumentation#isModifiableModule} method.
+ * @param redefineModule The {@code java.lang.instrument.Instrumentation#redefineModule} method.
+ */
+ protected Enabled(Method getModule,
+ Method getClassLoader,
+ Method isNamed,
+ Method getName,
+ Method getResourceAsStream,
+ Method canRead,
+ Method isModifiableModule,
+ Method redefineModule) {
+ this.getModule = getModule;
+ this.getClassLoader = getClassLoader;
+ this.isNamed = isNamed;
+ this.getName = getName;
+ this.getResourceAsStream = getResourceAsStream;
+ this.canRead = canRead;
+ this.isModifiableModule = isModifiableModule;
+ this.redefineModule = redefineModule;
+ }
+
+ @Override
+ public boolean isAlive() {
+ return true;
+ }
+
+ @Override
+ public JavaModule moduleOf(Class<?> type) {
+ try {
+ return new JavaModule(getModule.invoke(type));
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access " + getModule, exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Cannot invoke " + getModule, exception.getCause());
+ }
+ }
+
+ @Override
+ public InputStream getResourceAsStream(Object module, String name) {
+ try {
+ return (InputStream) getResourceAsStream.invoke(module, name);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access " + getResourceAsStream, exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Cannot invoke " + getResourceAsStream, exception.getCause());
+ }
+ }
+
+ @Override
+ public ClassLoader getClassLoader(Object module) {
+ try {
+ return (ClassLoader) getClassLoader.invoke(module);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access " + getClassLoader, exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Cannot invoke " + getClassLoader, exception.getCause());
+ }
+ }
+
+ @Override
+ public boolean isNamed(Object module) {
+ try {
+ return (Boolean) isNamed.invoke(module);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access " + isNamed, exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Cannot invoke " + isNamed, exception.getCause());
+ }
+ }
+
+ @Override
+ public String getName(Object module) {
+ try {
+ return (String) getName.invoke(module);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access " + getName, exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Cannot invoke " + getName, exception.getCause());
+ }
+ }
+
+ @Override
+ public boolean canRead(Object source, Object target) {
+ try {
+ return (Boolean) canRead.invoke(source, target);
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access " + canRead, exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Cannot invoke " + canRead, exception.getCause());
+ }
+ }
+
+ @Override
+ public void addReads(Instrumentation instrumentation, Object source, Object target) {
+ try {
+ if (!(Boolean) isModifiableModule.invoke(instrumentation, source)) {
+ throw new IllegalStateException(source + " is not modifable");
+ }
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access " + redefineModule, exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Cannot invoke " + redefineModule, exception.getCause());
+ }
+ try {
+ redefineModule.invoke(instrumentation, source,
+ Collections.singleton(target),
+ Collections.emptyMap(),
+ Collections.emptyMap(),
+ Collections.emptySet(),
+ Collections.emptyMap());
+ } catch (IllegalAccessException exception) {
+ throw new IllegalStateException("Cannot access " + redefineModule, exception);
+ } catch (InvocationTargetException exception) {
+ throw new IllegalStateException("Cannot invoke " + redefineModule, exception.getCause());
+ }
+ }
+ }
+
+ /**
+ * A disabled dispatcher for a VM that does not support the {@code java.lang.Module} API.
+ */
+ enum Disabled implements Dispatcher {
+
+ /**
+ * The singleton instance.
+ */
+ INSTANCE;
+
+ @Override
+ public boolean isAlive() {
+ return false;
+ }
+
+ @Override
+ public JavaModule moduleOf(Class<?> type) {
+ return UNSUPPORTED;
+ }
+
+ @Override
+ public ClassLoader getClassLoader(Object module) {
+ throw new IllegalStateException("Current VM does not support modules");
+ }
+
+ @Override
+ public boolean isNamed(Object module) {
+ throw new IllegalStateException("Current VM does not support modules");
+ }
+
+ @Override
+ public String getName(Object module) {
+ throw new IllegalStateException("Current VM does not support modules");
+ }
+
+ @Override
+ public InputStream getResourceAsStream(Object module, String name) {
+ throw new IllegalStateException("Current VM does not support modules");
+ }
+
+ @Override
+ public boolean canRead(Object source, Object target) {
+ throw new IllegalStateException("Current VM does not support modules");
+ }
+
+ @Override
+ public void addReads(Instrumentation instrumentation, Object source, Object target) {
+ throw new IllegalStateException("Current VM does not support modules");
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/utility/JavaType.java b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/JavaType.java
new file mode 100644
index 0000000..a360459
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/JavaType.java
@@ -0,0 +1,98 @@
+package net.bytebuddy.utility;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import org.objectweb.asm.Opcodes;
+
+import java.io.Serializable;
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.GenericDeclaration;
+import java.lang.reflect.Member;
+
+/**
+ * Representations of Java types that do not exist in Java 6 but that have a special meaning to the JVM.
+ */
+public enum JavaType {
+
+ /**
+ * The Java 7 {@code java.lang.invoke.MethodHandle} type.
+ */
+ METHOD_HANDLE("java.lang.invoke.MethodHandle", Opcodes.ACC_PUBLIC | Opcodes.ACC_ABSTRACT, Object.class),
+
+ /**
+ * The Java 7 {@code java.lang.invoke.MethodType} type.
+ */
+ METHOD_TYPE("java.lang.invoke.MethodType", Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, Object.class, Serializable.class),
+
+ /**
+ * The Java 7 {@code java.lang.invoke.MethodTypes.Lookup} type.
+ */
+ METHOD_HANDLES_LOOKUP("java.lang.invoke.MethodHandles$Lookup", Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, Object.class),
+
+ /**
+ * The Java 7 {@code java.lang.invoke.CallSite} type.
+ */
+ CALL_SITE("java.lang.invoke.CallSite", Opcodes.ACC_PUBLIC | Opcodes.ACC_ABSTRACT, Object.class),
+
+ /**
+ * The Java 8 {@code java.lang.reflect.Parameter} type.
+ */
+ PARAMETER("java.lang.reflect.Parameter", Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, Object.class, AnnotatedElement.class),
+
+ /**
+ * The {@code java.lang.reflect.Executable} type.
+ */
+ EXECUTABLE("java.lang.reflect.Executable", Opcodes.ACC_PUBLIC | Opcodes.ACC_ABSTRACT, AccessibleObject.class, Member.class, GenericDeclaration.class),
+
+ /**
+ * The {@code java.lang.Module} type.
+ */
+ MODULE("java.lang.Module", Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, Object.class);
+
+ /**
+ * The type description to represent this type which is either a loaded type or a stub.
+ */
+ private final TypeDescription typeDescription;
+
+ /**
+ * Creates a new java type representation.
+ *
+ * @param typeName The binary name of this type.
+ * @param modifiers The modifiers of this type when creating a stub.
+ * @param superClass The super class of this type when creating a stub.
+ * @param interfaces The interfaces of this type when creating a stub.
+ */
+ JavaType(String typeName, int modifiers, Class<?> superClass, Class<?>... interfaces) {
+ TypeDescription typeDescription;
+ try {
+ typeDescription = new TypeDescription.ForLoadedType(Class.forName(typeName));
+ } catch (Exception ignored) {
+ typeDescription = new TypeDescription.Latent(typeName,
+ modifiers,
+ new TypeDescription.Generic.OfNonGenericType.ForLoadedType(superClass),
+ new TypeList.Generic.ForLoadedTypes(interfaces));
+ }
+ this.typeDescription = typeDescription;
+ }
+
+ /**
+ * Returns at least a stub representing this type where the stub does not define any methods or fields. If a type exists for
+ * the current runtime, a loaded type representation is returned.
+ *
+ * @return A type description for this Java type.
+ */
+ public TypeDescription getTypeStub() {
+ return typeDescription;
+ }
+
+ /**
+ * Loads the class that is represented by this Java type.
+ *
+ * @return A loaded type of this Java type.
+ * @throws ClassNotFoundException If the represented type cannot be loaded.
+ */
+ public Class<?> load() throws ClassNotFoundException {
+ return Class.forName(typeDescription.getName());
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/utility/RandomString.java b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/RandomString.java
new file mode 100644
index 0000000..acd072f
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/RandomString.java
@@ -0,0 +1,121 @@
+package net.bytebuddy.utility;
+
+import java.util.Random;
+
+/**
+ * A provider of randomized {@link java.lang.String} values.
+ */
+public class RandomString {
+
+ /**
+ * The default length of a randomized {@link java.lang.String}.
+ */
+ public static final int DEFAULT_LENGTH = 8;
+
+ /**
+ * The symbols which are used to create a random {@link java.lang.String}.
+ */
+ private static final char[] SYMBOL;
+
+ /**
+ * The amount of bits to extract out of an integer for each key generated.
+ */
+ private static final int KEY_BITS;
+
+ /*
+ * Creates the symbol array.
+ */
+ static {
+ StringBuilder symbol = new StringBuilder();
+ for (char character = '0'; character <= '9'; character++) {
+ symbol.append(character);
+ }
+ for (char character = 'a'; character <= 'z'; character++) {
+ symbol.append(character);
+ }
+ for (char character = 'A'; character <= 'Z'; character++) {
+ symbol.append(character);
+ }
+ SYMBOL = symbol.toString().toCharArray();
+ int bits = Integer.SIZE - Integer.numberOfLeadingZeros(SYMBOL.length);
+ KEY_BITS = bits - (Integer.bitCount(SYMBOL.length) == bits ? 0 : 1);
+ }
+
+ /**
+ * A provider of random values.
+ */
+ private final Random random;
+
+ /**
+ * The length of the random strings that are created by this instance.
+ */
+ private final int length;
+
+ /**
+ * Creates a random {@link java.lang.String} provider where each {@link java.lang.String} is of
+ * {@link net.bytebuddy.utility.RandomString#DEFAULT_LENGTH} length.
+ */
+ public RandomString() {
+ this(DEFAULT_LENGTH);
+ }
+
+ /**
+ * Creates a random {@link java.lang.String} provider where each value is of the given length.
+ *
+ * @param length The length of the random {@link String}.
+ */
+ public RandomString(int length) {
+ if (length <= 0) {
+ throw new IllegalArgumentException("A random string's length cannot be zero or negative");
+ }
+ this.length = length;
+ random = new Random();
+ }
+
+ /**
+ * Creates a random {@link java.lang.String} of {@link net.bytebuddy.utility.RandomString#DEFAULT_LENGTH} length.
+ *
+ * @return A random {@link java.lang.String}.
+ */
+ public static String make() {
+ return make(DEFAULT_LENGTH);
+ }
+
+ /**
+ * Creates a random {@link java.lang.String} of the given {@code length}.
+ *
+ * @param length The length of the random {@link String}.
+ * @return A random {@link java.lang.String}.
+ */
+ public static String make(int length) {
+ return new RandomString(length).nextString();
+ }
+
+ /**
+ * Represents an integer value as a string hash. This string is not technically random but generates a fixed character
+ * sequence based on the hash provided.
+ *
+ * @param value The value to represent as a string.
+ * @return A string representing the supplied value as a string.
+ */
+ public static String hashOf(int value) {
+ char[] buffer = new char[(Integer.SIZE / KEY_BITS) + ((Integer.SIZE % KEY_BITS) == 0 ? 0 : 1)];
+ for (int index = 0; index < buffer.length; index++) {
+ buffer[index] = SYMBOL[(value >>> index * KEY_BITS) & (-1 >>> (Integer.SIZE - KEY_BITS))];
+ }
+ return new String(buffer);
+ }
+
+ /**
+ * Creates a new random {@link java.lang.String}.
+ *
+ * @return A random {@link java.lang.String} of the given length for this instance.
+ */
+ public String nextString() {
+ char[] buffer = new char[length];
+ for (int index = 0; index < length; index++) {
+ buffer[index] = SYMBOL[random.nextInt(SYMBOL.length)];
+ }
+ return new String(buffer);
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/utility/StreamDrainer.java b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/StreamDrainer.java
new file mode 100644
index 0000000..fd5c172
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/StreamDrainer.java
@@ -0,0 +1,86 @@
+package net.bytebuddy.utility;
+
+import lombok.EqualsAndHashCode;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A utility for draining the contents of an {@link java.io.InputStream} into a {@code byte} array.
+ */
+ at EqualsAndHashCode
+public class StreamDrainer {
+
+ /**
+ * The default size of the buffer for draining a stream.
+ */
+ public static final int DEFAULT_BUFFER_SIZE = 1024;
+
+ /**
+ * A default instance using the {@link StreamDrainer#DEFAULT_BUFFER_SIZE}.
+ */
+ public static final StreamDrainer DEFAULT = new StreamDrainer();
+
+ /**
+ * A convenience constant referring to the value representing the end of a stream.
+ */
+ private static final int END_OF_STREAM = -1;
+
+ /**
+ * A convenience constant referring to the value representing the start of a stream.
+ */
+ private static final int FROM_BEGINNING = 0;
+
+ /**
+ * The buffer size for reading from a given stream.
+ */
+ private final int bufferSize;
+
+ /**
+ * Creates a stream drainer with the default buffer size.
+ */
+ public StreamDrainer() {
+ this(DEFAULT_BUFFER_SIZE);
+ }
+
+ /**
+ * Creates a stream drainer with the given buffer size.
+ *
+ * @param bufferSize The buffer size for reading from a given stream.
+ */
+ public StreamDrainer(int bufferSize) {
+ this.bufferSize = bufferSize;
+ }
+
+ /**
+ * Drains an input stream into a byte array. The given input stream is not closed.
+ *
+ * @param inputStream The input stream to drain.
+ * @return A byte array containing the content of the input stream.
+ * @throws IOException If the stream reading causes an error.
+ */
+ public byte[] drain(InputStream inputStream) throws IOException {
+ List<byte[]> previousBytes = new ArrayList<byte[]>();
+ byte[] currentArray = new byte[bufferSize];
+ int currentIndex = 0;
+ int currentRead;
+ do {
+ currentRead = inputStream.read(currentArray, currentIndex, bufferSize - currentIndex);
+ currentIndex += currentRead > 0 ? currentRead : 0;
+ if (currentIndex == bufferSize) {
+ previousBytes.add(currentArray);
+ currentArray = new byte[bufferSize];
+ currentIndex = 0;
+ }
+ } while (currentRead != END_OF_STREAM);
+ byte[] result = new byte[previousBytes.size() * bufferSize + currentIndex];
+ int arrayIndex = 0;
+ for (byte[] previousByte : previousBytes) {
+ System.arraycopy(previousByte, FROM_BEGINNING, result, arrayIndex++ * bufferSize, bufferSize);
+ }
+ System.arraycopy(currentArray, FROM_BEGINNING, result, arrayIndex * bufferSize, currentIndex);
+ return result;
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/utility/package-info.java b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/package-info.java
new file mode 100644
index 0000000..45492a3
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * This package contains utility classes for common use within any Byte Buddy logic.
+ */
+package net.bytebuddy.utility;
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/utility/privilege/GetSystemPropertyAction.java b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/privilege/GetSystemPropertyAction.java
new file mode 100644
index 0000000..cea1e28
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/privilege/GetSystemPropertyAction.java
@@ -0,0 +1,31 @@
+package net.bytebuddy.utility.privilege;
+
+import lombok.EqualsAndHashCode;
+
+import java.security.PrivilegedAction;
+
+/**
+ * An action for reading a system property as a privileged action.
+ */
+ at EqualsAndHashCode
+public class GetSystemPropertyAction implements PrivilegedAction<String> {
+
+ /**
+ * The property key.
+ */
+ private final String key;
+
+ /**
+ * Creates a new action for reading a system property.
+ *
+ * @param key The property key.
+ */
+ public GetSystemPropertyAction(String key) {
+ this.key = key;
+ }
+
+ @Override
+ public String run() {
+ return System.getProperty(key);
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/utility/privilege/SetAccessibleAction.java b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/privilege/SetAccessibleAction.java
new file mode 100644
index 0000000..ec45b3e
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/privilege/SetAccessibleAction.java
@@ -0,0 +1,35 @@
+package net.bytebuddy.utility.privilege;
+
+import lombok.EqualsAndHashCode;
+
+import java.lang.reflect.AccessibleObject;
+import java.security.PrivilegedAction;
+
+/**
+ * An action for making an {@link AccessibleObject} accessible.
+ *
+ * @param <T> The type of the accessible object.
+ */
+ at EqualsAndHashCode
+public class SetAccessibleAction<T extends AccessibleObject> implements PrivilegedAction<T> {
+
+ /**
+ * The accessible object.
+ */
+ private final T accessibleObject;
+
+ /**
+ * Creates a new access action.
+ *
+ * @param accessibleObject The accessible object.
+ */
+ public SetAccessibleAction(T accessibleObject) {
+ this.accessibleObject = accessibleObject;
+ }
+
+ @Override
+ public T run() {
+ accessibleObject.setAccessible(true);
+ return accessibleObject;
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/utility/privilege/package-info.java b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/privilege/package-info.java
new file mode 100644
index 0000000..1f0a37c
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/privilege/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * A package containing {@link java.security.PrivilegedAction}s that are used for invoking sensitive methods.
+ */
+package net.bytebuddy.utility.privilege;
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/utility/visitor/ExceptionTableSensitiveMethodVisitor.java b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/visitor/ExceptionTableSensitiveMethodVisitor.java
new file mode 100644
index 0000000..b85f7a8
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/visitor/ExceptionTableSensitiveMethodVisitor.java
@@ -0,0 +1,299 @@
+package net.bytebuddy.utility.visitor;
+
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+
+/**
+ * A {@link MethodVisitor} that adds a callback after visiting the exception table of a method.
+ */
+public abstract class ExceptionTableSensitiveMethodVisitor extends MethodVisitor {
+
+ /**
+ * {@code true} if the exception table callback was already triggered.
+ */
+ private boolean trigger;
+
+ /**
+ * Creates an exception table sensitive method visitor.
+ *
+ * @param api The ASM API version.
+ * @param methodVisitor The delegating method visitor.
+ */
+ protected ExceptionTableSensitiveMethodVisitor(int api, MethodVisitor methodVisitor) {
+ super(api, methodVisitor);
+ trigger = true;
+ }
+
+ /**
+ * Considers if the end of the exception table was reached.
+ */
+ private void considerEndOfExceptionTable() {
+ if (trigger) {
+ trigger = false;
+ onAfterExceptionTable();
+ }
+ }
+
+ /**
+ * Invoked after the exception table was visited. Typically, the exception table is visited by ASM at the beginning
+ * of a method. It is however possible that a user adds exception table entries at a later point. Normally, this is
+ * however not meaningful use of ASM.
+ */
+ protected abstract void onAfterExceptionTable();
+
+ @Override
+ public final void visitLabel(Label label) {
+ considerEndOfExceptionTable();
+ onVisitLabel(label);
+ }
+
+ /**
+ * Visits a label.
+ *
+ * @param label The visited label.
+ * @see MethodVisitor#visitLabel(Label)
+ */
+ protected void onVisitLabel(Label label) {
+ super.visitLabel(label);
+ }
+
+ @Override
+ public final void visitIntInsn(int opcode, int operand) {
+ considerEndOfExceptionTable();
+ onVisitIntInsn(opcode, operand);
+ }
+
+ /**
+ * Visits an integer opcode.
+ *
+ * @param opcode The visited opcode.
+ * @param operand The visited operand.
+ */
+ protected void onVisitIntInsn(int opcode, int operand) {
+ super.visitIntInsn(opcode, operand);
+ }
+
+ @Override
+ public final void visitVarInsn(int opcode, int var) {
+ considerEndOfExceptionTable();
+ onVisitVarInsn(opcode, var);
+ }
+
+ /**
+ * Visits an variable instruction.
+ *
+ * @param opcode The visited opcode.
+ * @param offset The visited offset.
+ */
+ protected void onVisitVarInsn(int opcode, int offset) {
+ super.visitVarInsn(opcode, offset);
+ }
+
+ @Override
+ public final void visitTypeInsn(int opcode, String type) {
+ considerEndOfExceptionTable();
+ onVisitTypeInsn(opcode, type);
+ }
+
+ /**
+ * Visits a type instruction.
+ *
+ * @param opcode The visited opcode.
+ * @param type The type name.
+ */
+ protected void onVisitTypeInsn(int opcode, String type) {
+ super.visitTypeInsn(opcode, type);
+ }
+
+ @Override
+ public final void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ considerEndOfExceptionTable();
+ onVisitFieldInsn(opcode, owner, name, desc);
+ }
+
+ /**
+ * Visits a field instruction.
+ *
+ * @param opcode The visited opcode.
+ * @param owner The field's owner.
+ * @param name The field's name.
+ * @param descriptor The field's descriptor.
+ */
+ protected void onVisitFieldInsn(int opcode, String owner, String name, String descriptor) {
+ super.visitFieldInsn(opcode, owner, name, descriptor);
+ }
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public final void visitMethodInsn(int opcode, String owner, String name, String desc) {
+ considerEndOfExceptionTable();
+ onVisitMethodInsn(opcode, owner, name, desc);
+ }
+
+ /**
+ * Visits a method instruction.
+ *
+ * @param opcode The visited opcode.
+ * @param owner The method's owner.
+ * @param name The method's internal name.
+ * @param descriptor The method's descriptor.
+ * @deprecated Use {@link ExceptionTableSensitiveMethodVisitor#onVisitMethodInsn(int, String, String, String, boolean)} instead.
+ */
+ @Deprecated
+ @SuppressWarnings("deprecation")
+ protected void onVisitMethodInsn(int opcode, String owner, String name, String descriptor) {
+ considerEndOfExceptionTable();
+ super.visitMethodInsn(opcode, owner, name, descriptor);
+ }
+
+ @Override
+ public final void visitMethodInsn(int opcode, String owner, String name, String desc, boolean iFace) {
+ considerEndOfExceptionTable();
+ onVisitMethodInsn(opcode, owner, name, desc, iFace);
+ }
+
+ /**
+ * Visits a method instruction.
+ *
+ * @param opcode The visited opcode.
+ * @param owner The method's owner.
+ * @param name The method's internal name.
+ * @param descriptor The method's descriptor.
+ * @param iFace {@code true} if the method belongs to an interface.
+ */
+ protected void onVisitMethodInsn(int opcode, String owner, String name, String descriptor, boolean iFace) {
+ super.visitMethodInsn(opcode, owner, name, descriptor, iFace);
+ }
+
+ @Override
+ public final void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
+ considerEndOfExceptionTable();
+ onVisitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
+ }
+
+ /**
+ * Visits an invoke dynamic instruction.
+ *
+ * @param name The name of the method.
+ * @param descriptor The descriptor of the method.
+ * @param handle The bootstrap method handle.
+ * @param argument The bootstrap method arguments.
+ */
+ protected void onVisitInvokeDynamicInsn(String name, String descriptor, Handle handle, Object... argument) {
+ super.visitInvokeDynamicInsn(name, descriptor, handle, argument);
+ }
+
+ @Override
+ public final void visitJumpInsn(int opcode, Label label) {
+ considerEndOfExceptionTable();
+ onVisitJumpInsn(opcode, label);
+ }
+
+ /**
+ * Visits a jump instruction.
+ *
+ * @param opcode The visited opcode.
+ * @param label The visited label.
+ */
+ protected void onVisitJumpInsn(int opcode, Label label) {
+ super.visitJumpInsn(opcode, label);
+ }
+
+ @Override
+ public final void visitLdcInsn(Object cst) {
+ considerEndOfExceptionTable();
+ onVisitLdcInsn(cst);
+ }
+
+ /**
+ * Visits a constant pool access instruction.
+ *
+ * @param constant The constant pool value.
+ */
+ protected void onVisitLdcInsn(Object constant) {
+ super.visitLdcInsn(constant);
+ }
+
+ @Override
+ public final void visitIincInsn(int var, int increment) {
+ considerEndOfExceptionTable();
+ onVisitIincInsn(var, increment);
+ }
+
+ /**
+ * Visits an increment instruction.
+ *
+ * @param offset The offset of the accessed variable.
+ * @param increment The value with which to increment.
+ */
+ protected void onVisitIincInsn(int offset, int increment) {
+ super.visitIincInsn(offset, increment);
+ }
+
+ @Override
+ public final void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
+ considerEndOfExceptionTable();
+ onVisitTableSwitchInsn(min, max, dflt, labels);
+ }
+
+ /**
+ * Visits a table switch instruction.
+ *
+ * @param min The minimum index.
+ * @param max The maximum index.
+ * @param defaultTarget A label indicating the default value.
+ * @param label Labels indicating the jump targets.
+ */
+ protected void onVisitTableSwitchInsn(int min, int max, Label defaultTarget, Label... label) {
+ super.visitTableSwitchInsn(min, max, defaultTarget, label);
+ }
+
+ @Override
+ public final void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
+ considerEndOfExceptionTable();
+ onVisitLookupSwitchInsn(dflt, keys, labels);
+ }
+
+ /**
+ * Visits a lookup switch instruction.
+ *
+ * @param defaultTarget The default option.
+ * @param keys The key values.
+ * @param key The targets for each key.
+ */
+ protected void onVisitLookupSwitchInsn(Label defaultTarget, int[] keys, Label[] key) {
+ super.visitLookupSwitchInsn(defaultTarget, keys, key);
+ }
+
+ @Override
+ public final void visitMultiANewArrayInsn(String desc, int dims) {
+ considerEndOfExceptionTable();
+ onVisitMultiANewArrayInsn(desc, dims);
+ }
+
+ /**
+ * Visits an instruction for creating a multidimensional array.
+ *
+ * @param descriptor The type descriptor of the array's component type.
+ * @param dimensions The dimensions of the array.
+ */
+ protected void onVisitMultiANewArrayInsn(String descriptor, int dimensions) {
+ super.visitMultiANewArrayInsn(descriptor, dimensions);
+ }
+
+ @Override
+ public final void visitInsn(int opcode) {
+ considerEndOfExceptionTable();
+ onVisitInsn(opcode);
+ }
+
+ /**
+ * Visits a simple instruction.
+ *
+ * @param opcode The opcode of the instruction.
+ */
+ protected void onVisitInsn(int opcode) {
+ super.visitInsn(opcode);
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/utility/visitor/LineNumberPrependingMethodVisitor.java b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/visitor/LineNumberPrependingMethodVisitor.java
new file mode 100644
index 0000000..b6d95c2
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/visitor/LineNumberPrependingMethodVisitor.java
@@ -0,0 +1,46 @@
+package net.bytebuddy.utility.visitor;
+
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * A method visitor that maps the first available line number information, if available, to the beginning of the method.
+ */
+public class LineNumberPrependingMethodVisitor extends ExceptionTableSensitiveMethodVisitor {
+
+ /**
+ * A label indicating the start of the method.
+ */
+ private final Label startOfMethod;
+
+ /**
+ * {@code true} if the first line number was not yet discovered.
+ */
+ private boolean prependLineNumber;
+
+ /**
+ * Creates a new line number prepending method visitor.
+ *
+ * @param methodVisitor The method visitor to delegate to.
+ */
+ public LineNumberPrependingMethodVisitor(MethodVisitor methodVisitor) {
+ super(Opcodes.ASM5, methodVisitor);
+ startOfMethod = new Label();
+ prependLineNumber = true;
+ }
+
+ @Override
+ protected void onAfterExceptionTable() {
+ super.visitLabel(startOfMethod);
+ }
+
+ @Override
+ public void visitLineNumber(int line, Label start) {
+ if (prependLineNumber) {
+ start = startOfMethod;
+ prependLineNumber = false;
+ }
+ super.visitLineNumber(line, start);
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/utility/visitor/StackAwareMethodVisitor.java b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/visitor/StackAwareMethodVisitor.java
new file mode 100644
index 0000000..f69e795
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/visitor/StackAwareMethodVisitor.java
@@ -0,0 +1,362 @@
+package net.bytebuddy.utility.visitor;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.utility.CompoundList;
+import org.objectweb.asm.*;
+
+import java.util.*;
+
+/**
+ * A method visitor that is aware of the current size of the operand stack at all times. Additionally, this method takes
+ * care of maintaining an index for the next currently unused index of the local variable array.
+ */
+public class StackAwareMethodVisitor extends MethodVisitor {
+
+ /**
+ * An array mapping any opcode to its size impact onto the operand stack. This mapping is taken from
+ * {@link org.objectweb.asm.Frame} with the difference that the {@link Opcodes#JSR} instruction is
+ * mapped to a size of {@code 0} as it does not impact the stack after returning from the instruction.
+ */
+ private static final int[] SIZE_CHANGE;
+
+ /*
+ * Computes a mapping of byte codes to their size impact onto the operand stack.
+ */
+ static {
+ SIZE_CHANGE = new int[202];
+ String encoded = "EFFFFFFFFGGFFFGGFFFEEFGFGFEEEEEEEEEEEEEEEEEEEEDEDEDDDDDCD" +
+ "CDEEEEEEEEEEEEEEEEEEEEBABABBBBDCFFFGGGEDCDCDCDCDCDCDCDCDCDCEEEEDDD" +
+ "DDDDCDCDCEFEFDDEEFFDEDEEEBDDBBDDDDDDCCCCCCCCEEEDDDCDCDEEEEEEEEEEFE" +
+ "EEEEEDDEEDDEE";
+ for (int index = 0; index < SIZE_CHANGE.length; index++) {
+ SIZE_CHANGE[index] = encoded.charAt(index) - 'E';
+ }
+ }
+
+ /**
+ * A list of the current elements on the operand stack.
+ */
+ private List<StackSize> current;
+
+ /**
+ * A mapping of labels to the operand stack size that is expected at this label. Lists stored in this
+ * map must not be mutated.
+ */
+ private final Map<Label, List<StackSize>> sizes;
+
+ /**
+ * The next index of the local variable array that is available.
+ */
+ private int freeIndex;
+
+ /**
+ * Creates a new stack aware method visitor.
+ *
+ * @param methodVisitor The method visitor to delegate operations to.
+ * @param instrumentedMethod The method description for which this method visitor is applied.
+ */
+ public StackAwareMethodVisitor(MethodVisitor methodVisitor, MethodDescription instrumentedMethod) {
+ super(Opcodes.ASM5, methodVisitor);
+ current = new ArrayList<StackSize>();
+ sizes = new HashMap<Label, List<StackSize>>();
+ freeIndex = instrumentedMethod.getStackSize();
+ }
+
+ /**
+ * Adjusts the current state of the operand stack.
+ *
+ * @param delta The change of the current operation of the operand stack. Must not be larger than {@code 2}.
+ */
+ private void adjustStack(int delta) {
+ adjustStack(delta, 0);
+ }
+
+ /**
+ * Adjusts the current state of the operand stack.
+ *
+ * @param delta The change of the current operation of the operand stack. Must not be larger than {@code 2}.
+ * @param offset The offset of the value within the operand stack. Must be bigger then {@code 0} and smaller than
+ * the current stack size. Only permitted if the supplied {@code delta} is positive.
+ */
+ private void adjustStack(int delta, int offset) {
+ if (delta > 2) {
+ throw new IllegalStateException("Cannot push multiple values onto the operand stack: " + delta);
+ } else if (delta > 0) {
+ int position = current.size();
+ // The operand stack can legally underflow while traversing dead code.
+ while (offset > 0 && position > 0) {
+ offset -= current.get(--position).getSize();
+ }
+ if (offset < 0) {
+ throw new IllegalStateException("Unexpected offset underflow: " + offset);
+ }
+ current.add(position, StackSize.of(delta));
+ } else if (offset != 0) {
+ throw new IllegalStateException("Cannot specify non-zero offset " + offset + " for non-incrementing value: " + delta);
+ } else {
+ while (delta < 0) {
+ // The operand stack can legally underflow while traversing dead code.
+ if (current.isEmpty()) {
+ return;
+ }
+ delta += current.remove(current.size() - 1).getSize();
+ }
+ if (delta == 1) {
+ current.add(StackSize.SINGLE);
+ } else if (delta != 0) {
+ throw new IllegalStateException("Unexpected remainder on the operand stack: " + delta);
+ }
+ }
+ }
+
+ /**
+ * Pops all values currently on the stack.
+ */
+ public void drainStack() {
+ doDrain(current);
+ }
+
+ /**
+ * Drains the stack to only contain the top value. For this, the value on top of the stack is temporarily stored
+ * in the local variable array until all values on the stack are popped off. Subsequently, the top value is pushed
+ * back onto the operand stack.
+ *
+ * @param store The opcode used for storing the top value.
+ * @param load The opcode used for loading the top value.
+ * @param size The size of the value on top of the operand stack.
+ * @return The minimal size of the local variable array that is required to perform the operation.
+ */
+ public int drainStack(int store, int load, StackSize size) {
+ int difference = current.get(current.size() - 1).getSize() - size.getSize();
+ if (current.size() == 1 && difference == 0) {
+ return 0;
+ } else {
+ super.visitVarInsn(store, freeIndex);
+ if (difference == 1) {
+ super.visitInsn(Opcodes.POP);
+ } else if (difference != 0) {
+ throw new IllegalStateException("Unexpected remainder on the operand stack: " + difference);
+ }
+ doDrain(current.subList(0, current.size() - 1));
+ super.visitVarInsn(load, freeIndex);
+ return freeIndex + size.getSize();
+ }
+ }
+
+ /**
+ * Drains all supplied elements of the operand stack.
+ *
+ * @param stackSizes The stack sizes of the elements to drain.
+ */
+ private void doDrain(List<StackSize> stackSizes) {
+ ListIterator<StackSize> iterator = stackSizes.listIterator(stackSizes.size());
+ while (iterator.hasPrevious()) {
+ StackSize current = iterator.previous();
+ switch (current) {
+ case SINGLE:
+ super.visitInsn(Opcodes.POP);
+ break;
+ case DOUBLE:
+ super.visitInsn(Opcodes.POP2);
+ break;
+ default:
+ throw new IllegalStateException("Unexpected stack size: " + current);
+ }
+ }
+ }
+
+ /**
+ * Explicitly registers a label to define a given stack state.
+ *
+ * @param label The label to register a stack state for.
+ * @param stackSizes The stack sizes to assume when reaching the supplied label.
+ */
+ public void register(Label label, List<StackSize> stackSizes) {
+ sizes.put(label, stackSizes);
+ }
+
+ @Override
+ public void visitInsn(int opcode) {
+ switch (opcode) {
+ case Opcodes.RETURN:
+ case Opcodes.ARETURN:
+ case Opcodes.IRETURN:
+ case Opcodes.LRETURN:
+ case Opcodes.FRETURN:
+ case Opcodes.DRETURN:
+ case Opcodes.ATHROW:
+ current.clear();
+ break;
+ case Opcodes.DUP_X1:
+ case Opcodes.DUP2_X1:
+ adjustStack(SIZE_CHANGE[opcode], SIZE_CHANGE[opcode] + 1);
+ break;
+ case Opcodes.DUP_X2:
+ case Opcodes.DUP2_X2:
+ adjustStack(SIZE_CHANGE[opcode], SIZE_CHANGE[opcode] + 2);
+ break;
+ case Opcodes.D2I:
+ case Opcodes.D2F:
+ case Opcodes.L2F:
+ case Opcodes.L2I:
+ adjustStack(-2);
+ adjustStack(1);
+ break;
+ case Opcodes.I2D:
+ case Opcodes.I2L:
+ case Opcodes.F2D:
+ case Opcodes.F2L:
+ adjustStack(-1);
+ adjustStack(2);
+ break;
+ case Opcodes.LALOAD:
+ case Opcodes.DALOAD:
+ adjustStack(-2);
+ adjustStack(+2);
+ break;
+ default:
+ adjustStack(SIZE_CHANGE[opcode]);
+ }
+ super.visitInsn(opcode);
+ }
+
+ @Override
+ public void visitIntInsn(int opcode, int operand) {
+ adjustStack(SIZE_CHANGE[opcode]);
+ super.visitIntInsn(opcode, operand);
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "SF_SWITCH_NO_DEFAULT", justification = "No default behavior is applied")
+ public void visitVarInsn(int opcode, int variable) {
+ switch (opcode) {
+ case Opcodes.ASTORE:
+ case Opcodes.ISTORE:
+ case Opcodes.FSTORE:
+ freeIndex = Math.max(freeIndex, variable + 1);
+ break;
+ case Opcodes.LSTORE:
+ case Opcodes.DSTORE:
+ freeIndex = Math.max(freeIndex, variable + 2);
+ break;
+ case Opcodes.RET:
+ current.clear();
+ break;
+ }
+ adjustStack(SIZE_CHANGE[opcode]);
+ super.visitVarInsn(opcode, variable);
+ }
+
+ @Override
+ public void visitTypeInsn(int opcode, String type) {
+ adjustStack(SIZE_CHANGE[opcode]);
+ super.visitTypeInsn(opcode, type);
+ }
+
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
+ int baseline = Type.getType(descriptor).getSize();
+ switch (opcode) {
+ case Opcodes.GETFIELD:
+ adjustStack(-1);
+ adjustStack(baseline);
+ break;
+ case Opcodes.GETSTATIC:
+ adjustStack(baseline);
+ break;
+ case Opcodes.PUTFIELD:
+ adjustStack(-baseline - 1);
+ break;
+ case Opcodes.PUTSTATIC:
+ adjustStack(-baseline);
+ break;
+ default:
+ throw new IllegalStateException("Unexpected opcode: " + opcode);
+ }
+ super.visitFieldInsn(opcode, owner, name, descriptor);
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
+ int baseline = Type.getArgumentsAndReturnSizes(descriptor);
+ adjustStack(-(baseline >> 2) + (opcode == Opcodes.INVOKESTATIC ? 1 : 0));
+ adjustStack(baseline & 0x03);
+ super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ }
+
+ @Override
+ public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrap, Object... bootstrapArguments) {
+ int baseline = Type.getArgumentsAndReturnSizes(descriptor);
+ adjustStack(-(baseline >> 2) + 1);
+ adjustStack(baseline & 0x03);
+ super.visitInvokeDynamicInsn(name, descriptor, bootstrap, bootstrapArguments);
+ }
+
+ @Override
+ public void visitLdcInsn(Object value) {
+ adjustStack((value instanceof Long || value instanceof Double) ? 2 : 1);
+ super.visitLdcInsn(value);
+ }
+
+ @Override
+ public void visitMultiANewArrayInsn(String descriptor, int dimension) {
+ adjustStack(1 - dimension);
+ super.visitMultiANewArrayInsn(descriptor, dimension);
+ }
+
+ @Override
+ public void visitJumpInsn(int opcode, Label label) {
+ adjustStack(SIZE_CHANGE[opcode]);
+ sizes.put(label, new ArrayList<StackSize>(opcode == Opcodes.JSR
+ ? CompoundList.of(current, StackSize.SINGLE)
+ : current));
+ if (opcode == Opcodes.GOTO) {
+ current.clear();
+ }
+ super.visitJumpInsn(opcode, label);
+ }
+
+ @Override
+ public void visitLabel(Label label) {
+ List<StackSize> current = sizes.get(label);
+ if (current != null) {
+ this.current = new ArrayList<StackSize>(current);
+ }
+ super.visitLabel(label);
+ }
+
+ @Override
+ public void visitLineNumber(int line, Label start) {
+ super.visitLineNumber(line, start);
+ }
+
+ @Override
+ public void visitTableSwitchInsn(int minimum, int maximum, Label defaultOption, Label... option) {
+ adjustStack(-1);
+ List<StackSize> current = new ArrayList<StackSize>(this.current);
+ sizes.put(defaultOption, current);
+ for (Label label : option) {
+ sizes.put(label, current);
+ }
+ super.visitTableSwitchInsn(minimum, maximum, defaultOption, option);
+ }
+
+ @Override
+ public void visitLookupSwitchInsn(Label defaultOption, int[] key, Label[] option) {
+ adjustStack(-1);
+ List<StackSize> current = new ArrayList<StackSize>(this.current);
+ sizes.put(defaultOption, current);
+ for (Label label : option) {
+ sizes.put(label, current);
+ }
+ super.visitLookupSwitchInsn(defaultOption, key, option);
+ }
+
+ @Override
+ public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
+ sizes.put(handler, Collections.singletonList(StackSize.SINGLE));
+ super.visitTryCatchBlock(start, end, handler, type);
+ }
+}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/utility/visitor/package-info.java b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/visitor/package-info.java
new file mode 100644
index 0000000..5d16ef4
--- /dev/null
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/visitor/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * A package containing visitor classes for ASM.
+ */
+package net.bytebuddy.utility.visitor;
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/ByteBuddyTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/ByteBuddyTest.java
new file mode 100644
index 0000000..6b1bbfb
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/ByteBuddyTest.java
@@ -0,0 +1,128 @@
+package net.bytebuddy;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.TypeResolutionStrategy;
+import net.bytebuddy.dynamic.loading.ByteArrayClassLoader;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.dynamic.loading.PackageDefinitionStrategy;
+import net.bytebuddy.implementation.MethodDelegation;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.lang.reflect.Modifier;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Collections;
+
+import static net.bytebuddy.matcher.ElementMatchers.isTypeInitializer;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class ByteBuddyTest {
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testEnumWithoutValuesIsIllegal() throws Exception {
+ new ByteBuddy().makeEnumeration();
+ }
+
+ @Test
+ public void testEnumeration() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .makeEnumeration("foo")
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(Modifier.isPublic(type.getModifiers()), is(true));
+ assertThat(type.isEnum(), is(true));
+ assertThat(type.isInterface(), is(false));
+ assertThat(type.isAnnotation(), is(false));
+ }
+
+ @Test
+ public void testInterface() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .makeInterface()
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(Modifier.isPublic(type.getModifiers()), is(true));
+ assertThat(type.isEnum(), is(false));
+ assertThat(type.isInterface(), is(true));
+ assertThat(type.isAnnotation(), is(false));
+ }
+
+ @Test
+ public void testAnnotation() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .makeAnnotation()
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(Modifier.isPublic(type.getModifiers()), is(true));
+ assertThat(type.isEnum(), is(false));
+ assertThat(type.isInterface(), is(true));
+ assertThat(type.isAnnotation(), is(true));
+ }
+
+ @Test
+ public void testTypeInitializerInstrumentation() throws Exception {
+ Recorder recorder = new Recorder();
+ Class<?> type = new ByteBuddy()
+ .subclass(Object.class)
+ .invokable(isTypeInitializer())
+ .intercept(MethodDelegation.to(recorder))
+ .make(new TypeResolutionStrategy.Active())
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredConstructor().newInstance(), instanceOf(type));
+ assertThat(recorder.counter, is(1));
+ }
+
+ @Test
+ public void testImplicitStrategyBootstrap() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .subclass(Object.class)
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER)
+ .getLoaded();
+ assertThat(type.getClassLoader(), notNullValue(ClassLoader.class));
+ }
+
+ @Test
+ public void testImplicitStrategyNonBootstrap() throws Exception {
+ ClassLoader classLoader = new URLClassLoader(new URL[0], ClassLoadingStrategy.BOOTSTRAP_LOADER);
+ Class<?> type = new ByteBuddy()
+ .subclass(Object.class)
+ .make()
+ .load(classLoader)
+ .getLoaded();
+ assertThat(type.getClassLoader(), is(classLoader));
+ }
+
+ @Test
+ public void testImplicitStrategyInjectable() throws Exception {
+ ClassLoader classLoader = new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER, Collections.<String, byte[]>emptyMap());
+ Class<?> type = new ByteBuddy()
+ .subclass(Object.class)
+ .make()
+ .load(classLoader)
+ .getLoaded();
+ assertThat(type.getClassLoader(), is(classLoader));
+ }
+
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ByteBuddy.class).apply();
+ ObjectPropertyAssertion.of(ByteBuddy.EnumerationImplementation.class).apply();
+ }
+
+ public static class Recorder {
+
+ public int counter;
+
+ public void instrument() {
+ counter++;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/ByteBuddyTutorialExamplesTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/ByteBuddyTutorialExamplesTest.java
new file mode 100644
index 0000000..b683ba9
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/ByteBuddyTutorialExamplesTest.java
@@ -0,0 +1,826 @@
+package net.bytebuddy;
+
+import net.bytebuddy.agent.ByteBuddyAgent;
+import net.bytebuddy.agent.builder.AgentBuilder;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
+import net.bytebuddy.implementation.*;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bind.annotation.*;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.assign.primitive.PrimitiveTypeAwareAssigner;
+import net.bytebuddy.implementation.bytecode.constant.IntegerConstant;
+import net.bytebuddy.implementation.bytecode.constant.TextConstant;
+import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
+import net.bytebuddy.implementation.bytecode.member.MethodReturn;
+import net.bytebuddy.pool.TypePool;
+import net.bytebuddy.test.utility.AgentAttachmentRule;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import net.bytebuddy.utility.JavaModule;
+import org.hamcrest.CoreMatchers;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.instrument.Instrumentation;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import static net.bytebuddy.matcher.ElementMatchers.*;
+import static net.bytebuddy.matcher.ElementMatchers.not;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class ByteBuddyTutorialExamplesTest {
+
+ private static final String DEFAULT_METHOD_INTERFACE = "net.bytebuddy.test.precompiled.SingleDefaultMethodInterface";
+
+ private static final String CONFLICTING_DEFAULT_METHOD_INTERFACE = "net.bytebuddy.test.precompiled.SingleDefaultMethodConflictingInterface";
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ @Rule
+ public MethodRule agentAttachmentRule = new AgentAttachmentRule();
+
+ // Other than in the tutorial, the tests use a wrapper strategy for class loading to retain the test's repeatability.
+
+ @SuppressWarnings("unused")
+ private static void println(String s) {
+ /* do nothing */
+ }
+
+ @Test
+ public void testHelloWorld() throws Exception {
+ Class<?> dynamicType = new ByteBuddy()
+ .subclass(Object.class)
+ .method(named("toString")).intercept(FixedValue.value("Hello World!"))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(dynamicType.getDeclaredConstructor().newInstance().toString(), is("Hello World!"));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testExtensiveExample() throws Exception {
+ Class<? extends Function> dynamicType = new ByteBuddy()
+ .subclass(Function.class)
+ .method(named("apply"))
+ .intercept(MethodDelegation.to(new GreetingInterceptor()))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(dynamicType.getDeclaredConstructor().newInstance().apply("Byte Buddy"), is((Object) "Hello from Byte Buddy"));
+ }
+
+ @Test
+ public void testTutorialGettingStartedUnnamed() throws Exception {
+ DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
+ .subclass(Object.class)
+ .make();
+ assertThat(dynamicType, notNullValue());
+ }
+
+ @Test
+ public void testTutorialGettingStartedNamed() throws Exception {
+ DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
+ .subclass(Object.class)
+ .name("example.Type")
+ .make();
+ assertThat(dynamicType, notNullValue());
+ }
+
+ @Test
+ public void testTutorialGettingStartedNamingStrategy() throws Exception {
+ DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
+ .with(new GettingStartedNamingStrategy())
+ .subclass(Object.class)
+ .make();
+ assertThat(dynamicType, notNullValue());
+ Class<?> type = dynamicType.load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER).getLoaded();
+ assertThat(type.getName(), is("i.love.ByteBuddy.Object"));
+ }
+
+ @Test
+ public void testTutorialGettingStartedClassLoading() throws Exception {
+ Class<?> dynamicType = new ByteBuddy()
+ .subclass(Object.class)
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(dynamicType, notNullValue());
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce(redefinesClasses = true)
+ public void testTutorialGettingStartedClassReloading() throws Exception {
+ ByteBuddyAgent.install();
+ FooReloading foo = new FooReloading();
+ try {
+ new ByteBuddy()
+ .redefine(BarReloading.class)
+ .name(FooReloading.class.getName())
+ .make()
+ .load(FooReloading.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
+ assertThat(foo.m(), is("bar"));
+ } finally {
+ ClassReloadingStrategy.fromInstalledAgent().reset(FooReloading.class); // Assure repeatability.
+ }
+ assertThat(foo.m(), is("foo"));
+ }
+
+ @Test
+ public void testTutorialGettingStartedTypePool() throws Exception {
+ TypePool typePool = TypePool.Default.ofClassPath();
+ ClassLoader classLoader = new URLClassLoader(new URL[0], null); // Assure repeatability.
+ new ByteBuddy().redefine(typePool.describe(UnloadedBar.class.getName()).resolve(),
+ ClassFileLocator.ForClassLoader.ofClassPath())
+ .defineField("qux", String.class)
+ .make()
+ .load(classLoader, ClassLoadingStrategy.Default.INJECTION);
+ assertThat(classLoader.loadClass(UnloadedBar.class.getName()).getDeclaredField("qux"), notNullValue(java.lang.reflect.Field.class));
+ }
+
+ @Test
+ public void testTutorialGettingStartedJavaAgent() throws Exception {
+ new AgentBuilder.Default().type(isAnnotatedWith(Rebase.class)).transform(new AgentBuilder.Transformer() {
+ @Override
+ public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {
+ return builder.method(named("toString")).intercept(FixedValue.value("transformed"));
+ }
+ }).installOn(mock(Instrumentation.class));
+ }
+
+ @Test
+ public void testFieldsAndMethodsToString() throws Exception {
+ String toString = new ByteBuddy()
+ .subclass(Object.class)
+ .name("example.Type")
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance()
+ .toString();
+ assertThat(toString, startsWith("example.Type"));
+ }
+
+ @Test
+ public void testFieldsAndMethodsDetailedMatcher() throws Exception {
+ assertThat(TypeDescription.OBJECT
+ .getDeclaredMethods()
+ .filter(named("toString").and(returns(String.class)).and(takesArguments(0))).size(), is(1));
+ }
+
+ @Test
+ public void testFieldsAndMethodsMatcherStack() throws Exception {
+ Foo foo = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class)).intercept(FixedValue.value("Hello World!"))
+ .method(named("foo")).intercept(FixedValue.value("Hello Foo!"))
+ .method(named("foo").and(takesArguments(1))).intercept(FixedValue.value("..."))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance();
+ assertThat(foo.bar(), is("Hello World!"));
+ assertThat(foo.foo(), is("Hello Foo!"));
+ assertThat(foo.foo(null), is("..."));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testFieldsAndMethodsIllegalAssignment() throws Exception {
+ new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class)).intercept(FixedValue.value(0))
+ .make();
+ }
+
+ @Test
+ public void testFieldsAndMethodsMethodDelegation() throws Exception {
+ String helloWorld = new ByteBuddy()
+ .subclass(Source.class)
+ .method(named("hello")).intercept(MethodDelegation.to(Target.class))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance()
+ .hello("World");
+ assertThat(helloWorld, is("Hello World!"));
+ }
+
+ @Test
+ public void testFieldsAndMethodsMethodDelegationAlternatives() throws Exception {
+ String helloWorld = new ByteBuddy()
+ .subclass(Source.class)
+ .method(named("hello")).intercept(MethodDelegation.to(Target2.class))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance()
+ .hello("World");
+ assertThat(helloWorld, is("Hello World!"));
+ }
+
+ @Test
+ public void testFieldsAndMethodsMethodSuperCall() throws Exception {
+ MemoryDatabase loggingDatabase = new ByteBuddy()
+ .subclass(MemoryDatabase.class)
+ .method(named("load")).intercept(MethodDelegation.to(LoggerInterceptor.class))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance();
+ assertThat(loggingDatabase.load("qux"), is(Arrays.asList("qux: foo", "qux: bar")));
+ }
+
+ @Test
+ public void testFieldsAndMethodsMethodSuperCallExplicit() throws Exception {
+ assertThat(new LoggingMemoryDatabase().load("qux"), is(Arrays.asList("qux: foo", "qux: bar")));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testFieldsAndMethodsMethodDefaultCall() throws Exception {
+ // This test differs from the tutorial by only conditionally expressing the Java 8 types.
+ Object instance = new ByteBuddy(ClassFileVersion.JAVA_V8)
+ .subclass(Object.class)
+ .implement(Class.forName(DEFAULT_METHOD_INTERFACE))
+ .implement(Class.forName(CONFLICTING_DEFAULT_METHOD_INTERFACE))
+ .method(named("foo")).intercept(DefaultMethodCall.prioritize(Class.forName(DEFAULT_METHOD_INTERFACE)))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance();
+ Method method = instance.getClass().getMethod("foo");
+ assertThat(method.invoke(instance), is((Object) "foo"));
+ }
+
+ @Test
+ public void testFieldsAndMethodsExplicitMethodCall() throws Exception {
+ Object object = new ByteBuddy()
+ .subclass(Object.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
+ .defineConstructor(Visibility.PUBLIC).withParameters(int.class)
+ .intercept(MethodCall.invoke(Object.class.getDeclaredConstructor()))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded()
+ .getDeclaredConstructor(int.class)
+ .newInstance(42);
+ assertThat(object.getClass(), CoreMatchers.not(CoreMatchers.<Class<?>>is(Object.class)));
+ }
+
+ @Test
+ public void testFieldsAndMethodsSuper() throws Exception {
+ MemoryDatabase loggingDatabase = new ByteBuddy()
+ .subclass(MemoryDatabase.class)
+ .method(named("load")).intercept(MethodDelegation.to(ChangingLoggerInterceptor.class))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance();
+ assertThat(loggingDatabase.load("qux"), is(Arrays.asList("qux (logged access): foo", "qux (logged access): bar")));
+ }
+
+ @Test
+ public void testFieldsAndMethodsRuntimeType() throws Exception {
+ Loop trivialGetterBean = new ByteBuddy()
+ .subclass(Loop.class)
+ .method(isDeclaredBy(Loop.class)).intercept(MethodDelegation.to(Interceptor.class))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance();
+ assertThat(trivialGetterBean.loop(42), is(42));
+ assertThat(trivialGetterBean.loop("foo"), is("foo"));
+ }
+
+ @Test
+ public void testFieldsAndMethodsForwarding() throws Exception {
+ MemoryDatabase memoryDatabase = new MemoryDatabase();
+ MemoryDatabase loggingDatabase = new ByteBuddy()
+ .subclass(MemoryDatabase.class)
+ .method(named("load")).intercept(MethodDelegation
+ .withDefaultConfiguration()
+ .withBinders(Pipe.Binder.install(Forwarder.class))
+ .to(new ForwardingLoggerInterceptor(memoryDatabase)))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance();
+ assertThat(loggingDatabase.load("qux"), is(Arrays.asList("qux: foo", "qux: bar")));
+ }
+
+ @Test
+ public void testFieldsAndMethodsFieldAccess() throws Exception {
+ ByteBuddy byteBuddy = new ByteBuddy();
+ Class<? extends UserType> dynamicUserType = byteBuddy
+ .subclass(UserType.class)
+ .defineField("interceptor", Interceptor2.class, Visibility.PRIVATE)
+ .method(not(isDeclaredBy(Object.class))).intercept(MethodDelegation.toField("interceptor"))
+ .implement(InterceptionAccessor.class).intercept(FieldAccessor.ofBeanProperty())
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ InstanceCreator factory = byteBuddy
+ .subclass(InstanceCreator.class)
+ .method(not(isDeclaredBy(Object.class))).intercept(MethodDelegation.toConstructor(dynamicUserType))
+ .make()
+ .load(dynamicUserType.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance();
+ UserType userType = (UserType) factory.makeInstance();
+ ((InterceptionAccessor) userType).setInterceptor(new HelloWorldInterceptor());
+ assertThat(userType.doSomething(), is("Hello World!"));
+ }
+
+ @Test
+ public void testAttributesAndAnnotationForClass() throws Exception {
+ assertThat(new ByteBuddy()
+ .subclass(Object.class)
+ .annotateType(new RuntimeDefinitionImpl())
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded()
+ .isAnnotationPresent(RuntimeDefinition.class), is(true));
+ }
+
+ @Test
+ public void testAttributesAndAnnotationForMethodAndField() throws Exception {
+ Class<?> dynamicType = new ByteBuddy()
+ .subclass(Object.class)
+ .method(named("toString"))
+ .intercept(SuperMethodCall.INSTANCE)
+ .annotateMethod(new RuntimeDefinitionImpl())
+ .defineField("foo", Object.class)
+ .annotateField(new RuntimeDefinitionImpl())
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(dynamicType.getDeclaredMethod("toString").isAnnotationPresent(RuntimeDefinition.class), is(true));
+ assertThat(dynamicType.getDeclaredField("foo").isAnnotationPresent(RuntimeDefinition.class), is(true));
+ }
+
+ @Test
+ public void testCustomImplementationMethodImplementation() throws Exception {
+ assertThat(new ByteBuddy()
+ .subclass(SumExample.class)
+ .method(named("calculate")).intercept(SumImplementation.INSTANCE)
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance()
+ .calculate(), is(60));
+ }
+
+ @Test
+ public void testCustomImplementationAssigner() throws Exception {
+ assertThat(new ByteBuddy()
+ .subclass(Object.class)
+ .method(named("toString"))
+ .intercept(FixedValue.value(42).withAssigner(new PrimitiveTypeAwareAssigner(ToStringAssigner.INSTANCE), Assigner.Typing.STATIC))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance()
+ .toString(), is("42"));
+ }
+
+ @Test
+ public void testCustomImplementationDelegationAnnotation() throws Exception {
+ assertThat(new ByteBuddy()
+ .subclass(Object.class)
+ .method(named("toString"))
+ .intercept(MethodDelegation.withDefaultConfiguration()
+ .withBinders(StringValueBinder.INSTANCE)
+ .to(ToStringInterceptor.class))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance()
+ .toString(), is("Hello!"));
+ }
+
+ public enum IntegerSum implements StackManipulation {
+
+ INSTANCE;
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
+ methodVisitor.visitInsn(Opcodes.IADD);
+ return new Size(-1, 0);
+ }
+ }
+
+ public enum SumMethod implements ByteCodeAppender {
+
+ INSTANCE;
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor,
+ Implementation.Context implementationContext,
+ MethodDescription instrumentedMethod) {
+ if (!instrumentedMethod.getReturnType().asErasure().represents(int.class)) {
+ throw new IllegalArgumentException(instrumentedMethod + " must return int");
+ }
+ StackManipulation.Size operandStackSize = new StackManipulation.Compound(
+ IntegerConstant.forValue(10),
+ IntegerConstant.forValue(50),
+ IntegerSum.INSTANCE,
+ MethodReturn.INTEGER
+ ).apply(methodVisitor, implementationContext);
+ return new Size(operandStackSize.getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+ }
+
+ public enum SumImplementation implements Implementation {
+ INSTANCE;
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return SumMethod.INSTANCE;
+ }
+ }
+
+ public enum ToStringAssigner implements Assigner {
+ INSTANCE;
+
+ @Override
+ public StackManipulation assign(TypeDescription.Generic source, TypeDescription.Generic target, Typing typing) {
+ if (!source.isPrimitive() && target.represents(String.class)) {
+ MethodDescription toStringMethod = TypeDescription.OBJECT.getDeclaredMethods()
+ .filter(named("toString"))
+ .getOnly();
+ return MethodInvocation.invoke(toStringMethod).virtual(source.asErasure());
+ } else {
+ return StackManipulation.Illegal.INSTANCE;
+ }
+ }
+ }
+
+ public enum StringValueBinder implements TargetMethodAnnotationDrivenBinder.ParameterBinder<StringValue> {
+
+ INSTANCE;
+
+ @Override
+ public Class<StringValue> getHandledType() {
+ return StringValue.class;
+ }
+
+ @Override
+ public MethodDelegationBinder.ParameterBinding<?> bind(AnnotationDescription.Loadable<StringValue> annotation,
+ MethodDescription source,
+ ParameterDescription target,
+ Implementation.Target implementationTarget,
+ Assigner assigner,
+ Assigner.Typing typing) {
+ if (!target.getType().asErasure().represents(String.class)) {
+ throw new IllegalStateException(target + " makes wrong use of StringValue");
+ }
+ StackManipulation constant = new TextConstant(annotation.loadSilent().value());
+ return new MethodDelegationBinder.ParameterBinding.Anonymous(constant);
+ }
+ }
+
+ public interface Forwarder<T, S> {
+
+ T to(S target);
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ private @interface Unsafe {
+ /* empty */
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ private @interface Secured {
+ /* empty */
+ }
+
+ @SuppressWarnings("unused")
+ public interface Interceptor2 {
+
+ String doSomethingElse();
+ }
+
+ @SuppressWarnings("unused")
+ public interface InterceptionAccessor {
+
+ Interceptor2 getInterceptor();
+
+ void setInterceptor(Interceptor2 interceptor);
+ }
+
+ @SuppressWarnings("unused")
+ public interface InstanceCreator {
+
+ Object makeInstance();
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface RuntimeDefinition {
+
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface StringValue {
+
+ String value();
+ }
+
+ private @interface Rebase {
+
+ }
+
+ public interface Function {
+
+ Object apply(Object arg);
+ }
+
+ public static class GreetingInterceptor {
+
+ public Object greet(Object argument) {
+ return "Hello from " + argument;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static class FooReloading {
+
+ public String m() {
+ return "foo";
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static class BarReloading {
+
+ public String m() {
+ return "bar";
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class Account {
+
+ private int amount = 100;
+
+ @Unsafe
+ public String transfer(int amount, String recipient) {
+ this.amount -= amount;
+ return "transferred $" + amount + " to " + recipient;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class Bank {
+
+ public static String obfuscate(@Argument(1) String recipient,
+ @Argument(0) Integer amount,
+ @Super Account zuper) {
+ //System.out.println("Transfer " + amount + " to " + recipient);
+ return zuper.transfer(amount, recipient.substring(0, 3) + "XXX") + " (obfuscated)";
+ }
+ }
+
+ private static class GettingStartedNamingStrategy extends NamingStrategy.AbstractBase {
+
+ @Override
+ protected String name(TypeDescription superClass) {
+ return "i.love.ByteBuddy." + superClass.getSimpleName();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class Foo {
+
+ public String foo() {
+ return null;
+ }
+
+ public String foo(Object o) {
+ return null;
+ }
+
+ public String bar() {
+ return null;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class Source {
+
+ public String hello(String name) {
+ return null;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class Target {
+
+ public static String hello(String name) {
+ return "Hello " + name + "!";
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class Target2 {
+
+ public static String intercept(String name) {
+ return "Hello " + name + "!";
+ }
+
+ public static String intercept(int i) {
+ return Integer.toString(i);
+ }
+
+ public static String intercept(Object o) {
+ return o.toString();
+ }
+ }
+
+ public static class MemoryDatabase {
+
+ public List<String> load(String info) {
+ return Arrays.asList(info + ": foo", info + ": bar");
+ }
+ }
+
+ public static class LoggerInterceptor {
+
+ public static List<String> log(@SuperCall Callable<List<String>> zuper) throws Exception {
+ println("Calling database");
+ try {
+ return zuper.call();
+ } finally {
+ println("Returned from database");
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ChangingLoggerInterceptor {
+
+ public static List<String> log(String info, @Super MemoryDatabase zuper) {
+ println("Calling database");
+ try {
+ return zuper.load(info + " (logged access)");
+ } finally {
+ println("Returned from database");
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class Loop {
+
+ public String loop(String value) {
+ return value;
+ }
+
+ public int loop(int value) {
+ return value;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class Interceptor {
+
+ @RuntimeType
+ public static Object intercept(@RuntimeType Object value) {
+ println("Invoked method with: " + value);
+ return value;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class UserType {
+
+ public String doSomething() {
+ return null;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class HelloWorldInterceptor implements Interceptor2 {
+
+ @Override
+ public String doSomethingElse() {
+ return "Hello World!";
+ }
+ }
+
+ private static class RuntimeDefinitionImpl implements RuntimeDefinition {
+
+ @Override
+ public Class<? extends Annotation> annotationType() {
+ return RuntimeDefinition.class;
+ }
+ }
+
+ public abstract static class SumExample {
+
+ public abstract int calculate();
+ }
+
+ public static class ToStringInterceptor {
+
+ public static String makeString(@StringValue("Hello!") String value) {
+ return value;
+ }
+ }
+
+ private static class UnloadedBar {
+
+ }
+
+ public class ForwardingLoggerInterceptor {
+
+ private final MemoryDatabase memoryDatabase;
+
+ public ForwardingLoggerInterceptor(MemoryDatabase memoryDatabase) {
+ this.memoryDatabase = memoryDatabase;
+ }
+
+ public List<String> log(@Pipe Forwarder<List<String>, MemoryDatabase> pipe) {
+ println("Calling database");
+ try {
+ return pipe.to(memoryDatabase);
+ } finally {
+ println("Returned from database");
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ class LoggingMemoryDatabase extends MemoryDatabase {
+
+ @Override
+ public List<String> load(String info) {
+ try {
+ return LoggerInterceptor.log(new LoadMethodSuperCall(info));
+ } catch (Exception exception) {
+ throw new RuntimeException(exception);
+ }
+ }
+
+ private class LoadMethodSuperCall implements Callable {
+
+ private final String info;
+
+ private LoadMethodSuperCall(String info) {
+ this.info = info;
+ }
+
+ @Override
+ public Object call() throws Exception {
+ return LoggingMemoryDatabase.super.load(info);
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/ClassFileVersionKnownVersionsTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/ClassFileVersionKnownVersionsTest.java
new file mode 100644
index 0000000..e1cbecb
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/ClassFileVersionKnownVersionsTest.java
@@ -0,0 +1,120 @@
+package net.bytebuddy;
+
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class ClassFileVersionKnownVersionsTest {
+
+ private final int javaVersion;
+
+ private final int minorMajorVersion;
+
+ private final int majorVersion;
+
+ private final int minorVersion;
+
+ private final boolean atLeastJava5;
+
+ private final boolean atLeastJava7;
+
+ private final boolean atLeastJava8;
+
+ public ClassFileVersionKnownVersionsTest(int javaVersion,
+ int minorMajorVersion,
+ int majorVersion,
+ int minorVersion,
+ boolean atLeastJava5,
+ boolean atLeastJava7,
+ boolean atLeastJava8) {
+ this.javaVersion = javaVersion;
+ this.minorMajorVersion = minorMajorVersion;
+ this.majorVersion = majorVersion;
+ this.minorVersion = minorVersion;
+ this.atLeastJava5 = atLeastJava5;
+ this.atLeastJava7 = atLeastJava7;
+ this.atLeastJava8 = atLeastJava8;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {1, Opcodes.V1_1, 45, 3, false, false, false},
+ {2, Opcodes.V1_2, 46, 0, false, false, false},
+ {3, Opcodes.V1_3, 47, 0, false, false, false},
+ {4, Opcodes.V1_4, 48, 0, false, false, false},
+ {5, Opcodes.V1_5, 49, 0, true, false, false},
+ {6, Opcodes.V1_6, 50, 0, true, false, false},
+ {7, Opcodes.V1_7, 51, 0, true, true, false},
+ {8, Opcodes.V1_8, 52, 0, true, true, true},
+ {9, 53, 53, 0, true, true, true}
+ });
+ }
+
+ @Test
+ public void testVersion() throws Exception {
+ assertThat(ClassFileVersion.ofJavaVersion(javaVersion).getMinorMajorVersion(), is(minorMajorVersion));
+ }
+
+ @Test
+ public void testMinorVersion() throws Exception {
+ assertThat(ClassFileVersion.ofJavaVersion(javaVersion).getMinorVersion(), is(minorVersion));
+ }
+
+ @Test
+ public void testMajorVersion() throws Exception {
+ assertThat(ClassFileVersion.ofJavaVersion(javaVersion).getMajorVersion(), is(majorVersion));
+ }
+
+ @Test
+ public void testAtLeastJava5() throws Exception {
+ assertThat(ClassFileVersion.ofJavaVersion(javaVersion).isAtLeast(ClassFileVersion.JAVA_V5), is(atLeastJava5));
+ }
+
+ @Test
+ public void testAtLeastJava7() throws Exception {
+ assertThat(ClassFileVersion.ofJavaVersion(javaVersion).isAtLeast(ClassFileVersion.JAVA_V7), is(atLeastJava7));
+ }
+
+ @Test
+ public void testAtLeastJava8() throws Exception {
+ assertThat(ClassFileVersion.ofJavaVersion(javaVersion).isAtLeast(ClassFileVersion.JAVA_V8), is(atLeastJava8));
+ }
+
+ @Test
+ public void testJavaVersion() throws Exception {
+ assertThat(ClassFileVersion.ofJavaVersion(javaVersion).getJavaVersion(), is(javaVersion));
+ }
+
+ @Test
+ public void testLessThanJava8() throws Exception {
+ assertThat(ClassFileVersion.ofJavaVersion(javaVersion).isLessThan(ClassFileVersion.JAVA_V8), is(!atLeastJava8));
+ }
+
+ @Test
+ public void testSimpleClassCreation() throws Exception {
+ ClassFileVersion classFileVersion = ClassFileVersion.ofJavaVersion(javaVersion);
+ if (ClassFileVersion.ofThisVm().compareTo(classFileVersion) >= 0) {
+ Class<?> type = new ByteBuddy(classFileVersion)
+ .subclass(Foo.class)
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredConstructor().newInstance(), notNullValue(Object.class));
+ }
+ }
+
+ public static class Foo {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/ClassFileVersionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/ClassFileVersionTest.java
new file mode 100644
index 0000000..735e64f
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/ClassFileVersionTest.java
@@ -0,0 +1,63 @@
+package net.bytebuddy;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+import org.objectweb.asm.Opcodes;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Iterator;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class ClassFileVersionTest {
+
+ @Test
+ public void testExplicitConstructionOfUnknownVersion() throws Exception {
+ assertThat(ClassFileVersion.ofMinorMajor(Opcodes.V1_8 + 1).getMinorMajorVersion(), is(Opcodes.V1_8 + 1));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testIllegalVersion() throws Exception {
+ ClassFileVersion.ofMinorMajor(ClassFileVersion.BASE_VERSION);
+ }
+
+ @Test
+ public void testComparison() throws Exception {
+ assertThat(new ClassFileVersion(Opcodes.V1_1).compareTo(new ClassFileVersion(Opcodes.V1_1)), is(0));
+ assertThat(new ClassFileVersion(Opcodes.V1_1).compareTo(new ClassFileVersion(Opcodes.V1_2)), is(-1));
+ assertThat(new ClassFileVersion(Opcodes.V1_2).compareTo(new ClassFileVersion(Opcodes.V1_1)), is(1));
+ assertThat(new ClassFileVersion(Opcodes.V1_2).compareTo(new ClassFileVersion(Opcodes.V1_2)), is(0));
+ assertThat(new ClassFileVersion(Opcodes.V1_3).compareTo(new ClassFileVersion(Opcodes.V1_2)), is(1));
+ assertThat(new ClassFileVersion(Opcodes.V1_2).compareTo(new ClassFileVersion(Opcodes.V1_3)), is(-1));
+ }
+
+ @Test
+ public void testVersionPropertyAction() throws Exception {
+ assertThat(ClassFileVersion.VersionLocator.ForLegacyVm.INSTANCE.run(), is(System.getProperty("java.version")));
+ }
+
+ @Test
+ public void testVersionOfClass() throws Exception {
+ assertThat(ClassFileVersion.of(Foo.class).compareTo(ClassFileVersion.ofThisVm()) < 1, is(true));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ClassFileVersion.class).apply();
+ ObjectPropertyAssertion.of(ClassFileVersion.VersionLocator.CreationAction.class).apply();
+ ObjectPropertyAssertion.of(ClassFileVersion.VersionLocator.ForLegacyVm.class).apply();
+ final Iterator<Method> methods = Arrays.asList(Object.class.getDeclaredMethods()).iterator();
+ ObjectPropertyAssertion.of(ClassFileVersion.VersionLocator.ForJava9CapableVm.class).create(new ObjectPropertyAssertion.Creator<Method>() {
+ @Override
+ public Method create() {
+ return methods.next();
+ }
+ }).apply();
+ }
+
+ private static class Foo {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/NamingStrategyTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/NamingStrategyTest.java
new file mode 100644
index 0000000..a56260c
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/NamingStrategyTest.java
@@ -0,0 +1,140 @@
+package net.bytebuddy;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.StringStartsWith.startsWith;
+import static org.mockito.Mockito.*;
+
+public class NamingStrategyTest {
+
+ private static final String FOO = "foo", BAR = "bar", JAVA_QUX = "java.qux";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private NamingStrategy.SuffixingRandom.BaseNameResolver baseNameResolver;
+
+ @Mock
+ private TypeDescription.Generic typeDescription;
+
+ @Mock
+ private TypeDescription rawTypeDescription;
+
+ @Before
+ public void setUp() throws Exception {
+ when(typeDescription.asErasure()).thenReturn(rawTypeDescription);
+ }
+
+ @Test
+ public void testSuffixingRandomSubclassNonConflictingPackage() throws Exception {
+ when(rawTypeDescription.getName()).thenReturn(FOO);
+ NamingStrategy namingStrategy = new NamingStrategy.SuffixingRandom(BAR);
+ assertThat(namingStrategy.subclass(typeDescription), startsWith(FOO + "$" + BAR + "$"));
+ verify(typeDescription, atLeast(1)).asErasure();
+ verifyNoMoreInteractions(typeDescription);
+ verify(rawTypeDescription).getName();
+ verifyNoMoreInteractions(rawTypeDescription);
+ }
+
+ @Test
+ public void testSuffixingRandomSubclassConflictingPackage() throws Exception {
+ when(baseNameResolver.resolve(rawTypeDescription)).thenReturn(JAVA_QUX);
+ NamingStrategy namingStrategy = new NamingStrategy.SuffixingRandom(FOO, baseNameResolver, BAR);
+ assertThat(namingStrategy.subclass(typeDescription), startsWith(BAR + "." + JAVA_QUX + "$" + FOO + "$"));
+ verify(typeDescription).asErasure();
+ verifyNoMoreInteractions(typeDescription);
+ verifyZeroInteractions(rawTypeDescription);
+ verify(baseNameResolver).resolve(rawTypeDescription);
+ verifyNoMoreInteractions(baseNameResolver);
+ }
+
+ @Test
+ public void testSuffixingRandomSubclassConflictingPackageDisabled() throws Exception {
+ when(baseNameResolver.resolve(rawTypeDescription)).thenReturn(JAVA_QUX);
+ NamingStrategy namingStrategy = new NamingStrategy.SuffixingRandom(FOO, baseNameResolver, NamingStrategy.SuffixingRandom.NO_PREFIX);
+ assertThat(namingStrategy.subclass(typeDescription), startsWith(JAVA_QUX + "$" + FOO + "$"));
+ verify(typeDescription).asErasure();
+ verifyNoMoreInteractions(typeDescription);
+ verifyZeroInteractions(rawTypeDescription);
+ verify(baseNameResolver).resolve(rawTypeDescription);
+ verifyNoMoreInteractions(baseNameResolver);
+ }
+
+ @Test
+ public void testSuffixingRandomRebase() throws Exception {
+ when(rawTypeDescription.getName()).thenReturn(FOO);
+ NamingStrategy namingStrategy = new NamingStrategy.SuffixingRandom(BAR);
+ assertThat(namingStrategy.rebase(rawTypeDescription), is(FOO));
+ verify(rawTypeDescription).getName();
+ verifyNoMoreInteractions(rawTypeDescription);
+ }
+
+ @Test
+ public void testSuffixingRandomRedefine() throws Exception {
+ when(rawTypeDescription.getName()).thenReturn(FOO);
+ NamingStrategy namingStrategy = new NamingStrategy.SuffixingRandom(BAR);
+ assertThat(namingStrategy.redefine(rawTypeDescription), is(FOO));
+ verify(rawTypeDescription).getName();
+ verifyNoMoreInteractions(rawTypeDescription);
+ }
+
+ @Test
+ public void testBaseNameResolvers() throws Exception {
+ assertThat(new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue(FOO).resolve(rawTypeDescription), is(FOO));
+ when(rawTypeDescription.getName()).thenReturn(FOO);
+ assertThat(new NamingStrategy.SuffixingRandom.BaseNameResolver.ForGivenType(rawTypeDescription).resolve(rawTypeDescription), is(FOO));
+ assertThat(NamingStrategy.SuffixingRandom.BaseNameResolver.ForUnnamedType.INSTANCE.resolve(rawTypeDescription), is(FOO));
+ }
+
+ @Test
+ public void testSuffixingRandomObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(NamingStrategy.SuffixingRandom.class).apply();
+ ObjectPropertyAssertion.of(NamingStrategy.SuffixingRandom.BaseNameResolver.ForGivenType.class).apply();
+ ObjectPropertyAssertion.of(NamingStrategy.SuffixingRandom.BaseNameResolver.ForUnnamedType.class).apply();
+ ObjectPropertyAssertion.of(NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue.class).apply();
+ }
+
+ @Test
+ public void testPrefixingRandom() throws Exception {
+ when(rawTypeDescription.getName()).thenReturn(BAR);
+ NamingStrategy namingStrategy = new NamingStrategy.PrefixingRandom(FOO);
+ assertThat(namingStrategy.subclass(typeDescription), startsWith(FOO + "." + BAR));
+ verify(typeDescription).asErasure();
+ verifyNoMoreInteractions(typeDescription);
+ verify(rawTypeDescription).getName();
+ verifyNoMoreInteractions(rawTypeDescription);
+ }
+
+ @Test
+ public void testPrefixingRandomRebase() throws Exception {
+ when(rawTypeDescription.getName()).thenReturn(FOO);
+ NamingStrategy namingStrategy = new NamingStrategy.PrefixingRandom(BAR);
+ assertThat(namingStrategy.rebase(rawTypeDescription), is(FOO));
+ verify(rawTypeDescription).getName();
+ verifyNoMoreInteractions(rawTypeDescription);
+ }
+
+ @Test
+ public void testPrefixingRandomRedefine() throws Exception {
+ when(rawTypeDescription.getName()).thenReturn(FOO);
+ NamingStrategy namingStrategy = new NamingStrategy.PrefixingRandom(BAR);
+ assertThat(namingStrategy.redefine(rawTypeDescription), is(FOO));
+ verify(rawTypeDescription).getName();
+ verifyNoMoreInteractions(rawTypeDescription);
+ }
+
+ @Test
+ public void testPrefixingRandomEqualsHashCode() throws Exception {
+ ObjectPropertyAssertion.of(NamingStrategy.PrefixingRandom.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/TypeCacheTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/TypeCacheTest.java
new file mode 100644
index 0000000..10f6691
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/TypeCacheTest.java
@@ -0,0 +1,164 @@
+package net.bytebuddy;
+
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.lang.ref.Reference;
+import java.lang.ref.SoftReference;
+import java.lang.ref.WeakReference;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.concurrent.Callable;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.CoreMatchers.is;
+import static org.mockito.Mockito.*;
+
+public class TypeCacheTest {
+
+ @Test
+ public void testCache() throws Exception {
+ TypeCache<Object> typeCache = new TypeCache<Object>(TypeCache.Sort.WEAK);
+ Object key = new Object();
+ assertThat(typeCache.find(ClassLoader.getSystemClassLoader(), key), nullValue(Class.class));
+ assertThat(typeCache.insert(ClassLoader.getSystemClassLoader(), key, Void.class), is((Object) Void.class));
+ assertThat(typeCache.find(ClassLoader.getSystemClassLoader(), key), is((Object) Void.class));
+ assertThat(typeCache.find(mock(ClassLoader.class), key), nullValue(Class.class));
+ typeCache.clear();
+ assertThat(typeCache.find(ClassLoader.getSystemClassLoader(), key), nullValue(Class.class));
+ }
+
+ @Test
+ public void testCacheInline() throws Exception {
+ TypeCache<Object> typeCache = new TypeCache.WithInlineExpunction<Object>(TypeCache.Sort.WEAK);
+ Object key = new Object();
+ assertThat(typeCache.find(ClassLoader.getSystemClassLoader(), key), nullValue(Class.class));
+ assertThat(typeCache.insert(ClassLoader.getSystemClassLoader(), key, Void.class), is((Object) Void.class));
+ assertThat(typeCache.find(ClassLoader.getSystemClassLoader(), key), is((Object) Void.class));
+ assertThat(typeCache.find(mock(ClassLoader.class), key), nullValue(Class.class));
+ typeCache.clear();
+ assertThat(typeCache.find(ClassLoader.getSystemClassLoader(), key), nullValue(Class.class));
+ }
+
+ @Test
+ public void testCacheNullLoader() throws Exception {
+ TypeCache<Object> typeCache = new TypeCache<Object>(TypeCache.Sort.WEAK);
+ Object key = new Object();
+ assertThat(typeCache.find(null, key), nullValue(Class.class));
+ assertThat(typeCache.insert(null, key, Void.class), is((Object) Void.class));
+ assertThat(typeCache.find(null, key), is((Object) Void.class));
+ assertThat(typeCache.find(mock(ClassLoader.class), key), nullValue(Class.class));
+ typeCache.clear();
+ assertThat(typeCache.find(null, key), nullValue(Class.class));
+ }
+
+ @Test
+ public void testCacheCollection() throws Exception {
+ TypeCache<Object> typeCache = new TypeCache<Object>(TypeCache.Sort.WEAK);
+ Object key = new Object();
+ ClassLoader classLoader = mock(ClassLoader.class);
+ assertThat(typeCache.find(classLoader, key), nullValue(Class.class));
+ assertThat(typeCache.insert(classLoader, key, Void.class), is((Object) Void.class));
+ assertThat(typeCache.find(classLoader, key), is((Object) Void.class));
+ classLoader = null; // Make eligible for GC
+ for (int index = 0; index < 2; index++) {
+ System.gc();
+ Thread.sleep(50L);
+ }
+ typeCache.expungeStaleEntries();
+ assertThat(typeCache.cache.isEmpty(), is(true));
+ }
+
+ @Test
+ public void testCacheTypeCollection() throws Exception {
+ TypeCache<Object> typeCache = new TypeCache<Object>(TypeCache.Sort.WEAK);
+ Object key = new Object();
+ ClassLoader classLoader = mock(ClassLoader.class);
+ assertThat(typeCache.find(classLoader, key), nullValue(Class.class));
+ Class<?> type = new ByteBuddy().subclass(Object.class)
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(typeCache.insert(classLoader, key, type), is((Object) type));
+ assertThat(typeCache.find(classLoader, key), is((Object) type));
+ type = null; // Make eligable for GC
+ for (int index = 0; index < 2; index++) {
+ System.gc();
+ Thread.sleep(50L);
+ }
+ assertThat(typeCache.find(classLoader, key), nullValue(Class.class));
+ assertThat(typeCache.insert(classLoader, key, Void.class), is((Object) Void.class));
+ assertThat(typeCache.find(classLoader, key), is((Object) Void.class));
+ }
+
+ @Test
+ public void testWeakReference() throws Exception {
+ Reference<Class<?>> reference = TypeCache.Sort.WEAK.wrap(Void.class);
+ assertThat(reference, instanceOf(WeakReference.class));
+ assertThat(reference.get(), is((Object) Void.class));
+ }
+
+ @Test
+ public void testSoftReference() throws Exception {
+ Reference<Class<?>> reference = TypeCache.Sort.SOFT.wrap(Void.class);
+ assertThat(reference, instanceOf(SoftReference.class));
+ assertThat(reference.get(), is((Object) Void.class));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testFindOrInsert() throws Exception {
+ TypeCache<Object> typeCache = new TypeCache<Object>(TypeCache.Sort.WEAK);
+ Object key = new Object();
+ assertThat(typeCache.find(ClassLoader.getSystemClassLoader(), key), nullValue(Class.class));
+ Callable<Class<?>> callable = mock(Callable.class);
+ when(callable.call()).thenReturn((Class) Void.class);
+ assertThat(typeCache.findOrInsert(ClassLoader.getSystemClassLoader(), key, callable, new Object()), is((Object) Void.class));
+ verify(callable).call();
+ assertThat(typeCache.findOrInsert(ClassLoader.getSystemClassLoader(), key, callable), is((Object) Void.class));
+ assertThat(typeCache.findOrInsert(ClassLoader.getSystemClassLoader(), key, callable, new Object()), is((Object) Void.class));
+ verifyNoMoreInteractions(callable);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ @SuppressWarnings("unchecked")
+ public void testCreationException() throws Exception {
+ TypeCache<Object> typeCache = new TypeCache<Object>(TypeCache.Sort.WEAK);
+ Callable<Class<?>> callable = mock(Callable.class);
+ when(callable.call()).thenThrow(RuntimeException.class);
+ typeCache.findOrInsert(ClassLoader.getSystemClassLoader(), new Object(), callable, new Object());
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeCache.Sort.class).apply();
+ final Iterator<Class<?>> iterator = Arrays.<Class<?>>asList(Object.class,
+ String.class,
+ Void.class,
+ Integer.class,
+ Long.class,
+ Byte.class,
+ Boolean.class,
+ Character.class,
+ Short.class,
+ Float.class,
+ Long.class).iterator();
+ ObjectPropertyAssertion.of(TypeCache.SimpleKey.class).create(new ObjectPropertyAssertion.Creator<Collection<Class<?>>>() {
+ @Override
+ public Collection<Class<?>> create() {
+ return Collections.<Class<?>>singleton(iterator.next());
+ }
+ }).create(new ObjectPropertyAssertion.Creator<Class<?>>() {
+ @Override
+ public Class<?> create() {
+ return iterator.next();
+ }
+ }).apply();
+
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderCircularityLockTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderCircularityLockTest.java
new file mode 100644
index 0000000..797c1c0
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderCircularityLockTest.java
@@ -0,0 +1,54 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.util.concurrent.TimeUnit;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class AgentBuilderCircularityLockTest {
+
+ @Test
+ public void testCircularityLockDefault() throws Exception {
+ AgentBuilder.CircularityLock.Default circularityLock = new AgentBuilder.CircularityLock.Default();
+ assertThat(circularityLock.acquire(), is(true));
+ assertThat(circularityLock.acquire(), is(false));
+ circularityLock.release();
+ assertThat(circularityLock.acquire(), is(true));
+ assertThat(circularityLock.acquire(), is(false));
+ circularityLock.release();
+ assertThat(circularityLock.get(), nullValue(Boolean.class));
+ }
+
+ @Test
+ public void testCircularityLockInactive() throws Exception {
+ AgentBuilder.CircularityLock circularityLock = AgentBuilder.CircularityLock.Inactive.INSTANCE;
+ assertThat(circularityLock.acquire(), is(true));
+ assertThat(circularityLock.acquire(), is(true));
+ circularityLock.release();
+ }
+
+ @Test
+ public void testGlobalLock() throws Exception {
+ AgentBuilder.CircularityLock circularityLock = new AgentBuilder.CircularityLock.Global();
+ assertThat(circularityLock.acquire(), is(true));
+ assertThat(circularityLock.acquire(), is(true));
+ circularityLock.release();
+ }
+
+ @Test
+ public void testGlobalLockWithTimeout() throws Exception {
+ AgentBuilder.CircularityLock circularityLock = new AgentBuilder.CircularityLock.Global(10, TimeUnit.MILLISECONDS);
+ assertThat(circularityLock.acquire(), is(true));
+ assertThat(circularityLock.acquire(), is(true));
+ circularityLock.release();
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AgentBuilder.CircularityLock.Inactive.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderDefaultApplicationRedefineTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderDefaultApplicationRedefineTest.java
new file mode 100644
index 0000000..6678152
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderDefaultApplicationRedefineTest.java
@@ -0,0 +1,184 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.agent.ByteBuddyAgent;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ByteArrayClassLoader;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.dynamic.scaffold.TypeValidation;
+import net.bytebuddy.implementation.FixedValue;
+import net.bytebuddy.matcher.ElementMatchers;
+import net.bytebuddy.test.packaging.SimpleOptionalType;
+import net.bytebuddy.test.packaging.SimpleType;
+import net.bytebuddy.test.utility.AgentAttachmentRule;
+import net.bytebuddy.test.utility.ClassFileExtraction;
+import net.bytebuddy.test.utility.IntegrationRule;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import net.bytebuddy.utility.JavaModule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.Instrumentation;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.none;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class AgentBuilderDefaultApplicationRedefineTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ List<Object[]> list = new ArrayList<Object[]>();
+ for (AgentBuilder.DescriptionStrategy descriptionStrategy : AgentBuilder.DescriptionStrategy.Default.values()) {
+ list.add(new Object[]{descriptionStrategy});
+ }
+ list.add(new Object[]{new AgentBuilder.DescriptionStrategy.SuperTypeLoading(AgentBuilder.DescriptionStrategy.Default.POOL_ONLY)});
+ return list;
+ }
+
+ private final AgentBuilder.DescriptionStrategy descriptionStrategy;
+
+ public AgentBuilderDefaultApplicationRedefineTest(AgentBuilder.DescriptionStrategy descriptionStrategy) {
+ this.descriptionStrategy = descriptionStrategy;
+ }
+
+ @Rule
+ public MethodRule agentAttachmentRule = new AgentAttachmentRule();
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ @Rule
+ public MethodRule integrationRule = new IntegrationRule();
+
+ private ClassLoader simpleTypeLoader, optionalTypeLoader;
+
+ @Before
+ public void setUp() throws Exception {
+ simpleTypeLoader = new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER,
+ ClassFileExtraction.of(SimpleType.class),
+ ByteArrayClassLoader.PersistenceHandler.MANIFEST);
+ optionalTypeLoader = new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER,
+ ClassFileExtraction.of(SimpleOptionalType.class),
+ ByteArrayClassLoader.PersistenceHandler.MANIFEST);
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce(redefinesClasses = true)
+ @IntegrationRule.Enforce
+ public void testRedefinition() throws Exception {
+ // A redefinition reflects on loaded types which are eagerly validated types (Java 7- for redefinition).
+ // This causes type equality for outer/inner classes to fail which is why an external class is used.
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ assertThat(simpleTypeLoader.loadClass(SimpleType.class.getName()).getName(), is(SimpleType.class.getName())); // ensure that class is loaded
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
+ .ignore(none())
+ .disableClassFormatChanges()
+ .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
+ .with(descriptionStrategy)
+ .type(ElementMatchers.is(SimpleType.class), ElementMatchers.is(simpleTypeLoader)).transform(new FooTransformer())
+ .installOnByteBuddyAgent();
+ try {
+ Class<?> type = simpleTypeLoader.loadClass(SimpleType.class.getName());
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) BAR));
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ }
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce(redefinesClasses = true)
+ @IntegrationRule.Enforce
+ public void testRedefinitionOptionalType() throws Exception {
+ // A redefinition reflects on loaded types which are eagerly validated types (Java 7- for redefinition).
+ // This causes type equality for outer/inner classes to fail which is why an external class is used.
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ assertThat(optionalTypeLoader.loadClass(SimpleOptionalType.class.getName()).getName(), is(SimpleOptionalType.class.getName())); // ensure that class is loaded
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default(new ByteBuddy().with(TypeValidation.DISABLED))
+ .ignore(none())
+ .disableClassFormatChanges()
+ .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
+ .with(descriptionStrategy)
+ .type(ElementMatchers.is(SimpleOptionalType.class), ElementMatchers.is(optionalTypeLoader)).transform(new FooTransformer())
+ .installOnByteBuddyAgent();
+ try {
+ Class<?> type = optionalTypeLoader.loadClass(SimpleOptionalType.class.getName());
+ // The hybrid strategy cannot transform optional types.
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) BAR));
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ }
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce(retransformsClasses = true)
+ @IntegrationRule.Enforce
+ public void testRetransformation() throws Exception {
+ // A redefinition reflects on loaded types which are eagerly validated types (Java 7- for redefinition).
+ // This causes type equality for outer/inner classes to fail which is why an external class is used.
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ assertThat(simpleTypeLoader.loadClass(SimpleType.class.getName()).getName(), is(SimpleType.class.getName())); // ensure that class is loaded
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
+ .ignore(none())
+ .disableClassFormatChanges()
+ .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
+ .with(descriptionStrategy)
+ .type(ElementMatchers.is(SimpleType.class), ElementMatchers.is(simpleTypeLoader)).transform(new FooTransformer())
+ .installOnByteBuddyAgent();
+ try {
+ Class<?> type = simpleTypeLoader.loadClass(SimpleType.class.getName());
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) BAR));
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ }
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce(retransformsClasses = true)
+ @IntegrationRule.Enforce
+ public void testRetransformationOptionalType() throws Exception {
+ // A redefinition reflects on loaded types which are eagerly validated types (Java 7- for redefinition).
+ // This causes type equality for outer/inner classes to fail which is why an external class is used.
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ assertThat(optionalTypeLoader.loadClass(SimpleOptionalType.class.getName()).getName(), is(SimpleOptionalType.class.getName())); // ensure that class is loaded
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default(new ByteBuddy().with(TypeValidation.DISABLED))
+ .ignore(none())
+ .disableClassFormatChanges()
+ .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
+ .with(descriptionStrategy)
+ .type(ElementMatchers.is(SimpleOptionalType.class), ElementMatchers.is(optionalTypeLoader)).transform(new FooTransformer())
+ .installOnByteBuddyAgent();
+ try {
+ Class<?> type = optionalTypeLoader.loadClass(SimpleOptionalType.class.getName());
+ // The hybrid strategy cannot transform optional types.
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) BAR));
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ }
+ }
+
+ private static class FooTransformer implements AgentBuilder.Transformer {
+
+ @Override
+ public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder,
+ TypeDescription typeDescription,
+ ClassLoader classLoader,
+ JavaModule module) {
+ return builder.method(named(FOO)).intercept(FixedValue.value(BAR));
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderDefaultApplicationRedefinitionReiterationTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderDefaultApplicationRedefinitionReiterationTest.java
new file mode 100644
index 0000000..df5c3c9
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderDefaultApplicationRedefinitionReiterationTest.java
@@ -0,0 +1,152 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.agent.ByteBuddyAgent;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.dynamic.loading.ByteArrayClassLoader;
+import net.bytebuddy.implementation.bytecode.Removal;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.matcher.ElementMatchers;
+import net.bytebuddy.test.utility.AgentAttachmentRule;
+import net.bytebuddy.test.utility.ClassFileExtraction;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.Instrumentation;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.none;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class AgentBuilderDefaultApplicationRedefinitionReiterationTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux";
+
+ @Rule
+ public MethodRule agentAttachmentRule = new AgentAttachmentRule();
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ private ClassLoader classLoader;
+
+ @Before
+ public void setUp() throws Exception {
+ classLoader = new ByteArrayClassLoader.ChildFirst(getClass().getClassLoader(),
+ ClassFileExtraction.of(Foo.class, Bar.class),
+ ByteArrayClassLoader.PersistenceHandler.MANIFEST);
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce(retransformsClasses = true)
+ public void testAdviceWithoutLoadedClasses() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ ClassFileTransformer classFileTransformer = installInstrumentation();
+ try {
+ assertAdvice();
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ }
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce(retransformsClasses = true)
+ public void testAdviceWithOneLoadedClass() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ classLoader.loadClass(Foo.class.getName());
+ ClassFileTransformer classFileTransformer = installInstrumentation();
+ try {
+ assertAdvice();
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ }
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce(retransformsClasses = true)
+ public void testAdviceWithTwoLoadedClasses() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ classLoader.loadClass(Foo.class.getName());
+ classLoader.loadClass(Bar.class.getName());
+ ClassFileTransformer classFileTransformer = installInstrumentation();
+ try {
+ assertAdvice();
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ }
+ }
+
+ private void assertAdvice() throws Exception {
+ Class<?> type = classLoader.loadClass(Foo.class.getName());
+ assertThat(type.getDeclaredMethod("createBar").invoke(type.getDeclaredConstructor().newInstance()).toString(), is((Object) (QUX + FOO + BAR)));
+ }
+
+ private ClassFileTransformer installInstrumentation() {
+ return new AgentBuilder.Default()
+ .disableClassFormatChanges()
+ .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
+ .with(AgentBuilder.RedefinitionStrategy.DiscoveryStrategy.Reiterating.INSTANCE)
+ .ignore(none())
+ .type(named(Foo.class.getName()), ElementMatchers.is(classLoader))
+ .transform(new AgentBuilder.Transformer.ForAdvice()
+ .with(AgentBuilder.LocationStrategy.ForClassLoader.STRONG)
+ .include(FooAdvice.class.getClassLoader())
+ .with(Assigner.DEFAULT)
+ .withExceptionHandler(Removal.SINGLE)
+ .advice(named("createBar"), FooAdvice.class.getName()))
+ .asDecorator()
+ .type(ElementMatchers.named(Bar.class.getName()), ElementMatchers.is(classLoader))
+ .transform(new AgentBuilder.Transformer.ForAdvice()
+ .with(AgentBuilder.LocationStrategy.ForClassLoader.STRONG)
+ .include(BarAdvice.class.getClassLoader())
+ .with(Assigner.DEFAULT)
+ .withExceptionHandler(Removal.SINGLE)
+ .advice(named("toString"), BarAdvice.class.getName()))
+ .asDecorator()
+ .installOnByteBuddyAgent();
+ }
+
+ public static class Foo {
+
+ @SuppressWarnings("unused")
+ public Bar createBar() throws Exception {
+ return new Bar();
+ }
+
+ }
+
+ public static class Bar {
+
+ private String x = QUX;
+
+ public void append(String x) {
+ this.x += x;
+ }
+
+ @Override
+ public String toString() {
+ return x;
+ }
+ }
+
+ private static class FooAdvice {
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return Bar value) {
+ value.append(FOO);
+ }
+ }
+
+ private static class BarAdvice {
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return(readOnly = false) String value) {
+ value += BAR;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderDefaultApplicationResubmissionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderDefaultApplicationResubmissionTest.java
new file mode 100644
index 0000000..b274f19
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderDefaultApplicationResubmissionTest.java
@@ -0,0 +1,126 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.agent.ByteBuddyAgent;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ByteArrayClassLoader;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.dynamic.scaffold.TypeValidation;
+import net.bytebuddy.implementation.FixedValue;
+import net.bytebuddy.matcher.ElementMatchers;
+import net.bytebuddy.test.utility.*;
+import net.bytebuddy.utility.JavaModule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.Instrumentation;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.none;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+public class AgentBuilderDefaultApplicationResubmissionTest {
+
+ private static final String FOO = "foo";
+
+ private static final long TIMEOUT = 1L;
+
+ @Rule
+ public MethodRule agentAttachmentRule = new AgentAttachmentRule();
+
+ @Rule
+ public MethodRule integrationRule = new IntegrationRule();
+
+ private ClassLoader classLoader;
+
+ @Before
+ public void setUp() throws Exception {
+ classLoader = new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassFileExtraction.of(Foo.class));
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce(retransformsClasses = true)
+ @IntegrationRule.Enforce
+ public void testResubmission() throws Exception {
+ // A redefinition reflects on loaded types which are eagerly validated types (Java 7- for redefinition).
+ // This causes type equality for outer/inner classes to fail which is why an external class is used.
+ final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
+ try {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default(new ByteBuddy().with(TypeValidation.DISABLED))
+ .ignore(none())
+ .disableClassFormatChanges()
+ .with(AgentBuilder.LocationStrategy.NoOp.INSTANCE)
+ .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
+ .withResubmission(new AgentBuilder.RedefinitionStrategy.ResubmissionScheduler() {
+ @Override
+ public boolean isAlive() {
+ return true;
+ }
+
+ @Override
+ public Cancelable schedule(final Runnable job) {
+ return new Cancelable.ForFuture(scheduledExecutorService.scheduleWithFixedDelay(job, TIMEOUT, TIMEOUT, TimeUnit.SECONDS));
+ }
+ })
+ .type(ElementMatchers.is(Foo.class), ElementMatchers.is(classLoader)).transform(new FooTransformer())
+ .installOnByteBuddyAgent();
+ try {
+ Class<?> type = classLoader.loadClass(Foo.class.getName());
+ Thread.sleep(TimeUnit.SECONDS.toMillis(TIMEOUT * 3));
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ }
+ } finally {
+ scheduledExecutorService.shutdown();
+ }
+ }
+
+ @Test
+ public void testResubmissionCancelationNonOperational() throws Exception {
+ AgentBuilder.RedefinitionStrategy.ResubmissionScheduler.Cancelable.NoOp.INSTANCE.cancel();
+ }
+
+ @Test
+ public void testResubmissionCancelationForFuture() throws Exception {
+ Future<?> future = mock(Future.class);
+ new AgentBuilder.RedefinitionStrategy.ResubmissionScheduler.Cancelable.ForFuture(future).cancel();
+ verify(future).cancel(true);
+ verifyNoMoreInteractions(future);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AgentBuilder.RedefinitionStrategy.ResubmissionScheduler.Cancelable.NoOp.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.RedefinitionStrategy.ResubmissionScheduler.Cancelable.ForFuture.class).apply();
+ }
+
+ public static class Foo {
+
+ public String foo() {
+ return null;
+ }
+ }
+
+ private static class FooTransformer implements AgentBuilder.Transformer {
+
+ @Override
+ public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {
+ return builder.method(named(FOO)).intercept(FixedValue.value(FOO));
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderDefaultApplicationSuperTypeLoadingTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderDefaultApplicationSuperTypeLoadingTest.java
new file mode 100644
index 0000000..98ee60d
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderDefaultApplicationSuperTypeLoadingTest.java
@@ -0,0 +1,126 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.agent.ByteBuddyAgent;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ByteArrayClassLoader;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.FixedValue;
+import net.bytebuddy.matcher.ElementMatchers;
+import net.bytebuddy.test.utility.AgentAttachmentRule;
+import net.bytebuddy.test.utility.ClassFileExtraction;
+import net.bytebuddy.test.utility.IntegrationRule;
+import net.bytebuddy.utility.JavaModule;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.Instrumentation;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.none;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class AgentBuilderDefaultApplicationSuperTypeLoadingTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ @Rule
+ public MethodRule agentAttachmentRule = new AgentAttachmentRule();
+
+ @Rule
+ public MethodRule integrationRule = new IntegrationRule();
+
+ private ClassLoader classLoader;
+
+ private ExecutorService executorService;
+
+ @Before
+ public void setUp() throws Exception {
+ classLoader = new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER,
+ ClassFileExtraction.of(Foo.class, Bar.class, AgentBuilderDefaultApplicationSuperTypeLoadingTest.class),
+ ByteArrayClassLoader.PersistenceHandler.MANIFEST);
+ executorService = Executors.newCachedThreadPool();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ executorService.shutdownNow();
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce
+ @IntegrationRule.Enforce
+ public void testSynchronousSuperTypeLoading() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
+ .with(AgentBuilder.DescriptionStrategy.Default.POOL_ONLY.withSuperTypeLoading())
+ .ignore(none())
+ .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
+ .type(ElementMatchers.isSubTypeOf(Foo.class), ElementMatchers.is(classLoader)).transform(new ConstantTransformer())
+ .installOnByteBuddyAgent();
+ try {
+ Class<?> type = classLoader.loadClass(Bar.class.getName());
+ assertThat(type.getDeclaredMethod(BAR).invoke(type.getDeclaredConstructor().newInstance()), is((Object) BAR));
+ assertThat(type.getSuperclass().getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), nullValue(Object.class));
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ }
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce
+ @IntegrationRule.Enforce
+ public void testAsynchronousSuperTypeLoading() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
+ .with(AgentBuilder.DescriptionStrategy.Default.POOL_ONLY.withSuperTypeLoading(executorService))
+ .ignore(none())
+ .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
+ .type(ElementMatchers.isSubTypeOf(Foo.class), ElementMatchers.is(classLoader)).transform(new ConstantTransformer())
+ .installOnByteBuddyAgent();
+ try {
+ Class<?> type = classLoader.loadClass(Bar.class.getName());
+ assertThat(type.getDeclaredMethod(BAR).invoke(type.getDeclaredConstructor().newInstance()), is((Object) BAR));
+ assertThat(type.getSuperclass().getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ }
+ }
+
+ public static class Foo {
+
+ public String foo() {
+ return null;
+ }
+ }
+
+ public static class Bar extends Foo {
+
+ public String bar() {
+ return null;
+ }
+ }
+
+ private static class ConstantTransformer implements AgentBuilder.Transformer {
+
+ @Override
+ public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder,
+ TypeDescription typeDescription,
+ ClassLoader classLoader,
+ JavaModule module) {
+ return builder
+ .method(isDeclaredBy(typeDescription).and(named(FOO))).intercept(FixedValue.value(FOO))
+ .method(isDeclaredBy(typeDescription).and(named(BAR))).intercept(FixedValue.value(BAR));
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderDefaultApplicationTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderDefaultApplicationTest.java
new file mode 100644
index 0000000..4712e62
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderDefaultApplicationTest.java
@@ -0,0 +1,1020 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.agent.ByteBuddyAgent;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.TargetType;
+import net.bytebuddy.dynamic.loading.ByteArrayClassLoader;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.FixedValue;
+import net.bytebuddy.implementation.MethodDelegation;
+import net.bytebuddy.implementation.SuperMethodCall;
+import net.bytebuddy.implementation.bind.annotation.Super;
+import net.bytebuddy.implementation.bind.annotation.SuperCall;
+import net.bytebuddy.implementation.bytecode.Removal;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.matcher.ElementMatchers;
+import net.bytebuddy.test.packaging.SimpleType;
+import net.bytebuddy.test.utility.AgentAttachmentRule;
+import net.bytebuddy.test.utility.ClassFileExtraction;
+import net.bytebuddy.test.utility.IntegrationRule;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import net.bytebuddy.utility.JavaModule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.Instrumentation;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.Callable;
+import java.util.logging.Logger;
+
+import static net.bytebuddy.matcher.ElementMatchers.any;
+import static net.bytebuddy.matcher.ElementMatchers.*;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class AgentBuilderDefaultApplicationTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux";
+
+ private static final String LAMBDA_SAMPLE_FACTORY = "net.bytebuddy.test.precompiled.LambdaSampleFactory";
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ // Travis runs out of memory if all of these tests are run. This property serves as a protection (on some profiles).
+ if (Boolean.getBoolean("net.bytebuddy.test.travis")) {
+ Logger.getLogger("net.bytebuddy").info("Running only a subset of type locator tests on Travis CI.");
+ return Arrays.asList(new Object[][]{
+ {AgentBuilder.PoolStrategy.Default.EXTENDED},
+ {AgentBuilder.PoolStrategy.Eager.EXTENDED},
+ {AgentBuilder.PoolStrategy.ClassLoading.EXTENDED}
+ });
+ }
+ return Arrays.asList(new Object[][]{
+ {AgentBuilder.PoolStrategy.Default.EXTENDED},
+ {AgentBuilder.PoolStrategy.Default.FAST},
+ {AgentBuilder.PoolStrategy.Eager.EXTENDED},
+ {AgentBuilder.PoolStrategy.Eager.FAST},
+ {AgentBuilder.PoolStrategy.ClassLoading.EXTENDED},
+ {AgentBuilder.PoolStrategy.ClassLoading.FAST}
+ });
+ }
+
+ @Rule
+ public MethodRule agentAttachmentRule = new AgentAttachmentRule();
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ @Rule
+ public MethodRule integrationRule = new IntegrationRule();
+
+ private ClassLoader classLoader;
+
+ private final AgentBuilder.PoolStrategy poolStrategy;
+
+ public AgentBuilderDefaultApplicationTest(AgentBuilder.PoolStrategy poolStrategy) {
+ this.poolStrategy = poolStrategy;
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ classLoader = new ByteArrayClassLoader.ChildFirst(getClass().getClassLoader(),
+ ClassFileExtraction.of(Foo.class,
+ Bar.class,
+ Qux.class,
+ Baz.class,
+ QuxBaz.class,
+ SimpleType.class),
+ ByteArrayClassLoader.PersistenceHandler.MANIFEST);
+ }
+
+ private ClassLoader lambdaSamples() throws Exception {
+ return new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER,
+ ClassFileExtraction.of(Class.forName(LAMBDA_SAMPLE_FACTORY)),
+ ByteArrayClassLoader.PersistenceHandler.MANIFEST);
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce
+ @IntegrationRule.Enforce
+ public void testAgentWithoutSelfInitialization() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(none())
+ .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
+ .type(ElementMatchers.is(Foo.class), ElementMatchers.is(classLoader)).transform(new FooTransformer())
+ .installOnByteBuddyAgent();
+ try {
+ Class<?> type = classLoader.loadClass(Foo.class.getName());
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) BAR));
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ }
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce
+ @IntegrationRule.Enforce
+ public void testAgentSelfInitialization() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(none())
+ .type(ElementMatchers.is(Bar.class), ElementMatchers.is(classLoader)).transform(new BarTransformer())
+ .installOnByteBuddyAgent();
+ try {
+ Class<?> type = classLoader.loadClass(Bar.class.getName());
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) BAR));
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ }
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce
+ @IntegrationRule.Enforce
+ public void testAgentSelfInitializationAuxiliaryTypeEager() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(none())
+ .type(ElementMatchers.is(Qux.class), ElementMatchers.is(classLoader)).transform(new QuxTransformer())
+ .installOnByteBuddyAgent();
+ try {
+ Class<?> type = classLoader.loadClass(Qux.class.getName());
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) (FOO + BAR)));
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ }
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce
+ @IntegrationRule.Enforce
+ public void testAgentSelfInitializationAuxiliaryTypeLazy() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(none())
+ .type(ElementMatchers.is(QuxBaz.class), ElementMatchers.is(classLoader)).transform(new QuxBazTransformer())
+ .installOnByteBuddyAgent();
+ try {
+ Class<?> type = classLoader.loadClass(QuxBaz.class.getName());
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) (FOO + BAR)));
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ }
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce
+ @IntegrationRule.Enforce
+ public void testAgentWithoutSelfInitializationWithNativeMethodPrefix() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(none())
+ .enableNativeMethodPrefix(QUX)
+ .type(ElementMatchers.is(Baz.class), ElementMatchers.is(classLoader)).transform(new FooTransformer())
+ .installOnByteBuddyAgent();
+ try {
+ Class<?> type = classLoader.loadClass(Baz.class.getName());
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) BAR));
+ assertThat(type.getDeclaredMethod(QUX + FOO), notNullValue(Method.class));
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ }
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce(redefinesClasses = true)
+ @IntegrationRule.Enforce
+ public void testRedefinition() throws Exception {
+ // A redefinition reflects on loaded types which are eagerly validated types (Java 7- for redefinition).
+ // This causes type equality for outer/inner classes to fail which is why an external class is used.
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ assertThat(classLoader.loadClass(SimpleType.class.getName()).getName(), is(SimpleType.class.getName())); // ensure that class is loaded
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(none())
+ .disableClassFormatChanges()
+ .with(AgentBuilder.TypeStrategy.Default.REDEFINE)
+ .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
+ .type(ElementMatchers.is(SimpleType.class), ElementMatchers.is(classLoader)).transform(new FooTransformer())
+ .installOnByteBuddyAgent();
+ try {
+ Class<?> type = classLoader.loadClass(SimpleType.class.getName());
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) BAR));
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ }
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce(redefinesClasses = true)
+ @IntegrationRule.Enforce
+ public void testRedefinitionWithReset() throws Exception {
+ // A redefinition reflects on loaded types which are eagerly validated types (Java 7- for redefinition).
+ // This causes type equality for outer/inner classes to fail which is why an external class is used.
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ assertThat(classLoader.loadClass(SimpleType.class.getName()).getName(), is(SimpleType.class.getName())); // ensure that class is loaded
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(none())
+ .disableClassFormatChanges()
+ .with(AgentBuilder.TypeStrategy.Default.REDEFINE)
+ .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
+ .type(ElementMatchers.is(SimpleType.class), ElementMatchers.is(classLoader)).transform(new FooTransformer())
+ .installOnByteBuddyAgent();
+ try {
+ Class<?> type = classLoader.loadClass(SimpleType.class.getName());
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) BAR));
+ } finally {
+ assertThat(classFileTransformer.reset(ByteBuddyAgent.getInstrumentation(), AgentBuilder.RedefinitionStrategy.REDEFINITION), is(true));
+ }
+ Class<?> type = classLoader.loadClass(SimpleType.class.getName());
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce(redefinesClasses = true)
+ @IntegrationRule.Enforce
+ public void testEmptyRedefinition() throws Exception {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(any())
+ .disableClassFormatChanges()
+ .with(AgentBuilder.TypeStrategy.Default.REDEFINE)
+ .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
+ .installOnByteBuddyAgent());
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce(redefinesClasses = true)
+ @IntegrationRule.Enforce
+ public void testChunkedRedefinition() throws Exception {
+ // A redefinition reflects on loaded types which are eagerly validated types (Java 7- for redefinition).
+ // This causes type equality for outer/inner classes to fail which is why an external class is used.
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ assertThat(classLoader.loadClass(SimpleType.class.getName()).getName(), is(SimpleType.class.getName())); // ensure that class is loaded
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(none())
+ .disableClassFormatChanges()
+ .with(AgentBuilder.TypeStrategy.Default.REDEFINE)
+ .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
+ .with(AgentBuilder.RedefinitionStrategy.BatchAllocator.ForFixedSize.ofSize(1))
+ .type(ElementMatchers.is(SimpleType.class), ElementMatchers.is(classLoader)).transform(new FooTransformer())
+ .installOnByteBuddyAgent();
+ try {
+ Class<?> type = classLoader.loadClass(SimpleType.class.getName());
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) BAR));
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ }
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce(redefinesClasses = true)
+ @IntegrationRule.Enforce
+ public void testEmptyChunkedRedefinition() throws Exception {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(any())
+ .disableClassFormatChanges()
+ .with(AgentBuilder.TypeStrategy.Default.REDEFINE)
+ .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
+ .with(AgentBuilder.RedefinitionStrategy.BatchAllocator.ForFixedSize.ofSize(1))
+ .installOnByteBuddyAgent());
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce(redefinesClasses = true)
+ @IntegrationRule.Enforce
+ public void testRedefinitionWithExplicitTypes() throws Exception {
+ // A redefinition reflects on loaded types which are eagerly validated types (Java 7- for redefinition).
+ // This causes type equality for outer/inner classes to fail which is why an external class is used.
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ assertThat(classLoader.loadClass(SimpleType.class.getName()).getName(), is(SimpleType.class.getName())); // ensure that class is loaded
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(none())
+ .disableClassFormatChanges()
+ .with(AgentBuilder.TypeStrategy.Default.REDEFINE)
+ .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
+ .redefineOnly(classLoader.loadClass(SimpleType.class.getName()))
+ .type(ElementMatchers.is(SimpleType.class), ElementMatchers.is(classLoader)).transform(new FooTransformer())
+ .installOnByteBuddyAgent();
+ try {
+ Class<?> type = classLoader.loadClass(SimpleType.class.getName());
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) BAR));
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ }
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce(retransformsClasses = true)
+ @IntegrationRule.Enforce
+ public void testRetransformation() throws Exception {
+ // A redefinition reflects on loaded types which are eagerly validated types (Java 7- for redefinition).
+ // This causes type equality for outer/inner classes to fail which is why an external class is used.
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ assertThat(classLoader.loadClass(SimpleType.class.getName()).getName(), is(SimpleType.class.getName())); // ensure that class is loaded
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(none())
+ .disableClassFormatChanges()
+ .with(AgentBuilder.TypeStrategy.Default.REDEFINE)
+ .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
+ .type(ElementMatchers.is(SimpleType.class), ElementMatchers.is(classLoader)).transform(new FooTransformer())
+ .installOnByteBuddyAgent();
+ try {
+ Class<?> type = classLoader.loadClass(SimpleType.class.getName());
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) BAR));
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ }
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce(retransformsClasses = true)
+ @IntegrationRule.Enforce
+ public void testRetransformationWithReset() throws Exception {
+ // A redefinition reflects on loaded types which are eagerly validated types (Java 7- for redefinition).
+ // This causes type equality for outer/inner classes to fail which is why an external class is used.
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ assertThat(classLoader.loadClass(SimpleType.class.getName()).getName(), is(SimpleType.class.getName())); // ensure that class is loaded
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(none())
+ .disableClassFormatChanges()
+ .with(AgentBuilder.TypeStrategy.Default.REDEFINE)
+ .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
+ .type(ElementMatchers.is(SimpleType.class), ElementMatchers.is(classLoader)).transform(new FooTransformer())
+ .installOnByteBuddyAgent();
+ try {
+ Class<?> type = classLoader.loadClass(SimpleType.class.getName());
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) BAR));
+ } finally {
+ assertThat(classFileTransformer.reset(ByteBuddyAgent.getInstrumentation(), AgentBuilder.RedefinitionStrategy.RETRANSFORMATION), is(true));
+ }
+ Class<?> type = classLoader.loadClass(SimpleType.class.getName());
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce(retransformsClasses = true)
+ @IntegrationRule.Enforce
+ public void testEmptyRetransformation() throws Exception {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(any())
+ .disableClassFormatChanges()
+ .with(AgentBuilder.TypeStrategy.Default.REDEFINE)
+ .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
+ .installOnByteBuddyAgent());
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce(retransformsClasses = true)
+ @IntegrationRule.Enforce
+ public void testChunkedRetransformation() throws Exception {
+ // A redefinition reflects on loaded types which are eagerly validated types (Java 7- for redefinition).
+ // This causes type equality for outer/inner classes to fail which is why an external class is used.
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ assertThat(classLoader.loadClass(SimpleType.class.getName()).getName(), is(SimpleType.class.getName())); // ensure that class is loaded
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(none())
+ .disableClassFormatChanges()
+ .with(AgentBuilder.TypeStrategy.Default.REDEFINE)
+ .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
+ .with(AgentBuilder.RedefinitionStrategy.BatchAllocator.ForFixedSize.ofSize(1))
+ .type(ElementMatchers.is(SimpleType.class), ElementMatchers.is(classLoader)).transform(new FooTransformer())
+ .installOnByteBuddyAgent();
+ try {
+ Class<?> type = classLoader.loadClass(SimpleType.class.getName());
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) BAR));
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ }
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce(retransformsClasses = true)
+ @IntegrationRule.Enforce
+ public void testChunkedEmptyRetransformation() throws Exception {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(any())
+ .disableClassFormatChanges()
+ .with(AgentBuilder.TypeStrategy.Default.REDEFINE)
+ .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
+ .with(AgentBuilder.RedefinitionStrategy.BatchAllocator.ForFixedSize.ofSize(1))
+ .installOnByteBuddyAgent());
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce(retransformsClasses = true)
+ @IntegrationRule.Enforce
+ public void testRetransformationWithExplicitTypes() throws Exception {
+ // A redefinition reflects on loaded types which are eagerly validated types (Java 7- for redefinition).
+ // This causes type equality for outer/inner classes to fail which is why an external class is used.
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ assertThat(classLoader.loadClass(SimpleType.class.getName()).getName(), is(SimpleType.class.getName())); // ensure that class is loaded
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(none())
+ .disableClassFormatChanges()
+ .with(AgentBuilder.TypeStrategy.Default.REDEFINE)
+ .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
+ .redefineOnly(classLoader.loadClass(SimpleType.class.getName()))
+ .type(ElementMatchers.is(SimpleType.class), ElementMatchers.is(classLoader)).transform(new FooTransformer())
+ .installOnByteBuddyAgent();
+ try {
+ Class<?> type = classLoader.loadClass(SimpleType.class.getName());
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) BAR));
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ }
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce
+ @IntegrationRule.Enforce
+ public void testChainedAgent() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ AgentBuilder agentBuilder = new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(none())
+ .type(ElementMatchers.is(Qux.class), ElementMatchers.is(classLoader)).transform(new QuxTransformer());
+ ClassFileTransformer firstTransformer = agentBuilder.installOnByteBuddyAgent();
+ ClassFileTransformer secondTransformer = agentBuilder.installOnByteBuddyAgent();
+ try {
+ Class<?> type = classLoader.loadClass(Qux.class.getName());
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) (FOO + BAR + BAR)));
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(firstTransformer);
+ ByteBuddyAgent.getInstrumentation().removeTransformer(secondTransformer);
+ }
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce
+ @IntegrationRule.Enforce
+ public void testSignatureTypesAreAvailableAfterLoad() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(none())
+ .type(ElementMatchers.is(Foo.class), ElementMatchers.is(classLoader)).transform(new ConstructorTransformer())
+ .installOnByteBuddyAgent();
+ try {
+ Class<?> type = classLoader.loadClass(Foo.class.getName());
+ assertThat(type.getDeclaredConstructors().length, is(2));
+ assertThat(type.getDeclaredConstructor().newInstance(), notNullValue(Object.class));
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ }
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce
+ @IntegrationRule.Enforce
+ public void testDecoration() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(none())
+ .type(ElementMatchers.is(Foo.class), ElementMatchers.is(classLoader)).transform(new BarAdviceTransformer())
+ .type(ElementMatchers.is(Foo.class), ElementMatchers.is(classLoader)).transform(new QuxAdviceTransformer()).asDecorator()
+ .installOnByteBuddyAgent();
+ try {
+ Class<?> type = classLoader.loadClass(Foo.class.getName());
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) (FOO + BAR + QUX)));
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ }
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce
+ @IntegrationRule.Enforce
+ public void testDecorationFallThrough() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(none())
+ .type(ElementMatchers.is(Foo.class), ElementMatchers.is(classLoader)).transform(new BarAdviceTransformer()).asDecorator()
+ .type(ElementMatchers.is(Foo.class), ElementMatchers.is(classLoader)).transform(new QuxAdviceTransformer()).asDecorator()
+ .installOnByteBuddyAgent();
+ try {
+ Class<?> type = classLoader.loadClass(Foo.class.getName());
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) (FOO + BAR + QUX)));
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ }
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce
+ @IntegrationRule.Enforce
+ public void testDecorationBlocked() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(none())
+ .type(ElementMatchers.is(Foo.class), ElementMatchers.is(classLoader)).transform(new BarAdviceTransformer()).asDecorator()
+ .type(ElementMatchers.is(Foo.class), ElementMatchers.is(classLoader)).transform(new QuxAdviceTransformer())
+ .installOnByteBuddyAgent();
+ try {
+ Class<?> type = classLoader.loadClass(Foo.class.getName());
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) (FOO + QUX)));
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ }
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @AgentAttachmentRule.Enforce
+ @IntegrationRule.Enforce
+ public void testNonCapturingLambda() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ ClassLoader classLoader = lambdaSamples();
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(none())
+ .with(AgentBuilder.LambdaInstrumentationStrategy.ENABLED)
+ .type(isSubTypeOf(Callable.class)).transform(new SingleMethodReplacer("call"))
+ .installOn(ByteBuddyAgent.getInstrumentation());
+ try {
+ Class<?> sampleFactory = classLoader.loadClass(LAMBDA_SAMPLE_FACTORY);
+ @SuppressWarnings("unchecked")
+ Callable<String> instance = (Callable<String>) sampleFactory.getDeclaredMethod("nonCapturing").invoke(sampleFactory.getDeclaredConstructor().newInstance());
+ assertThat(instance.call(), is(BAR));
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ AgentBuilder.LambdaInstrumentationStrategy.release(classFileTransformer, ByteBuddyAgent.getInstrumentation());
+ }
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @AgentAttachmentRule.Enforce
+ @IntegrationRule.Enforce
+ public void testNonCapturingLambdaIsConstant() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ ClassLoader classLoader = lambdaSamples();
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(none())
+ .with(AgentBuilder.LambdaInstrumentationStrategy.ENABLED)
+ .type(isSubTypeOf(Callable.class)).transform(new SingleMethodReplacer("call"))
+ .installOn(ByteBuddyAgent.getInstrumentation());
+ try {
+ Class<?> sampleFactory = classLoader.loadClass(LAMBDA_SAMPLE_FACTORY);
+ assertThat(sampleFactory.getDeclaredMethod("nonCapturing").invoke(sampleFactory.getDeclaredConstructor().newInstance()),
+ sameInstance(sampleFactory.getDeclaredMethod("nonCapturing").invoke(sampleFactory.getDeclaredConstructor().newInstance())));
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ AgentBuilder.LambdaInstrumentationStrategy.release(classFileTransformer, ByteBuddyAgent.getInstrumentation());
+ }
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @AgentAttachmentRule.Enforce
+ @IntegrationRule.Enforce
+ public void testLambdaFactoryIsReset() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ ClassLoader classLoader = lambdaSamples();
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(none())
+ .with(AgentBuilder.LambdaInstrumentationStrategy.ENABLED)
+ .installOn(ByteBuddyAgent.getInstrumentation());
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ AgentBuilder.LambdaInstrumentationStrategy.release(classFileTransformer, ByteBuddyAgent.getInstrumentation());
+ Class<?> sampleFactory = classLoader.loadClass(LAMBDA_SAMPLE_FACTORY);
+ @SuppressWarnings("unchecked")
+ Callable<String> instance = (Callable<String>) sampleFactory.getDeclaredMethod("nonCapturing").invoke(sampleFactory.getDeclaredConstructor().newInstance());
+ assertThat(instance.call(), is(FOO));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @AgentAttachmentRule.Enforce
+ @IntegrationRule.Enforce
+ public void testArgumentCapturingLambda() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ ClassLoader classLoader = lambdaSamples();
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(none())
+ .with(AgentBuilder.LambdaInstrumentationStrategy.ENABLED)
+ .type(isSubTypeOf(Callable.class)).transform(new SingleMethodReplacer("call"))
+ .installOn(ByteBuddyAgent.getInstrumentation());
+ try {
+ Class<?> sampleFactory = classLoader.loadClass(LAMBDA_SAMPLE_FACTORY);
+ @SuppressWarnings("unchecked")
+ Callable<String> instance = (Callable<String>) sampleFactory.getDeclaredMethod("argumentCapturing", String.class).invoke(sampleFactory.getDeclaredConstructor().newInstance(), FOO);
+ assertThat(instance.call(), is(BAR));
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ AgentBuilder.LambdaInstrumentationStrategy.release(classFileTransformer, ByteBuddyAgent.getInstrumentation());
+ }
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @AgentAttachmentRule.Enforce
+ @IntegrationRule.Enforce
+ public void testArgumentCapturingLambdaIsNotConstant() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ ClassLoader classLoader = lambdaSamples();
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(none())
+ .with(AgentBuilder.LambdaInstrumentationStrategy.ENABLED)
+ .type(isSubTypeOf(Callable.class)).transform(new SingleMethodReplacer("call"))
+ .installOn(ByteBuddyAgent.getInstrumentation());
+ try {
+ Class<?> sampleFactory = classLoader.loadClass(LAMBDA_SAMPLE_FACTORY);
+ assertThat(sampleFactory.getDeclaredMethod("argumentCapturing", String.class).invoke(sampleFactory.getDeclaredConstructor().newInstance(), FOO),
+ not(sameInstance(sampleFactory.getDeclaredMethod("argumentCapturing", String.class).invoke(sampleFactory.getDeclaredConstructor().newInstance(), FOO))));
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ AgentBuilder.LambdaInstrumentationStrategy.release(classFileTransformer, ByteBuddyAgent.getInstrumentation());
+ }
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @AgentAttachmentRule.Enforce
+ @IntegrationRule.Enforce
+ public void testInstanceCapturingLambda() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ ClassLoader classLoader = lambdaSamples();
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(none())
+ .with(AgentBuilder.LambdaInstrumentationStrategy.ENABLED)
+ .type(isSubTypeOf(Callable.class)).transform(new SingleMethodReplacer("call"))
+ .installOn(ByteBuddyAgent.getInstrumentation());
+ try {
+ Class<?> sampleFactory = classLoader.loadClass(LAMBDA_SAMPLE_FACTORY);
+ @SuppressWarnings("unchecked")
+ Callable<String> instance = (Callable<String>) sampleFactory.getDeclaredMethod("instanceCapturing").invoke(sampleFactory.getDeclaredConstructor().newInstance());
+ assertThat(instance.call(), is(BAR));
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ AgentBuilder.LambdaInstrumentationStrategy.release(classFileTransformer, ByteBuddyAgent.getInstrumentation());
+ }
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @AgentAttachmentRule.Enforce
+ @IntegrationRule.Enforce
+ public void testNonCapturingLambdaWithArguments() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ ClassLoader classLoader = lambdaSamples();
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(none())
+ .with(AgentBuilder.LambdaInstrumentationStrategy.ENABLED)
+ .type(isSubTypeOf(Class.forName("java.util.function.Function"))).transform(new SingleMethodReplacer("apply"))
+ .installOn(ByteBuddyAgent.getInstrumentation());
+ try {
+ Class<?> sampleFactory = classLoader.loadClass(LAMBDA_SAMPLE_FACTORY);
+ Object instance = sampleFactory.getDeclaredMethod("nonCapturingWithArguments").invoke(sampleFactory.getDeclaredConstructor().newInstance());
+ assertThat(instance.getClass().getMethod("apply", Object.class).invoke(instance, FOO), is((Object) BAR));
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ AgentBuilder.LambdaInstrumentationStrategy.release(classFileTransformer, ByteBuddyAgent.getInstrumentation());
+ }
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @AgentAttachmentRule.Enforce
+ @IntegrationRule.Enforce
+ public void testCapturingLambdaWithArguments() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ ClassLoader classLoader = lambdaSamples();
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(none())
+ .with(AgentBuilder.LambdaInstrumentationStrategy.ENABLED)
+ .type(isSubTypeOf(Class.forName("java.util.function.Function"))).transform(new SingleMethodReplacer("apply"))
+ .installOn(ByteBuddyAgent.getInstrumentation());
+ try {
+ Class<?> sampleFactory = classLoader.loadClass(LAMBDA_SAMPLE_FACTORY);
+ Object instance = sampleFactory.getDeclaredMethod("capturingWithArguments", String.class).invoke(sampleFactory.getDeclaredConstructor().newInstance(), FOO);
+ assertThat(instance.getClass().getMethod("apply", Object.class).invoke(instance, FOO), is((Object) BAR));
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ AgentBuilder.LambdaInstrumentationStrategy.release(classFileTransformer, ByteBuddyAgent.getInstrumentation());
+ }
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @AgentAttachmentRule.Enforce
+ @IntegrationRule.Enforce
+ public void testSerializableLambda() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ ClassLoader classLoader = lambdaSamples();
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(none())
+ .with(AgentBuilder.LambdaInstrumentationStrategy.ENABLED)
+ .installOn(ByteBuddyAgent.getInstrumentation());
+ try {
+ Class<?> sampleFactory = classLoader.loadClass(LAMBDA_SAMPLE_FACTORY);
+ @SuppressWarnings("unchecked")
+ Callable<String> instance = (Callable<String>) sampleFactory.getDeclaredMethod("serializable", String.class).invoke(sampleFactory.getDeclaredConstructor().newInstance(), FOO);
+ assertThat(instance.call(), is(FOO));
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
+ objectOutputStream.writeObject(instance);
+ objectOutputStream.close();
+ ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(outputStream.toByteArray()));
+ @SuppressWarnings("unchecked")
+ Callable<String> deserialized = (Callable<String>) objectInputStream.readObject();
+ assertThat(deserialized.call(), is(FOO));
+ objectInputStream.close();
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ AgentBuilder.LambdaInstrumentationStrategy.release(classFileTransformer, ByteBuddyAgent.getInstrumentation());
+ }
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @AgentAttachmentRule.Enforce
+ @IntegrationRule.Enforce
+ public void testReturnTypeTransformingLambda() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ ClassLoader classLoader = lambdaSamples();
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(none())
+ .with(AgentBuilder.LambdaInstrumentationStrategy.ENABLED)
+ .type(isSubTypeOf(Callable.class)).transform(new SingleMethodReplacer("call"))
+ .installOn(ByteBuddyAgent.getInstrumentation());
+ try {
+ Class<?> sampleFactory = classLoader.loadClass(LAMBDA_SAMPLE_FACTORY);
+ Runnable instance = (Runnable) sampleFactory.getDeclaredMethod("returnTypeTransforming").invoke(sampleFactory.getDeclaredConstructor().newInstance());
+ instance.run();
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ AgentBuilder.LambdaInstrumentationStrategy.release(classFileTransformer, ByteBuddyAgent.getInstrumentation());
+ }
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @AgentAttachmentRule.Enforce
+ @IntegrationRule.Enforce
+ public void testInstanceReturningLambda() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ ClassLoader classLoader = lambdaSamples();
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(none())
+ .with(AgentBuilder.LambdaInstrumentationStrategy.ENABLED)
+ .type(isSubTypeOf(Callable.class)).transform(new SingleMethodReplacer("call"))
+ .installOn(ByteBuddyAgent.getInstrumentation());
+ try {
+ Class<?> sampleFactory = classLoader.loadClass(LAMBDA_SAMPLE_FACTORY);
+ Callable<?> instance = (Callable<?>) sampleFactory.getDeclaredMethod("instanceReturning").invoke(sampleFactory.getDeclaredConstructor().newInstance());
+ assertThat(instance.call(), notNullValue(Object.class));
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ AgentBuilder.LambdaInstrumentationStrategy.release(classFileTransformer, ByteBuddyAgent.getInstrumentation());
+ }
+ }
+
+ @Test
+ @IntegrationRule.Enforce
+ public void testAdviceTransformer() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default()
+ .with(poolStrategy)
+ .ignore(none())
+ .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
+ .type(ElementMatchers.is(Foo.class), ElementMatchers.is(classLoader)).transform(new AgentBuilder.Transformer.ForAdvice()
+ .with(poolStrategy)
+ .with(AgentBuilder.LocationStrategy.ForClassLoader.STRONG)
+ .include(BarAdvice.class.getClassLoader())
+ .with(Assigner.DEFAULT)
+ .withExceptionHandler(Removal.SINGLE)
+ .advice(named(FOO), BarAdvice.class.getName()))
+ .installOnByteBuddyAgent();
+ try {
+ Class<?> type = classLoader.loadClass(Foo.class.getName());
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) (FOO + BAR)));
+ } finally {
+ ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
+ }
+ }
+
+ private static class FooTransformer implements AgentBuilder.Transformer {
+
+ @Override
+ public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder,
+ TypeDescription typeDescription,
+ ClassLoader classLoader,
+ JavaModule module) {
+ return builder.method(named(FOO)).intercept(FixedValue.value(BAR));
+ }
+ }
+
+ public static class Foo {
+
+ public String foo() {
+ return FOO;
+ }
+ }
+
+ public static class Baz {
+
+ public String foo() {
+ return FOO;
+ }
+ }
+
+ public static class BarTransformer implements AgentBuilder.Transformer {
+
+ @Override
+ public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder,
+ TypeDescription typeDescription,
+ ClassLoader classLoader,
+ JavaModule module) {
+ try {
+ return builder.method(named(FOO)).intercept(MethodDelegation.to(new Interceptor()));
+ } catch (Exception exception) {
+ throw new AssertionError(exception);
+ }
+ }
+
+ public static class Interceptor {
+
+ public String intercept() {
+ return BAR;
+ }
+ }
+ }
+
+ public static class Bar {
+
+ public String foo() {
+ return FOO;
+ }
+ }
+
+ public static class QuxTransformer implements AgentBuilder.Transformer {
+
+ @Override
+ public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder,
+ TypeDescription typeDescription,
+ ClassLoader classLoader,
+ JavaModule module) {
+ try {
+ return builder.method(named(FOO)).intercept(MethodDelegation.to(new Interceptor()));
+ } catch (Exception exception) {
+ throw new AssertionError(exception);
+ }
+ }
+
+ public static class Interceptor {
+
+ public String intercept(@SuperCall Callable<String> zuper) throws Exception {
+ return zuper.call() + BAR;
+ }
+ }
+ }
+
+ public static class Qux {
+
+ public String foo() {
+ return FOO;
+ }
+ }
+
+ public static class QuxBazTransformer implements AgentBuilder.Transformer {
+
+ @Override
+ public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder,
+ TypeDescription typeDescription,
+ ClassLoader classLoader,
+ JavaModule module) {
+ try {
+ return builder.method(named(FOO)).intercept(MethodDelegation.to(new Interceptor()));
+ } catch (Exception exception) {
+ throw new AssertionError(exception);
+ }
+ }
+
+ public static class Interceptor {
+
+ // Interceptor cannot reference QuxBaz as the system class loader type does not equal the child-first type
+ public String intercept(@Super(proxyType = TargetType.class) Object zuper) throws Exception {
+ return zuper.getClass().getClassLoader().loadClass(QuxBaz.class.getName()).getDeclaredMethod("foo").invoke(zuper) + BAR;
+ }
+ }
+ }
+
+ public static class QuxBaz {
+
+ public String foo() {
+ return FOO;
+ }
+ }
+
+ public static class ConstructorTransformer implements AgentBuilder.Transformer {
+
+ @Override
+ public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder,
+ TypeDescription typeDescription,
+ ClassLoader classLoader,
+ JavaModule module) {
+ return builder.constructor(ElementMatchers.any()).intercept(SuperMethodCall.INSTANCE);
+ }
+ }
+
+ private static class SingleMethodReplacer implements AgentBuilder.Transformer {
+
+ private final String methodName;
+
+ public SingleMethodReplacer(String methodName) {
+ this.methodName = methodName;
+ }
+
+ @Override
+ public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder,
+ TypeDescription typeDescription,
+ ClassLoader classLoader,
+ JavaModule module) {
+ return builder.method(named(methodName)).intercept(FixedValue.value(BAR));
+ }
+ }
+
+ public static class BarAdviceTransformer implements AgentBuilder.Transformer {
+
+ @Override
+ public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder,
+ TypeDescription typeDescription,
+ ClassLoader classLoader,
+ JavaModule module) {
+ return builder.visit(Advice.to(BarAdvice.class).on(named(FOO)));
+ }
+ }
+
+ public static class QuxAdviceTransformer implements AgentBuilder.Transformer {
+
+ @Override
+ public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder,
+ TypeDescription typeDescription,
+ ClassLoader classLoader,
+ JavaModule module) {
+ return builder.visit(Advice.to(QuxAdvice.class).on(named(FOO)));
+ }
+ }
+
+ private static class BarAdvice {
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return(readOnly = false) String value) {
+ value += BAR;
+ }
+ }
+
+ private static class QuxAdvice {
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return(readOnly = false) String value) {
+ value += QUX;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderDefaultNativeMethodStrategyTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderDefaultNativeMethodStrategyTest.java
new file mode 100644
index 0000000..00db544
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderDefaultNativeMethodStrategyTest.java
@@ -0,0 +1,82 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.lang.instrument.Instrumentation;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class AgentBuilderDefaultNativeMethodStrategyTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodDescription methodDescription;
+
+ @Before
+ public void setUp() throws Exception {
+ when(methodDescription.getInternalName()).thenReturn(BAR);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testDisabledStrategyThrowsExceptionForPrefix() throws Exception {
+ AgentBuilder.Default.NativeMethodStrategy.Disabled.INSTANCE.getPrefix();
+ }
+
+ @Test
+ public void testDisabledStrategyIsDisabled() throws Exception {
+ assertThat(AgentBuilder.Default.NativeMethodStrategy.Disabled.INSTANCE.isEnabled(mock(Instrumentation.class)), is(false));
+ }
+
+ @Test
+ public void testDisabledStrategySuffixesNames() throws Exception {
+ assertThat(AgentBuilder.Default.NativeMethodStrategy.Disabled.INSTANCE.resolve().transform(methodDescription), startsWith(BAR));
+ assertThat(AgentBuilder.Default.NativeMethodStrategy.Disabled.INSTANCE.resolve().transform(methodDescription), not(BAR));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testEnabledStrategyMustNotBeEmptyString() throws Exception {
+ AgentBuilder.Default.NativeMethodStrategy.ForPrefix.of("");
+ }
+
+ @Test
+ public void testEnabledStrategyReturnsPrefix() throws Exception {
+ assertThat(new AgentBuilder.Default.NativeMethodStrategy.ForPrefix(FOO).getPrefix(), is(FOO));
+ }
+
+ @Test
+ public void testEnabledStrategyIsEnabled() throws Exception {
+ Instrumentation instrumentation = mock(Instrumentation.class);
+ when(instrumentation.isNativeMethodPrefixSupported()).thenReturn(true);
+ assertThat(new AgentBuilder.Default.NativeMethodStrategy.ForPrefix(FOO).isEnabled(instrumentation), is(true));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testEnabledStrategyThrowsExceptionIfNotSupported() throws Exception {
+ new AgentBuilder.Default.NativeMethodStrategy.ForPrefix(FOO).isEnabled(mock(Instrumentation.class));
+ }
+
+ @Test
+ public void testEnabledStrategySuffixesNames() throws Exception {
+ assertThat(new AgentBuilder.Default.NativeMethodStrategy.ForPrefix(FOO).resolve().transform(methodDescription), is(FOO + BAR));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AgentBuilder.Default.NativeMethodStrategy.Disabled.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Default.NativeMethodStrategy.ForPrefix.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderDefaultTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderDefaultTest.java
new file mode 100644
index 0000000..f4522f2
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderDefaultTest.java
@@ -0,0 +1,2275 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.build.EntryPoint;
+import net.bytebuddy.build.Plugin;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.TypeResolutionStrategy;
+import net.bytebuddy.dynamic.loading.ClassInjector;
+import net.bytebuddy.dynamic.scaffold.inline.MethodNameTransformer;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.LoadedTypeInitializer;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.matcher.ElementMatchers;
+import net.bytebuddy.pool.TypePool;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.utility.JavaModule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+import org.junit.rules.TestRule;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.File;
+import java.lang.instrument.ClassDefinition;
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.Instrumentation;
+import java.lang.instrument.UnmodifiableClassException;
+import java.lang.reflect.Constructor;
+import java.security.ProtectionDomain;
+import java.util.*;
+
+import static net.bytebuddy.matcher.ElementMatchers.none;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.*;
+
+public class AgentBuilderDefaultTest {
+
+ private static final String FOO = "foo";
+
+ private static final byte[] QUX = new byte[]{1, 2, 3}, BAZ = new byte[]{4, 5, 6};
+
+ private static final Class<?> REDEFINED = Foo.class, AUXILIARY = Bar.class, OTHER = Qux.class;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ @Mock
+ private Instrumentation instrumentation;
+
+ @Mock
+ private ByteBuddy byteBuddy;
+
+ @Mock
+ private DynamicType.Builder<?> builder;
+
+ @Mock
+ private DynamicType.Unloaded<?> dynamicType;
+
+ @Mock
+ private LoadedTypeInitializer loadedTypeInitializer;
+
+ @Mock
+ private AgentBuilder.RawMatcher typeMatcher;
+
+ @Mock
+ private AgentBuilder.Transformer transformer;
+
+ @Mock
+ private AgentBuilder.PoolStrategy poolStrategy;
+
+ @Mock
+ private AgentBuilder.TypeStrategy typeStrategy;
+
+ @Mock
+ private AgentBuilder.LocationStrategy locationStrategy;
+
+ @Mock
+ private AgentBuilder.InitializationStrategy initializationStrategy;
+
+ @Mock
+ private AgentBuilder.InitializationStrategy.Dispatcher dispatcher;
+
+ @Mock
+ private TypePool typePool;
+
+ @Mock
+ private TypePool.Resolution resolution;
+
+ @Mock
+ private AgentBuilder.Listener listener;
+
+ @Mock
+ private AgentBuilder.InstallationListener installationListener;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ when(builder.make(TypeResolutionStrategy.Disabled.INSTANCE, typePool)).thenReturn((DynamicType.Unloaded) dynamicType);
+ when(dynamicType.getTypeDescription()).thenReturn(new TypeDescription.ForLoadedType(REDEFINED));
+ when(typeStrategy.builder(any(TypeDescription.class),
+ eq(byteBuddy),
+ any(ClassFileLocator.class),
+ any(MethodNameTransformer.class))).thenReturn((DynamicType.Builder) builder);
+ Map<TypeDescription, LoadedTypeInitializer> loadedTypeInitializers = new HashMap<TypeDescription, LoadedTypeInitializer>();
+ loadedTypeInitializers.put(new TypeDescription.ForLoadedType(REDEFINED), loadedTypeInitializer);
+ when(dynamicType.getLoadedTypeInitializers()).thenReturn(loadedTypeInitializers);
+ when(dynamicType.getBytes()).thenReturn(BAZ);
+ when(transformer.transform(builder, new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED)))
+ .thenReturn((DynamicType.Builder) builder);
+ when(poolStrategy.typePool(any(ClassFileLocator.class), any(ClassLoader.class))).thenReturn(typePool);
+ when(typePool.describe(REDEFINED.getName())).thenReturn(resolution);
+ when(instrumentation.getAllLoadedClasses()).thenReturn(new Class<?>[]{REDEFINED});
+ when(initializationStrategy.dispatcher()).thenReturn(dispatcher);
+ when(dispatcher.apply(builder)).thenReturn((DynamicType.Builder) builder);
+ }
+
+ @Test
+ public void testSuccessfulWithoutExistingClass() throws Exception {
+ when(dynamicType.getBytes()).thenReturn(BAZ);
+ when(resolution.resolve()).thenReturn(new TypeDescription.ForLoadedType(REDEFINED));
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), null, REDEFINED.getProtectionDomain()))
+ .thenReturn(true);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ assertThat(transform(classFileTransformer, JavaModule.ofType(REDEFINED), REDEFINED.getClassLoader(), REDEFINED.getName(), null, REDEFINED.getProtectionDomain(), QUX), is(BAZ));
+ verify(listener).onDiscovery(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), false);
+ verify(listener).onTransformation(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), false, dynamicType);
+ verify(listener).onComplete(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), false);
+ verifyNoMoreInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, false);
+ verifyNoMoreInteractions(instrumentation);
+ verify(initializationStrategy).dispatcher();
+ verifyNoMoreInteractions(initializationStrategy);
+ verify(dispatcher).apply(builder);
+ verify(dispatcher).register(dynamicType,
+ REDEFINED.getClassLoader(),
+ new AgentBuilder.Default.Transformation.Simple.Resolution.BootstrapClassLoaderCapableInjectorFactory(
+ AgentBuilder.Default.BootstrapInjectionStrategy.Disabled.INSTANCE,
+ REDEFINED.getClassLoader(),
+ REDEFINED.getProtectionDomain()));
+ verifyNoMoreInteractions(dispatcher);
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), null, REDEFINED.getProtectionDomain());
+ verifyNoMoreInteractions(typeMatcher);
+ verify(transformer).transform(builder, new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED));
+ verifyNoMoreInteractions(transformer);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test
+ public void testSuccessfulWithoutExistingClassConjunction() throws Exception {
+ when(dynamicType.getBytes()).thenReturn(BAZ);
+ when(resolution.resolve()).thenReturn(new TypeDescription.ForLoadedType(REDEFINED));
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), null, REDEFINED.getProtectionDomain()))
+ .thenReturn(true);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(ElementMatchers.any()).and(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ assertThat(transform(classFileTransformer, JavaModule.ofType(REDEFINED), REDEFINED.getClassLoader(), REDEFINED.getName(), null, REDEFINED.getProtectionDomain(), QUX), is(BAZ));
+ verify(listener).onDiscovery(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), false);
+ verify(listener).onTransformation(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), false, dynamicType);
+ verify(listener).onComplete(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), false);
+ verifyNoMoreInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, false);
+ verifyNoMoreInteractions(instrumentation);
+ verify(initializationStrategy).dispatcher();
+ verifyNoMoreInteractions(initializationStrategy);
+ verify(dispatcher).apply(builder);
+ verify(dispatcher).register(dynamicType,
+ REDEFINED.getClassLoader(),
+ new AgentBuilder.Default.Transformation.Simple.Resolution.BootstrapClassLoaderCapableInjectorFactory(
+ AgentBuilder.Default.BootstrapInjectionStrategy.Disabled.INSTANCE,
+ REDEFINED.getClassLoader(),
+ REDEFINED.getProtectionDomain()));
+ verifyNoMoreInteractions(dispatcher);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test
+ public void testSuccessfulWithoutExistingClassDisjunction() throws Exception {
+ when(dynamicType.getBytes()).thenReturn(BAZ);
+ when(resolution.resolve()).thenReturn(new TypeDescription.ForLoadedType(REDEFINED));
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), null, REDEFINED.getProtectionDomain()))
+ .thenReturn(true);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(none()).or(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ assertThat(transform(classFileTransformer, JavaModule.ofType(REDEFINED), REDEFINED.getClassLoader(), REDEFINED.getName(), null, REDEFINED.getProtectionDomain(), QUX), is(BAZ));
+ verify(listener).onDiscovery(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), false);
+ verify(listener).onTransformation(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), false, dynamicType);
+ verify(listener).onComplete(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), false);
+ verifyNoMoreInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, false);
+ verifyNoMoreInteractions(instrumentation);
+ verify(initializationStrategy).dispatcher();
+ verifyNoMoreInteractions(initializationStrategy);
+ verify(dispatcher).apply(builder);
+ verify(dispatcher).register(dynamicType,
+ REDEFINED.getClassLoader(),
+ new AgentBuilder.Default.Transformation.Simple.Resolution.BootstrapClassLoaderCapableInjectorFactory(
+ AgentBuilder.Default.BootstrapInjectionStrategy.Disabled.INSTANCE,
+ REDEFINED.getClassLoader(),
+ REDEFINED.getProtectionDomain()));
+ verifyNoMoreInteractions(dispatcher);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test
+ public void testSuccessfulWithExistingClass() throws Exception {
+ when(dynamicType.getBytes()).thenReturn(BAZ);
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain()))
+ .thenReturn(true);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ assertThat(transform(classFileTransformer, JavaModule.ofType(REDEFINED), REDEFINED.getClassLoader(), REDEFINED.getName(), REDEFINED, REDEFINED.getProtectionDomain(), QUX), is(BAZ));
+ verify(listener).onDiscovery(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verify(listener).onTransformation(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true, dynamicType);
+ verify(listener).onComplete(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verifyNoMoreInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, false);
+ verifyNoMoreInteractions(instrumentation);
+ verify(initializationStrategy).dispatcher();
+ verifyNoMoreInteractions(initializationStrategy);
+ verify(dispatcher).apply(builder);
+ verify(dispatcher).register(dynamicType,
+ REDEFINED.getClassLoader(),
+ new AgentBuilder.Default.Transformation.Simple.Resolution.BootstrapClassLoaderCapableInjectorFactory(
+ AgentBuilder.Default.BootstrapInjectionStrategy.Disabled.INSTANCE,
+ REDEFINED.getClassLoader(),
+ REDEFINED.getProtectionDomain()));
+ verifyNoMoreInteractions(dispatcher);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test
+ public void testSuccessfulWithExistingClassFallback() throws Exception {
+ when(dynamicType.getBytes()).thenReturn(BAZ);
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain()))
+ .thenThrow(new RuntimeException());
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), null, REDEFINED.getProtectionDomain()))
+ .thenReturn(true);
+ when(resolution.resolve()).thenReturn(new TypeDescription.ForLoadedType(REDEFINED));
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .with(AgentBuilder.FallbackStrategy.Simple.ENABLED)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ assertThat(transform(classFileTransformer, JavaModule.ofType(REDEFINED), REDEFINED.getClassLoader(), REDEFINED.getName(), REDEFINED, REDEFINED.getProtectionDomain(), QUX), is(BAZ));
+ verify(listener).onDiscovery(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verify(listener).onTransformation(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true, dynamicType);
+ verify(listener).onComplete(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verifyNoMoreInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, false);
+ verifyNoMoreInteractions(instrumentation);
+ verify(initializationStrategy).dispatcher();
+ verifyNoMoreInteractions(initializationStrategy);
+ verify(dispatcher).apply(builder);
+ verify(dispatcher).register(dynamicType,
+ REDEFINED.getClassLoader(),
+ new AgentBuilder.Default.Transformation.Simple.Resolution.BootstrapClassLoaderCapableInjectorFactory(
+ AgentBuilder.Default.BootstrapInjectionStrategy.Disabled.INSTANCE,
+ REDEFINED.getClassLoader(),
+ REDEFINED.getProtectionDomain()));
+ verifyNoMoreInteractions(dispatcher);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test
+ public void testResetDisabled() throws Exception {
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ when(instrumentation.removeTransformer(classFileTransformer)).thenReturn(true);
+ classFileTransformer.reset(instrumentation, AgentBuilder.RedefinitionStrategy.DISABLED);
+ verifyZeroInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, false);
+ verify(instrumentation).removeTransformer(classFileTransformer);
+ verifyNoMoreInteractions(instrumentation);
+ }
+
+ @Test
+ public void testResetObsolete() throws Exception {
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ when(instrumentation.removeTransformer(classFileTransformer)).thenReturn(false);
+ assertThat(classFileTransformer.reset(instrumentation, AgentBuilder.RedefinitionStrategy.DISABLED), is(false));
+ verifyZeroInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, false);
+ verify(instrumentation).removeTransformer(classFileTransformer);
+ verifyNoMoreInteractions(instrumentation);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testResetRedefinitionUnsupported() throws Exception {
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ when(instrumentation.removeTransformer(classFileTransformer)).thenReturn(true);
+ classFileTransformer.reset(instrumentation, AgentBuilder.RedefinitionStrategy.REDEFINITION);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testResetRetransformationUnsupported() throws Exception {
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ when(instrumentation.removeTransformer(classFileTransformer)).thenReturn(true);
+ classFileTransformer.reset(instrumentation, AgentBuilder.RedefinitionStrategy.RETRANSFORMATION);
+ }
+
+ @Test
+ public void testResetRedefinitionWithError() throws Exception {
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ when(instrumentation.removeTransformer(classFileTransformer)).thenReturn(true);
+ when(instrumentation.isRedefineClassesSupported()).thenReturn(true);
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(instrumentation.getAllLoadedClasses()).thenReturn(new Class<?>[]{REDEFINED});
+ Throwable throwable = new RuntimeException();
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain()))
+ .thenThrow(throwable);
+ assertThat(classFileTransformer.reset(instrumentation, AgentBuilder.RedefinitionStrategy.REDEFINITION), is(true));
+ verifyZeroInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, false);
+ verify(instrumentation).getAllLoadedClasses();
+ verify(instrumentation).isRedefineClassesSupported();
+ verify(instrumentation).isModifiableClass(REDEFINED);
+ verify(instrumentation).removeTransformer(classFileTransformer);
+ verifyNoMoreInteractions(instrumentation);
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain());
+ verifyNoMoreInteractions(typeMatcher);
+ }
+
+ @Test
+ public void testResetRetransformationWithError() throws Exception {
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ when(instrumentation.removeTransformer(classFileTransformer)).thenReturn(true);
+ when(instrumentation.isRetransformClassesSupported()).thenReturn(true);
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(instrumentation.getAllLoadedClasses()).thenReturn(new Class<?>[]{REDEFINED});
+ Throwable throwable = new RuntimeException();
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain()))
+ .thenThrow(throwable);
+ assertThat(classFileTransformer.reset(instrumentation, AgentBuilder.RedefinitionStrategy.RETRANSFORMATION), is(true));
+ verifyZeroInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, false);
+ verify(instrumentation).isRetransformClassesSupported();
+ verify(instrumentation).getAllLoadedClasses();
+ verify(instrumentation).isModifiableClass(REDEFINED);
+ verify(instrumentation).removeTransformer(classFileTransformer);
+ verifyNoMoreInteractions(instrumentation);
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain());
+ verifyNoMoreInteractions(typeMatcher);
+ }
+
+ @Test
+ public void testResetRedefinitionWithErrorFromFallback() throws Exception {
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(AgentBuilder.FallbackStrategy.Simple.ENABLED)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ when(instrumentation.removeTransformer(classFileTransformer)).thenReturn(true);
+ when(instrumentation.isRedefineClassesSupported()).thenReturn(true);
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(instrumentation.getAllLoadedClasses()).thenReturn(new Class<?>[]{REDEFINED});
+ when(resolution.resolve()).thenReturn(new TypeDescription.ForLoadedType(REDEFINED));
+ Throwable throwable = new RuntimeException(), suppressed = new RuntimeException();
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain()))
+ .thenThrow(suppressed);
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), null, REDEFINED.getProtectionDomain()))
+ .thenThrow(throwable);
+ assertThat(classFileTransformer.reset(instrumentation, AgentBuilder.RedefinitionStrategy.REDEFINITION), is(true));
+ verifyZeroInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, false);
+ verify(instrumentation).getAllLoadedClasses();
+ verify(instrumentation).isRedefineClassesSupported();
+ verify(instrumentation).isModifiableClass(REDEFINED);
+ verify(instrumentation).removeTransformer(classFileTransformer);
+ verifyNoMoreInteractions(instrumentation);
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain());
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), null, REDEFINED.getProtectionDomain());
+ verifyNoMoreInteractions(typeMatcher);
+ }
+
+ @Test
+ public void testResetRetransformationWithErrorFromFallback() throws Exception {
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(AgentBuilder.FallbackStrategy.Simple.ENABLED)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ when(instrumentation.removeTransformer(classFileTransformer)).thenReturn(true);
+ when(instrumentation.isRetransformClassesSupported()).thenReturn(true);
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(instrumentation.getAllLoadedClasses()).thenReturn(new Class<?>[]{REDEFINED});
+ when(resolution.resolve()).thenReturn(new TypeDescription.ForLoadedType(REDEFINED));
+ Throwable throwable = new RuntimeException(), suppressed = new RuntimeException();
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain()))
+ .thenThrow(suppressed);
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), null, REDEFINED.getProtectionDomain()))
+ .thenThrow(throwable);
+ assertThat(classFileTransformer.reset(instrumentation, AgentBuilder.RedefinitionStrategy.RETRANSFORMATION), is(true));
+ verifyZeroInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, false);
+ verify(instrumentation).getAllLoadedClasses();
+ verify(instrumentation).isRetransformClassesSupported();
+ verify(instrumentation).isModifiableClass(REDEFINED);
+ verify(instrumentation).removeTransformer(classFileTransformer);
+ verifyNoMoreInteractions(instrumentation);
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain());
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), null, REDEFINED.getProtectionDomain());
+ verifyNoMoreInteractions(typeMatcher);
+ }
+
+ @Test
+ public void testSuccessfulWithRetransformationMatched() throws Exception {
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain())).thenReturn(true);
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(instrumentation.isRetransformClassesSupported()).thenReturn(true);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ verifyZeroInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, true);
+ verify(instrumentation).getAllLoadedClasses();
+ verify(instrumentation).isModifiableClass(REDEFINED);
+ verify(instrumentation).retransformClasses(REDEFINED);
+ verify(instrumentation).isRetransformClassesSupported();
+ verifyNoMoreInteractions(instrumentation);
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain());
+ verifyNoMoreInteractions(typeMatcher);
+ verifyZeroInteractions(initializationStrategy);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test
+ public void testSuccessfulWithRetransformationMatchedFallback() throws Exception {
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain()))
+ .thenThrow(new RuntimeException());
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), null, REDEFINED.getProtectionDomain()))
+ .thenReturn(true);
+ when(resolution.resolve()).thenReturn(new TypeDescription.ForLoadedType(REDEFINED));
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(instrumentation.isRetransformClassesSupported()).thenReturn(true);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(AgentBuilder.FallbackStrategy.Simple.ENABLED)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ verifyZeroInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, true);
+ verify(instrumentation).getAllLoadedClasses();
+ verify(instrumentation).isModifiableClass(REDEFINED);
+ verify(instrumentation).retransformClasses(REDEFINED);
+ verify(instrumentation).isRetransformClassesSupported();
+ verifyNoMoreInteractions(instrumentation);
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain());
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), null, REDEFINED.getProtectionDomain());
+ verifyNoMoreInteractions(typeMatcher);
+ verifyZeroInteractions(initializationStrategy);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test
+ public void testSuccessfulWithRetransformationMatchedAndReset() throws Exception {
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain())).thenReturn(true);
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(instrumentation.isRetransformClassesSupported()).thenReturn(true);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ when(instrumentation.removeTransformer(classFileTransformer)).thenReturn(true);
+ assertThat(classFileTransformer.reset(instrumentation, AgentBuilder.RedefinitionStrategy.RETRANSFORMATION), is(true));
+ verifyZeroInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, true);
+ verify(instrumentation).removeTransformer(classFileTransformer);
+ verify(instrumentation, times(2)).getAllLoadedClasses();
+ verify(instrumentation, times(2)).isModifiableClass(REDEFINED);
+ verify(instrumentation, times(2)).retransformClasses(REDEFINED);
+ verify(instrumentation, times(2)).isRetransformClassesSupported();
+ verifyNoMoreInteractions(instrumentation);
+ verify(typeMatcher, times(2)).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain());
+ verifyNoMoreInteractions(typeMatcher);
+ verifyZeroInteractions(initializationStrategy);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onReset(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test
+ public void testSuccessfulWithRetransformationMatchedFallbackAndReset() throws Exception {
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain()))
+ .thenThrow(new RuntimeException());
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), null, REDEFINED.getProtectionDomain()))
+ .thenReturn(true);
+ when(resolution.resolve()).thenReturn(new TypeDescription.ForLoadedType(REDEFINED));
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(instrumentation.isRetransformClassesSupported()).thenReturn(true);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(AgentBuilder.FallbackStrategy.Simple.ENABLED)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ when(instrumentation.removeTransformer(classFileTransformer)).thenReturn(true);
+ assertThat(classFileTransformer.reset(instrumentation, AgentBuilder.RedefinitionStrategy.RETRANSFORMATION), is(true));
+ verifyZeroInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, true);
+ verify(instrumentation).removeTransformer(classFileTransformer);
+ verify(instrumentation, times(2)).getAllLoadedClasses();
+ verify(instrumentation, times(2)).isModifiableClass(REDEFINED);
+ verify(instrumentation, times(2)).retransformClasses(REDEFINED);
+ verify(instrumentation, times(2)).isRetransformClassesSupported();
+ verifyNoMoreInteractions(instrumentation);
+ verify(typeMatcher, times(2)).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain());
+ verify(typeMatcher, times(2)).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), null, REDEFINED.getProtectionDomain());
+ verifyNoMoreInteractions(typeMatcher);
+ verifyZeroInteractions(initializationStrategy);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onReset(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test
+ public void testSkipRetransformationWithNonRedefinable() throws Exception {
+ when(dynamicType.getBytes()).thenReturn(BAZ);
+ when(resolution.resolve()).thenReturn(new TypeDescription.ForLoadedType(REDEFINED));
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain()))
+ .thenReturn(true);
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(false);
+ when(instrumentation.isRetransformClassesSupported()).thenReturn(true);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ verify(listener).onIgnored(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verify(listener).onComplete(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verifyNoMoreInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, true);
+ verify(instrumentation).isModifiableClass(REDEFINED);
+ verify(instrumentation).getAllLoadedClasses();
+ verify(instrumentation).isRetransformClassesSupported();
+ verifyNoMoreInteractions(instrumentation);
+ verifyZeroInteractions(typeMatcher);
+ verifyZeroInteractions(initializationStrategy);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test
+ public void testSkipRetransformationWithNonMatched() throws Exception {
+ when(dynamicType.getBytes()).thenReturn(BAZ);
+ when(resolution.resolve()).thenReturn(new TypeDescription.ForLoadedType(REDEFINED));
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain()))
+ .thenReturn(false);
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(instrumentation.isRetransformClassesSupported()).thenReturn(true);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ verify(listener).onIgnored(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verify(listener).onComplete(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verifyNoMoreInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, true);
+ verify(instrumentation).isModifiableClass(REDEFINED);
+ verify(instrumentation).getAllLoadedClasses();
+ verify(instrumentation).isRetransformClassesSupported();
+ verifyNoMoreInteractions(instrumentation);
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain());
+ verifyNoMoreInteractions(typeMatcher);
+ verifyZeroInteractions(initializationStrategy);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test
+ public void testSkipRetransformationWithNonMatchedListenerException() throws Exception {
+ when(dynamicType.getBytes()).thenReturn(BAZ);
+ when(resolution.resolve()).thenReturn(new TypeDescription.ForLoadedType(REDEFINED));
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain()))
+ .thenReturn(false);
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(instrumentation.isRetransformClassesSupported()).thenReturn(true);
+ doThrow(new RuntimeException()).when(listener).onIgnored(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ verify(listener).onIgnored(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verify(listener).onComplete(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verifyNoMoreInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, true);
+ verify(instrumentation).isModifiableClass(REDEFINED);
+ verify(instrumentation).getAllLoadedClasses();
+ verify(instrumentation).isRetransformClassesSupported();
+ verifyNoMoreInteractions(instrumentation);
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain());
+ verifyNoMoreInteractions(typeMatcher);
+ verifyZeroInteractions(initializationStrategy);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test
+ public void testSkipRetransformationWithNonMatchedListenerCompleteException() throws Exception {
+ when(dynamicType.getBytes()).thenReturn(BAZ);
+ when(resolution.resolve()).thenReturn(new TypeDescription.ForLoadedType(REDEFINED));
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain()))
+ .thenReturn(false);
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(instrumentation.isRetransformClassesSupported()).thenReturn(true);
+ doThrow(new RuntimeException()).when(listener).onComplete(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ verify(listener).onIgnored(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verify(listener).onComplete(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verifyNoMoreInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, true);
+ verify(instrumentation).isModifiableClass(REDEFINED);
+ verify(instrumentation).getAllLoadedClasses();
+ verify(instrumentation).isRetransformClassesSupported();
+ verifyNoMoreInteractions(instrumentation);
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain());
+ verifyNoMoreInteractions(typeMatcher);
+ verifyZeroInteractions(initializationStrategy);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testSuccessfulWithRetransformationMatchedChunked() throws Exception {
+ when(instrumentation.getAllLoadedClasses()).thenReturn(new Class<?>[]{REDEFINED, OTHER});
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain())).thenReturn(true);
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(OTHER), OTHER.getClassLoader(), JavaModule.ofType(OTHER), OTHER, OTHER.getProtectionDomain())).thenReturn(true);
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(instrumentation.isModifiableClass(OTHER)).thenReturn(true);
+ when(instrumentation.isRetransformClassesSupported()).thenReturn(true);
+ AgentBuilder.RedefinitionStrategy.BatchAllocator redefinitionBatchAllocator = mock(AgentBuilder.RedefinitionStrategy.BatchAllocator.class);
+ when(redefinitionBatchAllocator.batch(Arrays.asList(REDEFINED, OTHER)))
+ .thenReturn((Iterable) Arrays.asList(Collections.singletonList(REDEFINED), Collections.singletonList(OTHER)));
+ AgentBuilder.RedefinitionStrategy.Listener redefinitionListener = mock(AgentBuilder.RedefinitionStrategy.Listener.class);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
+ .with(redefinitionBatchAllocator)
+ .with(redefinitionListener)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ verifyZeroInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, true);
+ verify(instrumentation).getAllLoadedClasses();
+ verify(instrumentation).isModifiableClass(REDEFINED);
+ verify(instrumentation).isModifiableClass(OTHER);
+ verify(instrumentation).retransformClasses(REDEFINED);
+ verify(instrumentation).retransformClasses(OTHER);
+ verify(instrumentation).isRetransformClassesSupported();
+ verifyNoMoreInteractions(instrumentation);
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain());
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(OTHER), OTHER.getClassLoader(), JavaModule.ofType(OTHER), OTHER, OTHER.getProtectionDomain());
+ verifyNoMoreInteractions(typeMatcher);
+ verifyZeroInteractions(initializationStrategy);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ verify(redefinitionBatchAllocator).batch(Arrays.asList(REDEFINED, OTHER));
+ verifyNoMoreInteractions(redefinitionBatchAllocator);
+ verify(redefinitionListener).onBatch(0, Collections.<Class<?>>singletonList(REDEFINED), Arrays.asList(REDEFINED, OTHER));
+ verify(redefinitionListener).onBatch(1, Collections.<Class<?>>singletonList(OTHER), Arrays.asList(REDEFINED, OTHER));
+ verify(redefinitionListener).onComplete(2, Arrays.asList(REDEFINED, OTHER), Collections.<List<Class<?>>, Throwable>emptyMap());
+ verifyNoMoreInteractions(redefinitionListener);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testRetransformationChunkedOneFails() throws Exception {
+ when(instrumentation.getAllLoadedClasses()).thenReturn(new Class<?>[]{REDEFINED, OTHER});
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain())).thenReturn(true);
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(OTHER), OTHER.getClassLoader(), JavaModule.ofType(OTHER), OTHER, OTHER.getProtectionDomain())).thenReturn(true);
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(instrumentation.isModifiableClass(OTHER)).thenReturn(true);
+ when(instrumentation.isRetransformClassesSupported()).thenReturn(true);
+ Throwable throwable = new UnmodifiableClassException();
+ doThrow(throwable).when(instrumentation).retransformClasses(OTHER);
+ AgentBuilder.RedefinitionStrategy.BatchAllocator redefinitionBatchAllocator = mock(AgentBuilder.RedefinitionStrategy.BatchAllocator.class);
+ when(redefinitionBatchAllocator.batch(Arrays.asList(REDEFINED, OTHER)))
+ .thenReturn((Iterable) Arrays.asList(Collections.singletonList(REDEFINED), Collections.singletonList(OTHER)));
+ AgentBuilder.RedefinitionStrategy.Listener redefinitionListener = mock(AgentBuilder.RedefinitionStrategy.Listener.class);
+ when(redefinitionListener.onError(1, Collections.<Class<?>>singletonList(OTHER), throwable, Arrays.asList(REDEFINED, OTHER))).thenReturn((Iterable) Collections.emptyList());
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
+ .with(redefinitionBatchAllocator)
+ .with(redefinitionListener)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ verifyZeroInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, true);
+ verify(instrumentation).getAllLoadedClasses();
+ verify(instrumentation).isModifiableClass(REDEFINED);
+ verify(instrumentation).isModifiableClass(OTHER);
+ verify(instrumentation).retransformClasses(REDEFINED);
+ verify(instrumentation).retransformClasses(OTHER);
+ verify(instrumentation).isRetransformClassesSupported();
+ verifyNoMoreInteractions(instrumentation);
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain());
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(OTHER), OTHER.getClassLoader(), JavaModule.ofType(OTHER), OTHER, OTHER.getProtectionDomain());
+ verifyNoMoreInteractions(typeMatcher);
+ verifyZeroInteractions(initializationStrategy);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ verify(redefinitionBatchAllocator).batch(Arrays.asList(REDEFINED, OTHER));
+ verifyNoMoreInteractions(redefinitionBatchAllocator);
+ verify(redefinitionListener).onBatch(0, Collections.<Class<?>>singletonList(REDEFINED), Arrays.asList(REDEFINED, OTHER));
+ verify(redefinitionListener).onBatch(1, Collections.<Class<?>>singletonList(OTHER), Arrays.asList(REDEFINED, OTHER));
+ verify(redefinitionListener).onError(1, Collections.<Class<?>>singletonList(OTHER), throwable, Arrays.asList(REDEFINED, OTHER));
+ verify(redefinitionListener).onComplete(2, Arrays.asList(REDEFINED, OTHER), Collections.singletonMap(Collections.<Class<?>>singletonList(OTHER), throwable));
+ verifyNoMoreInteractions(redefinitionListener);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testRetransformationChunkedOneFailsResubmit() throws Exception {
+ when(instrumentation.getAllLoadedClasses()).thenReturn(new Class<?>[]{REDEFINED, OTHER});
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain())).thenReturn(true);
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(OTHER), OTHER.getClassLoader(), JavaModule.ofType(OTHER), OTHER, OTHER.getProtectionDomain())).thenReturn(true);
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(instrumentation.isModifiableClass(OTHER)).thenReturn(true);
+ when(instrumentation.isRetransformClassesSupported()).thenReturn(true);
+ Throwable throwable = new UnmodifiableClassException();
+ doThrow(throwable).when(instrumentation).retransformClasses(OTHER);
+ AgentBuilder.RedefinitionStrategy.BatchAllocator redefinitionBatchAllocator = mock(AgentBuilder.RedefinitionStrategy.BatchAllocator.class);
+ when(redefinitionBatchAllocator.batch(Arrays.asList(REDEFINED, OTHER)))
+ .thenReturn((Iterable) Arrays.asList(Collections.singletonList(REDEFINED), Collections.singletonList(OTHER)));
+ AgentBuilder.RedefinitionStrategy.Listener redefinitionListener = mock(AgentBuilder.RedefinitionStrategy.Listener.class);
+ when(redefinitionListener.onError(1, Collections.<Class<?>>singletonList(OTHER), throwable, Arrays.asList(REDEFINED, OTHER)))
+ .thenReturn((Iterable) Collections.singleton(Collections.singletonList(OTHER)));
+ when(redefinitionListener.onError(2, Collections.<Class<?>>singletonList(OTHER), throwable, Arrays.asList(REDEFINED, OTHER)))
+ .thenReturn((Iterable) Collections.emptyList());
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
+ .with(redefinitionBatchAllocator)
+ .with(redefinitionListener)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ verifyZeroInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, true);
+ verify(instrumentation).getAllLoadedClasses();
+ verify(instrumentation).isModifiableClass(REDEFINED);
+ verify(instrumentation).isModifiableClass(OTHER);
+ verify(instrumentation).retransformClasses(REDEFINED);
+ verify(instrumentation, times(2)).retransformClasses(OTHER);
+ verify(instrumentation).isRetransformClassesSupported();
+ verifyNoMoreInteractions(instrumentation);
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain());
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(OTHER), OTHER.getClassLoader(), JavaModule.ofType(OTHER), OTHER, OTHER.getProtectionDomain());
+ verifyNoMoreInteractions(typeMatcher);
+ verifyZeroInteractions(initializationStrategy);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ verify(redefinitionBatchAllocator).batch(Arrays.asList(REDEFINED, OTHER));
+ verifyNoMoreInteractions(redefinitionBatchAllocator);
+ verify(redefinitionListener).onBatch(0, Collections.<Class<?>>singletonList(REDEFINED), Arrays.asList(REDEFINED, OTHER));
+ verify(redefinitionListener).onBatch(1, Collections.<Class<?>>singletonList(OTHER), Arrays.asList(REDEFINED, OTHER));
+ verify(redefinitionListener).onBatch(2, Collections.<Class<?>>singletonList(OTHER), Arrays.asList(REDEFINED, OTHER));
+ verify(redefinitionListener).onError(1, Collections.<Class<?>>singletonList(OTHER), throwable, Arrays.asList(REDEFINED, OTHER));
+ verify(redefinitionListener).onError(2, Collections.<Class<?>>singletonList(OTHER), throwable, Arrays.asList(REDEFINED, OTHER));
+ verify(redefinitionListener).onComplete(3, Arrays.asList(REDEFINED, OTHER), Collections.singletonMap(Collections.<Class<?>>singletonList(OTHER), throwable));
+ verifyNoMoreInteractions(redefinitionListener);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testRetransformationChunkedOneFailsEscalated() throws Exception {
+ when(instrumentation.getAllLoadedClasses()).thenReturn(new Class<?>[]{REDEFINED, OTHER});
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain())).thenReturn(true);
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(OTHER), OTHER.getClassLoader(), JavaModule.ofType(OTHER), OTHER, OTHER.getProtectionDomain())).thenReturn(true);
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(instrumentation.isModifiableClass(OTHER)).thenReturn(true);
+ when(instrumentation.isRetransformClassesSupported()).thenReturn(true);
+ Throwable throwable = new RuntimeException();
+ doThrow(throwable).when(instrumentation).retransformClasses(REDEFINED, OTHER);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
+ .with(AgentBuilder.RedefinitionStrategy.BatchAllocator.ForTotal.INSTANCE)
+ .with(AgentBuilder.RedefinitionStrategy.Listener.ErrorEscalating.FAIL_FAST)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ verifyZeroInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, true);
+ verify(instrumentation).getAllLoadedClasses();
+ verify(instrumentation).isModifiableClass(REDEFINED);
+ verify(instrumentation).isModifiableClass(OTHER);
+ verify(instrumentation).retransformClasses(REDEFINED, OTHER);
+ verify(instrumentation).isRetransformClassesSupported();
+ verifyNoMoreInteractions(instrumentation);
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain());
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(OTHER), OTHER.getClassLoader(), JavaModule.ofType(OTHER), OTHER, OTHER.getProtectionDomain());
+ verifyNoMoreInteractions(typeMatcher);
+ verifyZeroInteractions(initializationStrategy);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onError(eq(instrumentation), eq(classFileTransformer), argThat(new CauseMatcher(throwable)));
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testRetransformationNotSupported() throws Exception {
+ new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ }
+
+ @Test
+ public void testSuccessfulWithRedefinitionMatched() throws Exception {
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain()))
+ .thenReturn(true);
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(instrumentation.isRedefineClassesSupported()).thenReturn(true);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ verifyZeroInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, false);
+ verify(instrumentation).getAllLoadedClasses();
+ verify(instrumentation).isModifiableClass(REDEFINED);
+ verify(instrumentation).redefineClasses(any(ClassDefinition.class));
+ verify(instrumentation).isRedefineClassesSupported();
+ verifyNoMoreInteractions(instrumentation);
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain());
+ verifyNoMoreInteractions(typeMatcher);
+ verifyZeroInteractions(dispatcher);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test
+ public void testSuccessfulWithRedefinitionMatchedFallback() throws Exception {
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain()))
+ .thenThrow(new RuntimeException());
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), null, REDEFINED.getProtectionDomain()))
+ .thenReturn(true);
+ when(resolution.resolve()).thenReturn(new TypeDescription.ForLoadedType(REDEFINED));
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(instrumentation.isRedefineClassesSupported()).thenReturn(true);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(AgentBuilder.FallbackStrategy.Simple.ENABLED)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ verifyZeroInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, false);
+ verify(instrumentation).getAllLoadedClasses();
+ verify(instrumentation).isModifiableClass(REDEFINED);
+ verify(instrumentation).redefineClasses(any(ClassDefinition.class));
+ verify(instrumentation).isRedefineClassesSupported();
+ verifyNoMoreInteractions(instrumentation);
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain());
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), null, REDEFINED.getProtectionDomain());
+ verifyNoMoreInteractions(typeMatcher);
+ verifyZeroInteractions(initializationStrategy);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test
+ public void testSuccessfulWithRedefinitionMatchedAndReset() throws Exception {
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain()))
+ .thenReturn(true);
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(instrumentation.isRedefineClassesSupported()).thenReturn(true);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ when(instrumentation.removeTransformer(classFileTransformer)).thenReturn(true);
+ assertThat(classFileTransformer.reset(instrumentation, AgentBuilder.RedefinitionStrategy.REDEFINITION), is(true));
+ verifyZeroInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, false);
+ verify(instrumentation).removeTransformer(classFileTransformer);
+ verify(instrumentation, times(2)).getAllLoadedClasses();
+ verify(instrumentation, times(2)).isModifiableClass(REDEFINED);
+ verify(instrumentation, times(2)).redefineClasses(any(ClassDefinition.class));
+ verify(instrumentation, times(2)).isRedefineClassesSupported();
+ verifyNoMoreInteractions(instrumentation);
+ verify(typeMatcher, times(2)).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain());
+ verifyNoMoreInteractions(typeMatcher);
+ verifyZeroInteractions(dispatcher);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onReset(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test
+ public void testSuccessfulWithRedefinitionMatchedFallbackAndReset() throws Exception {
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain()))
+ .thenThrow(new RuntimeException());
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), null, REDEFINED.getProtectionDomain()))
+ .thenReturn(true);
+ when(resolution.resolve()).thenReturn(new TypeDescription.ForLoadedType(REDEFINED));
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(instrumentation.isRedefineClassesSupported()).thenReturn(true);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(AgentBuilder.FallbackStrategy.Simple.ENABLED)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ when(instrumentation.removeTransformer(classFileTransformer)).thenReturn(true);
+ assertThat(classFileTransformer.reset(instrumentation, AgentBuilder.RedefinitionStrategy.REDEFINITION), is(true));
+ verifyZeroInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, false);
+ verify(instrumentation).removeTransformer(classFileTransformer);
+ verify(instrumentation, times(2)).getAllLoadedClasses();
+ verify(instrumentation, times(2)).isModifiableClass(REDEFINED);
+ verify(instrumentation, times(2)).redefineClasses(any(ClassDefinition.class));
+ verify(instrumentation, times(2)).isRedefineClassesSupported();
+ verifyNoMoreInteractions(instrumentation);
+ verify(typeMatcher, times(2)).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain());
+ verify(typeMatcher, times(2)).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), null, REDEFINED.getProtectionDomain());
+ verifyNoMoreInteractions(typeMatcher);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onReset(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test
+ public void testSkipRedefinitionWithNonRedefinable() throws Exception {
+ when(dynamicType.getBytes()).thenReturn(BAZ);
+ when(resolution.resolve()).thenReturn(new TypeDescription.ForLoadedType(REDEFINED));
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain()))
+ .thenReturn(true);
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(false);
+ when(instrumentation.isRedefineClassesSupported()).thenReturn(true);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ verify(listener).onIgnored(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verify(listener).onComplete(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verifyNoMoreInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, false);
+ verify(instrumentation).isModifiableClass(REDEFINED);
+ verify(instrumentation).getAllLoadedClasses();
+ verify(instrumentation).isRedefineClassesSupported();
+ verifyNoMoreInteractions(instrumentation);
+ verifyZeroInteractions(typeMatcher);
+ verifyZeroInteractions(initializationStrategy);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test
+ public void testSkipRedefinitionWithNonMatched() throws Exception {
+ when(dynamicType.getBytes()).thenReturn(BAZ);
+ when(resolution.resolve()).thenReturn(new TypeDescription.ForLoadedType(REDEFINED));
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain()))
+ .thenReturn(false);
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(instrumentation.isRedefineClassesSupported()).thenReturn(true);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ verify(listener).onIgnored(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verify(listener).onComplete(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verifyNoMoreInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, false);
+ verify(instrumentation).isModifiableClass(REDEFINED);
+ verify(instrumentation).getAllLoadedClasses();
+ verify(instrumentation).isRedefineClassesSupported();
+ verifyNoMoreInteractions(instrumentation);
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain());
+ verifyNoMoreInteractions(typeMatcher);
+ verifyZeroInteractions(initializationStrategy);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test
+ public void testSkipRedefinitionWithIgnoredType() throws Exception {
+ when(dynamicType.getBytes()).thenReturn(BAZ);
+ when(resolution.resolve()).thenReturn(new TypeDescription.ForLoadedType(REDEFINED));
+ @SuppressWarnings("unchecked")
+ ElementMatcher<? super TypeDescription> ignoredTypes = mock(ElementMatcher.class);
+ when(ignoredTypes.matches(new TypeDescription.ForLoadedType(REDEFINED))).thenReturn(true);
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(instrumentation.isRedefineClassesSupported()).thenReturn(true);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(ignoredTypes)
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ verify(listener).onIgnored(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verify(listener).onComplete(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verifyNoMoreInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, false);
+ verify(instrumentation).isModifiableClass(REDEFINED);
+ verify(instrumentation).getAllLoadedClasses();
+ verify(instrumentation).isRedefineClassesSupported();
+ verifyNoMoreInteractions(instrumentation);
+ verifyZeroInteractions(typeMatcher);
+ verifyZeroInteractions(initializationStrategy);
+ verify(ignoredTypes).matches(new TypeDescription.ForLoadedType(REDEFINED));
+ verifyNoMoreInteractions(ignoredTypes);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test
+ public void testSkipRedefinitionWithIgnoredClassLoader() throws Exception {
+ when(dynamicType.getBytes()).thenReturn(BAZ);
+ when(resolution.resolve()).thenReturn(new TypeDescription.ForLoadedType(REDEFINED));
+ @SuppressWarnings("unchecked")
+ ElementMatcher<? super TypeDescription> ignoredTypes = mock(ElementMatcher.class);
+ when(ignoredTypes.matches(new TypeDescription.ForLoadedType(REDEFINED))).thenReturn(true);
+ @SuppressWarnings("unchecked")
+ ElementMatcher<? super ClassLoader> ignoredClassLoaders = mock(ElementMatcher.class);
+ when(ignoredClassLoaders.matches(REDEFINED.getClassLoader())).thenReturn(true);
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(instrumentation.isRedefineClassesSupported()).thenReturn(true);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(ignoredTypes, ignoredClassLoaders)
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ verify(listener).onIgnored(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verify(listener).onComplete(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verifyNoMoreInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, false);
+ verify(instrumentation).isModifiableClass(REDEFINED);
+ verify(instrumentation).getAllLoadedClasses();
+ verify(instrumentation).isRedefineClassesSupported();
+ verifyNoMoreInteractions(instrumentation);
+ verifyZeroInteractions(typeMatcher);
+ verifyZeroInteractions(initializationStrategy);
+ verify(ignoredClassLoaders).matches(REDEFINED.getClassLoader());
+ verifyNoMoreInteractions(ignoredClassLoaders);
+ verify(ignoredTypes).matches(new TypeDescription.ForLoadedType(REDEFINED));
+ verifyNoMoreInteractions(ignoredTypes);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test
+ public void testSkipRedefinitionWithIgnoredTypeChainedConjunction() throws Exception {
+ when(dynamicType.getBytes()).thenReturn(BAZ);
+ when(resolution.resolve()).thenReturn(new TypeDescription.ForLoadedType(REDEFINED));
+ @SuppressWarnings("unchecked")
+ ElementMatcher<? super TypeDescription> ignoredTypes = mock(ElementMatcher.class);
+ when(ignoredTypes.matches(new TypeDescription.ForLoadedType(REDEFINED))).thenReturn(true);
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(instrumentation.isRedefineClassesSupported()).thenReturn(true);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(ElementMatchers.any()).and(ignoredTypes)
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ verify(listener).onIgnored(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verify(listener).onComplete(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verifyNoMoreInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, false);
+ verify(instrumentation).isModifiableClass(REDEFINED);
+ verify(instrumentation).getAllLoadedClasses();
+ verify(instrumentation).isRedefineClassesSupported();
+ verifyNoMoreInteractions(instrumentation);
+ verifyZeroInteractions(typeMatcher);
+ verifyZeroInteractions(initializationStrategy);
+ verify(ignoredTypes).matches(new TypeDescription.ForLoadedType(REDEFINED));
+ verifyNoMoreInteractions(ignoredTypes);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test
+ public void testSkipRedefinitionWithIgnoredTypeChainedDisjunction() throws Exception {
+ when(dynamicType.getBytes()).thenReturn(BAZ);
+ when(resolution.resolve()).thenReturn(new TypeDescription.ForLoadedType(REDEFINED));
+ @SuppressWarnings("unchecked")
+ ElementMatcher<? super TypeDescription> ignoredTypes = mock(ElementMatcher.class);
+ when(ignoredTypes.matches(new TypeDescription.ForLoadedType(REDEFINED))).thenReturn(true);
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(instrumentation.isRedefineClassesSupported()).thenReturn(true);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none()).or(ignoredTypes)
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ verify(listener).onIgnored(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verify(listener).onComplete(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verifyNoMoreInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, false);
+ verify(instrumentation).isModifiableClass(REDEFINED);
+ verify(instrumentation).getAllLoadedClasses();
+ verify(instrumentation).isRedefineClassesSupported();
+ verifyNoMoreInteractions(instrumentation);
+ verifyZeroInteractions(typeMatcher);
+ verifyZeroInteractions(initializationStrategy);
+ verify(ignoredTypes).matches(new TypeDescription.ForLoadedType(REDEFINED));
+ verifyNoMoreInteractions(ignoredTypes);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test
+ public void testSkipRedefinitionWithNonMatchedListenerException() throws Exception {
+ when(dynamicType.getBytes()).thenReturn(BAZ);
+ when(resolution.resolve()).thenReturn(new TypeDescription.ForLoadedType(REDEFINED));
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain()))
+ .thenReturn(false);
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(instrumentation.isRedefineClassesSupported()).thenReturn(true);
+ doThrow(new RuntimeException()).when(listener).onIgnored(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ verify(listener).onIgnored(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verify(listener).onComplete(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verifyNoMoreInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, false);
+ verify(instrumentation).isModifiableClass(REDEFINED);
+ verify(instrumentation).getAllLoadedClasses();
+ verify(instrumentation).isRedefineClassesSupported();
+ verifyNoMoreInteractions(instrumentation);
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain());
+ verifyNoMoreInteractions(typeMatcher);
+ verifyZeroInteractions(initializationStrategy);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test
+ public void testSkipRedefinitionWithNonMatchedListenerFinishedException() throws Exception {
+ when(dynamicType.getBytes()).thenReturn(BAZ);
+ when(resolution.resolve()).thenReturn(new TypeDescription.ForLoadedType(REDEFINED));
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain()))
+ .thenReturn(false);
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(instrumentation.isRedefineClassesSupported()).thenReturn(true);
+ doThrow(new RuntimeException()).when(listener).onComplete(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ verify(listener).onIgnored(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verify(listener).onComplete(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verifyNoMoreInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, false);
+ verify(instrumentation).isModifiableClass(REDEFINED);
+ verify(instrumentation).getAllLoadedClasses();
+ verify(instrumentation).isRedefineClassesSupported();
+ verifyNoMoreInteractions(instrumentation);
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain());
+ verifyNoMoreInteractions(typeMatcher);
+ verifyZeroInteractions(initializationStrategy);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testSuccessfulWithRedefinitionMatchedChunked() throws Exception {
+ when(instrumentation.getAllLoadedClasses()).thenReturn(new Class<?>[]{REDEFINED, OTHER});
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain())).thenReturn(true);
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(OTHER), OTHER.getClassLoader(), JavaModule.ofType(OTHER), OTHER, OTHER.getProtectionDomain())).thenReturn(true);
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(instrumentation.isModifiableClass(OTHER)).thenReturn(true);
+ when(instrumentation.isRedefineClassesSupported()).thenReturn(true);
+ AgentBuilder.RedefinitionStrategy.BatchAllocator redefinitionBatchAllocator = mock(AgentBuilder.RedefinitionStrategy.BatchAllocator.class);
+ when(redefinitionBatchAllocator.batch(Arrays.asList(REDEFINED, OTHER)))
+ .thenReturn((Iterable) Arrays.asList(Collections.singletonList(REDEFINED), Collections.singletonList(OTHER)));
+ AgentBuilder.RedefinitionStrategy.Listener redefinitionListener = mock(AgentBuilder.RedefinitionStrategy.Listener.class);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
+ .with(redefinitionBatchAllocator)
+ .with(redefinitionListener)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ verifyZeroInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, false);
+ verify(instrumentation).getAllLoadedClasses();
+ verify(instrumentation).isModifiableClass(REDEFINED);
+ verify(instrumentation).isModifiableClass(OTHER);
+ verify(instrumentation, times(2)).redefineClasses(any(ClassDefinition.class));
+ verify(instrumentation).isRedefineClassesSupported();
+ verifyNoMoreInteractions(instrumentation);
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain());
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(OTHER), OTHER.getClassLoader(), JavaModule.ofType(OTHER), OTHER, OTHER.getProtectionDomain());
+ verifyNoMoreInteractions(typeMatcher);
+ verifyZeroInteractions(initializationStrategy);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ verify(redefinitionBatchAllocator).batch(Arrays.asList(REDEFINED, OTHER));
+ verifyNoMoreInteractions(redefinitionBatchAllocator);
+ verify(redefinitionListener).onBatch(0, Collections.<Class<?>>singletonList(REDEFINED), Arrays.asList(REDEFINED, OTHER));
+ verify(redefinitionListener).onBatch(1, Collections.<Class<?>>singletonList(OTHER), Arrays.asList(REDEFINED, OTHER));
+ verify(redefinitionListener).onComplete(2, Arrays.asList(REDEFINED, OTHER), Collections.<List<Class<?>>, Throwable>emptyMap());
+ verifyNoMoreInteractions(redefinitionListener);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testRedefinitionChunkedOneFails() throws Exception {
+ when(instrumentation.getAllLoadedClasses()).thenReturn(new Class<?>[]{REDEFINED, OTHER});
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain())).thenReturn(true);
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(OTHER), OTHER.getClassLoader(), JavaModule.ofType(OTHER), OTHER, OTHER.getProtectionDomain())).thenReturn(true);
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(instrumentation.isModifiableClass(OTHER)).thenReturn(true);
+ when(instrumentation.isRedefineClassesSupported()).thenReturn(true);
+ Throwable throwable = new ClassNotFoundException();
+ doThrow(throwable).when(instrumentation).redefineClasses(argThat(new ClassRedefinitionMatcher(OTHER)));
+ AgentBuilder.RedefinitionStrategy.BatchAllocator redefinitionBatchAllocator = mock(AgentBuilder.RedefinitionStrategy.BatchAllocator.class);
+ when(redefinitionBatchAllocator.batch(Arrays.asList(REDEFINED, OTHER)))
+ .thenReturn((Iterable) Arrays.asList(Collections.singletonList(REDEFINED), Collections.singletonList(OTHER)));
+ AgentBuilder.RedefinitionStrategy.Listener redefinitionListener = mock(AgentBuilder.RedefinitionStrategy.Listener.class);
+ when(redefinitionListener.onError(1, Collections.<Class<?>>singletonList(OTHER), throwable, Arrays.asList(REDEFINED, OTHER))).thenReturn((Iterable) Collections.emptyList());
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
+ .with(redefinitionBatchAllocator)
+ .with(redefinitionListener)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ verifyZeroInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, false);
+ verify(instrumentation).getAllLoadedClasses();
+ verify(instrumentation).isModifiableClass(REDEFINED);
+ verify(instrumentation).isModifiableClass(OTHER);
+ verify(instrumentation).redefineClasses(argThat(new ClassRedefinitionMatcher(REDEFINED)));
+ verify(instrumentation).redefineClasses(argThat(new ClassRedefinitionMatcher(OTHER)));
+ verify(instrumentation).isRedefineClassesSupported();
+ verifyNoMoreInteractions(instrumentation);
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain());
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(OTHER), OTHER.getClassLoader(), JavaModule.ofType(OTHER), OTHER, OTHER.getProtectionDomain());
+ verifyNoMoreInteractions(typeMatcher);
+ verifyZeroInteractions(initializationStrategy);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ verify(redefinitionBatchAllocator).batch(Arrays.asList(REDEFINED, OTHER));
+ verifyNoMoreInteractions(redefinitionBatchAllocator);
+ verify(redefinitionListener).onBatch(0, Collections.<Class<?>>singletonList(REDEFINED), Arrays.asList(REDEFINED, OTHER));
+ verify(redefinitionListener).onBatch(1, Collections.<Class<?>>singletonList(OTHER), Arrays.asList(REDEFINED, OTHER));
+ verify(redefinitionListener).onError(1, Collections.<Class<?>>singletonList(OTHER), throwable, Arrays.asList(REDEFINED, OTHER));
+ verify(redefinitionListener).onComplete(2, Arrays.asList(REDEFINED, OTHER), Collections.singletonMap(Collections.<Class<?>>singletonList(OTHER), throwable));
+ verifyNoMoreInteractions(redefinitionListener);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testRedefinitionChunkedOneFailsResubmit() throws Exception {
+ when(instrumentation.getAllLoadedClasses()).thenReturn(new Class<?>[]{REDEFINED, OTHER});
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain())).thenReturn(true);
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(OTHER), OTHER.getClassLoader(), JavaModule.ofType(OTHER), OTHER, OTHER.getProtectionDomain())).thenReturn(true);
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(instrumentation.isModifiableClass(OTHER)).thenReturn(true);
+ when(instrumentation.isRedefineClassesSupported()).thenReturn(true);
+ Throwable throwable = new ClassNotFoundException();
+ doThrow(throwable).when(instrumentation).redefineClasses(argThat(new ClassRedefinitionMatcher(OTHER)));
+ AgentBuilder.RedefinitionStrategy.BatchAllocator redefinitionBatchAllocator = mock(AgentBuilder.RedefinitionStrategy.BatchAllocator.class);
+ when(redefinitionBatchAllocator.batch(Arrays.asList(REDEFINED, OTHER)))
+ .thenReturn((Iterable) Arrays.asList(Collections.singletonList(REDEFINED), Collections.singletonList(OTHER)));
+ AgentBuilder.RedefinitionStrategy.Listener redefinitionListener = mock(AgentBuilder.RedefinitionStrategy.Listener.class);
+ when(redefinitionListener.onError(1, Collections.<Class<?>>singletonList(OTHER), throwable, Arrays.asList(REDEFINED, OTHER)))
+ .thenReturn((Iterable) Collections.singleton(Collections.singletonList(OTHER)));
+ when(redefinitionListener.onError(2, Collections.<Class<?>>singletonList(OTHER), throwable, Arrays.asList(REDEFINED, OTHER)))
+ .thenReturn((Iterable) Collections.emptyList());
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
+ .with(redefinitionBatchAllocator)
+ .with(redefinitionListener)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ verifyZeroInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, false);
+ verify(instrumentation).getAllLoadedClasses();
+ verify(instrumentation).isModifiableClass(REDEFINED);
+ verify(instrumentation).isModifiableClass(OTHER);
+ verify(instrumentation).redefineClasses(argThat(new ClassRedefinitionMatcher(REDEFINED)));
+ verify(instrumentation, times(2)).redefineClasses(argThat(new ClassRedefinitionMatcher(OTHER)));
+ verify(instrumentation).isRedefineClassesSupported();
+ verifyNoMoreInteractions(instrumentation);
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain());
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(OTHER), OTHER.getClassLoader(), JavaModule.ofType(OTHER), OTHER, OTHER.getProtectionDomain());
+ verifyNoMoreInteractions(typeMatcher);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ verifyZeroInteractions(installationListener);
+ verify(redefinitionBatchAllocator).batch(Arrays.asList(REDEFINED, OTHER));
+ verifyNoMoreInteractions(redefinitionBatchAllocator);
+ verify(redefinitionListener).onBatch(0, Collections.<Class<?>>singletonList(REDEFINED), Arrays.asList(REDEFINED, OTHER));
+ verify(redefinitionListener).onBatch(1, Collections.<Class<?>>singletonList(OTHER), Arrays.asList(REDEFINED, OTHER));
+ verify(redefinitionListener).onBatch(2, Collections.<Class<?>>singletonList(OTHER), Arrays.asList(REDEFINED, OTHER));
+ verify(redefinitionListener).onError(1, Collections.<Class<?>>singletonList(OTHER), throwable, Arrays.asList(REDEFINED, OTHER));
+ verify(redefinitionListener).onError(2, Collections.<Class<?>>singletonList(OTHER), throwable, Arrays.asList(REDEFINED, OTHER));
+ verify(redefinitionListener).onComplete(3, Arrays.asList(REDEFINED, OTHER), Collections.singletonMap(Collections.<Class<?>>singletonList(OTHER), throwable));
+ verifyNoMoreInteractions(redefinitionListener);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testRedefinitionChunkedOneFailsEscalated() throws Exception {
+ when(instrumentation.getAllLoadedClasses()).thenReturn(new Class<?>[]{REDEFINED, OTHER});
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain())).thenReturn(true);
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(OTHER), OTHER.getClassLoader(), JavaModule.ofType(OTHER), OTHER, OTHER.getProtectionDomain())).thenReturn(true);
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(instrumentation.isModifiableClass(OTHER)).thenReturn(true);
+ when(instrumentation.isRedefineClassesSupported()).thenReturn(true);
+ Throwable throwable = new RuntimeException();
+ doThrow(throwable).when(instrumentation).redefineClasses(any(ClassDefinition.class), any(ClassDefinition.class));
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
+ .with(AgentBuilder.RedefinitionStrategy.BatchAllocator.ForTotal.INSTANCE)
+ .with(AgentBuilder.RedefinitionStrategy.Listener.ErrorEscalating.FAIL_FAST)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ verifyZeroInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, false);
+ verify(instrumentation).getAllLoadedClasses();
+ verify(instrumentation).isModifiableClass(REDEFINED);
+ verify(instrumentation).isModifiableClass(OTHER);
+ verify(instrumentation).redefineClasses(any(ClassDefinition.class), any(ClassDefinition.class));
+ verify(instrumentation).isRedefineClassesSupported();
+ verifyNoMoreInteractions(instrumentation);
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain());
+ verify(typeMatcher).matches(new TypeDescription.ForLoadedType(OTHER), OTHER.getClassLoader(), JavaModule.ofType(OTHER), OTHER, OTHER.getProtectionDomain());
+ verifyNoMoreInteractions(typeMatcher);
+ verifyZeroInteractions(initializationStrategy);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onError(eq(instrumentation), eq(classFileTransformer), argThat(new CauseMatcher(throwable)));
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testRedefinitionNotSupported() throws Exception {
+ new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ }
+
+ @Test
+ public void testTransformationWithError() throws Exception {
+ when(dynamicType.getBytes()).thenReturn(BAZ);
+ RuntimeException exception = new RuntimeException();
+ when(resolution.resolve()).thenThrow(exception);
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain()))
+ .thenReturn(true);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ assertThat(transform(classFileTransformer, JavaModule.ofType(REDEFINED), REDEFINED.getClassLoader(), REDEFINED.getName(), null, REDEFINED.getProtectionDomain(), QUX),
+ nullValue(byte[].class));
+ verify(listener).onDiscovery(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), false);
+ verify(listener).onError(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), false, exception);
+ verify(listener).onComplete(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), false);
+ verifyNoMoreInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, false);
+ verifyNoMoreInteractions(instrumentation);
+ verifyZeroInteractions(initializationStrategy);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test
+ public void testIgnored() throws Exception {
+ when(dynamicType.getBytes()).thenReturn(BAZ);
+ when(resolution.resolve()).thenReturn(new TypeDescription.ForLoadedType(REDEFINED));
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain()))
+ .thenReturn(false);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ assertThat(transform(classFileTransformer, JavaModule.ofType(REDEFINED), REDEFINED.getClassLoader(), REDEFINED.getName(), REDEFINED, REDEFINED.getProtectionDomain(), QUX),
+ nullValue(byte[].class));
+ verify(listener).onDiscovery(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verify(listener).onIgnored(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verify(listener).onComplete(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verifyNoMoreInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, false);
+ verifyNoMoreInteractions(instrumentation);
+ verifyZeroInteractions(initializationStrategy);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testEmptyPrefixThrowsException() throws Exception {
+ new AgentBuilder.Default(byteBuddy).enableNativeMethodPrefix("");
+ }
+
+ @Test
+ public void testAuxiliaryTypeInitialization() throws Exception {
+ when(dynamicType.getAuxiliaryTypes()).thenReturn(Collections.<TypeDescription, byte[]>singletonMap(new TypeDescription.ForLoadedType(AUXILIARY), QUX));
+ Map<TypeDescription, LoadedTypeInitializer> loadedTypeInitializers = new HashMap<TypeDescription, LoadedTypeInitializer>();
+ loadedTypeInitializers.put(new TypeDescription.ForLoadedType(REDEFINED), loadedTypeInitializer);
+ LoadedTypeInitializer auxiliaryInitializer = mock(LoadedTypeInitializer.class);
+ loadedTypeInitializers.put(new TypeDescription.ForLoadedType(AUXILIARY), auxiliaryInitializer);
+ when(dynamicType.getLoadedTypeInitializers()).thenReturn(loadedTypeInitializers);
+ when(dynamicType.getBytes()).thenReturn(BAZ);
+ when(resolution.resolve()).thenReturn(new TypeDescription.ForLoadedType(REDEFINED));
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain()))
+ .thenReturn(true);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ assertThat(transform(classFileTransformer, JavaModule.ofType(REDEFINED), REDEFINED.getClassLoader(), REDEFINED.getName(), REDEFINED, REDEFINED.getProtectionDomain(), QUX), is(BAZ));
+ verify(listener).onDiscovery(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verify(listener).onTransformation(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true, dynamicType);
+ verify(listener).onComplete(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verifyNoMoreInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, false);
+ verifyNoMoreInteractions(instrumentation);
+ verify(initializationStrategy).dispatcher();
+ verifyNoMoreInteractions(initializationStrategy);
+ verify(dispatcher).apply(builder);
+ verify(dispatcher).register(dynamicType,
+ REDEFINED.getClassLoader(),
+ new AgentBuilder.Default.Transformation.Simple.Resolution.BootstrapClassLoaderCapableInjectorFactory(
+ AgentBuilder.Default.BootstrapInjectionStrategy.Disabled.INSTANCE,
+ REDEFINED.getClassLoader(),
+ REDEFINED.getProtectionDomain()));
+ verifyNoMoreInteractions(dispatcher);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test
+ public void testRedefinitionConsiderationException() throws Exception {
+ RuntimeException exception = new RuntimeException();
+ when(instrumentation.isRedefineClassesSupported()).thenReturn(true);
+ when(instrumentation.getAllLoadedClasses()).thenReturn(new Class<?>[]{REDEFINED});
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain()))
+ .thenThrow(exception);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ verify(instrumentation).addTransformer(classFileTransformer, false);
+ verify(listener).onError(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true, exception);
+ verify(listener).onComplete(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verifyNoMoreInteractions(listener);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test
+ public void testRetransformationConsiderationException() throws Exception {
+ RuntimeException exception = new RuntimeException();
+ when(instrumentation.isRetransformClassesSupported()).thenReturn(true);
+ when(instrumentation.getAllLoadedClasses()).thenReturn(new Class<?>[]{REDEFINED});
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain()))
+ .thenThrow(exception);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ verify(instrumentation).addTransformer(classFileTransformer, true);
+ verify(listener).onError(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true, exception);
+ verify(listener).onComplete(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verifyNoMoreInteractions(listener);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test
+ public void testRedefinitionConsiderationExceptionListenerException() throws Exception {
+ RuntimeException exception = new RuntimeException();
+ when(instrumentation.isRedefineClassesSupported()).thenReturn(true);
+ when(instrumentation.getAllLoadedClasses()).thenReturn(new Class<?>[]{REDEFINED});
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain()))
+ .thenThrow(exception);
+ doThrow(new RuntimeException()).when(listener).onError(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true, exception);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ verify(instrumentation).addTransformer(classFileTransformer, false);
+ verify(listener).onError(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true, exception);
+ verify(listener).onComplete(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verifyNoMoreInteractions(listener);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test
+ public void testRetransformationConsiderationExceptionListenerException() throws Exception {
+ RuntimeException exception = new RuntimeException();
+ when(instrumentation.isRetransformClassesSupported()).thenReturn(true);
+ when(instrumentation.getAllLoadedClasses()).thenReturn(new Class<?>[]{REDEFINED});
+ when(instrumentation.isModifiableClass(REDEFINED)).thenReturn(true);
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain()))
+ .thenThrow(exception);
+ doThrow(new RuntimeException()).when(listener).onError(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true, exception);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ verify(instrumentation).addTransformer(classFileTransformer, true);
+ verify(listener).onError(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true, exception);
+ verify(listener).onComplete(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verifyNoMoreInteractions(listener);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test
+ public void testDecoratedTransformation() throws Exception {
+ when(dynamicType.getBytes()).thenReturn(BAZ);
+ when(resolution.resolve()).thenReturn(new TypeDescription.ForLoadedType(REDEFINED));
+ when(typeMatcher.matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain()))
+ .thenReturn(true);
+ ResettableClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(none())
+ .type(typeMatcher).transform(transformer)
+ .type(typeMatcher).transform(transformer).asDecorator()
+ .installOn(instrumentation);
+ assertThat(transform(classFileTransformer, JavaModule.ofType(REDEFINED), REDEFINED.getClassLoader(), REDEFINED.getName(), REDEFINED, REDEFINED.getProtectionDomain(), QUX), is(BAZ));
+ verify(listener).onDiscovery(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verify(listener).onTransformation(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true, dynamicType);
+ verify(listener).onComplete(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verifyNoMoreInteractions(listener);
+ verify(instrumentation).addTransformer(classFileTransformer, false);
+ verifyNoMoreInteractions(instrumentation);
+ verify(initializationStrategy).dispatcher();
+ verifyNoMoreInteractions(initializationStrategy);
+ verify(dispatcher).apply(builder);
+ verify(dispatcher).register(dynamicType,
+ REDEFINED.getClassLoader(),
+ new AgentBuilder.Default.Transformation.Simple.Resolution.BootstrapClassLoaderCapableInjectorFactory(
+ AgentBuilder.Default.BootstrapInjectionStrategy.Disabled.INSTANCE,
+ REDEFINED.getClassLoader(),
+ REDEFINED.getProtectionDomain()));
+ verifyNoMoreInteractions(dispatcher);
+ verify(typeMatcher, times(2)).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain());
+ verifyNoMoreInteractions(typeMatcher);
+ verify(transformer, times(2)).transform(builder, new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED));
+ verifyNoMoreInteractions(transformer);
+ verify(installationListener).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(installationListener).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(installationListener);
+ }
+
+ @Test
+ public void testBootstrapClassLoaderCapableInjectorFactoryReflection() throws Exception {
+ AgentBuilder.Default.BootstrapInjectionStrategy bootstrapInjectionStrategy = mock(AgentBuilder.Default.BootstrapInjectionStrategy.class);
+ ClassLoader classLoader = mock(ClassLoader.class);
+ ProtectionDomain protectionDomain = mock(ProtectionDomain.class);
+ assertThat(new AgentBuilder.Default.Transformation.Simple.Resolution.BootstrapClassLoaderCapableInjectorFactory(bootstrapInjectionStrategy,
+ classLoader,
+ protectionDomain).resolve(), is((ClassInjector) new ClassInjector.UsingReflection(classLoader, protectionDomain)));
+ verifyZeroInteractions(bootstrapInjectionStrategy);
+ }
+
+ @Test
+ public void testBootstrapClassLoaderCapableInjectorFactoryInstrumentation() throws Exception {
+ AgentBuilder.Default.BootstrapInjectionStrategy bootstrapInjectionStrategy = mock(AgentBuilder.Default.BootstrapInjectionStrategy.class);
+ ProtectionDomain protectionDomain = mock(ProtectionDomain.class);
+ ClassInjector classInjector = mock(ClassInjector.class);
+ when(bootstrapInjectionStrategy.make(protectionDomain)).thenReturn(classInjector);
+ assertThat(new AgentBuilder.Default.Transformation.Simple.Resolution.BootstrapClassLoaderCapableInjectorFactory(bootstrapInjectionStrategy,
+ null,
+ protectionDomain).resolve(), is(classInjector));
+ verify(bootstrapInjectionStrategy).make(protectionDomain);
+ verifyNoMoreInteractions(bootstrapInjectionStrategy);
+ }
+
+ @Test
+ public void testEnabledBootstrapInjection() throws Exception {
+ assertThat(new AgentBuilder.Default.BootstrapInjectionStrategy.Enabled(mock(File.class), mock(Instrumentation.class))
+ .make(mock(ProtectionDomain.class)), instanceOf(ClassInjector.UsingInstrumentation.class));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testDisabledBootstrapInjection() throws Exception {
+ AgentBuilder.Default.BootstrapInjectionStrategy.Disabled.INSTANCE.make(mock(ProtectionDomain.class));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testExecutingTransformerReturnsNullValue() throws Exception {
+ assertThat(new AgentBuilder.Default.ExecutingTransformer(byteBuddy,
+ listener,
+ poolStrategy,
+ typeStrategy,
+ locationStrategy,
+ mock(AgentBuilder.Default.NativeMethodStrategy.class),
+ initializationStrategy,
+ mock(AgentBuilder.Default.BootstrapInjectionStrategy.class),
+ AgentBuilder.LambdaInstrumentationStrategy.DISABLED,
+ AgentBuilder.DescriptionStrategy.Default.HYBRID,
+ mock(AgentBuilder.FallbackStrategy.class),
+ mock(AgentBuilder.InstallationListener.class),
+ mock(AgentBuilder.RawMatcher.class),
+ mock(AgentBuilder.Default.Transformation.class),
+ new AgentBuilder.CircularityLock.Default())
+ .transform(mock(ClassLoader.class),
+ FOO,
+ Object.class,
+ mock(ProtectionDomain.class),
+ new byte[0]), nullValue(byte[].class));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testExecutingTransformerReturnsRequiresLock() throws Exception {
+ new AgentBuilder.Default()
+ .with(mock(AgentBuilder.CircularityLock.class))
+ .installOn(mock(Instrumentation.class));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testExecutingTransformerDoesNotRecurse() throws Exception {
+ final AgentBuilder.Default.ExecutingTransformer executingTransformer = new AgentBuilder.Default.ExecutingTransformer(byteBuddy,
+ listener,
+ poolStrategy,
+ typeStrategy,
+ locationStrategy,
+ mock(AgentBuilder.Default.NativeMethodStrategy.class),
+ initializationStrategy,
+ mock(AgentBuilder.Default.BootstrapInjectionStrategy.class),
+ AgentBuilder.LambdaInstrumentationStrategy.DISABLED,
+ AgentBuilder.DescriptionStrategy.Default.HYBRID,
+ mock(AgentBuilder.FallbackStrategy.class),
+ mock(AgentBuilder.InstallationListener.class),
+ mock(AgentBuilder.RawMatcher.class),
+ mock(AgentBuilder.Default.Transformation.class),
+ new AgentBuilder.Default.CircularityLock.Default());
+ final ClassLoader classLoader = mock(ClassLoader.class);
+ final ProtectionDomain protectionDomain = mock(ProtectionDomain.class);
+ doAnswer(new Answer() {
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ assertThat(executingTransformer.transform(classLoader,
+ FOO,
+ Object.class,
+ protectionDomain,
+ new byte[0]), nullValue(byte[].class));
+ return null;
+ }
+ }).when(listener).onComplete(FOO, classLoader, JavaModule.UNSUPPORTED, true);
+ assertThat(executingTransformer.transform(classLoader,
+ FOO,
+ Object.class,
+ protectionDomain,
+ new byte[0]), nullValue(byte[].class));
+ verify(listener).onComplete(FOO, classLoader, JavaModule.UNSUPPORTED, true);
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(9)
+ public void testExecutingTransformerDoesNotRecurseWithModules() throws Exception {
+ final AgentBuilder.Default.ExecutingTransformer executingTransformer = new AgentBuilder.Default.ExecutingTransformer(byteBuddy,
+ listener,
+ poolStrategy,
+ typeStrategy,
+ locationStrategy,
+ mock(AgentBuilder.Default.NativeMethodStrategy.class),
+ initializationStrategy,
+ mock(AgentBuilder.Default.BootstrapInjectionStrategy.class),
+ AgentBuilder.LambdaInstrumentationStrategy.DISABLED,
+ AgentBuilder.DescriptionStrategy.Default.HYBRID,
+ mock(AgentBuilder.FallbackStrategy.class),
+ mock(AgentBuilder.InstallationListener.class),
+ mock(AgentBuilder.RawMatcher.class),
+ mock(AgentBuilder.Default.Transformation.class),
+ new AgentBuilder.CircularityLock.Default());
+ final ClassLoader classLoader = mock(ClassLoader.class);
+ final ProtectionDomain protectionDomain = mock(ProtectionDomain.class);
+ doAnswer(new Answer() {
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ assertThat(executingTransformer.transform(JavaModule.ofType(Object.class).unwrap(),
+ classLoader,
+ FOO,
+ Object.class,
+ protectionDomain,
+ new byte[0]), nullValue(byte[].class));
+ return null;
+ }
+ }).when(listener).onComplete(FOO, classLoader, JavaModule.ofType(Object.class), true);
+ assertThat(executingTransformer.transform(JavaModule.of(Object.class).unwrap(),
+ classLoader,
+ FOO,
+ Object.class,
+ protectionDomain,
+ new byte[0]), nullValue(byte[].class));
+ verify(listener).onComplete(FOO, classLoader, JavaModule.ofType(Object.class), true);
+ }
+
+ @Test
+ public void testIgnoredTypeMatcherOnlyAppliedOnceWithMultipleTransformations() throws Exception {
+ AgentBuilder.RawMatcher ignored = mock(AgentBuilder.RawMatcher.class);
+ ClassFileTransformer classFileTransformer = new AgentBuilder.Default(byteBuddy)
+ .with(initializationStrategy)
+ .with(poolStrategy)
+ .with(typeStrategy)
+ .with(installationListener)
+ .with(listener)
+ .disableNativeMethodPrefix()
+ .ignore(ignored)
+ .type(typeMatcher).transform(transformer)
+ .type(typeMatcher).transform(transformer)
+ .installOn(instrumentation);
+ assertThat(transform(classFileTransformer, JavaModule.ofType(REDEFINED), REDEFINED.getClassLoader(), REDEFINED.getName(), REDEFINED, REDEFINED.getProtectionDomain(), QUX), nullValue(byte[].class));
+ verify(listener).onDiscovery(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verify(listener).onIgnored(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verify(listener).onComplete(REDEFINED.getName(), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), true);
+ verifyNoMoreInteractions(listener);
+ verify(ignored).matches(new TypeDescription.ForLoadedType(REDEFINED), REDEFINED.getClassLoader(), JavaModule.ofType(REDEFINED), REDEFINED, REDEFINED.getProtectionDomain());
+ verifyNoMoreInteractions(ignored);
+ }
+
+ @Test
+ public void testDisableClassFormatChanges() throws Exception {
+ assertThat(new AgentBuilder.Default().disableClassFormatChanges(), is(new AgentBuilder.Default(new ByteBuddy()
+ .with(Implementation.Context.Disabled.Factory.INSTANCE))
+ .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
+ .with(AgentBuilder.TypeStrategy.Default.REDEFINE_FROZEN)));
+ }
+
+ @Test
+ public void testBuildPlugin() throws Exception {
+ Plugin plugin = mock(Plugin.class);
+ assertThat(AgentBuilder.Default.of(plugin), is((AgentBuilder) new AgentBuilder.Default()
+ .with(new AgentBuilder.TypeStrategy.ForBuildEntryPoint(EntryPoint.Default.REBASE))
+ .type(plugin)
+ .transform(new AgentBuilder.Transformer.ForBuildPlugin(plugin))));
+ }
+
+ @Test
+ public void testBuildPluginWithEntryPoint() throws Exception {
+ Plugin plugin = mock(Plugin.class);
+ EntryPoint entryPoint = mock(EntryPoint.class);
+ ByteBuddy byteBuddy = mock(ByteBuddy.class);
+ when(entryPoint.getByteBuddy()).thenReturn(byteBuddy);
+ assertThat(AgentBuilder.Default.of(entryPoint, plugin), is((AgentBuilder) new AgentBuilder.Default(byteBuddy)
+ .with(new AgentBuilder.TypeStrategy.ForBuildEntryPoint(entryPoint))
+ .type(plugin)
+ .transform(new AgentBuilder.Transformer.ForBuildPlugin(plugin))));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testRetransformationDisabledNotEnabledAllocator() throws Exception {
+ new AgentBuilder.Default()
+ .with(AgentBuilder.RedefinitionStrategy.DISABLED)
+ .with(mock(AgentBuilder.RedefinitionStrategy.BatchAllocator.class));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testRetransformationDisabledNotEnabledListener() throws Exception {
+ new AgentBuilder.Default()
+ .with(AgentBuilder.RedefinitionStrategy.DISABLED)
+ .with(mock(AgentBuilder.RedefinitionStrategy.Listener.class));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testRetransformationDisabledNotEnabledResubmission() throws Exception {
+ new AgentBuilder.Default()
+ .with(AgentBuilder.RedefinitionStrategy.DISABLED)
+ .withResubmission(mock(AgentBuilder.RedefinitionStrategy.ResubmissionScheduler.class));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AgentBuilder.Default.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Default.Ignoring.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Default.Transforming.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Default.Transformation.Simple.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Default.Transformation.Simple.Resolution.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Default.Transformation.Ignored.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Default.Transformation.Compound.class).create(new ObjectPropertyAssertion.Creator<List<?>>() {
+ @Override
+ public List<?> create() {
+ return Collections.singletonList(mock(AgentBuilder.Default.Transformation.class));
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Default.Transformation.Resolution.Unresolved.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Default.Transformation.Resolution.Sort.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Default.BootstrapInjectionStrategy.Enabled.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Default.BootstrapInjectionStrategy.Unsafe.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Default.BootstrapInjectionStrategy.Disabled.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Default.ExecutingTransformer.Factory.CreationAction.class);
+ final Iterator<Class<?>> java9Dispatcher = Arrays.<Class<?>>asList(Object.class, String.class, Integer.class, Double.class, Float.class).iterator();
+ ObjectPropertyAssertion.of(AgentBuilder.Default.ExecutingTransformer.Java9CapableVmDispatcher.class).create(new ObjectPropertyAssertion.Creator<Class<?>>() {
+ @Override
+ public Class<?> create() {
+ return java9Dispatcher.next();
+ }
+ }).apply();
+ final Iterator<Class<?>> legacyDispatcher = Arrays.<Class<?>>asList(Object.class, String.class, Integer.class, Double.class, Float.class).iterator();
+ ObjectPropertyAssertion.of(AgentBuilder.Default.ExecutingTransformer.LegacyVmDispatcher.class).create(new ObjectPropertyAssertion.Creator<Class<?>>() {
+ @Override
+ public Class<?> create() {
+ return legacyDispatcher.next();
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Default.Transformation.Simple.Resolution.BootstrapClassLoaderCapableInjectorFactory.class).apply();
+ final Iterator<Constructor<?>> iterator = Arrays.<Constructor<?>>asList(String.class.getDeclaredConstructors()).iterator();
+ ObjectPropertyAssertion.of(AgentBuilder.Default.ExecutingTransformer.Factory.ForJava9CapableVm.class).create(new ObjectPropertyAssertion.Creator<Constructor<?>>() {
+ @Override
+ public Constructor<?> create() {
+ return iterator.next();
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Default.ExecutingTransformer.Factory.ForLegacyVm.class).apply();
+ }
+
+ public static class Foo {
+ /* empty */
+ }
+
+ public static class Bar {
+ /* empty */
+ }
+
+ public static class Qux {
+ /* empty */
+ }
+
+ private static byte[] transform(ClassFileTransformer classFileTransformer,
+ JavaModule javaModule,
+ ClassLoader classLoader,
+ String typeName,
+ Class<?> type,
+ ProtectionDomain protectionDomain,
+ byte[] binaryRepresentation) throws Exception {
+ try {
+ return (byte[]) ClassFileTransformer.class.getDeclaredMethod("transform", Class.forName("java.lang.Module"), ClassLoader.class, String.class, Class.class, ProtectionDomain.class, byte[].class)
+ .invoke(classFileTransformer, javaModule.unwrap(), classLoader, typeName, type, protectionDomain, binaryRepresentation);
+ } catch (Exception ignored) {
+ return classFileTransformer.transform(classLoader, typeName, type, protectionDomain, binaryRepresentation);
+ }
+ }
+
+ private static class ClassRedefinitionMatcher implements ArgumentMatcher<ClassDefinition> {
+
+ private final Class<?> type;
+
+ private ClassRedefinitionMatcher(Class<?> type) {
+ this.type = type;
+ }
+
+ @Override
+ public boolean matches(ClassDefinition classDefinition) {
+ return classDefinition.getDefinitionClass() == type;
+ }
+ }
+
+ private static class CauseMatcher implements ArgumentMatcher<Throwable> {
+
+ private final Throwable throwable;
+
+ private CauseMatcher(Throwable throwable) {
+ this.throwable = throwable;
+ }
+
+ @Override
+ public boolean matches(Throwable item) {
+ return throwable.equals((item).getCause());
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderDescriptionStrategyTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderDescriptionStrategyTest.java
new file mode 100644
index 0000000..eab077e
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderDescriptionStrategyTest.java
@@ -0,0 +1,101 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.pool.TypePool;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.utility.JavaModule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.concurrent.ExecutorService;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class AgentBuilderDescriptionStrategyTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private AgentBuilder.LocationStrategy locationStrategy;
+
+ @Mock
+ private TypePool typePool;
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Test
+ public void testDescriptionHybridWithLoaded() throws Exception {
+ ClassFileLocator classFileLocator = ClassFileLocator.ForClassLoader.of(Object.class.getClassLoader());
+ when(typePool.describe(Object.class.getName())).thenReturn(new TypePool.Resolution.Simple(typeDescription));
+ when(locationStrategy.classFileLocator(Object.class.getClassLoader(), JavaModule.ofType(Object.class))).thenReturn(classFileLocator);
+ TypeDescription typeDescription = AgentBuilder.DescriptionStrategy.Default.HYBRID.apply(Object.class.getName(),
+ Object.class,
+ typePool,
+ mock(AgentBuilder.CircularityLock.class),
+ Object.class.getClassLoader(),
+ JavaModule.ofType(Object.class));
+ assertThat(typeDescription, is(TypeDescription.OBJECT));
+ assertThat(typeDescription, instanceOf(TypeDescription.ForLoadedType.class));
+ }
+
+ @Test
+ public void testDescriptionHybridWithoutLoaded() throws Exception {
+ when(typePool.describe(Object.class.getName())).thenReturn(new TypePool.Resolution.Simple(typeDescription));
+ TypeDescription typeDescription = AgentBuilder.DescriptionStrategy.Default.HYBRID.apply(Object.class.getName(),
+ null,
+ typePool,
+ mock(AgentBuilder.CircularityLock.class),
+ Object.class.getClassLoader(),
+ JavaModule.ofType(Object.class));
+ assertThat(typeDescription, is(this.typeDescription));
+ }
+
+ @Test
+ public void testDescriptionPoolOnly() throws Exception {
+ when(typePool.describe(Object.class.getName())).thenReturn(new TypePool.Resolution.Simple(typeDescription));
+ assertThat(AgentBuilder.DescriptionStrategy.Default.POOL_ONLY.apply(Object.class.getName(),
+ Object.class,
+ typePool,
+ mock(AgentBuilder.CircularityLock.class),
+ Object.class.getClassLoader(),
+ JavaModule.ofType(Object.class)), is(typeDescription));
+ }
+
+ @Test
+ public void testSuperTypeLoading() throws Exception {
+ assertThat(AgentBuilder.DescriptionStrategy.Default.HYBRID.withSuperTypeLoading(), is((AgentBuilder.DescriptionStrategy) new AgentBuilder.DescriptionStrategy.SuperTypeLoading(AgentBuilder.DescriptionStrategy.Default.HYBRID)));
+ assertThat(AgentBuilder.DescriptionStrategy.Default.POOL_FIRST.withSuperTypeLoading(), is((AgentBuilder.DescriptionStrategy) new AgentBuilder.DescriptionStrategy.SuperTypeLoading(AgentBuilder.DescriptionStrategy.Default.POOL_FIRST)));
+ assertThat(AgentBuilder.DescriptionStrategy.Default.POOL_ONLY.withSuperTypeLoading(), is((AgentBuilder.DescriptionStrategy) new AgentBuilder.DescriptionStrategy.SuperTypeLoading(AgentBuilder.DescriptionStrategy.Default.POOL_ONLY)));
+ }
+
+ @Test
+ public void testAsynchronousSuperTypeLoading() throws Exception {
+ ExecutorService executorService = mock(ExecutorService.class);
+ assertThat(AgentBuilder.DescriptionStrategy.Default.HYBRID.withSuperTypeLoading(executorService),
+ is((AgentBuilder.DescriptionStrategy) new AgentBuilder.DescriptionStrategy.SuperTypeLoading.Asynchronous(AgentBuilder.DescriptionStrategy.Default.HYBRID, executorService)));
+ assertThat(AgentBuilder.DescriptionStrategy.Default.POOL_FIRST.withSuperTypeLoading(executorService),
+ is((AgentBuilder.DescriptionStrategy) new AgentBuilder.DescriptionStrategy.SuperTypeLoading.Asynchronous(AgentBuilder.DescriptionStrategy.Default.POOL_FIRST, executorService)));
+ assertThat(AgentBuilder.DescriptionStrategy.Default.POOL_ONLY.withSuperTypeLoading(executorService),
+ is((AgentBuilder.DescriptionStrategy) new AgentBuilder.DescriptionStrategy.SuperTypeLoading.Asynchronous(AgentBuilder.DescriptionStrategy.Default.POOL_ONLY, executorService)));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AgentBuilder.DescriptionStrategy.Default.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.DescriptionStrategy.SuperTypeLoading.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.DescriptionStrategy.SuperTypeLoading.UnlockingClassLoadingDelegate.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.DescriptionStrategy.SuperTypeLoading.Asynchronous.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.DescriptionStrategy.SuperTypeLoading.Asynchronous.ThreadSwitchingClassLoadingDelegate.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.DescriptionStrategy.SuperTypeLoading.Asynchronous.ThreadSwitchingClassLoadingDelegate.SimpleClassLoadingAction.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderFallbackStrategyByThrowableTypeTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderFallbackStrategyByThrowableTypeTest.java
new file mode 100644
index 0000000..251e2a2
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderFallbackStrategyByThrowableTypeTest.java
@@ -0,0 +1,42 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Iterator;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class AgentBuilderFallbackStrategyByThrowableTypeTest {
+
+ @Test
+ @SuppressWarnings("unchecked") // In absence of @SafeVarargs for Java 6
+ public void testIsFallback() throws Exception {
+ assertThat(new AgentBuilder.FallbackStrategy.ByThrowableType(Exception.class).isFallback(Object.class, new Exception()), is(true));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked") // In absence of @SafeVarargs for Java 6
+ public void testIsFallbackInherited() throws Exception {
+ assertThat(new AgentBuilder.FallbackStrategy.ByThrowableType(Exception.class).isFallback(Object.class, new RuntimeException()), is(true));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked") // In absence of @SafeVarargs for Java 6
+ public void testIsNoFallback() throws Exception {
+ assertThat(new AgentBuilder.FallbackStrategy.ByThrowableType(RuntimeException.class).isFallback(Object.class, new Exception()), is(false));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ final Iterator<Class<?>> iterator = Arrays.<Class<?>>asList(Object.class, String.class).iterator();
+ ObjectPropertyAssertion.of(AgentBuilder.FallbackStrategy.ByThrowableType.class).create(new ObjectPropertyAssertion.Creator<Class<?>>() {
+ @Override
+ public Class<?> create() {
+ return iterator.next();
+ }
+ }).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderFallbackStrategySimpleTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderFallbackStrategySimpleTest.java
new file mode 100644
index 0000000..89df035
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderFallbackStrategySimpleTest.java
@@ -0,0 +1,25 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class AgentBuilderFallbackStrategySimpleTest {
+
+ @Test
+ public void testEnabled() throws Exception {
+ assertThat(AgentBuilder.FallbackStrategy.Simple.ENABLED.isFallback(Object.class, new Throwable()), is(true));
+ }
+
+ @Test
+ public void testDisabled() throws Exception {
+ assertThat(AgentBuilder.FallbackStrategy.Simple.DISABLED.isFallback(Object.class, new Throwable()), is(false));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AgentBuilder.FallbackStrategy.Simple.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderInitializationStrategySelfInjectionDispatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderInitializationStrategySelfInjectionDispatcherTest.java
new file mode 100644
index 0000000..8e95eb9
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderInitializationStrategySelfInjectionDispatcherTest.java
@@ -0,0 +1,220 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.Nexus;
+import net.bytebuddy.dynamic.NexusAccessor;
+import net.bytebuddy.dynamic.loading.ClassInjector;
+import net.bytebuddy.implementation.LoadedTypeInitializer;
+import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.lang.annotation.Annotation;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class AgentBuilderInitializationStrategySelfInjectionDispatcherTest {
+
+ private static final int IDENTIFIER = 42;
+
+ private static final byte[] FOO = new byte[]{1, 2, 3}, BAR = new byte[]{4, 5, 6};
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private DynamicType.Builder<?> builder, appendedBuilder;
+
+ @Mock
+ private DynamicType dynamicType;
+
+ @Mock
+ private AgentBuilder.InitializationStrategy.Dispatcher.InjectorFactory injectorFactory;
+
+ @Mock
+ private ClassInjector classInjector;
+
+ @Mock
+ private TypeDescription instrumented, dependent, independent;
+
+ @Mock
+ private LoadedTypeInitializer instrumentedInitializer, dependentInitializer, independentInitializer;
+
+ private NexusAccessor nexusAccessor = new NexusAccessor();
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ when(builder.initializer((any(ByteCodeAppender.class)))).thenReturn((DynamicType.Builder) appendedBuilder);
+ when(injectorFactory.resolve()).thenReturn(classInjector);
+ when(dynamicType.getTypeDescription()).thenReturn(instrumented);
+ Map<TypeDescription, byte[]> auxiliaryTypes = new HashMap<TypeDescription, byte[]>();
+ auxiliaryTypes.put(dependent, FOO);
+ auxiliaryTypes.put(independent, BAR);
+ when(dynamicType.getAuxiliaryTypes()).thenReturn(auxiliaryTypes);
+ Map<TypeDescription, LoadedTypeInitializer> loadedTypeInitializers = new HashMap<TypeDescription, LoadedTypeInitializer>();
+ loadedTypeInitializers.put(instrumented, instrumentedInitializer);
+ loadedTypeInitializers.put(dependent, dependentInitializer);
+ loadedTypeInitializers.put(independent, independentInitializer);
+ when(dynamicType.getLoadedTypeInitializers()).thenReturn(loadedTypeInitializers);
+ when(instrumented.getName()).thenReturn(Qux.class.getName());
+ when(classInjector.inject(any(Map.class))).then(new Answer<Map<TypeDescription, Class<?>>>() {
+ @Override
+ public Map<TypeDescription, Class<?>> answer(InvocationOnMock invocationOnMock) throws Throwable {
+ Map<TypeDescription, Class<?>> loaded = new HashMap<TypeDescription, Class<?>>();
+ for (TypeDescription typeDescription : ((Map<TypeDescription, byte[]>) invocationOnMock.getArguments()[0]).keySet()) {
+ if (typeDescription.equals(dependent)) {
+ loaded.put(dependent, Foo.class);
+ } else if (typeDescription.equals(independent)) {
+ loaded.put(independent, Bar.class);
+ } else {
+ throw new AssertionError();
+ }
+ }
+ return loaded;
+ }
+ });
+ Annotation eagerAnnotation = mock(AuxiliaryType.SignatureRelevant.class);
+ when(eagerAnnotation.annotationType()).thenReturn((Class) AuxiliaryType.SignatureRelevant.class);
+ when(independent.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(eagerAnnotation));
+ when(dependent.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(instrumentedInitializer.isAlive()).thenReturn(true);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testSplitInitialization() throws Exception {
+ AgentBuilder.InitializationStrategy.Dispatcher dispatcher = new AgentBuilder.InitializationStrategy.SelfInjection.Split.Dispatcher(nexusAccessor, IDENTIFIER);
+ assertThat(dispatcher.apply(builder), is((DynamicType.Builder) appendedBuilder));
+ verify(builder).initializer(new NexusAccessor.InitializationAppender(IDENTIFIER));
+ verifyNoMoreInteractions(builder);
+ verifyZeroInteractions(appendedBuilder);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testLazyInitialization() throws Exception {
+ AgentBuilder.InitializationStrategy.Dispatcher dispatcher = new AgentBuilder.InitializationStrategy.SelfInjection.Lazy.Dispatcher(nexusAccessor, IDENTIFIER);
+ assertThat(dispatcher.apply(builder), is((DynamicType.Builder) appendedBuilder));
+ verify(builder).initializer(new NexusAccessor.InitializationAppender(IDENTIFIER));
+ verifyNoMoreInteractions(builder);
+ verifyZeroInteractions(appendedBuilder);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testEagerInitialization() throws Exception {
+ AgentBuilder.InitializationStrategy.Dispatcher dispatcher = new AgentBuilder.InitializationStrategy.SelfInjection.Eager.Dispatcher(nexusAccessor, IDENTIFIER);
+ assertThat(dispatcher.apply(builder), is((DynamicType.Builder) appendedBuilder));
+ verify(builder).initializer(new NexusAccessor.InitializationAppender(IDENTIFIER));
+ verifyNoMoreInteractions(builder);
+ verifyZeroInteractions(appendedBuilder);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testSplit() throws Exception {
+ AgentBuilder.InitializationStrategy.Dispatcher dispatcher = new AgentBuilder.InitializationStrategy.SelfInjection.Split.Dispatcher(nexusAccessor, IDENTIFIER);
+ dispatcher.register(dynamicType, Qux.class.getClassLoader(), injectorFactory);
+ verify(classInjector).inject(Collections.singletonMap(independent, BAR));
+ verifyNoMoreInteractions(classInjector);
+ verify(independentInitializer).onLoad(Bar.class);
+ verifyNoMoreInteractions(independentInitializer);
+ Nexus.initialize(Qux.class, IDENTIFIER);
+ verify(classInjector).inject(Collections.singletonMap(dependent, FOO));
+ verifyNoMoreInteractions(classInjector);
+ verify(dependentInitializer).onLoad(Foo.class);
+ verifyNoMoreInteractions(dependentInitializer);
+ verify(instrumentedInitializer).onLoad(Qux.class);
+ verifyNoMoreInteractions(instrumentedInitializer);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testEager() throws Exception {
+ AgentBuilder.InitializationStrategy.Dispatcher dispatcher = new AgentBuilder.InitializationStrategy.SelfInjection.Eager.Dispatcher(nexusAccessor, IDENTIFIER);
+ dispatcher.register(dynamicType, Qux.class.getClassLoader(), injectorFactory);
+ Map<TypeDescription, byte[]> injected = new HashMap<TypeDescription, byte[]>();
+ injected.put(independent, BAR);
+ injected.put(dependent, FOO);
+ verify(classInjector).inject(injected);
+ verifyNoMoreInteractions(classInjector);
+ verify(independentInitializer).onLoad(Bar.class);
+ verifyNoMoreInteractions(independentInitializer);
+ verify(dependentInitializer).onLoad(Foo.class);
+ verifyNoMoreInteractions(dependentInitializer);
+ Nexus.initialize(Qux.class, IDENTIFIER);
+ verify(instrumentedInitializer).onLoad(Qux.class);
+ verify(instrumentedInitializer).isAlive();
+ verifyNoMoreInteractions(instrumentedInitializer);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testLazy() throws Exception {
+ AgentBuilder.InitializationStrategy.Dispatcher dispatcher = new AgentBuilder.InitializationStrategy.SelfInjection.Lazy.Dispatcher(nexusAccessor, IDENTIFIER);
+ dispatcher.register(dynamicType, Qux.class.getClassLoader(), injectorFactory);
+ verifyZeroInteractions(classInjector, dependentInitializer, independentInitializer);
+ Nexus.initialize(Qux.class, IDENTIFIER);
+ Map<TypeDescription, byte[]> injected = new HashMap<TypeDescription, byte[]>();
+ injected.put(independent, BAR);
+ injected.put(dependent, FOO);
+ verify(classInjector).inject(injected);
+ verifyNoMoreInteractions(classInjector);
+ verify(independentInitializer).onLoad(Bar.class);
+ verifyNoMoreInteractions(independentInitializer);
+ verify(dependentInitializer).onLoad(Foo.class);
+ verifyNoMoreInteractions(dependentInitializer);
+ verify(instrumentedInitializer).onLoad(Qux.class);
+ verifyNoMoreInteractions(instrumentedInitializer);
+ }
+
+ @Test
+ public void testDispatcherCreation() throws Exception {
+ assertThat(new AgentBuilder.InitializationStrategy.SelfInjection.Split().dispatcher(),
+ instanceOf(AgentBuilder.InitializationStrategy.SelfInjection.Split.Dispatcher.class));
+ assertThat(new AgentBuilder.InitializationStrategy.SelfInjection.Eager().dispatcher(),
+ instanceOf(AgentBuilder.InitializationStrategy.SelfInjection.Eager.Dispatcher.class));
+ assertThat(new AgentBuilder.InitializationStrategy.SelfInjection.Lazy().dispatcher(),
+ instanceOf(AgentBuilder.InitializationStrategy.SelfInjection.Lazy.Dispatcher.class));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AgentBuilder.InitializationStrategy.SelfInjection.Lazy.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.InitializationStrategy.SelfInjection.Lazy.Dispatcher.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.InitializationStrategy.SelfInjection.Eager.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.InitializationStrategy.SelfInjection.Eager.Dispatcher.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.InitializationStrategy.SelfInjection.Split.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.InitializationStrategy.SelfInjection.Split.Dispatcher.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.InitializationStrategy.SelfInjection.Dispatcher.InjectingInitializer.class).apply();
+ }
+
+ private static class Foo {
+ /* empty */
+ }
+
+ private static class Bar {
+ /* empty */
+ }
+
+ private static class Qux {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderInitializationStrategyTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderInitializationStrategyTest.java
new file mode 100644
index 0000000..0661530
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderInitializationStrategyTest.java
@@ -0,0 +1,120 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassInjector;
+import net.bytebuddy.implementation.LoadedTypeInitializer;
+import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.lang.annotation.Annotation;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class AgentBuilderInitializationStrategyTest {
+
+ private static final byte[] QUX = new byte[]{1, 2, 3}, BAZ = new byte[]{4, 5, 6};
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private DynamicType.Builder<?> builder;
+
+ @Mock
+ private DynamicType dynamicType;
+
+ @Mock
+ private LoadedTypeInitializer loadedTypeInitializer;
+
+ @Mock
+ private ClassLoader classLoader;
+
+ @Mock
+ private AgentBuilder.InitializationStrategy.Dispatcher.InjectorFactory injectorFactory;
+
+ @Test
+ public void testNoOp() throws Exception {
+ assertThat(AgentBuilder.Default.InitializationStrategy.NoOp.INSTANCE.dispatcher(),
+ is((AgentBuilder.InitializationStrategy.Dispatcher) AgentBuilder.Default.InitializationStrategy.NoOp.INSTANCE));
+ }
+
+ @Test
+ public void testNoOpApplication() throws Exception {
+ assertThat(AgentBuilder.Default.InitializationStrategy.NoOp.INSTANCE.apply(builder), is((DynamicType.Builder) builder));
+ }
+
+ @Test
+ public void testNoOpRegistration() throws Exception {
+ AgentBuilder.Default.InitializationStrategy.NoOp.INSTANCE.register(dynamicType, classLoader, injectorFactory);
+ verifyZeroInteractions(dynamicType);
+ verifyZeroInteractions(classLoader);
+ verifyZeroInteractions(injectorFactory);
+ }
+
+ @Test
+ public void testPremature() throws Exception {
+ assertThat(AgentBuilder.InitializationStrategy.Minimal.INSTANCE.dispatcher(),
+ is((AgentBuilder.InitializationStrategy.Dispatcher) AgentBuilder.InitializationStrategy.Minimal.INSTANCE));
+ }
+
+ @Test
+ public void testPrematureApplication() throws Exception {
+ assertThat(AgentBuilder.InitializationStrategy.Minimal.INSTANCE.apply(builder), is((DynamicType.Builder) builder));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMinimalRegistrationIndependentType() throws Exception {
+ Annotation eagerAnnotation = mock(AuxiliaryType.SignatureRelevant.class);
+ when(eagerAnnotation.annotationType()).thenReturn((Class) AuxiliaryType.SignatureRelevant.class);
+ TypeDescription independent = mock(TypeDescription.class), dependent = mock(TypeDescription.class);
+ when(independent.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(eagerAnnotation));
+ when(dependent.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ Map<TypeDescription, byte[]> map = new HashMap<TypeDescription, byte[]>();
+ map.put(independent, QUX);
+ map.put(dependent, BAZ);
+ when(dynamicType.getAuxiliaryTypes()).thenReturn(map);
+ ClassInjector classInjector = mock(ClassInjector.class);
+ when(injectorFactory.resolve()).thenReturn(classInjector);
+ when(classInjector.inject(Collections.singletonMap(independent, QUX)))
+ .thenReturn(Collections.<TypeDescription, Class<?>>singletonMap(independent, Foo.class));
+ LoadedTypeInitializer loadedTypeInitializer = mock(LoadedTypeInitializer.class);
+ when(dynamicType.getLoadedTypeInitializers()).thenReturn(Collections.singletonMap(independent, loadedTypeInitializer));
+ AgentBuilder.InitializationStrategy.Minimal.INSTANCE.register(dynamicType, classLoader, injectorFactory);
+ verify(classInjector).inject(Collections.singletonMap(independent, QUX));
+ verifyNoMoreInteractions(classInjector);
+ verify(loadedTypeInitializer).onLoad(Foo.class);
+ verifyNoMoreInteractions(loadedTypeInitializer);
+ }
+
+ @Test
+ public void testMinimalRegistrationDependentType() throws Exception {
+ TypeDescription dependent = mock(TypeDescription.class);
+ when(dependent.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(dynamicType.getAuxiliaryTypes()).thenReturn(Collections.singletonMap(dependent, BAZ));
+ AgentBuilder.InitializationStrategy.Minimal.INSTANCE.register(dynamicType, classLoader, injectorFactory);
+ verifyZeroInteractions(injectorFactory);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AgentBuilder.InitializationStrategy.NoOp.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.InitializationStrategy.Minimal.class).apply();
+ }
+
+ private static class Foo {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderInstallationListenerTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderInstallationListenerTest.java
new file mode 100644
index 0000000..26d2720
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderInstallationListenerTest.java
@@ -0,0 +1,183 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.io.PrintStream;
+import java.lang.instrument.Instrumentation;
+import java.util.Collections;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class AgentBuilderInstallationListenerTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private Instrumentation instrumentation;
+
+ @Mock
+ private ResettableClassFileTransformer classFileTransformer;
+
+ @Mock
+ private Throwable throwable;
+
+ @Test
+ public void testNoOpListener() throws Exception {
+ AgentBuilder.InstallationListener.NoOp.INSTANCE.onBeforeInstall(instrumentation, classFileTransformer);
+ AgentBuilder.InstallationListener.NoOp.INSTANCE.onInstall(instrumentation, classFileTransformer);
+ assertThat(AgentBuilder.InstallationListener.NoOp.INSTANCE.onError(instrumentation, classFileTransformer, throwable), is(throwable));
+ AgentBuilder.InstallationListener.NoOp.INSTANCE.onReset(instrumentation, classFileTransformer);
+ verifyZeroInteractions(instrumentation, classFileTransformer, throwable);
+ }
+
+ @Test
+ public void testPseudoAdapter() throws Exception {
+ AgentBuilder.InstallationListener pseudoAdapter = new PseudoAdapter();
+ pseudoAdapter.onBeforeInstall(instrumentation, classFileTransformer);
+ pseudoAdapter.onInstall(instrumentation, classFileTransformer);
+ assertThat(pseudoAdapter.onError(instrumentation, classFileTransformer, throwable), is(throwable));
+ pseudoAdapter.onReset(instrumentation, classFileTransformer);
+ verifyZeroInteractions(instrumentation, classFileTransformer, throwable);
+ }
+
+ @Test
+ public void testErrorSuppressing() throws Exception {
+ AgentBuilder.InstallationListener.ErrorSuppressing.INSTANCE.onBeforeInstall(instrumentation, classFileTransformer);
+ AgentBuilder.InstallationListener.ErrorSuppressing.INSTANCE.onInstall(instrumentation, classFileTransformer);
+ AgentBuilder.InstallationListener.NoOp.INSTANCE.onReset(instrumentation, classFileTransformer);
+ verifyZeroInteractions(instrumentation, classFileTransformer, throwable);
+ }
+
+ @Test
+ public void testErrorSuppressingError() throws Exception {
+ assertThat(AgentBuilder.InstallationListener.ErrorSuppressing.INSTANCE.onError(instrumentation, classFileTransformer, throwable),
+ nullValue(Throwable.class));
+ }
+
+ @Test
+ public void testStreamWritingListenerBeforeInstall() throws Exception {
+ PrintStream printStream = mock(PrintStream.class);
+ AgentBuilder.InstallationListener installationListener = new AgentBuilder.InstallationListener.StreamWriting(printStream);
+ installationListener.onBeforeInstall(instrumentation, classFileTransformer);
+ verify(printStream).printf("[Byte Buddy] BEFORE_INSTALL %s on %s%n", classFileTransformer, instrumentation);
+ verifyNoMoreInteractions(printStream);
+ }
+
+ @Test
+ public void testStreamWritingListenerInstall() throws Exception {
+ PrintStream printStream = mock(PrintStream.class);
+ AgentBuilder.InstallationListener installationListener = new AgentBuilder.InstallationListener.StreamWriting(printStream);
+ installationListener.onInstall(instrumentation, classFileTransformer);
+ verify(printStream).printf("[Byte Buddy] INSTALL %s on %s%n", classFileTransformer, instrumentation);
+ verifyNoMoreInteractions(printStream);
+ }
+
+ @Test
+ public void testStreamWritingListenerError() throws Exception {
+ PrintStream printStream = mock(PrintStream.class);
+ AgentBuilder.InstallationListener installationListener = new AgentBuilder.InstallationListener.StreamWriting(printStream);
+ assertThat(installationListener.onError(instrumentation, classFileTransformer, throwable), is(throwable));
+ verify(printStream).printf("[Byte Buddy] ERROR %s on %s%n", classFileTransformer, instrumentation);
+ verifyNoMoreInteractions(printStream);
+ verify(throwable).printStackTrace(printStream);
+ verifyNoMoreInteractions(throwable);
+ }
+
+ @Test
+ public void testStreamWritingListenerReset() throws Exception {
+ PrintStream printStream = mock(PrintStream.class);
+ AgentBuilder.InstallationListener installationListener = new AgentBuilder.InstallationListener.StreamWriting(printStream);
+ installationListener.onReset(instrumentation, classFileTransformer);
+ verify(printStream).printf("[Byte Buddy] RESET %s on %s%n", classFileTransformer, instrumentation);
+ verifyNoMoreInteractions(printStream);
+ }
+
+ @Test
+ public void testCompoundListenerBeforeInstall() throws Exception {
+ AgentBuilder.InstallationListener first = mock(AgentBuilder.InstallationListener.class), second = mock(AgentBuilder.InstallationListener.class);
+ AgentBuilder.InstallationListener installationListener = new AgentBuilder.InstallationListener.Compound(first, second);
+ installationListener.onBeforeInstall(instrumentation, classFileTransformer);
+ verify(first).onBeforeInstall(instrumentation, classFileTransformer);
+ verify(second).onBeforeInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(first, second);
+ }
+
+ @Test
+ public void testCompoundListenerInstall() throws Exception {
+ AgentBuilder.InstallationListener first = mock(AgentBuilder.InstallationListener.class), second = mock(AgentBuilder.InstallationListener.class);
+ AgentBuilder.InstallationListener installationListener = new AgentBuilder.InstallationListener.Compound(first, second);
+ installationListener.onInstall(instrumentation, classFileTransformer);
+ verify(first).onInstall(instrumentation, classFileTransformer);
+ verify(second).onInstall(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(first, second);
+ }
+
+ @Test
+ public void testCompoundListenerError() throws Exception {
+ AgentBuilder.InstallationListener first = mock(AgentBuilder.InstallationListener.class), second = mock(AgentBuilder.InstallationListener.class);
+ when(first.onError(instrumentation, classFileTransformer, throwable)).thenReturn(throwable);
+ when(second.onError(instrumentation, classFileTransformer, throwable)).thenReturn(throwable);
+ AgentBuilder.InstallationListener installationListener = new AgentBuilder.InstallationListener.Compound(first, second);
+ assertThat(installationListener.onError(instrumentation, classFileTransformer, throwable), is(throwable));
+ verify(first).onError(instrumentation, classFileTransformer, throwable);
+ verify(second).onError(instrumentation, classFileTransformer, throwable);
+ verifyNoMoreInteractions(first, second);
+ }
+
+ @Test
+ public void testCompoundListenerErrorHandled() throws Exception {
+ AgentBuilder.InstallationListener first = mock(AgentBuilder.InstallationListener.class), second = mock(AgentBuilder.InstallationListener.class);
+ when(first.onError(instrumentation, classFileTransformer, throwable)).thenReturn(null);
+ AgentBuilder.InstallationListener installationListener = new AgentBuilder.InstallationListener.Compound(first, second);
+ assertThat(installationListener.onError(instrumentation, classFileTransformer, throwable), nullValue(Throwable.class));
+ verify(first).onError(instrumentation, classFileTransformer, throwable);
+ verifyNoMoreInteractions(first);
+ verifyZeroInteractions(second);
+ }
+
+ @Test
+ public void testCompoundListenerReset() throws Exception {
+ AgentBuilder.InstallationListener first = mock(AgentBuilder.InstallationListener.class), second = mock(AgentBuilder.InstallationListener.class);
+ AgentBuilder.InstallationListener installationListener = new AgentBuilder.InstallationListener.Compound(first, second);
+ installationListener.onReset(instrumentation, classFileTransformer);
+ verify(first).onReset(instrumentation, classFileTransformer);
+ verify(second).onReset(instrumentation, classFileTransformer);
+ verifyNoMoreInteractions(first, second);
+ }
+
+ @Test
+ public void testStreamWritingToSystem() throws Exception {
+ assertThat(AgentBuilder.InstallationListener.StreamWriting.toSystemOut(),
+ is((AgentBuilder.InstallationListener) new AgentBuilder.InstallationListener.StreamWriting(System.out)));
+ assertThat(AgentBuilder.InstallationListener.StreamWriting.toSystemErr(),
+ is((AgentBuilder.InstallationListener) new AgentBuilder.InstallationListener.StreamWriting(System.err)));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AgentBuilder.InstallationListener.StreamWriting.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.InstallationListener.Compound.class)
+ .create(new ObjectPropertyAssertion.Creator<List<?>>() {
+ @Override
+ public List<?> create() {
+ return Collections.singletonList(mock(AgentBuilder.InstallationListener.class));
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.InstallationListener.ErrorSuppressing.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.InstallationListener.NoOp.class).apply();
+ }
+
+ private static class PseudoAdapter extends AgentBuilder.InstallationListener.Adapter {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderLambdaInstrumentationStrategyTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderLambdaInstrumentationStrategyTest.java
new file mode 100644
index 0000000..aa3b1c7
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderLambdaInstrumentationStrategyTest.java
@@ -0,0 +1,81 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.Instrumentation;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+public class AgentBuilderLambdaInstrumentationStrategyTest {
+
+ @Test
+ public void testEnabled() throws Exception {
+ assertThat(AgentBuilder.LambdaInstrumentationStrategy.of(true).isEnabled(), is(true));
+ assertThat(AgentBuilder.LambdaInstrumentationStrategy.of(false).isEnabled(), is(false));
+ }
+
+ @Test
+ public void testEnabledStrategyNeverThrowsException() throws Exception {
+ ClassFileTransformer initialClassFileTransformer = mock(ClassFileTransformer.class);
+ assertThat(LambdaFactory.register(initialClassFileTransformer,
+ mock(AgentBuilder.Default.LambdaInstrumentationStrategy.LambdaInstanceFactory.class)), is(true));
+ try {
+ ByteBuddy byteBuddy = mock(ByteBuddy.class);
+ Instrumentation instrumentation = mock(Instrumentation.class);
+ ClassFileTransformer classFileTransformer = mock(ClassFileTransformer.class);
+ try {
+ AgentBuilder.Default.LambdaInstrumentationStrategy.ENABLED.apply(byteBuddy, instrumentation, classFileTransformer);
+ } finally {
+ assertThat(LambdaFactory.release(classFileTransformer), is(false));
+ }
+ } finally {
+ assertThat(LambdaFactory.release(initialClassFileTransformer), is(true));
+ }
+ }
+
+ @Test
+ public void testDisabledStrategyIsNoOp() throws Exception {
+ ByteBuddy byteBuddy = mock(ByteBuddy.class);
+ Instrumentation instrumentation = mock(Instrumentation.class);
+ ClassFileTransformer classFileTransformer = mock(ClassFileTransformer.class);
+ AgentBuilder.Default.LambdaInstrumentationStrategy.DISABLED.apply(byteBuddy, instrumentation, classFileTransformer);
+ verifyZeroInteractions(byteBuddy);
+ verifyZeroInteractions(instrumentation);
+ verifyZeroInteractions(classFileTransformer);
+ }
+
+ @Test
+ public void testEnabledIsInstrumented() throws Exception {
+ assertThat(AgentBuilder.LambdaInstrumentationStrategy.ENABLED.isInstrumented(Object.class), is(true));
+ assertThat(AgentBuilder.LambdaInstrumentationStrategy.ENABLED.isInstrumented(null), is(true));
+ }
+
+ @Test
+ public void testDisabledIsInstrumented() throws Exception {
+ assertThat(AgentBuilder.LambdaInstrumentationStrategy.DISABLED.isInstrumented(Object.class), is(true));
+ assertThat(AgentBuilder.LambdaInstrumentationStrategy.DISABLED.isInstrumented(null), is(true));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AgentBuilder.Default.LambdaInstrumentationStrategy.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Default.LambdaInstrumentationStrategy.MetaFactoryRedirection.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Default.LambdaInstrumentationStrategy.AlternativeMetaFactoryRedirection.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Default.LambdaInstrumentationStrategy.LambdaInstanceFactory.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Default.LambdaInstrumentationStrategy.LambdaInstanceFactory.BridgeMethodImplementation.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Default.LambdaInstrumentationStrategy.LambdaInstanceFactory.BridgeMethodImplementation.Appender.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Default.LambdaInstrumentationStrategy.LambdaInstanceFactory.ConstructorImplementation.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Default.LambdaInstrumentationStrategy.LambdaInstanceFactory.ConstructorImplementation.Appender.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Default.LambdaInstrumentationStrategy.LambdaInstanceFactory.FactoryImplementation.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Default.LambdaInstrumentationStrategy.LambdaInstanceFactory.FactoryImplementation.Appender.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Default.LambdaInstrumentationStrategy.LambdaInstanceFactory.FactoryImplementation.Appender.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Default.LambdaInstrumentationStrategy.LambdaInstanceFactory.LambdaMethodImplementation.Appender.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Default.LambdaInstrumentationStrategy.LambdaInstanceFactory.SerializationImplementation.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderListenerTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderListenerTest.java
new file mode 100644
index 0000000..19b8052
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderListenerTest.java
@@ -0,0 +1,327 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.matcher.ElementMatchers;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.utility.JavaModule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.io.PrintStream;
+import java.lang.instrument.Instrumentation;
+import java.util.Collections;
+import java.util.List;
+
+import static net.bytebuddy.matcher.ElementMatchers.none;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class AgentBuilderListenerTest {
+
+ private static final String FOO = "foo";
+
+ private static final boolean LOADED = true;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private AgentBuilder.Listener first, second;
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Mock
+ private ClassLoader classLoader;
+
+ @Mock
+ private JavaModule module;
+
+ @Mock
+ private DynamicType dynamicType;
+
+ @Mock
+ private Throwable throwable;
+
+ @Before
+ public void setUp() throws Exception {
+ when(typeDescription.getName()).thenReturn(FOO);
+ }
+
+ @Test
+ public void testNoOp() throws Exception {
+ AgentBuilder.Listener.NoOp.INSTANCE.onDiscovery(FOO, classLoader, module, LOADED);
+ AgentBuilder.Listener.NoOp.INSTANCE.onTransformation(typeDescription, classLoader, module, LOADED, dynamicType);
+ verifyZeroInteractions(dynamicType);
+ AgentBuilder.Listener.NoOp.INSTANCE.onError(FOO, classLoader, module, LOADED, throwable);
+ verifyZeroInteractions(throwable);
+ AgentBuilder.Listener.NoOp.INSTANCE.onIgnored(typeDescription, classLoader, module, LOADED);
+ AgentBuilder.Listener.NoOp.INSTANCE.onComplete(FOO, classLoader, module, LOADED);
+ }
+
+ @Test
+ public void testPseudoAdapter() throws Exception {
+ AgentBuilder.Listener listener = new PseudoAdapter();
+ listener.onDiscovery(FOO, classLoader, module, LOADED);
+ listener.onTransformation(typeDescription, classLoader, module, LOADED, dynamicType);
+ verifyZeroInteractions(dynamicType);
+ listener.onError(FOO, classLoader, module, LOADED, throwable);
+ verifyZeroInteractions(throwable);
+ listener.onIgnored(typeDescription, classLoader, module, LOADED);
+ listener.onComplete(FOO, classLoader, module, LOADED);
+ }
+
+ @Test
+ public void testCompoundOnDiscovery() throws Exception {
+ new AgentBuilder.Listener.Compound(first, second).onDiscovery(FOO, classLoader, module, LOADED);
+ verify(first).onDiscovery(FOO, classLoader, module, LOADED);
+ verifyNoMoreInteractions(first);
+ verify(second).onDiscovery(FOO, classLoader, module, LOADED);
+ verifyNoMoreInteractions(second);
+ }
+
+ @Test
+ public void testCompoundOnTransformation() throws Exception {
+ new AgentBuilder.Listener.Compound(first, second).onTransformation(typeDescription, classLoader, module, LOADED, dynamicType);
+ verify(first).onTransformation(typeDescription, classLoader, module, LOADED, dynamicType);
+ verifyNoMoreInteractions(first);
+ verify(second).onTransformation(typeDescription, classLoader, module, LOADED, dynamicType);
+ verifyNoMoreInteractions(second);
+ }
+
+ @Test
+ public void testCompoundOnError() throws Exception {
+ new AgentBuilder.Listener.Compound(first, second).onError(FOO, classLoader, module, LOADED, throwable);
+ verify(first).onError(FOO, classLoader, module, LOADED, throwable);
+ verifyNoMoreInteractions(first);
+ verify(second).onError(FOO, classLoader, module, LOADED, throwable);
+ verifyNoMoreInteractions(second);
+ }
+
+ @Test
+ public void testCompoundOnIgnored() throws Exception {
+ new AgentBuilder.Listener.Compound(first, second).onIgnored(typeDescription, classLoader, module, LOADED);
+ verify(first).onIgnored(typeDescription, classLoader, module, LOADED);
+ verifyNoMoreInteractions(first);
+ verify(second).onIgnored(typeDescription, classLoader, module, LOADED);
+ verifyNoMoreInteractions(second);
+ }
+
+ @Test
+ public void testCompoundOnComplete() throws Exception {
+ new AgentBuilder.Listener.Compound(first, second).onComplete(FOO, classLoader, module, LOADED);
+ verify(first).onComplete(FOO, classLoader, module, LOADED);
+ verifyNoMoreInteractions(first);
+ verify(second).onComplete(FOO, classLoader, module, LOADED);
+ verifyNoMoreInteractions(second);
+ }
+
+ @Test
+ public void testStreamWritingOnDiscovery() throws Exception {
+ PrintStream printStream = mock(PrintStream.class);
+ AgentBuilder.Listener listener = new AgentBuilder.Listener.StreamWriting(printStream);
+ listener.onDiscovery(FOO, classLoader, module, LOADED);
+ verify(printStream).printf("[Byte Buddy] DISCOVERY %s [%s, %s, loaded=%b]%n", FOO, classLoader, module, LOADED);
+ verifyNoMoreInteractions(printStream);
+ }
+
+ @Test
+ public void testStreamWritingOnTransformation() throws Exception {
+ PrintStream printStream = mock(PrintStream.class);
+ AgentBuilder.Listener listener = new AgentBuilder.Listener.StreamWriting(printStream);
+ listener.onTransformation(typeDescription, classLoader, module, LOADED, dynamicType);
+ verify(printStream).printf("[Byte Buddy] TRANSFORM %s [%s, %s, loaded=%b]%n", FOO, classLoader, module, LOADED);
+ verifyNoMoreInteractions(printStream);
+ }
+
+ @Test
+ public void testStreamWritingOnError() throws Exception {
+ PrintStream printStream = mock(PrintStream.class);
+ AgentBuilder.Listener listener = new AgentBuilder.Listener.StreamWriting(printStream);
+ listener.onError(FOO, classLoader, module, LOADED, throwable);
+ verify(printStream).printf("[Byte Buddy] ERROR %s [%s, %s, loaded=%b]%n", FOO, classLoader, module, LOADED);
+ verifyNoMoreInteractions(printStream);
+ verify(throwable).printStackTrace(printStream);
+ verifyNoMoreInteractions(throwable);
+ }
+
+ @Test
+ public void testStreamWritingOnComplete() throws Exception {
+ PrintStream printStream = mock(PrintStream.class);
+ AgentBuilder.Listener listener = new AgentBuilder.Listener.StreamWriting(printStream);
+ listener.onComplete(FOO, classLoader, module, LOADED);
+ verify(printStream).printf("[Byte Buddy] COMPLETE %s [%s, %s, loaded=%b]%n", FOO, classLoader, module, LOADED);
+ verifyNoMoreInteractions(printStream);
+ }
+
+ @Test
+ public void testStreamWritingOnIgnore() throws Exception {
+ PrintStream printStream = mock(PrintStream.class);
+ AgentBuilder.Listener listener = new AgentBuilder.Listener.StreamWriting(printStream);
+ listener.onIgnored(typeDescription, classLoader, module, LOADED);
+ verify(printStream).printf("[Byte Buddy] IGNORE %s [%s, %s, loaded=%b]%n", FOO, classLoader, module, LOADED);
+ verifyNoMoreInteractions(printStream);
+ }
+
+ @Test
+ public void testStreamWritingStandardOutput() throws Exception {
+ assertThat(AgentBuilder.Listener.StreamWriting.toSystemOut(), is((AgentBuilder.Listener) new AgentBuilder.Listener.StreamWriting(System.out)));
+ }
+
+ @Test
+ public void testStreamWritingStandardError() throws Exception {
+ assertThat(AgentBuilder.Listener.StreamWriting.toSystemError(), is((AgentBuilder.Listener) new AgentBuilder.Listener.StreamWriting(System.err)));
+ }
+
+ @Test
+ public void testFilteringDoesNotMatch() throws Exception {
+ AgentBuilder.Listener delegate = mock(AgentBuilder.Listener.class);
+ AgentBuilder.Listener listener = new AgentBuilder.Listener.Filtering(none(), delegate);
+ listener.onDiscovery(FOO, classLoader, module, LOADED);
+ listener.onTransformation(typeDescription, classLoader, module, LOADED, dynamicType);
+ listener.onError(FOO, classLoader, module, LOADED, throwable);
+ listener.onIgnored(typeDescription, classLoader, module, LOADED);
+ listener.onComplete(FOO, classLoader, module, LOADED);
+ verifyZeroInteractions(delegate);
+ }
+
+ @Test
+ public void testFilteringMatch() throws Exception {
+ AgentBuilder.Listener delegate = mock(AgentBuilder.Listener.class);
+ AgentBuilder.Listener listener = new AgentBuilder.Listener.Filtering(ElementMatchers.any(), delegate);
+ listener.onDiscovery(FOO, classLoader, module, LOADED);
+ listener.onTransformation(typeDescription, classLoader, module, LOADED, dynamicType);
+ listener.onError(FOO, classLoader, module, LOADED, throwable);
+ listener.onIgnored(typeDescription, classLoader, module, LOADED);
+ listener.onComplete(FOO, classLoader, module, LOADED);
+ verify(delegate).onDiscovery(FOO, classLoader, module, LOADED);
+ verify(delegate).onTransformation(typeDescription, classLoader, module, LOADED, dynamicType);
+ verify(delegate).onError(FOO, classLoader, module, LOADED, throwable);
+ verify(delegate).onIgnored(typeDescription, classLoader, module, LOADED);
+ verify(delegate).onComplete(FOO, classLoader, module, LOADED);
+ verifyNoMoreInteractions(delegate);
+ }
+
+ @Test
+ public void testReadEdgeAddingListenerNotSupported() throws Exception {
+ Instrumentation instrumentation = mock(Instrumentation.class);
+ AgentBuilder.Listener listener = new AgentBuilder.Listener.ModuleReadEdgeCompleting(instrumentation, false, Collections.<JavaModule>emptySet());
+ listener.onTransformation(mock(TypeDescription.class), mock(ClassLoader.class), JavaModule.UNSUPPORTED, LOADED, mock(DynamicType.class));
+ }
+
+ @Test
+ public void testReadEdgeAddingListenerUnnamed() throws Exception {
+ Instrumentation instrumentation = mock(Instrumentation.class);
+ JavaModule source = mock(JavaModule.class), target = mock(JavaModule.class);
+ AgentBuilder.Listener listener = new AgentBuilder.Listener.ModuleReadEdgeCompleting(instrumentation, false, Collections.singleton(target));
+ listener.onTransformation(mock(TypeDescription.class), mock(ClassLoader.class), source, LOADED, mock(DynamicType.class));
+ verify(source).isNamed();
+ verifyNoMoreInteractions(source);
+ verifyZeroInteractions(target);
+ }
+
+ @Test
+ public void testReadEdgeAddingListenerCanRead() throws Exception {
+ Instrumentation instrumentation = mock(Instrumentation.class);
+ JavaModule source = mock(JavaModule.class), target = mock(JavaModule.class);
+ when(source.isNamed()).thenReturn(true);
+ when(source.canRead(target)).thenReturn(true);
+ AgentBuilder.Listener listener = new AgentBuilder.Listener.ModuleReadEdgeCompleting(instrumentation, false, Collections.singleton(target));
+ listener.onTransformation(mock(TypeDescription.class), mock(ClassLoader.class), source, LOADED, mock(DynamicType.class));
+ verify(source).isNamed();
+ verify(source).canRead(target);
+ verifyNoMoreInteractions(source);
+ verifyZeroInteractions(target);
+ }
+
+ @Test
+ public void testReadEdgeAddingListenerNamedCannotRead() throws Exception {
+ Instrumentation instrumentation = mock(Instrumentation.class);
+ JavaModule source = mock(JavaModule.class), target = mock(JavaModule.class);
+ when(source.isNamed()).thenReturn(true);
+ when(source.canRead(target)).thenReturn(false);
+ AgentBuilder.Listener listener = new AgentBuilder.Listener.ModuleReadEdgeCompleting(instrumentation, false, Collections.singleton(target));
+ listener.onTransformation(mock(TypeDescription.class), mock(ClassLoader.class), source, LOADED, mock(DynamicType.class));
+ verify(source).isNamed();
+ verify(source).canRead(target);
+ verify(source).addReads(instrumentation, target);
+ verifyNoMoreInteractions(source);
+ verifyZeroInteractions(target);
+ }
+
+ @Test
+ public void testReadEdgeAddingListenerDuplexNotSupported() throws Exception {
+ Instrumentation instrumentation = mock(Instrumentation.class);
+ AgentBuilder.Listener listener = new AgentBuilder.Listener.ModuleReadEdgeCompleting(instrumentation, true, Collections.<JavaModule>emptySet());
+ listener.onTransformation(mock(TypeDescription.class), mock(ClassLoader.class), JavaModule.UNSUPPORTED, LOADED, mock(DynamicType.class));
+ }
+
+ @Test
+ public void testReadEdgeAddingListenerDuplexUnnamed() throws Exception {
+ Instrumentation instrumentation = mock(Instrumentation.class);
+ JavaModule source = mock(JavaModule.class), target = mock(JavaModule.class);
+ AgentBuilder.Listener listener = new AgentBuilder.Listener.ModuleReadEdgeCompleting(instrumentation, true, Collections.singleton(target));
+ listener.onTransformation(mock(TypeDescription.class), mock(ClassLoader.class), source, LOADED, mock(DynamicType.class));
+ verify(source).isNamed();
+ verifyNoMoreInteractions(source);
+ verifyZeroInteractions(target);
+ }
+
+ @Test
+ public void testReadEdgeAddingListenerDuplexCanRead() throws Exception {
+ Instrumentation instrumentation = mock(Instrumentation.class);
+ JavaModule source = mock(JavaModule.class), target = mock(JavaModule.class);
+ when(source.isNamed()).thenReturn(true);
+ when(source.canRead(target)).thenReturn(true);
+ when(target.canRead(source)).thenReturn(true);
+ AgentBuilder.Listener listener = new AgentBuilder.Listener.ModuleReadEdgeCompleting(instrumentation, true, Collections.singleton(target));
+ listener.onTransformation(mock(TypeDescription.class), mock(ClassLoader.class), source, LOADED, mock(DynamicType.class));
+ verify(source).isNamed();
+ verify(source).canRead(target);
+ verifyNoMoreInteractions(source);
+ verify(target).canRead(source);
+ verifyNoMoreInteractions(target);
+ }
+
+ @Test
+ public void testReadEdgeAddingListenerNamedDuplexCannotRead() throws Exception {
+ Instrumentation instrumentation = mock(Instrumentation.class);
+ JavaModule source = mock(JavaModule.class), target = mock(JavaModule.class);
+ when(source.isNamed()).thenReturn(true);
+ when(source.canRead(target)).thenReturn(false);
+ when(target.canRead(source)).thenReturn(false);
+ AgentBuilder.Listener listener = new AgentBuilder.Listener.ModuleReadEdgeCompleting(instrumentation, true, Collections.singleton(target));
+ listener.onTransformation(mock(TypeDescription.class), mock(ClassLoader.class), source, LOADED, mock(DynamicType.class));
+ verify(source).isNamed();
+ verify(source).canRead(target);
+ verify(source).addReads(instrumentation, target);
+ verifyNoMoreInteractions(source);
+ verify(target).canRead(source);
+ verify(target).addReads(instrumentation, source);
+ verifyNoMoreInteractions(target);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AgentBuilder.Listener.NoOp.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Listener.StreamWriting.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Listener.Filtering.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Listener.Compound.class).create(new ObjectPropertyAssertion.Creator<List<?>>() {
+ @Override
+ public List<?> create() {
+ return Collections.singletonList(mock(AgentBuilder.Listener.class));
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Listener.ModuleReadEdgeCompleting.class).apply();
+ }
+
+ private static class PseudoAdapter extends AgentBuilder.Listener.Adapter {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderLocationStrategyCompoundTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderLocationStrategyCompoundTest.java
new file mode 100644
index 0000000..ccb56f9
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderLocationStrategyCompoundTest.java
@@ -0,0 +1,57 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.utility.JavaModule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.Collections;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class AgentBuilderLocationStrategyCompoundTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private AgentBuilder.LocationStrategy first, second;
+
+ @Mock
+ private ClassFileLocator firstLocator, secondLocator;
+
+ @Mock
+ private ClassLoader classLoader;
+
+ @Mock
+ private JavaModule module;
+
+ @Test
+ public void testApplication() throws Exception {
+ AgentBuilder.LocationStrategy locationStrategy = new AgentBuilder.LocationStrategy.Compound(first, second);
+ when(first.classFileLocator(classLoader, module)).thenReturn(firstLocator);
+ when(second.classFileLocator(classLoader, module)).thenReturn(secondLocator);
+ assertThat(locationStrategy.classFileLocator(classLoader, module), is((ClassFileLocator) new ClassFileLocator.Compound(firstLocator, secondLocator)));
+ verify(first).classFileLocator(classLoader, module);
+ verifyNoMoreInteractions(first);
+ verify(second).classFileLocator(classLoader, module);
+ verifyNoMoreInteractions(second);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AgentBuilder.LocationStrategy.Compound.class).create(new ObjectPropertyAssertion.Creator<List<?>>() {
+ @Override
+ public List<?> create() {
+ return Collections.singletonList(mock(AgentBuilder.LocationStrategy.class));
+ }
+ }).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderLocationStrategyForClassLoaderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderLocationStrategyForClassLoaderTest.java
new file mode 100644
index 0000000..11bc8f6
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderLocationStrategyForClassLoaderTest.java
@@ -0,0 +1,64 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.utility.JavaModule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class AgentBuilderLocationStrategyForClassLoaderTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private ClassLoader classLoader;
+
+ @Mock
+ private JavaModule module;
+
+ @Mock
+ private AgentBuilder.LocationStrategy fallback;
+
+ @Mock
+ private ClassFileLocator classFileLocator;
+
+ @Test
+ public void testStrongLocationStrategy() throws Exception {
+ assertThat(AgentBuilder.LocationStrategy.ForClassLoader.STRONG.classFileLocator(classLoader, module),
+ is(ClassFileLocator.ForClassLoader.of(classLoader)));
+ }
+
+ @Test
+ public void testWeakLocationStrategy() throws Exception {
+ assertThat(AgentBuilder.LocationStrategy.ForClassLoader.WEAK.classFileLocator(classLoader, module),
+ is(ClassFileLocator.ForClassLoader.WeaklyReferenced.of(classLoader)));
+ }
+
+ @Test
+ public void testFallback() throws Exception {
+ assertThat(AgentBuilder.LocationStrategy.ForClassLoader.STRONG.withFallbackTo(fallback),
+ is((AgentBuilder.LocationStrategy) new AgentBuilder.LocationStrategy.Compound(AgentBuilder.LocationStrategy.ForClassLoader.STRONG, fallback)));
+ assertThat(AgentBuilder.LocationStrategy.ForClassLoader.WEAK.withFallbackTo(fallback),
+ is((AgentBuilder.LocationStrategy) new AgentBuilder.LocationStrategy.Compound(AgentBuilder.LocationStrategy.ForClassLoader.WEAK, fallback)));
+ }
+
+ @Test
+ public void testFallbackLocator() throws Exception {
+ assertThat(AgentBuilder.LocationStrategy.ForClassLoader.STRONG.withFallbackTo(classFileLocator),
+ is((AgentBuilder.LocationStrategy) new AgentBuilder.LocationStrategy.Compound(AgentBuilder.LocationStrategy.ForClassLoader.STRONG, new AgentBuilder.LocationStrategy.Simple(classFileLocator))));
+ assertThat(AgentBuilder.LocationStrategy.ForClassLoader.WEAK.withFallbackTo(classFileLocator),
+ is((AgentBuilder.LocationStrategy) new AgentBuilder.LocationStrategy.Compound(AgentBuilder.LocationStrategy.ForClassLoader.WEAK, new AgentBuilder.LocationStrategy.Simple(classFileLocator))));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AgentBuilder.LocationStrategy.ForClassLoader.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderLocationStrategyNoOpTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderLocationStrategyNoOpTest.java
new file mode 100644
index 0000000..9565422
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderLocationStrategyNoOpTest.java
@@ -0,0 +1,24 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.utility.JavaModule;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class AgentBuilderLocationStrategyNoOpTest {
+
+ @Test
+ public void testApplication() throws Exception {
+ assertThat(AgentBuilder.LocationStrategy.NoOp.INSTANCE.classFileLocator(mock(ClassLoader.class), mock(JavaModule.class)),
+ is((ClassFileLocator) ClassFileLocator.NoOp.INSTANCE));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AgentBuilder.LocationStrategy.NoOp.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderLocationStrategySimpleTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderLocationStrategySimpleTest.java
new file mode 100644
index 0000000..2ce776b
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderLocationStrategySimpleTest.java
@@ -0,0 +1,24 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.utility.JavaModule;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class AgentBuilderLocationStrategySimpleTest {
+
+ @Test
+ public void testLocation() throws Exception {
+ ClassFileLocator classFileLocator = mock(ClassFileLocator.class);
+ assertThat(new AgentBuilder.LocationStrategy.Simple(classFileLocator).classFileLocator(mock(ClassLoader.class), mock(JavaModule.class)), is(classFileLocator));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AgentBuilder.LocationStrategy.Simple.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderPoolStrategyTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderPoolStrategyTest.java
new file mode 100644
index 0000000..832c016
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderPoolStrategyTest.java
@@ -0,0 +1,62 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.pool.TypePool;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class AgentBuilderPoolStrategyTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private ClassLoader classLoader;
+
+ @Mock
+ private ClassFileLocator classFileLocator;
+
+ @Test
+ public void testFastTypePool() throws Exception {
+ assertThat(AgentBuilder.PoolStrategy.Default.FAST.typePool(classFileLocator, classLoader), notNullValue(TypePool.class));
+ }
+
+ @Test
+ public void testExtendedTypePool() throws Exception {
+ assertThat(AgentBuilder.PoolStrategy.Default.EXTENDED.typePool(classFileLocator, classLoader), notNullValue(TypePool.class));
+ }
+
+ @Test
+ public void testFastEagerTypePool() throws Exception {
+ assertThat(AgentBuilder.PoolStrategy.Eager.FAST.typePool(classFileLocator, classLoader), notNullValue(TypePool.class));
+ }
+
+ @Test
+ public void testExtendedEagerTypePool() throws Exception {
+ assertThat(AgentBuilder.PoolStrategy.Eager.EXTENDED.typePool(classFileLocator, classLoader), notNullValue(TypePool.class));
+ }
+
+ @Test
+ public void testFastLoadingTypePool() throws Exception {
+ assertThat(AgentBuilder.PoolStrategy.ClassLoading.FAST.typePool(classFileLocator, classLoader), notNullValue(TypePool.class));
+ }
+
+ @Test
+ public void testExtendedLoadingTypePool() throws Exception {
+ assertThat(AgentBuilder.PoolStrategy.ClassLoading.EXTENDED.typePool(classFileLocator, classLoader), notNullValue(TypePool.class));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AgentBuilder.PoolStrategy.Default.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.PoolStrategy.Eager.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.PoolStrategy.ClassLoading.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderRawMatcherConjunctionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderRawMatcherConjunctionTest.java
new file mode 100644
index 0000000..a2dcf0b
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderRawMatcherConjunctionTest.java
@@ -0,0 +1,92 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.utility.JavaModule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.security.ProtectionDomain;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class AgentBuilderRawMatcherConjunctionTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private AgentBuilder.RawMatcher left, right;
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Mock
+ private ClassLoader classLoader;
+
+ @Mock
+ private JavaModule module;
+
+ @Mock
+ private ProtectionDomain protectionDomain;
+
+ @Test
+ public void testMatches() throws Exception {
+ when(left.matches(typeDescription, classLoader, module, Foo.class, protectionDomain)).thenReturn(true);
+ when(right.matches(typeDescription, classLoader, module, Foo.class, protectionDomain)).thenReturn(true);
+ AgentBuilder.RawMatcher rawMatcher = new AgentBuilder.RawMatcher.Conjunction(left, right);
+ assertThat(rawMatcher.matches(typeDescription, classLoader, module, Foo.class, protectionDomain), is(true));
+ verify(left).matches(typeDescription, classLoader, module, Foo.class, protectionDomain);
+ verifyNoMoreInteractions(left);
+ verify(right).matches(typeDescription, classLoader, module, Foo.class, protectionDomain);
+ verifyNoMoreInteractions(right);
+ }
+
+ @Test
+ public void testNotMatchesLeft() throws Exception {
+ when(left.matches(typeDescription, classLoader, module, Foo.class, protectionDomain)).thenReturn(true);
+ when(right.matches(typeDescription, classLoader, module, Foo.class, protectionDomain)).thenReturn(false);
+ AgentBuilder.RawMatcher rawMatcher = new AgentBuilder.RawMatcher.Conjunction(left, right);
+ assertThat(rawMatcher.matches(typeDescription, classLoader, module, Foo.class, protectionDomain), is(false));
+ verify(left).matches(typeDescription, classLoader, module, Foo.class, protectionDomain);
+ verifyNoMoreInteractions(left);
+ verify(right).matches(typeDescription, classLoader, module, Foo.class, protectionDomain);
+ verifyNoMoreInteractions(right);
+ }
+
+ @Test
+ public void testNotMatchesRight() throws Exception {
+ when(left.matches(typeDescription, classLoader, module, Foo.class, protectionDomain)).thenReturn(false);
+ when(right.matches(typeDescription, classLoader, module, Foo.class, protectionDomain)).thenReturn(true);
+ AgentBuilder.RawMatcher rawMatcher = new AgentBuilder.RawMatcher.Conjunction(left, right);
+ assertThat(rawMatcher.matches(typeDescription, classLoader, module, Foo.class, protectionDomain), is(false));
+ verify(left).matches(typeDescription, classLoader, module, Foo.class, protectionDomain);
+ verifyNoMoreInteractions(left);
+ verifyZeroInteractions(right);
+ }
+
+ @Test
+ public void testNotMatchesEither() throws Exception {
+ when(left.matches(typeDescription, classLoader, module, Foo.class, protectionDomain)).thenReturn(false);
+ when(right.matches(typeDescription, classLoader, module, Foo.class, protectionDomain)).thenReturn(false);
+ AgentBuilder.RawMatcher rawMatcher = new AgentBuilder.RawMatcher.Conjunction(left, right);
+ assertThat(rawMatcher.matches(typeDescription, classLoader, module, Foo.class, protectionDomain), is(false));
+ verify(left).matches(typeDescription, classLoader, module, Foo.class, protectionDomain);
+ verifyNoMoreInteractions(left);
+ verifyZeroInteractions(right);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AgentBuilder.RawMatcher.Conjunction.class).apply();
+ }
+
+ private static class Foo {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderRawMatcherDisjunctionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderRawMatcherDisjunctionTest.java
new file mode 100644
index 0000000..4115eb9
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderRawMatcherDisjunctionTest.java
@@ -0,0 +1,93 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.utility.JavaModule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.security.ProtectionDomain;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class AgentBuilderRawMatcherDisjunctionTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private AgentBuilder.RawMatcher left, right;
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Mock
+ private ClassLoader classLoader;
+
+ @Mock
+ private JavaModule module;
+
+ @Mock
+ private ProtectionDomain protectionDomain;
+
+ @Test
+ public void testMatches() throws Exception {
+ when(left.matches(typeDescription, classLoader, module, Foo.class, protectionDomain)).thenReturn(true);
+ when(right.matches(typeDescription, classLoader, module, Foo.class, protectionDomain)).thenReturn(true);
+ AgentBuilder.RawMatcher rawMatcher = new AgentBuilder.RawMatcher.Disjunction(left, right);
+ assertThat(rawMatcher.matches(typeDescription, classLoader, module, Foo.class, protectionDomain), is(true));
+ verify(left).matches(typeDescription, classLoader, module, Foo.class, protectionDomain);
+ verifyNoMoreInteractions(left);
+ verifyZeroInteractions(right);
+ }
+
+ @Test
+ public void testNotMatchesLeft() throws Exception {
+ when(left.matches(typeDescription, classLoader, module, Foo.class, protectionDomain)).thenReturn(true);
+ when(right.matches(typeDescription, classLoader, module, Foo.class, protectionDomain)).thenReturn(false);
+ AgentBuilder.RawMatcher rawMatcher = new AgentBuilder.RawMatcher.Disjunction(left, right);
+ assertThat(rawMatcher.matches(typeDescription, classLoader, module, Foo.class, protectionDomain), is(true));
+ verify(left).matches(typeDescription, classLoader, module, Foo.class, protectionDomain);
+ verifyNoMoreInteractions(left);
+ verifyZeroInteractions(right);
+ }
+
+ @Test
+ public void testNotMatchesRight() throws Exception {
+ when(left.matches(typeDescription, classLoader, module, Foo.class, protectionDomain)).thenReturn(false);
+ when(right.matches(typeDescription, classLoader, module, Foo.class, protectionDomain)).thenReturn(true);
+ AgentBuilder.RawMatcher rawMatcher = new AgentBuilder.RawMatcher.Disjunction(left, right);
+ assertThat(rawMatcher.matches(typeDescription, classLoader, module, Foo.class, protectionDomain), is(true));
+ verify(left).matches(typeDescription, classLoader, module, Foo.class, protectionDomain);
+ verifyNoMoreInteractions(left);
+ verify(right).matches(typeDescription, classLoader, module, Foo.class, protectionDomain);
+ verifyNoMoreInteractions(right);
+ }
+
+ @Test
+ public void testNotMatchesEither() throws Exception {
+ when(left.matches(typeDescription, classLoader, module, Foo.class, protectionDomain)).thenReturn(false);
+ when(right.matches(typeDescription, classLoader, module, Foo.class, protectionDomain)).thenReturn(false);
+ AgentBuilder.RawMatcher rawMatcher = new AgentBuilder.RawMatcher.Disjunction(left, right);
+ assertThat(rawMatcher.matches(typeDescription, classLoader, module, Foo.class, protectionDomain), is(false));
+ verify(left).matches(typeDescription, classLoader, module, Foo.class, protectionDomain);
+ verifyNoMoreInteractions(left);
+ verify(right).matches(typeDescription, classLoader, module, Foo.class, protectionDomain);
+ verifyNoMoreInteractions(right);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AgentBuilder.RawMatcher.Disjunction.class).apply();
+ }
+
+ private static class Foo {
+ /* empty */
+ }
+
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderRawMatcherForElementMatchersTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderRawMatcherForElementMatchersTest.java
new file mode 100644
index 0000000..7355318
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderRawMatcherForElementMatchersTest.java
@@ -0,0 +1,145 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.utility.JavaModule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.security.ProtectionDomain;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class AgentBuilderRawMatcherForElementMatchersTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private ElementMatcher<TypeDescription> typeMatcher;
+
+ @Mock
+ private ElementMatcher<ClassLoader> classLoaderMatcher;
+
+ @Mock
+ private ElementMatcher<JavaModule> moduleMatcher;
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Mock
+ private ClassLoader classLoader;
+
+ @Mock
+ private JavaModule module;
+
+ @Mock
+ private ProtectionDomain protectionDomain;
+
+ @Test
+ public void testNoneMatches() throws Exception {
+ assertThat(new AgentBuilder.RawMatcher.ForElementMatchers(typeMatcher, classLoaderMatcher, moduleMatcher)
+ .matches(typeDescription, classLoader, module, Object.class, protectionDomain), is(false));
+ verify(moduleMatcher).matches(module);
+ verifyNoMoreInteractions(moduleMatcher);
+ verifyNoMoreInteractions(classLoaderMatcher);
+ verifyZeroInteractions(typeMatcher);
+ }
+
+ @Test
+ public void testModuleMatches() throws Exception {
+ when(moduleMatcher.matches(module)).thenReturn(true);
+ assertThat(new AgentBuilder.RawMatcher.ForElementMatchers(typeMatcher, classLoaderMatcher, moduleMatcher)
+ .matches(typeDescription, classLoader, module, Object.class, protectionDomain), is(false));
+ verify(moduleMatcher).matches(module);
+ verifyNoMoreInteractions(moduleMatcher);
+ verify(classLoaderMatcher).matches(classLoader);
+ verifyNoMoreInteractions(classLoaderMatcher);
+ verifyZeroInteractions(typeMatcher);
+ }
+
+ @Test
+ public void testClassLoaderMatches() throws Exception {
+ when(classLoaderMatcher.matches(classLoader)).thenReturn(true);
+ assertThat(new AgentBuilder.RawMatcher.ForElementMatchers(typeMatcher, classLoaderMatcher, moduleMatcher)
+ .matches(typeDescription, classLoader, module, Object.class, protectionDomain), is(false));
+ verify(moduleMatcher).matches(module);
+ verifyNoMoreInteractions(moduleMatcher);
+ verifyZeroInteractions(classLoaderMatcher);
+ verifyNoMoreInteractions(typeMatcher);
+ }
+
+ @Test
+ public void testModuleAndClassLoaderMatches() throws Exception {
+ when(moduleMatcher.matches(module)).thenReturn(true);
+ when(classLoaderMatcher.matches(classLoader)).thenReturn(true);
+ assertThat(new AgentBuilder.RawMatcher.ForElementMatchers(typeMatcher, classLoaderMatcher, moduleMatcher)
+ .matches(typeDescription, classLoader, module, Object.class, protectionDomain), is(false));
+ verify(moduleMatcher).matches(module);
+ verifyNoMoreInteractions(moduleMatcher);
+ verify(classLoaderMatcher).matches(classLoader);
+ verifyNoMoreInteractions(classLoaderMatcher);
+ verify(typeMatcher).matches(typeDescription);
+ verifyNoMoreInteractions(typeMatcher);
+ }
+
+ @Test
+ public void testModuleAndTypeMatches() throws Exception {
+ when(moduleMatcher.matches(module)).thenReturn(true);
+ when(typeMatcher.matches(typeDescription)).thenReturn(true);
+ assertThat(new AgentBuilder.RawMatcher.ForElementMatchers(typeMatcher, classLoaderMatcher, moduleMatcher)
+ .matches(typeDescription, classLoader, module, Object.class, protectionDomain), is(false));
+ verify(moduleMatcher).matches(module);
+ verifyNoMoreInteractions(moduleMatcher);
+ verify(classLoaderMatcher).matches(classLoader);
+ verifyNoMoreInteractions(classLoaderMatcher);
+ verifyZeroInteractions(typeMatcher);
+ }
+
+ @Test
+ public void testClassLoaderAndTypeMatches() throws Exception {
+ when(classLoaderMatcher.matches(classLoader)).thenReturn(true);
+ when(typeMatcher.matches(typeDescription)).thenReturn(true);
+ assertThat(new AgentBuilder.RawMatcher.ForElementMatchers(typeMatcher, classLoaderMatcher, moduleMatcher)
+ .matches(typeDescription, classLoader, module, Object.class, protectionDomain), is(false));
+ verify(moduleMatcher).matches(module);
+ verifyNoMoreInteractions(moduleMatcher);
+ verifyZeroInteractions(classLoaderMatcher);
+ verifyZeroInteractions(typeMatcher);
+ }
+
+ @Test
+ public void testTypeMatches() throws Exception {
+ when(typeMatcher.matches(typeDescription)).thenReturn(true);
+ assertThat(new AgentBuilder.RawMatcher.ForElementMatchers(typeMatcher, classLoaderMatcher, moduleMatcher)
+ .matches(typeDescription, classLoader, module, Object.class, protectionDomain), is(false));
+ verify(moduleMatcher).matches(module);
+ verifyNoMoreInteractions(moduleMatcher);
+ verifyZeroInteractions(classLoaderMatcher);
+ verifyZeroInteractions(typeMatcher);
+ }
+
+ @Test
+ public void testAllMatches() throws Exception {
+ when(moduleMatcher.matches(module)).thenReturn(true);
+ when(classLoaderMatcher.matches(classLoader)).thenReturn(true);
+ when(typeMatcher.matches(typeDescription)).thenReturn(true);
+ assertThat(new AgentBuilder.RawMatcher.ForElementMatchers(typeMatcher, classLoaderMatcher, moduleMatcher)
+ .matches(typeDescription, classLoader, module, Object.class, protectionDomain), is(true));
+ verify(classLoaderMatcher).matches(classLoader);
+ verifyNoMoreInteractions(classLoaderMatcher);
+ verify(typeMatcher).matches(typeDescription);
+ verifyNoMoreInteractions(typeMatcher);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AgentBuilder.RawMatcher.ForElementMatchers.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderRawMatcherForLoadStateTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderRawMatcherForLoadStateTest.java
new file mode 100644
index 0000000..10be119
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderRawMatcherForLoadStateTest.java
@@ -0,0 +1,56 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.utility.JavaModule;
+import org.junit.Test;
+
+import java.security.ProtectionDomain;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.mock;
+
+public class AgentBuilderRawMatcherForLoadStateTest {
+
+ @Test
+ public void testLoadedOnLoaded() throws Exception {
+ assertThat(AgentBuilder.RawMatcher.ForLoadState.LOADED.matches(mock(TypeDescription.class),
+ mock(ClassLoader.class),
+ mock(JavaModule.class),
+ Object.class,
+ mock(ProtectionDomain.class)), is(true));
+ }
+
+ @Test
+ public void testLoadedOnUnloaded() throws Exception {
+ assertThat(AgentBuilder.RawMatcher.ForLoadState.LOADED.matches(mock(TypeDescription.class),
+ mock(ClassLoader.class),
+ mock(JavaModule.class),
+ null,
+ mock(ProtectionDomain.class)), is(false));
+ }
+
+ @Test
+ public void testUnloadedOnLoaded() throws Exception {
+ assertThat(AgentBuilder.RawMatcher.ForLoadState.UNLOADED.matches(mock(TypeDescription.class),
+ mock(ClassLoader.class),
+ mock(JavaModule.class),
+ Object.class,
+ mock(ProtectionDomain.class)), is(false));
+ }
+
+ @Test
+ public void testUnloadedOnUnloaded() throws Exception {
+ assertThat(AgentBuilder.RawMatcher.ForLoadState.UNLOADED.matches(mock(TypeDescription.class),
+ mock(ClassLoader.class),
+ mock(JavaModule.class),
+ null,
+ mock(ProtectionDomain.class)), is(true));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AgentBuilder.RawMatcher.ForLoadState.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderRawMatcherInversionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderRawMatcherInversionTest.java
new file mode 100644
index 0000000..088d840
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderRawMatcherInversionTest.java
@@ -0,0 +1,54 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.utility.JavaModule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.security.ProtectionDomain;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.when;
+
+public class AgentBuilderRawMatcherInversionTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private AgentBuilder.RawMatcher rawMatcher;
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Mock
+ private ClassLoader classLoader;
+
+ @Mock
+ private JavaModule module;
+
+ @Mock
+ private ProtectionDomain protectionDomain;
+
+ @Test
+ public void testInversionTrue() throws Exception {
+ when(rawMatcher.matches(typeDescription, classLoader, module, Object.class, protectionDomain)).thenReturn(true);
+ assertThat(new AgentBuilder.RawMatcher.Inversion(rawMatcher).matches(typeDescription, classLoader, module, Object.class, protectionDomain), is(false));
+ }
+
+ @Test
+ public void testInversionFalse() throws Exception {
+ when(rawMatcher.matches(typeDescription, classLoader, module, Object.class, protectionDomain)).thenReturn(false);
+ assertThat(new AgentBuilder.RawMatcher.Inversion(rawMatcher).matches(typeDescription, classLoader, module, Object.class, protectionDomain), is(true));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AgentBuilder.RawMatcher.Inversion.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderRawMatcherTrivialTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderRawMatcherTrivialTest.java
new file mode 100644
index 0000000..783e1fd
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderRawMatcherTrivialTest.java
@@ -0,0 +1,32 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.utility.JavaModule;
+import org.junit.Test;
+
+import java.security.ProtectionDomain;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class AgentBuilderRawMatcherTrivialTest {
+
+ @Test
+ public void testMatches() throws Exception {
+ assertThat(AgentBuilder.RawMatcher.Trivial.MATCHING.matches(mock(TypeDescription.class),
+ mock(ClassLoader.class),
+ mock(JavaModule.class),
+ Void.class,
+ mock(ProtectionDomain.class)), is(true));
+ }
+
+ @Test
+ public void testMatchesNot() throws Exception {
+ assertThat(AgentBuilder.RawMatcher.Trivial.NON_MATCHING.matches(mock(TypeDescription.class),
+ mock(ClassLoader.class),
+ mock(JavaModule.class),
+ Void.class,
+ mock(ProtectionDomain.class)), is(false));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderRedefinitionStrategyBatchAllocatorTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderRedefinitionStrategyBatchAllocatorTest.java
new file mode 100644
index 0000000..3283a8b
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderRedefinitionStrategyBatchAllocatorTest.java
@@ -0,0 +1,288 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.matcher.ElementMatchers;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class AgentBuilderRedefinitionStrategyBatchAllocatorTest {
+
+ @Test
+ public void testForTotalEmpty() throws Exception {
+ AgentBuilder.RedefinitionStrategy.BatchAllocator batchAllocator = AgentBuilder.RedefinitionStrategy.BatchAllocator.ForTotal.INSTANCE;
+ Iterator<? extends List<Class<?>>> iterator = batchAllocator.batch(Collections.<Class<?>>emptyList()).iterator();
+ assertThat(iterator.hasNext(), is(false));
+ }
+
+ @Test
+ public void testForTotal() throws Exception {
+ AgentBuilder.RedefinitionStrategy.BatchAllocator batchAllocator = AgentBuilder.RedefinitionStrategy.BatchAllocator.ForTotal.INSTANCE;
+ Iterator<? extends List<Class<?>>> iterator = batchAllocator.batch(Arrays.<Class<?>>asList(Object.class, Void.class, String.class)).iterator();
+ assertThat(iterator.hasNext(), is(true));
+ assertThat(iterator.next(), is(Arrays.<Class<?>>asList(Object.class, Void.class, String.class)));
+ assertThat(iterator.hasNext(), is(false));
+ }
+
+ @Test
+ public void testForFixed() throws Exception {
+ AgentBuilder.RedefinitionStrategy.BatchAllocator batchAllocator = new AgentBuilder.RedefinitionStrategy.BatchAllocator.ForFixedSize(2);
+ Iterator<? extends List<Class<?>>> iterator = batchAllocator.batch(Arrays.<Class<?>>asList(Object.class, Void.class, String.class)).iterator();
+ assertThat(iterator.hasNext(), is(true));
+ assertThat(iterator.next(), is(Arrays.<Class<?>>asList(Object.class, Void.class)));
+ assertThat(iterator.hasNext(), is(true));
+ assertThat(iterator.next(), is(Collections.<Class<?>>singletonList(String.class)));
+ assertThat(iterator.hasNext(), is(false));
+ }
+
+ @Test
+ public void testForFixedFactory() throws Exception {
+ assertThat(AgentBuilder.RedefinitionStrategy.BatchAllocator.ForFixedSize.ofSize(1),
+ is((AgentBuilder.RedefinitionStrategy.BatchAllocator) new AgentBuilder.RedefinitionStrategy.BatchAllocator.ForFixedSize(1)));
+ assertThat(AgentBuilder.RedefinitionStrategy.BatchAllocator.ForFixedSize.ofSize(0),
+ is((AgentBuilder.RedefinitionStrategy.BatchAllocator) AgentBuilder.RedefinitionStrategy.BatchAllocator.ForTotal.INSTANCE));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testForFixedFactoryIllegal() throws Exception {
+ AgentBuilder.RedefinitionStrategy.BatchAllocator.ForFixedSize.ofSize(-1);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testGrouping() throws Exception {
+ Iterator<? extends List<Class<?>>> batches = new AgentBuilder.RedefinitionStrategy.BatchAllocator.ForMatchedGrouping(ElementMatchers.is(Object.class),
+ ElementMatchers.is(Void.class)).batch(Arrays.<Class<?>>asList(Object.class, Void.class, String.class)).iterator();
+ assertThat(batches.hasNext(), is(true));
+ assertThat(batches.next(), is(Collections.<Class<?>>singletonList(Object.class)));
+ assertThat(batches.hasNext(), is(true));
+ assertThat(batches.next(), is(Collections.<Class<?>>singletonList(Void.class)));
+ assertThat(batches.hasNext(), is(true));
+ assertThat(batches.next(), is(Collections.<Class<?>>singletonList(String.class)));
+ assertThat(batches.hasNext(), is(false));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testEmptyGrouping() throws Exception {
+ Iterator<? extends List<Class<?>>> batches = new AgentBuilder.RedefinitionStrategy.BatchAllocator.ForMatchedGrouping(ElementMatchers.is(Object.class))
+ .batch(Collections.<Class<?>>emptyList()).iterator();
+ assertThat(batches.hasNext(), is(false));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMinimum() throws Exception {
+ Iterator<? extends List<Class<?>>> batches = new AgentBuilder.RedefinitionStrategy.BatchAllocator.ForMatchedGrouping(ElementMatchers.is(Object.class),
+ ElementMatchers.is(Void.class)).withMinimum(3).batch(Arrays.<Class<?>>asList(Object.class, Void.class, String.class)).iterator();
+ assertThat(batches.hasNext(), is(true));
+ assertThat(batches.next(), is(Arrays.<Class<?>>asList(Object.class, Void.class, String.class)));
+ assertThat(batches.hasNext(), is(false));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMinimumExcess() throws Exception {
+ Iterator<? extends List<Class<?>>> batches = new AgentBuilder.RedefinitionStrategy.BatchAllocator.ForMatchedGrouping(ElementMatchers.is(Object.class),
+ ElementMatchers.is(Void.class)).withMinimum(10).batch(Arrays.<Class<?>>asList(Object.class, Void.class, String.class)).iterator();
+ assertThat(batches.hasNext(), is(true));
+ assertThat(batches.next(), is(Arrays.<Class<?>>asList(Object.class, Void.class, String.class)));
+ assertThat(batches.hasNext(), is(false));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMinimumChunked() throws Exception {
+ Iterator<? extends List<Class<?>>> batches = new AgentBuilder.RedefinitionStrategy.BatchAllocator.ForMatchedGrouping(ElementMatchers.is(Object.class),
+ ElementMatchers.is(Void.class)).withMinimum(2).batch(Arrays.<Class<?>>asList(Object.class, Void.class, String.class)).iterator();
+ assertThat(batches.hasNext(), is(true));
+ assertThat(batches.next(), is(Arrays.<Class<?>>asList(Object.class, Void.class)));
+ assertThat(batches.hasNext(), is(true));
+ assertThat(batches.next(), is(Collections.<Class<?>>singletonList(String.class)));
+ assertThat(batches.hasNext(), is(false));
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ @SuppressWarnings("unchecked")
+ public void testMinimumCannotRemove() throws Exception {
+ new AgentBuilder.RedefinitionStrategy.BatchAllocator.ForMatchedGrouping(ElementMatchers.is(Object.class))
+ .withMinimum(2)
+ .batch(Collections.<Class<?>>singletonList(Object.class))
+ .iterator()
+ .remove();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ @SuppressWarnings("unchecked")
+ public void testMinimumNonPositive() throws Exception {
+ new AgentBuilder.RedefinitionStrategy.BatchAllocator.ForMatchedGrouping(ElementMatchers.is(Object.class)).withMinimum(0);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMaximum() throws Exception {
+ Iterator<? extends List<Class<?>>> batches = new AgentBuilder.RedefinitionStrategy.BatchAllocator.ForMatchedGrouping(ElementMatchers.is(Object.class)
+ .or(ElementMatchers.is(Void.class))).withMaximum(1).batch(Arrays.<Class<?>>asList(Object.class, Void.class, String.class)).iterator();
+ assertThat(batches.hasNext(), is(true));
+ assertThat(batches.next(), is(Collections.<Class<?>>singletonList(Object.class)));
+ assertThat(batches.hasNext(), is(true));
+ assertThat(batches.next(), is(Collections.<Class<?>>singletonList(Void.class)));
+ assertThat(batches.hasNext(), is(true));
+ assertThat(batches.next(), is(Collections.<Class<?>>singletonList(String.class)));
+ assertThat(batches.hasNext(), is(false));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMaximumExcess() throws Exception {
+ Iterator<? extends List<Class<?>>> batches = new AgentBuilder.RedefinitionStrategy.BatchAllocator.ForMatchedGrouping(ElementMatchers.is(Object.class)
+ .or(ElementMatchers.is(Void.class))).withMaximum(10).batch(Arrays.<Class<?>>asList(Object.class, Void.class, String.class)).iterator();
+ assertThat(batches.hasNext(), is(true));
+ assertThat(batches.next(), is(Arrays.<Class<?>>asList(Object.class, Void.class)));
+ assertThat(batches.hasNext(), is(true));
+ assertThat(batches.next(), is(Collections.<Class<?>>singletonList(String.class)));
+ assertThat(batches.hasNext(), is(false));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMaximumChunked() throws Exception {
+ Iterator<? extends List<Class<?>>> batches = new AgentBuilder.RedefinitionStrategy.BatchAllocator.ForMatchedGrouping(ElementMatchers.any())
+ .withMaximum(2)
+ .batch(Arrays.<Class<?>>asList(Object.class, Void.class, String.class))
+ .iterator();
+ assertThat(batches.hasNext(), is(true));
+ assertThat(batches.next(), is(Arrays.<Class<?>>asList(Object.class, Void.class)));
+ assertThat(batches.hasNext(), is(true));
+ assertThat(batches.next(), is(Collections.<Class<?>>singletonList(String.class)));
+ assertThat(batches.hasNext(), is(false));
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ @SuppressWarnings("unchecked")
+ public void testMaximumCannotRemove() throws Exception {
+ new AgentBuilder.RedefinitionStrategy.BatchAllocator.ForMatchedGrouping(ElementMatchers.is(Object.class))
+ .withMaximum(2)
+ .batch(Collections.<Class<?>>singletonList(Object.class))
+ .iterator()
+ .remove();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ @SuppressWarnings("unchecked")
+ public void testMaximumNonPositive() throws Exception {
+ new AgentBuilder.RedefinitionStrategy.BatchAllocator.ForMatchedGrouping(ElementMatchers.is(Object.class)).withMaximum(0);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testRange() throws Exception {
+ Iterator<? extends List<Class<?>>> batches = new AgentBuilder.RedefinitionStrategy.BatchAllocator.ForMatchedGrouping(ElementMatchers.is(Object.class)
+ .or(ElementMatchers.is(Void.class))).withinRange(1, 1).batch(Arrays.<Class<?>>asList(Object.class, Void.class, String.class)).iterator();
+ assertThat(batches.hasNext(), is(true));
+ assertThat(batches.next(), is(Collections.<Class<?>>singletonList(Object.class)));
+ assertThat(batches.hasNext(), is(true));
+ assertThat(batches.next(), is(Collections.<Class<?>>singletonList(Void.class)));
+ assertThat(batches.hasNext(), is(true));
+ assertThat(batches.next(), is(Collections.<Class<?>>singletonList(String.class)));
+ assertThat(batches.hasNext(), is(false));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testRangeExcess() throws Exception {
+ Iterator<? extends List<Class<?>>> batches = new AgentBuilder.RedefinitionStrategy.BatchAllocator.ForMatchedGrouping(ElementMatchers.is(Object.class)
+ .or(ElementMatchers.is(Void.class))).withinRange(1, 10).batch(Arrays.<Class<?>>asList(Object.class, Void.class, String.class)).iterator();
+ assertThat(batches.hasNext(), is(true));
+ assertThat(batches.next(), is(Arrays.<Class<?>>asList(Object.class, Void.class)));
+ assertThat(batches.hasNext(), is(true));
+ assertThat(batches.next(), is(Collections.<Class<?>>singletonList(String.class)));
+ assertThat(batches.hasNext(), is(false));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testRangeChunked() throws Exception {
+ Iterator<? extends List<Class<?>>> batches = new AgentBuilder.RedefinitionStrategy.BatchAllocator.ForMatchedGrouping(ElementMatchers.any())
+ .withinRange(1, 2)
+ .batch(Arrays.<Class<?>>asList(Object.class, Void.class, String.class))
+ .iterator();
+ assertThat(batches.hasNext(), is(true));
+ assertThat(batches.next(), is(Arrays.<Class<?>>asList(Object.class, Void.class)));
+ assertThat(batches.hasNext(), is(true));
+ assertThat(batches.next(), is(Collections.<Class<?>>singletonList(String.class)));
+ assertThat(batches.hasNext(), is(false));
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ @SuppressWarnings("unchecked")
+ public void testRangeCannotRemove() throws Exception {
+ new AgentBuilder.RedefinitionStrategy.BatchAllocator.ForMatchedGrouping(ElementMatchers.is(Object.class))
+ .withinRange(1, 2)
+ .batch(Collections.<Class<?>>singletonList(Object.class))
+ .iterator()
+ .remove();
+ }
+
+ @Test
+ public void testPartitioningWithoutReminder() throws Exception {
+ Iterator<? extends List<Class<?>>> iterator = new AgentBuilder.RedefinitionStrategy.BatchAllocator.Partitioning(2)
+ .batch(Arrays.<Class<?>>asList(Object.class, Void.class))
+ .iterator();
+ assertThat(iterator.hasNext(), is(true));
+ assertThat(iterator.next(), is(Collections.<Class<?>>singletonList(Object.class)));
+ assertThat(iterator.hasNext(), is(true));
+ assertThat(iterator.next(), is(Collections.<Class<?>>singletonList(Void.class)));
+ assertThat(iterator.hasNext(), is(false));
+ }
+
+ @Test
+ public void testPartitioningWithReminder() throws Exception {
+ Iterator<? extends List<Class<?>>> iterator = new AgentBuilder.RedefinitionStrategy.BatchAllocator.Partitioning(2)
+ .batch(Arrays.<Class<?>>asList(Object.class, Void.class, String.class))
+ .iterator();
+ assertThat(iterator.hasNext(), is(true));
+ assertThat(iterator.next(), is(Arrays.<Class<?>>asList(Object.class, Void.class)));
+ assertThat(iterator.hasNext(), is(true));
+ assertThat(iterator.next(), is(Collections.<Class<?>>singletonList(String.class)));
+ assertThat(iterator.hasNext(), is(false));
+ }
+
+ @Test
+ public void testPartitioningWithReminderAndNoRegularPartition() throws Exception {
+ Iterator<? extends List<Class<?>>> iterator = new AgentBuilder.RedefinitionStrategy.BatchAllocator.Partitioning(2)
+ .batch(Collections.<Class<?>>singletonList(Object.class))
+ .iterator();
+ assertThat(iterator.hasNext(), is(true));
+ assertThat(iterator.next(), is(Collections.<Class<?>>singletonList(Object.class)));
+ assertThat(iterator.hasNext(), is(false));
+ }
+
+ @Test
+ public void testPartitioningEmpty() throws Exception {
+ Iterator<? extends List<Class<?>>> iterator = new AgentBuilder.RedefinitionStrategy.BatchAllocator.Partitioning(2)
+ .batch(Collections.<Class<?>>emptyList())
+ .iterator();
+ assertThat(iterator.hasNext(), is(false));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testPartitioningIllegalArgument() throws Exception {
+ AgentBuilder.RedefinitionStrategy.BatchAllocator.Partitioning.of(0);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AgentBuilder.RedefinitionStrategy.BatchAllocator.ForTotal.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.RedefinitionStrategy.BatchAllocator.ForFixedSize.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.RedefinitionStrategy.BatchAllocator.ForMatchedGrouping.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.RedefinitionStrategy.BatchAllocator.Slicing.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.RedefinitionStrategy.BatchAllocator.Slicing.Partitioning.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderRedefinitionStrategyDiscoveryStrategyTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderRedefinitionStrategyDiscoveryStrategyTest.java
new file mode 100644
index 0000000..7c13c58
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderRedefinitionStrategyDiscoveryStrategyTest.java
@@ -0,0 +1,66 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.test.utility.MockitoRule;
+import org.hamcrest.CoreMatchers;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.lang.instrument.Instrumentation;
+import java.util.*;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class AgentBuilderRedefinitionStrategyDiscoveryStrategyTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private Instrumentation instrumentation;
+
+ @Test
+ public void testSinglePass() throws Exception {
+ when(instrumentation.getAllLoadedClasses()).thenReturn(new Class<?>[]{String.class, Integer.class});
+ Iterator<Iterable<Class<?>>> types = AgentBuilder.RedefinitionStrategy.DiscoveryStrategy.Reiterating.INSTANCE.resolve(instrumentation).iterator();
+ assertThat(types.hasNext(), is(true));
+ assertThat(types.next(), CoreMatchers.<Iterable<Class<?>>>equalTo(Arrays.<Class<?>>asList(String.class, Integer.class)));
+ assertThat(types.hasNext(), is(false));
+ }
+
+ @Test
+ public void testReiteration() throws Exception {
+ when(instrumentation.getAllLoadedClasses()).thenReturn(new Class<?>[]{String.class, Integer.class},
+ new Class<?>[]{String.class, Integer.class, Void.class});
+ Iterator<Iterable<Class<?>>> types = AgentBuilder.RedefinitionStrategy.DiscoveryStrategy.Reiterating.INSTANCE.resolve(instrumentation).iterator();
+ assertThat(types.hasNext(), is(true));
+ assertThat(types.next(), CoreMatchers.<Iterable<Class<?>>>equalTo(Arrays.<Class<?>>asList(String.class, Integer.class)));
+ assertThat(types.hasNext(), is(true));
+ assertThat(types.next(), CoreMatchers.<Iterable<Class<?>>>equalTo(Collections.<Class<?>>singletonList(Void.class)));
+ assertThat(types.hasNext(), is(false));
+ }
+
+ @Test(expected = NoSuchElementException.class)
+ public void testReiterationNoMoreElement() throws Exception {
+ when(instrumentation.getAllLoadedClasses()).thenReturn(new Class<?>[0]);
+ AgentBuilder.RedefinitionStrategy.DiscoveryStrategy.Reiterating.INSTANCE.resolve(instrumentation).iterator().next();
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testReiterationNoRemoval() throws Exception {
+ when(instrumentation.getAllLoadedClasses()).thenReturn(new Class<?>[] {Void.class});
+ AgentBuilder.RedefinitionStrategy.DiscoveryStrategy.Reiterating.INSTANCE.resolve(instrumentation).iterator().remove();
+ }
+
+ @Test
+ public void testExplicit() throws Exception {
+ Iterator<Iterable<Class<?>>> types = new AgentBuilder.RedefinitionStrategy.DiscoveryStrategy.Explicit(String.class, Integer.class)
+ .resolve(instrumentation).iterator();
+ assertThat(types.hasNext(), is(true));
+ assertThat(types.next(), CoreMatchers.<Iterable<Class<?>>>equalTo(new HashSet<Class<?>>(Arrays.<Class<?>>asList(String.class, Integer.class))));
+ assertThat(types.hasNext(), is(false));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderRedefinitionStrategyListenerTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderRedefinitionStrategyListenerTest.java
new file mode 100644
index 0000000..7ad8033
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderRedefinitionStrategyListenerTest.java
@@ -0,0 +1,168 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.io.PrintStream;
+import java.util.*;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class AgentBuilderRedefinitionStrategyListenerTest {
+
+ @Test
+ public void testNoOp() throws Exception {
+ AgentBuilder.RedefinitionStrategy.Listener.NoOp.INSTANCE.onBatch(0, Collections.<Class<?>>emptyList(), Collections.<Class<?>>emptyList());
+ AgentBuilder.RedefinitionStrategy.Listener.NoOp.INSTANCE.onError(0, Collections.<Class<?>>emptyList(), new Throwable(), Collections.<Class<?>>emptyList());
+ AgentBuilder.RedefinitionStrategy.Listener.NoOp.INSTANCE.onComplete(0, Collections.<Class<?>>emptyList(), Collections.<List<Class<?>>, Throwable>emptyMap());
+ }
+
+ @Test
+ public void testYielding() throws Exception {
+ AgentBuilder.RedefinitionStrategy.Listener.Yielding.INSTANCE.onBatch(0, Collections.<Class<?>>emptyList(), Collections.<Class<?>>emptyList());
+ AgentBuilder.RedefinitionStrategy.Listener.Yielding.INSTANCE.onBatch(1, Collections.<Class<?>>emptyList(), Collections.<Class<?>>emptyList());
+ AgentBuilder.RedefinitionStrategy.Listener.Yielding.INSTANCE.onError(0, Collections.<Class<?>>emptyList(), new Throwable(), Collections.<Class<?>>emptyList());
+ AgentBuilder.RedefinitionStrategy.Listener.Yielding.INSTANCE.onComplete(0, Collections.<Class<?>>emptyList(), Collections.<List<Class<?>>, Throwable>emptyMap());
+ }
+
+ @Test
+ public void testPausing() throws Exception {
+ AgentBuilder.RedefinitionStrategy.Listener listener = new AgentBuilder.RedefinitionStrategy.Listener.Pausing(1L);
+ listener.onBatch(0, Collections.<Class<?>>emptyList(), Collections.<Class<?>>emptyList());
+ listener.onBatch(1, Collections.<Class<?>>emptyList(), Collections.<Class<?>>emptyList());
+ listener.onError(0, Collections.<Class<?>>emptyList(), new Throwable(), Collections.<Class<?>>emptyList());
+ listener.onComplete(0, Collections.<Class<?>>emptyList(), Collections.<List<Class<?>>, Throwable>emptyMap());
+ }
+
+ @Test
+ public void testStreamWriting() throws Exception {
+ PrintStream printStream = mock(PrintStream.class);
+ AgentBuilder.RedefinitionStrategy.Listener listener = new AgentBuilder.RedefinitionStrategy.Listener.StreamWriting(printStream);
+ listener.onBatch(0, Collections.<Class<?>>emptyList(), Collections.<Class<?>>emptyList());
+ Throwable throwable = mock(Throwable.class);
+ listener.onError(0, Collections.<Class<?>>emptyList(), throwable, Collections.<Class<?>>emptyList());
+ listener.onComplete(0, Collections.<Class<?>>emptyList(), Collections.<List<Class<?>>, Throwable>emptyMap());
+ verify(printStream, times(3)).printf(any(String.class), anyInt(), anyInt(), anyInt());
+ verifyNoMoreInteractions(printStream);
+ verify(throwable).printStackTrace(printStream);
+ verifyNoMoreInteractions(throwable);
+ }
+
+ @Test
+ public void testStreamWritingFactories() throws Exception {
+ assertThat(AgentBuilder.RedefinitionStrategy.Listener.StreamWriting.toSystemOut(),
+ is((AgentBuilder.RedefinitionStrategy.Listener) new AgentBuilder.RedefinitionStrategy.Listener.StreamWriting(System.out)));
+ assertThat(AgentBuilder.RedefinitionStrategy.Listener.StreamWriting.toSystemError(),
+ is((AgentBuilder.RedefinitionStrategy.Listener) new AgentBuilder.RedefinitionStrategy.Listener.StreamWriting(System.err)));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testCompound() throws Exception {
+ Throwable throwable = new Throwable();
+ AgentBuilder.RedefinitionStrategy.Listener first = mock(AgentBuilder.RedefinitionStrategy.Listener.class), second = mock(AgentBuilder.RedefinitionStrategy.Listener.class);
+ when(first.onError(0, Collections.<Class<?>>emptyList(), throwable, Collections.<Class<?>>emptyList())).thenReturn((Iterable) Collections.singleton(Collections.singletonList(Object.class)));
+ when(second.onError(0, Collections.<Class<?>>emptyList(), throwable, Collections.<Class<?>>emptyList())).thenReturn((Iterable) Collections.singleton(Collections.singletonList(Void.class)));
+ AgentBuilder.RedefinitionStrategy.Listener listener = new AgentBuilder.RedefinitionStrategy.Listener.Compound(first, second);
+ listener.onBatch(0, Collections.<Class<?>>emptyList(), Collections.<Class<?>>emptyList());
+ Iterator<? extends List<Class<?>>> batched = listener.onError(0, Collections.<Class<?>>emptyList(), throwable, Collections.<Class<?>>emptyList()).iterator();
+ assertThat(batched.hasNext(), is(true));
+ assertThat(batched.next(), is(Collections.<Class<?>>singletonList(Object.class)));
+ assertThat(batched.hasNext(), is(true));
+ assertThat(batched.next(), is(Collections.<Class<?>>singletonList(Void.class)));
+ assertThat(batched.hasNext(), is(false));
+ listener.onComplete(0, Collections.<Class<?>>emptyList(), Collections.<List<Class<?>>, Throwable>emptyMap());
+ verify(first).onBatch(0, Collections.<Class<?>>emptyList(), Collections.<Class<?>>emptyList());
+ verify(first).onError(0, Collections.<Class<?>>emptyList(), throwable, Collections.<Class<?>>emptyList());
+ verify(first).onComplete(0, Collections.<Class<?>>emptyList(), Collections.<List<Class<?>>, Throwable>emptyMap());
+ verifyNoMoreInteractions(first);
+ verify(second).onBatch(0, Collections.<Class<?>>emptyList(), Collections.<Class<?>>emptyList());
+ verify(second).onError(0, Collections.<Class<?>>emptyList(), throwable, Collections.<Class<?>>emptyList());
+ verify(second).onComplete(0, Collections.<Class<?>>emptyList(), Collections.<List<Class<?>>, Throwable>emptyMap());
+ verifyNoMoreInteractions(second);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testErrorFailFast() throws Exception {
+ AgentBuilder.RedefinitionStrategy.Listener.ErrorEscalating.FAIL_FAST.onError(0,
+ Collections.<Class<?>>emptyList(),
+ mock(Throwable.class),
+ Collections.<Class<?>>emptyList());
+ }
+
+ @Test
+ public void testFailFastNoOp() throws Exception {
+ AgentBuilder.RedefinitionStrategy.Listener.ErrorEscalating.FAIL_FAST.onBatch(0, Collections.<Class<?>>emptyList(), Collections.<Class<?>>emptyList());
+ AgentBuilder.RedefinitionStrategy.Listener.ErrorEscalating.FAIL_FAST.onComplete(0, Collections.<Class<?>>emptyList(), Collections.<List<Class<?>>, Throwable>emptyMap());
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testErrorFailLast() throws Exception {
+ AgentBuilder.RedefinitionStrategy.Listener.ErrorEscalating.FAIL_LAST.onComplete(0,
+ Collections.<Class<?>>emptyList(),
+ Collections.singletonMap(Collections.<Class<?>>emptyList(), mock(Throwable.class)));
+ }
+
+ @Test
+ public void testFailLastNoOp() throws Exception {
+ AgentBuilder.RedefinitionStrategy.Listener.ErrorEscalating.FAIL_LAST.onBatch(0, Collections.<Class<?>>emptyList(), Collections.<Class<?>>emptyList());
+ AgentBuilder.RedefinitionStrategy.Listener.ErrorEscalating.FAIL_LAST.onError(0, Collections.<Class<?>>emptyList(), mock(Throwable.class), Collections.<Class<?>>emptyList());
+ AgentBuilder.RedefinitionStrategy.Listener.ErrorEscalating.FAIL_LAST.onComplete(0, Collections.<Class<?>>emptyList(), Collections.<List<Class<?>>, Throwable>emptyMap());
+ }
+
+ @Test(expected = NoSuchElementException.class)
+ public void testEmptyIterator() throws Exception {
+ new AgentBuilder.RedefinitionStrategy.Listener.Compound.CompoundIterable.CompoundIterator(Collections.<Iterable<? extends List<Class<?>>>>emptyList()).next();
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testIteratorRemove() throws Exception {
+ new AgentBuilder.RedefinitionStrategy.Listener.Compound.CompoundIterable.CompoundIterator(Collections.<Iterable<? extends List<Class<?>>>>emptyList()).remove();
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testBatchReallocatorNonBatchable() throws Exception {
+ AgentBuilder.RedefinitionStrategy.BatchAllocator delegate = mock(AgentBuilder.RedefinitionStrategy.BatchAllocator.class);
+ AgentBuilder.RedefinitionStrategy.Listener listener = new AgentBuilder.RedefinitionStrategy.Listener.BatchReallocator(delegate);
+ assertThat(listener.onError(0, Collections.<Class<?>>singletonList(Object.class), new Throwable(), Collections.<Class<?>>emptyList()), is((Iterable) Collections.emptyList()));
+ verifyZeroInteractions(delegate);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testBatchReallocatorBatchable() throws Exception {
+ AgentBuilder.RedefinitionStrategy.BatchAllocator delegate = mock(AgentBuilder.RedefinitionStrategy.BatchAllocator.class);
+ when(delegate.batch(Arrays.<Class<?>>asList(Object.class, Void.class))).thenReturn((Iterable) Collections.emptyList());
+ AgentBuilder.RedefinitionStrategy.Listener listener = new AgentBuilder.RedefinitionStrategy.Listener.BatchReallocator(delegate);
+ assertThat(listener.onError(0, Arrays.asList(Object.class, Void.class), new Throwable(), Collections.<Class<?>>emptyList()), is((Iterable) Collections.emptyList()));
+ verify(delegate).batch(Arrays.asList(Object.class, Void.class));
+ verifyNoMoreInteractions(delegate);
+ }
+
+ @Test
+ public void testSplittingBatchReallocator() throws Exception {
+ assertThat(AgentBuilder.RedefinitionStrategy.Listener.BatchReallocator.splitting(),
+ is((AgentBuilder.RedefinitionStrategy.Listener) new AgentBuilder.RedefinitionStrategy.Listener.BatchReallocator(
+ new AgentBuilder.RedefinitionStrategy.BatchAllocator.Partitioning(2))));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AgentBuilder.RedefinitionStrategy.Listener.NoOp.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.RedefinitionStrategy.Listener.Compound.class).create(new ObjectPropertyAssertion.Creator<List<?>>() {
+ @Override
+ public List<?> create() {
+ return Collections.singletonList(mock(AgentBuilder.RedefinitionStrategy.Listener.class));
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.RedefinitionStrategy.Listener.Compound.CompoundIterable.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.RedefinitionStrategy.Listener.Pausing.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.RedefinitionStrategy.Listener.StreamWriting.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.RedefinitionStrategy.Listener.Yielding.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.RedefinitionStrategy.Listener.ErrorEscalating.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.RedefinitionStrategy.Listener.BatchReallocator.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderRedefinitionStrategyResubmissionStrategyTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderRedefinitionStrategyResubmissionStrategyTest.java
new file mode 100644
index 0000000..8bc187d
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderRedefinitionStrategyResubmissionStrategyTest.java
@@ -0,0 +1,893 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.utility.JavaModule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.lang.instrument.ClassDefinition;
+import java.lang.instrument.Instrumentation;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class AgentBuilderRedefinitionStrategyResubmissionStrategyTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private Instrumentation instrumentation;
+
+ @Mock
+ private AgentBuilder.RedefinitionStrategy.ResubmissionScheduler resubmissionScheduler;
+
+ @Mock
+ private AgentBuilder.LocationStrategy locationStrategy;
+
+ @Mock
+ private AgentBuilder.Listener listener;
+
+ @Mock
+ private AgentBuilder.InstallationListener installationListener;
+
+ @Mock
+ private ResettableClassFileTransformer classFileTransformer;
+
+ @Mock
+ private AgentBuilder.CircularityLock circularityLock;
+
+ @Mock
+ private AgentBuilder.RawMatcher rawMatcher;
+
+ @Mock
+ private ElementMatcher<? super Throwable> matcher;
+
+ @Mock
+ private Throwable error;
+
+ @Mock
+ private AgentBuilder.RedefinitionStrategy.BatchAllocator redefinitionBatchAllocator;
+
+ @Mock
+ private AgentBuilder.RedefinitionStrategy.Listener redefinitionListener;
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testRetransformation() throws Exception {
+ when(instrumentation.isModifiableClass(Foo.class)).thenReturn(true);
+ when(redefinitionBatchAllocator.batch(Mockito.any(List.class))).thenAnswer(new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
+ return Collections.singleton(invocationOnMock.getArgument(0));
+ }
+ });
+ when(rawMatcher.matches(new TypeDescription.ForLoadedType(Foo.class),
+ Foo.class.getClassLoader(),
+ JavaModule.ofType(Foo.class),
+ Foo.class,
+ Foo.class.getProtectionDomain())).thenReturn(true);
+ when(matcher.matches(error)).thenReturn(true);
+ when(resubmissionScheduler.isAlive()).thenReturn(true);
+ AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Installation installation = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled(resubmissionScheduler, matcher).apply(instrumentation,
+ locationStrategy,
+ listener,
+ installationListener,
+ circularityLock,
+ rawMatcher,
+ AgentBuilder.RedefinitionStrategy.RETRANSFORMATION,
+ redefinitionBatchAllocator,
+ redefinitionListener);
+ installation.getInstallationListener().onInstall(instrumentation, classFileTransformer);
+ installation.getListener().onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
+ ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(resubmissionScheduler).isAlive();
+ verify(resubmissionScheduler).schedule(argumentCaptor.capture());
+ argumentCaptor.getValue().run();
+ verifyNoMoreInteractions(resubmissionScheduler);
+ verify(instrumentation).isModifiableClass(Foo.class);
+ verify(instrumentation).retransformClasses(Foo.class);
+ verifyNoMoreInteractions(instrumentation);
+ verify(rawMatcher).matches(new TypeDescription.ForLoadedType(Foo.class),
+ Foo.class.getClassLoader(),
+ JavaModule.ofType(Foo.class),
+ Foo.class,
+ Foo.class.getProtectionDomain());
+ verifyNoMoreInteractions(rawMatcher);
+ verify(redefinitionBatchAllocator).batch(Collections.<Class<?>>singletonList(Foo.class));
+ verifyNoMoreInteractions(redefinitionBatchAllocator);
+ verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
+ verifyNoMoreInteractions(listener);
+ verify(matcher).matches(error);
+ verifyNoMoreInteractions(matcher);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testRedefinition() throws Exception {
+ when(instrumentation.isModifiableClass(Foo.class)).thenReturn(true);
+ when(redefinitionBatchAllocator.batch(Mockito.any(List.class))).thenAnswer(new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
+ return Collections.singleton(invocationOnMock.getArgument(0));
+ }
+ });
+ when(rawMatcher.matches(new TypeDescription.ForLoadedType(Foo.class),
+ Foo.class.getClassLoader(),
+ JavaModule.ofType(Foo.class),
+ Foo.class,
+ Foo.class.getProtectionDomain())).thenReturn(true);
+ when(matcher.matches(error)).thenReturn(true);
+ when(resubmissionScheduler.isAlive()).thenReturn(true);
+ ClassFileLocator classFileLocator = mock(ClassFileLocator.class);
+ when(locationStrategy.classFileLocator(Foo.class.getClassLoader(), JavaModule.ofType(Foo.class))).thenReturn(classFileLocator);
+ when(classFileLocator.locate(Foo.class.getName())).thenReturn(new ClassFileLocator.Resolution.Explicit(new byte[]{1, 2, 3}));
+ AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Installation installation = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled(resubmissionScheduler, matcher).apply(instrumentation,
+ locationStrategy,
+ listener,
+ installationListener,
+ circularityLock,
+ rawMatcher,
+ AgentBuilder.RedefinitionStrategy.REDEFINITION,
+ redefinitionBatchAllocator,
+ redefinitionListener);
+ installation.getInstallationListener().onInstall(instrumentation, classFileTransformer);
+ installation.getListener().onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
+ ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(resubmissionScheduler).isAlive();
+ verify(resubmissionScheduler).schedule(argumentCaptor.capture());
+ argumentCaptor.getValue().run();
+ verifyNoMoreInteractions(resubmissionScheduler);
+ verify(instrumentation).isModifiableClass(Foo.class);
+ verify(instrumentation).redefineClasses(Mockito.argThat(new ArgumentMatcher<ClassDefinition>() {
+ @Override
+ public boolean matches(ClassDefinition classDefinition) {
+ return classDefinition.getDefinitionClass() == Foo.class && Arrays.equals(classDefinition.getDefinitionClassFile(), new byte[]{1, 2, 3});
+ }
+ }));
+ verifyNoMoreInteractions(instrumentation);
+ verify(rawMatcher).matches(new TypeDescription.ForLoadedType(Foo.class),
+ Foo.class.getClassLoader(),
+ JavaModule.ofType(Foo.class),
+ Foo.class,
+ Foo.class.getProtectionDomain());
+ verifyNoMoreInteractions(rawMatcher);
+ verify(redefinitionBatchAllocator).batch(Collections.<Class<?>>singletonList(Foo.class));
+ verifyNoMoreInteractions(redefinitionBatchAllocator);
+ verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
+ verifyNoMoreInteractions(listener);
+ verify(matcher).matches(error);
+ verifyNoMoreInteractions(matcher);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testRetransformationNonModifiable() throws Exception {
+ when(instrumentation.isModifiableClass(Foo.class)).thenReturn(false);
+ when(redefinitionBatchAllocator.batch(Mockito.any(List.class))).thenAnswer(new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
+ return Collections.singleton(invocationOnMock.getArgument(0));
+ }
+ });
+ when(rawMatcher.matches(new TypeDescription.ForLoadedType(Foo.class),
+ Foo.class.getClassLoader(),
+ JavaModule.ofType(Foo.class),
+ Foo.class,
+ Foo.class.getProtectionDomain())).thenReturn(true);
+ when(matcher.matches(error)).thenReturn(true);
+ when(resubmissionScheduler.isAlive()).thenReturn(true);
+ AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Installation installation = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled(resubmissionScheduler, matcher).apply(instrumentation,
+ locationStrategy,
+ listener,
+ installationListener,
+ circularityLock,
+ rawMatcher,
+ AgentBuilder.RedefinitionStrategy.RETRANSFORMATION,
+ redefinitionBatchAllocator,
+ redefinitionListener);
+ installation.getInstallationListener().onInstall(instrumentation, classFileTransformer);
+ installation.getListener().onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
+ ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(resubmissionScheduler).isAlive();
+ verify(resubmissionScheduler).schedule(argumentCaptor.capture());
+ argumentCaptor.getValue().run();
+ verifyNoMoreInteractions(resubmissionScheduler);
+ verify(instrumentation).isModifiableClass(Foo.class);
+ verifyNoMoreInteractions(instrumentation);
+ verifyZeroInteractions(rawMatcher);
+ verifyZeroInteractions(redefinitionBatchAllocator);
+ verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
+ verifyNoMoreInteractions(listener);
+ verify(matcher).matches(error);
+ verifyNoMoreInteractions(matcher);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testRedefinitionNonModifiable() throws Exception {
+ when(instrumentation.isModifiableClass(Foo.class)).thenReturn(false);
+ when(redefinitionBatchAllocator.batch(Mockito.any(List.class))).thenAnswer(new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
+ return Collections.singleton(invocationOnMock.getArgument(0));
+ }
+ });
+ when(rawMatcher.matches(new TypeDescription.ForLoadedType(Foo.class),
+ Foo.class.getClassLoader(),
+ JavaModule.ofType(Foo.class),
+ Foo.class,
+ Foo.class.getProtectionDomain())).thenReturn(true);
+ when(matcher.matches(error)).thenReturn(true);
+ when(resubmissionScheduler.isAlive()).thenReturn(true);
+ ClassFileLocator classFileLocator = mock(ClassFileLocator.class);
+ when(locationStrategy.classFileLocator(Foo.class.getClassLoader(), JavaModule.ofType(Foo.class))).thenReturn(classFileLocator);
+ when(classFileLocator.locate(Foo.class.getName())).thenReturn(new ClassFileLocator.Resolution.Explicit(new byte[]{1, 2, 3}));
+ AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Installation installation = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled(resubmissionScheduler, matcher).apply(instrumentation,
+ locationStrategy,
+ listener,
+ installationListener,
+ circularityLock,
+ rawMatcher,
+ AgentBuilder.RedefinitionStrategy.REDEFINITION,
+ redefinitionBatchAllocator,
+ redefinitionListener);
+ installation.getInstallationListener().onInstall(instrumentation, classFileTransformer);
+ installation.getListener().onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
+ ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(resubmissionScheduler).isAlive();
+ verify(resubmissionScheduler).schedule(argumentCaptor.capture());
+ argumentCaptor.getValue().run();
+ verifyNoMoreInteractions(resubmissionScheduler);
+ verify(instrumentation).isModifiableClass(Foo.class);
+ verifyNoMoreInteractions(instrumentation);
+ verifyZeroInteractions(rawMatcher);
+ verifyZeroInteractions(redefinitionBatchAllocator);
+ verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
+ verifyNoMoreInteractions(listener);
+ verify(matcher).matches(error);
+ verifyNoMoreInteractions(matcher);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testNoRetransformation() throws Exception {
+ when(instrumentation.isModifiableClass(Foo.class)).thenReturn(true);
+ when(redefinitionBatchAllocator.batch(Mockito.any(List.class))).thenAnswer(new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
+ return Collections.singleton(invocationOnMock.getArgument(0));
+ }
+ });
+ when(rawMatcher.matches(new TypeDescription.ForLoadedType(Foo.class),
+ Foo.class.getClassLoader(),
+ JavaModule.ofType(Foo.class),
+ Foo.class,
+ Foo.class.getProtectionDomain())).thenReturn(false);
+ when(matcher.matches(error)).thenReturn(true);
+ when(resubmissionScheduler.isAlive()).thenReturn(false);
+ AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Installation installation = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled(resubmissionScheduler, matcher).apply(instrumentation,
+ locationStrategy,
+ listener,
+ installationListener,
+ circularityLock,
+ rawMatcher,
+ AgentBuilder.RedefinitionStrategy.DISABLED,
+ redefinitionBatchAllocator,
+ redefinitionListener);
+ installation.getInstallationListener().onInstall(instrumentation, classFileTransformer);
+ installation.getListener().onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
+ verifyZeroInteractions(resubmissionScheduler);
+ verifyZeroInteractions(instrumentation);
+ verifyZeroInteractions(rawMatcher);
+ verifyZeroInteractions(redefinitionBatchAllocator);
+ verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
+ verifyNoMoreInteractions(listener);
+ verifyZeroInteractions(matcher);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testRetransformationNonAlive() throws Exception {
+ when(instrumentation.isModifiableClass(Foo.class)).thenReturn(true);
+ when(redefinitionBatchAllocator.batch(Mockito.any(List.class))).thenAnswer(new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
+ return Collections.singleton(invocationOnMock.getArgument(0));
+ }
+ });
+ when(rawMatcher.matches(new TypeDescription.ForLoadedType(Foo.class),
+ Foo.class.getClassLoader(),
+ JavaModule.ofType(Foo.class),
+ Foo.class,
+ Foo.class.getProtectionDomain())).thenReturn(false);
+ when(matcher.matches(error)).thenReturn(true);
+ when(resubmissionScheduler.isAlive()).thenReturn(false);
+ AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Installation installation = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled(resubmissionScheduler, matcher).apply(instrumentation,
+ locationStrategy,
+ listener,
+ installationListener,
+ circularityLock,
+ rawMatcher,
+ AgentBuilder.RedefinitionStrategy.RETRANSFORMATION,
+ redefinitionBatchAllocator,
+ redefinitionListener);
+ installation.getInstallationListener().onInstall(instrumentation, classFileTransformer);
+ installation.getListener().onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
+ verify(resubmissionScheduler).isAlive();
+ verifyNoMoreInteractions(resubmissionScheduler);
+ verifyZeroInteractions(instrumentation);
+ verifyZeroInteractions(rawMatcher);
+ verifyZeroInteractions(redefinitionBatchAllocator);
+ verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
+ verifyNoMoreInteractions(listener);
+ verifyZeroInteractions(matcher);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testRedefinitionNonAlive() throws Exception {
+ when(instrumentation.isModifiableClass(Foo.class)).thenReturn(true);
+ when(redefinitionBatchAllocator.batch(Mockito.any(List.class))).thenAnswer(new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
+ return Collections.singleton(invocationOnMock.getArgument(0));
+ }
+ });
+ when(rawMatcher.matches(new TypeDescription.ForLoadedType(Foo.class),
+ Foo.class.getClassLoader(),
+ JavaModule.ofType(Foo.class),
+ Foo.class,
+ Foo.class.getProtectionDomain())).thenReturn(false);
+ when(matcher.matches(error)).thenReturn(true);
+ when(resubmissionScheduler.isAlive()).thenReturn(false);
+ ClassFileLocator classFileLocator = mock(ClassFileLocator.class);
+ when(locationStrategy.classFileLocator(Foo.class.getClassLoader(), JavaModule.ofType(Foo.class))).thenReturn(classFileLocator);
+ when(classFileLocator.locate(Foo.class.getName())).thenReturn(new ClassFileLocator.Resolution.Explicit(new byte[]{1, 2, 3}));
+ AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Installation installation = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled(resubmissionScheduler, matcher).apply(instrumentation,
+ locationStrategy,
+ listener,
+ installationListener,
+ circularityLock,
+ rawMatcher,
+ AgentBuilder.RedefinitionStrategy.REDEFINITION,
+ redefinitionBatchAllocator,
+ redefinitionListener);
+ installation.getInstallationListener().onInstall(instrumentation, classFileTransformer);
+ installation.getListener().onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
+ verify(resubmissionScheduler).isAlive();
+ verifyNoMoreInteractions(resubmissionScheduler);
+ verifyZeroInteractions(instrumentation);
+ verifyZeroInteractions(rawMatcher);
+ verifyZeroInteractions(redefinitionBatchAllocator);
+ verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
+ verifyNoMoreInteractions(listener);
+ verifyZeroInteractions(matcher);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testRetransformationNonMatched() throws Exception {
+ when(instrumentation.isModifiableClass(Foo.class)).thenReturn(true);
+ when(redefinitionBatchAllocator.batch(Mockito.any(List.class))).thenAnswer(new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
+ return Collections.singleton(invocationOnMock.getArgument(0));
+ }
+ });
+ when(rawMatcher.matches(new TypeDescription.ForLoadedType(Foo.class),
+ Foo.class.getClassLoader(),
+ JavaModule.ofType(Foo.class),
+ Foo.class,
+ Foo.class.getProtectionDomain())).thenReturn(false);
+ when(matcher.matches(error)).thenReturn(true);
+ when(resubmissionScheduler.isAlive()).thenReturn(true);
+ AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Installation installation = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled(resubmissionScheduler, matcher).apply(instrumentation,
+ locationStrategy,
+ listener,
+ installationListener,
+ circularityLock,
+ rawMatcher,
+ AgentBuilder.RedefinitionStrategy.RETRANSFORMATION,
+ redefinitionBatchAllocator,
+ redefinitionListener);
+ installation.getInstallationListener().onInstall(instrumentation, classFileTransformer);
+ installation.getListener().onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
+ ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(resubmissionScheduler).isAlive();
+ verify(resubmissionScheduler).schedule(argumentCaptor.capture());
+ argumentCaptor.getValue().run();
+ verifyNoMoreInteractions(resubmissionScheduler);
+ verify(instrumentation).isModifiableClass(Foo.class);
+ verifyNoMoreInteractions(instrumentation);
+ verify(rawMatcher).matches(new TypeDescription.ForLoadedType(Foo.class),
+ Foo.class.getClassLoader(),
+ JavaModule.ofType(Foo.class),
+ Foo.class,
+ Foo.class.getProtectionDomain());
+ verifyNoMoreInteractions(rawMatcher);
+ verifyZeroInteractions(redefinitionBatchAllocator);
+ verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
+ verifyNoMoreInteractions(listener);
+ verify(matcher).matches(error);
+ verifyNoMoreInteractions(matcher);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testRedefinitionNonMatched() throws Exception {
+ when(instrumentation.isModifiableClass(Foo.class)).thenReturn(true);
+ when(redefinitionBatchAllocator.batch(Mockito.any(List.class))).thenAnswer(new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
+ return Collections.singleton(invocationOnMock.getArgument(0));
+ }
+ });
+ when(rawMatcher.matches(new TypeDescription.ForLoadedType(Foo.class),
+ Foo.class.getClassLoader(),
+ JavaModule.ofType(Foo.class),
+ Foo.class,
+ Foo.class.getProtectionDomain())).thenReturn(false);
+ when(matcher.matches(error)).thenReturn(true);
+ when(resubmissionScheduler.isAlive()).thenReturn(true);
+ ClassFileLocator classFileLocator = mock(ClassFileLocator.class);
+ when(locationStrategy.classFileLocator(Foo.class.getClassLoader(), JavaModule.ofType(Foo.class))).thenReturn(classFileLocator);
+ when(classFileLocator.locate(Foo.class.getName())).thenReturn(new ClassFileLocator.Resolution.Explicit(new byte[]{1, 2, 3}));
+ AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Installation installation = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled(resubmissionScheduler, matcher).apply(instrumentation,
+ locationStrategy,
+ listener,
+ installationListener,
+ circularityLock,
+ rawMatcher,
+ AgentBuilder.RedefinitionStrategy.REDEFINITION,
+ redefinitionBatchAllocator,
+ redefinitionListener);
+ installation.getInstallationListener().onInstall(instrumentation, classFileTransformer);
+ installation.getListener().onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
+ ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(resubmissionScheduler).isAlive();
+ verify(resubmissionScheduler).schedule(argumentCaptor.capture());
+ argumentCaptor.getValue().run();
+ verifyNoMoreInteractions(resubmissionScheduler);
+ verify(instrumentation).isModifiableClass(Foo.class);
+ verifyNoMoreInteractions(instrumentation);
+ verify(rawMatcher).matches(new TypeDescription.ForLoadedType(Foo.class),
+ Foo.class.getClassLoader(),
+ JavaModule.ofType(Foo.class),
+ Foo.class,
+ Foo.class.getProtectionDomain());
+ verifyNoMoreInteractions(rawMatcher);
+ verifyZeroInteractions(redefinitionBatchAllocator);
+ verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
+ verifyNoMoreInteractions(listener);
+ verify(matcher).matches(error);
+ verifyNoMoreInteractions(matcher);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testRetransformationAlreadyLoaded() throws Exception {
+ when(instrumentation.isModifiableClass(Foo.class)).thenReturn(true);
+ when(redefinitionBatchAllocator.batch(Mockito.any(List.class))).thenAnswer(new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
+ return Collections.singleton(invocationOnMock.getArgument(0));
+ }
+ });
+ when(rawMatcher.matches(new TypeDescription.ForLoadedType(Foo.class),
+ Foo.class.getClassLoader(),
+ JavaModule.ofType(Foo.class),
+ Foo.class,
+ Foo.class.getProtectionDomain())).thenReturn(true);
+ when(matcher.matches(error)).thenReturn(false);
+ when(resubmissionScheduler.isAlive()).thenReturn(true);
+ AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Installation installation = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled(resubmissionScheduler, matcher).apply(instrumentation,
+ locationStrategy,
+ listener,
+ installationListener,
+ circularityLock,
+ rawMatcher,
+ AgentBuilder.RedefinitionStrategy.RETRANSFORMATION,
+ redefinitionBatchAllocator,
+ redefinitionListener);
+ installation.getInstallationListener().onInstall(instrumentation, classFileTransformer);
+ installation.getListener().onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), true, error);
+ ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(resubmissionScheduler).isAlive();
+ verify(resubmissionScheduler).schedule(argumentCaptor.capture());
+ argumentCaptor.getValue().run();
+ verifyNoMoreInteractions(resubmissionScheduler);
+ verifyZeroInteractions(instrumentation);
+ verifyZeroInteractions(rawMatcher);
+ verifyZeroInteractions(redefinitionBatchAllocator);
+ verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), true, error);
+ verifyNoMoreInteractions(listener);
+ verifyZeroInteractions(matcher);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testRedefinitionAlreadyLoaded() throws Exception {
+ when(instrumentation.isModifiableClass(Foo.class)).thenReturn(true);
+ when(redefinitionBatchAllocator.batch(Mockito.any(List.class))).thenAnswer(new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
+ return Collections.singleton(invocationOnMock.getArgument(0));
+ }
+ });
+ when(rawMatcher.matches(new TypeDescription.ForLoadedType(Foo.class),
+ Foo.class.getClassLoader(),
+ JavaModule.ofType(Foo.class),
+ Foo.class,
+ Foo.class.getProtectionDomain())).thenReturn(true);
+ when(matcher.matches(error)).thenReturn(false);
+ when(resubmissionScheduler.isAlive()).thenReturn(true);
+ ClassFileLocator classFileLocator = mock(ClassFileLocator.class);
+ when(locationStrategy.classFileLocator(Foo.class.getClassLoader(), JavaModule.ofType(Foo.class))).thenReturn(classFileLocator);
+ when(classFileLocator.locate(Foo.class.getName())).thenReturn(new ClassFileLocator.Resolution.Explicit(new byte[]{1, 2, 3}));
+ AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Installation installation = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled(resubmissionScheduler, matcher).apply(instrumentation,
+ locationStrategy,
+ listener,
+ installationListener,
+ circularityLock,
+ rawMatcher,
+ AgentBuilder.RedefinitionStrategy.REDEFINITION,
+ redefinitionBatchAllocator,
+ redefinitionListener);
+ installation.getInstallationListener().onInstall(instrumentation, classFileTransformer);
+ installation.getListener().onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), true, error);
+ ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(resubmissionScheduler).isAlive();
+ verify(resubmissionScheduler).schedule(argumentCaptor.capture());
+ argumentCaptor.getValue().run();
+ verifyNoMoreInteractions(resubmissionScheduler);
+ verifyZeroInteractions(instrumentation);
+ verifyZeroInteractions(rawMatcher);
+ verifyZeroInteractions(redefinitionBatchAllocator);
+ verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), true, error);
+ verifyNoMoreInteractions(listener);
+ verifyZeroInteractions(matcher);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testRetransformationNonMatchedError() throws Exception {
+ when(instrumentation.isModifiableClass(Foo.class)).thenReturn(true);
+ when(redefinitionBatchAllocator.batch(Mockito.any(List.class))).thenAnswer(new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
+ return Collections.singleton(invocationOnMock.getArgument(0));
+ }
+ });
+ when(rawMatcher.matches(new TypeDescription.ForLoadedType(Foo.class),
+ Foo.class.getClassLoader(),
+ JavaModule.ofType(Foo.class),
+ Foo.class,
+ Foo.class.getProtectionDomain())).thenReturn(true);
+ when(matcher.matches(error)).thenReturn(false);
+ when(resubmissionScheduler.isAlive()).thenReturn(true);
+ AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Installation installation = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled(resubmissionScheduler, matcher).apply(instrumentation,
+ locationStrategy,
+ listener,
+ installationListener,
+ circularityLock,
+ rawMatcher,
+ AgentBuilder.RedefinitionStrategy.RETRANSFORMATION,
+ redefinitionBatchAllocator,
+ redefinitionListener);
+ installation.getInstallationListener().onInstall(instrumentation, classFileTransformer);
+ installation.getListener().onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
+ ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(resubmissionScheduler).isAlive();
+ verify(resubmissionScheduler).schedule(argumentCaptor.capture());
+ argumentCaptor.getValue().run();
+ verifyNoMoreInteractions(resubmissionScheduler);
+ verifyZeroInteractions(instrumentation);
+ verifyZeroInteractions(rawMatcher);
+ verifyZeroInteractions(redefinitionBatchAllocator);
+ verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
+ verifyNoMoreInteractions(listener);
+ verify(matcher).matches(error);
+ verifyNoMoreInteractions(matcher);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testRedefinitionNonMatchedError() throws Exception {
+ when(instrumentation.isModifiableClass(Foo.class)).thenReturn(true);
+ when(redefinitionBatchAllocator.batch(Mockito.any(List.class))).thenAnswer(new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
+ return Collections.singleton(invocationOnMock.getArgument(0));
+ }
+ });
+ when(rawMatcher.matches(new TypeDescription.ForLoadedType(Foo.class),
+ Foo.class.getClassLoader(),
+ JavaModule.ofType(Foo.class),
+ Foo.class,
+ Foo.class.getProtectionDomain())).thenReturn(true);
+ when(matcher.matches(error)).thenReturn(false);
+ when(resubmissionScheduler.isAlive()).thenReturn(true);
+ ClassFileLocator classFileLocator = mock(ClassFileLocator.class);
+ when(locationStrategy.classFileLocator(Foo.class.getClassLoader(), JavaModule.ofType(Foo.class))).thenReturn(classFileLocator);
+ when(classFileLocator.locate(Foo.class.getName())).thenReturn(new ClassFileLocator.Resolution.Explicit(new byte[]{1, 2, 3}));
+ AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Installation installation = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled(resubmissionScheduler, matcher).apply(instrumentation,
+ locationStrategy,
+ listener,
+ installationListener,
+ circularityLock,
+ rawMatcher,
+ AgentBuilder.RedefinitionStrategy.REDEFINITION,
+ redefinitionBatchAllocator,
+ redefinitionListener);
+ installation.getInstallationListener().onInstall(instrumentation, classFileTransformer);
+ installation.getListener().onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
+ ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(resubmissionScheduler).isAlive();
+ verify(resubmissionScheduler).schedule(argumentCaptor.capture());
+ argumentCaptor.getValue().run();
+ verifyNoMoreInteractions(resubmissionScheduler);
+ verifyZeroInteractions(instrumentation);
+ verifyZeroInteractions(rawMatcher);
+ verifyZeroInteractions(redefinitionBatchAllocator);
+ verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
+ verifyNoMoreInteractions(listener);
+ verify(matcher).matches(error);
+ verifyNoMoreInteractions(matcher);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testRetransformationError() throws Exception {
+ when(instrumentation.isModifiableClass(Foo.class)).thenReturn(true);
+ when(redefinitionBatchAllocator.batch(Mockito.any(List.class))).thenAnswer(new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
+ return Collections.singleton(invocationOnMock.getArgument(0));
+ }
+ });
+ RuntimeException runtimeException = new RuntimeException();
+ when(rawMatcher.matches(new TypeDescription.ForLoadedType(Foo.class),
+ Foo.class.getClassLoader(),
+ JavaModule.ofType(Foo.class),
+ Foo.class,
+ Foo.class.getProtectionDomain())).thenThrow(runtimeException);
+ when(matcher.matches(error)).thenReturn(true);
+ when(resubmissionScheduler.isAlive()).thenReturn(true);
+ AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Installation installation = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled(resubmissionScheduler, matcher).apply(instrumentation,
+ locationStrategy,
+ listener,
+ installationListener,
+ circularityLock,
+ rawMatcher,
+ AgentBuilder.RedefinitionStrategy.RETRANSFORMATION,
+ redefinitionBatchAllocator,
+ redefinitionListener);
+ installation.getInstallationListener().onInstall(instrumentation, classFileTransformer);
+ installation.getListener().onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
+ ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(resubmissionScheduler).isAlive();
+ verify(resubmissionScheduler).schedule(argumentCaptor.capture());
+ argumentCaptor.getValue().run();
+ verifyNoMoreInteractions(resubmissionScheduler);
+ verify(instrumentation).isModifiableClass(Foo.class);
+ verifyNoMoreInteractions(instrumentation);
+ verify(rawMatcher).matches(new TypeDescription.ForLoadedType(Foo.class),
+ Foo.class.getClassLoader(),
+ JavaModule.ofType(Foo.class),
+ Foo.class,
+ Foo.class.getProtectionDomain());
+ verifyNoMoreInteractions(rawMatcher);
+ verifyZeroInteractions(redefinitionBatchAllocator);
+ verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
+ verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), true, runtimeException);
+ verify(listener).onComplete(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), true);
+ verifyNoMoreInteractions(listener);
+ verify(matcher).matches(error);
+ verifyNoMoreInteractions(matcher);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testRedefinitionError() throws Exception {
+ when(instrumentation.isModifiableClass(Foo.class)).thenReturn(true);
+ when(redefinitionBatchAllocator.batch(Mockito.any(List.class))).thenAnswer(new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
+ return Collections.singleton(invocationOnMock.getArgument(0));
+ }
+ });
+ RuntimeException runtimeException = new RuntimeException();
+ when(rawMatcher.matches(new TypeDescription.ForLoadedType(Foo.class),
+ Foo.class.getClassLoader(),
+ JavaModule.ofType(Foo.class),
+ Foo.class,
+ Foo.class.getProtectionDomain())).thenThrow(runtimeException);
+ when(matcher.matches(error)).thenReturn(true);
+ when(resubmissionScheduler.isAlive()).thenReturn(true);
+ ClassFileLocator classFileLocator = mock(ClassFileLocator.class);
+ when(locationStrategy.classFileLocator(Foo.class.getClassLoader(), JavaModule.ofType(Foo.class))).thenReturn(classFileLocator);
+ when(classFileLocator.locate(Foo.class.getName())).thenReturn(new ClassFileLocator.Resolution.Explicit(new byte[]{1, 2, 3}));
+ AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Installation installation = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled(resubmissionScheduler, matcher).apply(instrumentation,
+ locationStrategy,
+ listener,
+ installationListener,
+ circularityLock,
+ rawMatcher,
+ AgentBuilder.RedefinitionStrategy.REDEFINITION,
+ redefinitionBatchAllocator,
+ redefinitionListener);
+ installation.getInstallationListener().onInstall(instrumentation, classFileTransformer);
+ installation.getListener().onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
+ ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(resubmissionScheduler).isAlive();
+ verify(resubmissionScheduler).schedule(argumentCaptor.capture());
+ argumentCaptor.getValue().run();
+ verifyNoMoreInteractions(resubmissionScheduler);
+ verify(instrumentation).isModifiableClass(Foo.class);
+ verifyNoMoreInteractions(instrumentation);
+ verify(rawMatcher).matches(new TypeDescription.ForLoadedType(Foo.class),
+ Foo.class.getClassLoader(),
+ JavaModule.ofType(Foo.class),
+ Foo.class,
+ Foo.class.getProtectionDomain());
+ verifyNoMoreInteractions(rawMatcher);
+ verifyZeroInteractions(redefinitionBatchAllocator);
+ verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), false, error);
+ verify(listener).onError(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), true, runtimeException);
+ verify(listener).onComplete(Foo.class.getName(), Foo.class.getClassLoader(), JavaModule.ofType(Foo.class), true);
+ verifyNoMoreInteractions(listener);
+ verify(matcher).matches(error);
+ verifyNoMoreInteractions(matcher);
+ }
+
+ @Test
+ public void testDisabledListener() throws Exception {
+ assertThat(AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Disabled.INSTANCE.apply(instrumentation,
+ locationStrategy,
+ listener,
+ installationListener,
+ circularityLock,
+ rawMatcher,
+ AgentBuilder.RedefinitionStrategy.REDEFINITION,
+ redefinitionBatchAllocator,
+ redefinitionListener).getListener(), sameInstance(listener));
+ }
+
+ @Test
+ public void testDisabledInstallationListener() throws Exception {
+ assertThat(AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Disabled.INSTANCE.apply(instrumentation,
+ locationStrategy,
+ listener,
+ installationListener,
+ circularityLock,
+ rawMatcher,
+ AgentBuilder.RedefinitionStrategy.REDEFINITION,
+ redefinitionBatchAllocator,
+ redefinitionListener).getInstallationListener(), sameInstance(installationListener));
+ }
+
+ @Test
+ public void testLookupKeyBootstrapLoaderReference() throws Exception {
+ AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.LookupKey key = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.LookupKey(ClassLoadingStrategy.BOOTSTRAP_LOADER);
+ assertThat(key.hashCode(), is(0));
+ AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.LookupKey other = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.LookupKey(new URLClassLoader(new URL[0]));
+ System.gc();
+ assertThat(key, not(is(other)));
+ assertThat(key, is(new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.LookupKey(ClassLoadingStrategy.BOOTSTRAP_LOADER)));
+ assertThat(key, is((Object) new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.StorageKey(ClassLoadingStrategy.BOOTSTRAP_LOADER)));
+ assertThat(key, not(is((Object) new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.StorageKey(new URLClassLoader(new URL[0])))));
+ assertThat(key, is(key));
+ assertThat(key, not(is(new Object())));
+ }
+
+ @Test
+ public void testLookupKeyNonBootstrapReference() throws Exception {
+ ClassLoader classLoader = new URLClassLoader(new URL[0]);
+ AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.LookupKey key = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.LookupKey(classLoader);
+ assertThat(key, is(new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.LookupKey(classLoader)));
+ assertThat(key.hashCode(), is(classLoader.hashCode()));
+ assertThat(key, not(is(new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.LookupKey(ClassLoadingStrategy.BOOTSTRAP_LOADER))));
+ assertThat(key, not(is((Object) new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.StorageKey(new URLClassLoader(new URL[0])))));
+ assertThat(key, is(key));
+ assertThat(key, not(is(new Object())));
+ }
+
+ @Test
+ public void testStorageKeyBootstrapLoaderReference() throws Exception {
+ AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.StorageKey key = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.StorageKey(ClassLoadingStrategy.BOOTSTRAP_LOADER);
+ assertThat(key.isBootstrapLoader(), is(true));
+ assertThat(key.hashCode(), is(0));
+ assertThat(key.get(), nullValue(ClassLoader.class));
+ AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.StorageKey other = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.StorageKey(new URLClassLoader(new URL[0]));
+ System.gc();
+ assertThat(other.get(), nullValue(ClassLoader.class));
+ assertThat(key, not(is(other)));
+ assertThat(key, is(new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.StorageKey(ClassLoadingStrategy.BOOTSTRAP_LOADER)));
+ assertThat(key, is((Object) new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.LookupKey(ClassLoadingStrategy.BOOTSTRAP_LOADER)));
+ assertThat(key, not(is((Object) new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.LookupKey(new URLClassLoader(new URL[0])))));
+ assertThat(key, is(key));
+ assertThat(key, not(is(new Object())));
+ }
+
+ @Test
+ public void testStorageKeyNonBootstrapReference() throws Exception {
+ ClassLoader classLoader = new URLClassLoader(new URL[0]);
+ AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.StorageKey key = new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.StorageKey(classLoader);
+ assertThat(key.isBootstrapLoader(), is(false));
+ assertThat(key, is(new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.StorageKey(classLoader)));
+ assertThat(key.hashCode(), is(classLoader.hashCode()));
+ assertThat(key.get(), is(classLoader));
+ classLoader = null; // Make GC eligible.
+ System.gc();
+ assertThat(key.get(), nullValue(ClassLoader.class));
+ assertThat(key, not(is(new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.StorageKey(ClassLoadingStrategy.BOOTSTRAP_LOADER))));
+ assertThat(key, not(is((Object) new AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.LookupKey(new URLClassLoader(new URL[0])))));
+ assertThat(key, is(key));
+ assertThat(key, not(is(new Object())));
+ assertThat(key.isBootstrapLoader(), is(false));
+ }
+
+ @Test
+ public void testSchedulerNoOp() throws Exception {
+ Runnable runnable = mock(Runnable.class);
+ AgentBuilder.RedefinitionStrategy.ResubmissionScheduler.NoOp.INSTANCE.schedule(runnable);
+ verifyZeroInteractions(runnable);
+ assertThat(AgentBuilder.RedefinitionStrategy.ResubmissionScheduler.NoOp.INSTANCE.isAlive(), is(false));
+ }
+
+ @Test
+ public void testSchedulerAtFixedRate() throws Exception {
+ ScheduledExecutorService scheduledExecutorService = mock(ScheduledExecutorService.class);
+ Runnable runnable = mock(Runnable.class);
+ new AgentBuilder.RedefinitionStrategy.ResubmissionScheduler.AtFixedRate(scheduledExecutorService, 42L, TimeUnit.SECONDS).schedule(runnable);
+ verify(scheduledExecutorService).scheduleAtFixedRate(runnable, 42L, 42L, TimeUnit.SECONDS);
+ }
+
+ @Test
+ public void testSchedulerAtFixedRateIsAlive() throws Exception {
+ ScheduledExecutorService scheduledExecutorService = mock(ScheduledExecutorService.class);
+ assertThat(new AgentBuilder.RedefinitionStrategy.ResubmissionScheduler.AtFixedRate(scheduledExecutorService, 42L, TimeUnit.SECONDS).isAlive(), is(true));
+ verify(scheduledExecutorService).isShutdown();
+ }
+
+ @Test
+ public void testSchedulerWithFixedDelay() throws Exception {
+ ScheduledExecutorService scheduledExecutorService = mock(ScheduledExecutorService.class);
+ assertThat(new AgentBuilder.RedefinitionStrategy.ResubmissionScheduler.WithFixedDelay(scheduledExecutorService, 42L, TimeUnit.SECONDS).isAlive(), is(true));
+ verify(scheduledExecutorService).isShutdown();
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Enabled.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.RedefinitionStrategy.ResubmissionStrategy.Disabled.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.RedefinitionStrategy.ResubmissionScheduler.NoOp.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.RedefinitionStrategy.ResubmissionScheduler.AtFixedRate.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.RedefinitionStrategy.ResubmissionScheduler.WithFixedDelay.class).apply();
+ }
+
+ private static class Foo {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderRedefinitionStrategyTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderRedefinitionStrategyTest.java
new file mode 100644
index 0000000..7ac7b7b
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderRedefinitionStrategyTest.java
@@ -0,0 +1,79 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.lang.instrument.Instrumentation;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class AgentBuilderRedefinitionStrategyTest {
+
+ @Test
+ public void testDisabledRedefinitionStrategyIsDisabled() throws Exception {
+ assertThat(AgentBuilder.RedefinitionStrategy.DISABLED.isEnabled(), is(false));
+ }
+
+ @Test
+ public void testRetransformationStrategyIsEnabled() throws Exception {
+ assertThat(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION.isEnabled(), is(true));
+ }
+
+ @Test
+ public void testRedefinitionStrategyIsEnabled() throws Exception {
+ assertThat(AgentBuilder.RedefinitionStrategy.REDEFINITION.isEnabled(), is(true));
+ }
+
+ @Test
+ public void testDisabledRedefinitionStrategyIsRetransforming() throws Exception {
+ assertThat(AgentBuilder.RedefinitionStrategy.DISABLED.isRetransforming(), is(false));
+ }
+
+ @Test
+ public void testRetransformationStrategyIsRetransforming() throws Exception {
+ assertThat(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION.isRetransforming(), is(true));
+ }
+
+ @Test
+ public void testRedefinitionStrategyIsRetransforming() throws Exception {
+ assertThat(AgentBuilder.RedefinitionStrategy.REDEFINITION.isRetransforming(), is(false));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testDisabledRedefinitionStrategyIsNotChecked() throws Exception {
+ AgentBuilder.RedefinitionStrategy.DISABLED.check(mock(Instrumentation.class));
+ }
+
+ @Test
+ public void testRetransformationStrategyIsChecked() throws Exception {
+ Instrumentation instrumentation = mock(Instrumentation.class);
+ when(instrumentation.isRetransformClassesSupported()).thenReturn(true);
+ AgentBuilder.RedefinitionStrategy.RETRANSFORMATION.check(instrumentation);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testRetransformationStrategyNotSupportedThrowsException() throws Exception {
+ AgentBuilder.RedefinitionStrategy.RETRANSFORMATION.check(mock(Instrumentation.class));
+ }
+
+ @Test
+ public void testRedefinitionStrategyIsChecked() throws Exception {
+ Instrumentation instrumentation = mock(Instrumentation.class);
+ when(instrumentation.isRedefineClassesSupported()).thenReturn(true);
+ AgentBuilder.RedefinitionStrategy.REDEFINITION.check(instrumentation);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testRedefinitionStrategyNotSupportedThrowsException() throws Exception {
+ AgentBuilder.RedefinitionStrategy.REDEFINITION.check(mock(Instrumentation.class));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AgentBuilder.RedefinitionStrategy.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderTransformerForBuildPluginTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderTransformerForBuildPluginTest.java
new file mode 100644
index 0000000..c420042
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderTransformerForBuildPluginTest.java
@@ -0,0 +1,51 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.build.Plugin;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.utility.JavaModule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class AgentBuilderTransformerForBuildPluginTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private Plugin plugin;
+
+ @Mock
+ private DynamicType.Builder<?> builder, result;
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Mock
+ private ClassLoader classLoader;
+
+ @Mock
+ private JavaModule module;
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testApplication() throws Exception {
+ when(plugin.apply(builder, typeDescription)).thenReturn((DynamicType.Builder) result);
+ assertThat(new AgentBuilder.Transformer.ForBuildPlugin(plugin).transform(builder, typeDescription, classLoader, module), is((DynamicType.Builder) result));
+ verify(plugin).apply(builder, typeDescription);
+ verifyNoMoreInteractions(plugin);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AgentBuilder.Transformer.ForBuildPlugin.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderTransformerTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderTransformerTest.java
new file mode 100644
index 0000000..77ebf2e
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderTransformerTest.java
@@ -0,0 +1,71 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.utility.JavaModule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.Collections;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class AgentBuilderTransformerTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private DynamicType.Builder<?> builder;
+
+ @Mock
+ private AgentBuilder.Transformer first, second;
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Mock
+ private ClassLoader classLoader;
+
+ @Mock
+ private JavaModule module;
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testNoOp() throws Exception {
+ assertThat(AgentBuilder.Transformer.NoOp.INSTANCE.transform(builder, typeDescription, classLoader, module), sameInstance((DynamicType.Builder) builder));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testCompound() throws Exception {
+ when(first.transform(builder, typeDescription, classLoader, module)).thenReturn((DynamicType.Builder) builder);
+ when(second.transform(builder, typeDescription, classLoader, module)).thenReturn((DynamicType.Builder) builder);
+ assertThat(new AgentBuilder.Transformer.Compound(first, second).transform(builder, typeDescription, classLoader, module), sameInstance((DynamicType.Builder) builder));
+ verify(first).transform(builder, typeDescription, classLoader, module);
+ verifyNoMoreInteractions(first);
+ verify(second).transform(builder, typeDescription, classLoader, module);
+ verifyNoMoreInteractions(second);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AgentBuilder.Transformer.NoOp.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Transformer.ForAdvice.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Transformer.ForAdvice.Entry.ForUnifiedAdvice.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Transformer.ForAdvice.Entry.ForSplitAdvice.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Transformer.Compound.class).create(new ObjectPropertyAssertion.Creator<List<?>>() {
+ @Override
+ public List<?> create() {
+ return Collections.singletonList(mock(AgentBuilder.Transformer.class));
+ }
+ }).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderTypeLocatorWithTypePoolCacheSimpleTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderTypeLocatorWithTypePoolCacheSimpleTest.java
new file mode 100644
index 0000000..a074643
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderTypeLocatorWithTypePoolCacheSimpleTest.java
@@ -0,0 +1,51 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.pool.TypePool;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+
+public class AgentBuilderTypeLocatorWithTypePoolCacheSimpleTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private ClassFileLocator classFileLocator;
+
+ @Mock
+ private ClassLoader first, second;
+
+ @Test
+ public void testSimpleImplementation() throws Exception {
+ ConcurrentMap<ClassLoader, TypePool.CacheProvider> cacheProviders = new ConcurrentHashMap<ClassLoader, TypePool.CacheProvider>();
+ AgentBuilder.PoolStrategy poolStrategy = new AgentBuilder.PoolStrategy.WithTypePoolCache.Simple(TypePool.Default.ReaderMode.FAST, cacheProviders);
+ assertThat(poolStrategy.typePool(classFileLocator, first), is(poolStrategy.typePool(classFileLocator, first)));
+ assertThat(poolStrategy.typePool(classFileLocator, first), not(poolStrategy.typePool(classFileLocator, second)));
+ }
+
+ @Test
+ public void testSimpleImplementationBootstrap() throws Exception {
+ ConcurrentMap<ClassLoader, TypePool.CacheProvider> cacheProviders = new ConcurrentHashMap<ClassLoader, TypePool.CacheProvider>();
+ AgentBuilder.PoolStrategy poolStrategy = new AgentBuilder.PoolStrategy.WithTypePoolCache.Simple(TypePool.Default.ReaderMode.FAST, cacheProviders);
+ assertThat(poolStrategy.typePool(classFileLocator, null), is(poolStrategy.typePool(classFileLocator, null)));
+ assertThat(poolStrategy.typePool(classFileLocator, null), not(poolStrategy.typePool(classFileLocator, second)));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AgentBuilder.PoolStrategy.WithTypePoolCache.Simple.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderTypeStrategyForBuildEntryPointTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderTypeStrategyForBuildEntryPointTest.java
new file mode 100644
index 0000000..a7af13b
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderTypeStrategyForBuildEntryPointTest.java
@@ -0,0 +1,57 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.build.EntryPoint;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.scaffold.inline.MethodNameTransformer;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class AgentBuilderTypeStrategyForBuildEntryPointTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private EntryPoint entryPoint;
+
+ @Mock
+ private ByteBuddy byteBuddy;
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Mock
+ private ClassFileLocator classFileLocator;
+
+ @Mock
+ private MethodNameTransformer methodNameTransformer;
+
+ @Mock
+ private DynamicType.Builder<?> builder;
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testApplication() throws Exception {
+ when(entryPoint.transform(typeDescription, byteBuddy, classFileLocator, methodNameTransformer)).thenReturn((DynamicType.Builder) builder);
+ assertThat(new AgentBuilder.TypeStrategy.ForBuildEntryPoint(entryPoint).builder(typeDescription, byteBuddy, classFileLocator, methodNameTransformer),
+ is((DynamicType.Builder) builder));
+ verify(entryPoint).transform(typeDescription, byteBuddy, classFileLocator, methodNameTransformer);
+ verifyNoMoreInteractions(entryPoint);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AgentBuilder.TypeStrategy.ForBuildEntryPoint.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderTypeStrategyTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderTypeStrategyTest.java
new file mode 100644
index 0000000..5792d17
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderTypeStrategyTest.java
@@ -0,0 +1,82 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.dynamic.scaffold.inline.MethodNameTransformer;
+import net.bytebuddy.matcher.LatentMatcher;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class AgentBuilderTypeStrategyTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Mock
+ private ByteBuddy byteBuddy;
+
+ @Mock
+ private ClassFileLocator classFileLocator;
+
+ @Mock
+ private MethodNameTransformer methodNameTransformer;
+
+ @Mock
+ private DynamicType.Builder<?> dynamicTypeBuilder;
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testRebase() throws Exception {
+ when(byteBuddy.rebase(typeDescription, classFileLocator, methodNameTransformer)).thenReturn((DynamicType.Builder) dynamicTypeBuilder);
+ assertThat(AgentBuilder.TypeStrategy.Default.REBASE.builder(typeDescription, byteBuddy, classFileLocator, methodNameTransformer),
+ is((DynamicType.Builder) dynamicTypeBuilder));
+ verify(byteBuddy).rebase(typeDescription, classFileLocator, methodNameTransformer);
+ verifyNoMoreInteractions(byteBuddy);
+ verifyZeroInteractions(dynamicTypeBuilder);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testRedefine() throws Exception {
+ when(byteBuddy.redefine(typeDescription, classFileLocator)).thenReturn((DynamicType.Builder) dynamicTypeBuilder);
+ assertThat(AgentBuilder.TypeStrategy.Default.REDEFINE.builder(typeDescription, byteBuddy, classFileLocator, methodNameTransformer),
+ is((DynamicType.Builder) dynamicTypeBuilder));
+ verify(byteBuddy).redefine(typeDescription, classFileLocator);
+ verifyNoMoreInteractions(byteBuddy);
+ verifyZeroInteractions(dynamicTypeBuilder);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testRedefineFrozen() throws Exception {
+ when(byteBuddy.with(InstrumentedType.Factory.Default.FROZEN)).thenReturn(byteBuddy);
+ when(byteBuddy.redefine(typeDescription, classFileLocator)).thenReturn((DynamicType.Builder) dynamicTypeBuilder);
+ when(dynamicTypeBuilder.ignoreAlso(LatentMatcher.ForSelfDeclaredMethod.NOT_DECLARED)).thenReturn((DynamicType.Builder) dynamicTypeBuilder);
+ assertThat(AgentBuilder.TypeStrategy.Default.REDEFINE_FROZEN.builder(typeDescription, byteBuddy, classFileLocator, methodNameTransformer),
+ is((DynamicType.Builder) dynamicTypeBuilder));
+ verify(byteBuddy).with(InstrumentedType.Factory.Default.FROZEN);
+ verify(byteBuddy).redefine(typeDescription, classFileLocator);
+ verifyNoMoreInteractions(byteBuddy);
+ verify(dynamicTypeBuilder).ignoreAlso(LatentMatcher.ForSelfDeclaredMethod.NOT_DECLARED);
+ verifyNoMoreInteractions(dynamicTypeBuilder);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AgentBuilder.TypeStrategy.Default.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/LambdaFactoryTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/LambdaFactoryTest.java
new file mode 100644
index 0000000..931ec34
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/LambdaFactoryTest.java
@@ -0,0 +1,127 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.*;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class LambdaFactoryTest {
+
+ private static final String FOO = "foo";
+
+ private static final byte[] BAR = new byte[]{1, 2, 3};
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private Object a1, a3, a4, a5, a6;
+
+ @Mock
+ private List<?> a8, a9;
+
+ @Mock
+ private ClassFileTransformer classFileTransformer, otherTransformer;
+
+ @Test
+ public void testValidFactory() throws Exception {
+ PseudoFactory pseudoFactory = new PseudoFactory();
+ assertThat(LambdaFactory.register(classFileTransformer, pseudoFactory), is(true));
+ try {
+ assertThat(LambdaFactory.class
+ .getMethod("make", Object.class, String.class, Object.class, Object.class, Object.class, Object.class, boolean.class, List.class, List.class)
+ .invoke(null, a1, FOO, a3, a4, a5, a6, true, a8, a9), is((Object) BAR));
+ assertThat(pseudoFactory.args[0], is(a1));
+ assertThat(pseudoFactory.args[1], is((Object) FOO));
+ assertThat(pseudoFactory.args[2], is(a3));
+ assertThat(pseudoFactory.args[3], is(a4));
+ assertThat(pseudoFactory.args[4], is(a5));
+ assertThat(pseudoFactory.args[5], is(a6));
+ assertThat(pseudoFactory.args[6], is((Object) true));
+ assertThat(pseudoFactory.args[7], is((Object) a8));
+ assertThat(pseudoFactory.args[8], is((Object) a9));
+ assertThat(pseudoFactory.args[9], is((Object) Collections.singleton(classFileTransformer)));
+ } finally {
+ assertThat(LambdaFactory.release(classFileTransformer), is(true));
+ }
+ }
+
+ @Test
+ public void testUnknownTransformer() throws Exception {
+ assertThat(LambdaFactory.release(classFileTransformer), is(false));
+ }
+
+ @Test
+ public void testPreviousTransformer() throws Exception {
+ PseudoFactory pseudoFactory = new PseudoFactory();
+ try {
+ assertThat(LambdaFactory.register(classFileTransformer, pseudoFactory), is(true));
+ assertThat(LambdaFactory.register(otherTransformer, pseudoFactory), is(false));
+ } finally {
+ assertThat(LambdaFactory.release(classFileTransformer), is(false));
+ assertThat(LambdaFactory.release(otherTransformer), is(true));
+ }
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIllegalTransformer() throws Exception {
+ LambdaFactory.register(classFileTransformer, new Object());
+ }
+
+ @Test
+ public void testTypeAndMethodArePublic() throws Exception {
+ assertThat(Modifier.isPublic(LambdaFactory.class.getModifiers()), is(true));
+ assertThat(Modifier.isPublic(LambdaFactory.class.getDeclaredMethod("make",
+ Object.class,
+ String.class,
+ Object.class,
+ Object.class,
+ Object.class,
+ Object.class,
+ boolean.class,
+ List.class,
+ List.class).getModifiers()), is(true));
+ assertThat(Modifier.isStatic(LambdaFactory.class.getDeclaredMethod("make",
+ Object.class,
+ String.class,
+ Object.class,
+ Object.class,
+ Object.class,
+ Object.class,
+ boolean.class,
+ List.class,
+ List.class).getModifiers()), is(true));
+
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ final Iterator<Method> methods = Arrays.asList(Object.class.getDeclaredMethods()).iterator();
+ ObjectPropertyAssertion.of(LambdaFactory.class).create(new ObjectPropertyAssertion.Creator<Method>() {
+ @Override
+ public Method create() {
+ return methods.next();
+ }
+ }).apply();
+ }
+
+ public static class PseudoFactory {
+
+ private Object[] args;
+
+ public byte[] make(Object a1, String a2, Object a3, Object a4, Object a5, Object a6, boolean a7, List<?> a8, List<?> a9, Collection<?> a10) {
+ args = new Object[]{a1, a2, a3, a4, a5, a6, a7, a8, a9, a10};
+ return BAR;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/RawMatcherForResolvableTypesTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/RawMatcherForResolvableTypesTest.java
new file mode 100644
index 0000000..9d23171
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/RawMatcherForResolvableTypesTest.java
@@ -0,0 +1,51 @@
+package net.bytebuddy.agent.builder;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.utility.JavaModule;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class RawMatcherForResolvableTypesTest {
+
+ @Test
+ public void testUnloadedMatches() throws Exception {
+ assertThat(AgentBuilder.RawMatcher.ForResolvableTypes.INSTANCE.matches(new TypeDescription.ForLoadedType(Foo.class),
+ Foo.class.getClassLoader(),
+ JavaModule.ofType(Foo.class),
+ null,
+ Foo.class.getProtectionDomain()), is(true));
+ }
+
+ @Test
+ public void testResolvableMatches() throws Exception {
+ assertThat(AgentBuilder.RawMatcher.ForResolvableTypes.INSTANCE.matches(new TypeDescription.ForLoadedType(Foo.class),
+ Foo.class.getClassLoader(),
+ JavaModule.ofType(Foo.class),
+ Foo.class,
+ Foo.class.getProtectionDomain()), is(true));
+ }
+
+ @Test
+ public void testUnresolvableDoesNotMatch() throws Exception {
+ assertThat(AgentBuilder.RawMatcher.ForResolvableTypes.INSTANCE.matches(new TypeDescription.ForLoadedType(Bar.class),
+ Bar.class.getClassLoader(),
+ JavaModule.ofType(Bar.class),
+ Bar.class,
+ Bar.class.getProtectionDomain()), is(false));
+ }
+
+ private static class Foo {
+ /* empty */
+ }
+
+ private static class Bar {
+
+ static {
+ if (true) {
+ throw new AssertionError();
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceAnnotationTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceAnnotationTest.java
new file mode 100644
index 0000000..4376b94
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceAnnotationTest.java
@@ -0,0 +1,59 @@
+package net.bytebuddy.asm;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.lang.annotation.*;
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class AdviceAnnotationTest {
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {Advice.Argument.class, ElementType.PARAMETER},
+ {Advice.AllArguments.class, ElementType.PARAMETER},
+ {Advice.FieldValue.class, ElementType.PARAMETER},
+ {Advice.Enter.class, ElementType.PARAMETER},
+ {Advice.Unused.class, ElementType.PARAMETER},
+ {Advice.Origin.class, ElementType.PARAMETER},
+ {Advice.Return.class, ElementType.PARAMETER},
+ {Advice.This.class, ElementType.PARAMETER},
+ {Advice.Thrown.class, ElementType.PARAMETER},
+ {Advice.StubValue.class, ElementType.PARAMETER},
+ {Advice.OnMethodEnter.class, ElementType.METHOD},
+ {Advice.OnMethodExit.class, ElementType.METHOD}
+ });
+ }
+
+ private final Class<? extends Annotation> type;
+
+ private final ElementType elementType;
+
+ public AdviceAnnotationTest(Class<? extends Annotation> type, ElementType elementType) {
+ this.type = type;
+ this.elementType = elementType;
+ }
+
+ @Test
+ public void testDocumented() throws Exception {
+ assertThat(type.isAnnotationPresent(Documented.class), is(true));
+ }
+
+ @Test
+ public void testVisible() throws Exception {
+ assertThat(type.getAnnotation(Retention.class).value(), is(RetentionPolicy.RUNTIME));
+ }
+
+ @Test
+ public void testTarget() throws Exception {
+ assertThat(type.getAnnotation(Target.class).value().length, is(1));
+ assertThat(type.getAnnotation(Target.class).value()[0], is(elementType));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceBoxedParameterAssignmentTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceBoxedParameterAssignmentTest.java
new file mode 100644
index 0000000..e47afba
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceBoxedParameterAssignmentTest.java
@@ -0,0 +1,209 @@
+package net.bytebuddy.asm;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class AdviceBoxedParameterAssignmentTest {
+
+ private static final String FOO = "foo";
+
+ private static final byte NUMERIC_VALUE = 42;
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {VoidAssignment.class, null, new Object[0], new Class<?>[0]},
+ {BooleanAssignment.class, true, new Object[]{false}, new Class<?>[]{boolean.class}},
+ {ByteAssignment.class, NUMERIC_VALUE, new Object[]{(byte) 0}, new Class<?>[]{byte.class}},
+ {ShortAssignment.class, (short) NUMERIC_VALUE, new Object[]{(short) 0}, new Class<?>[]{short.class}},
+ {CharacterAssignment.class, (char) NUMERIC_VALUE, new Object[]{(char) 0}, new Class<?>[]{char.class}},
+ {IntegerAssignment.class, (int) NUMERIC_VALUE, new Object[]{0}, new Class<?>[]{int.class}},
+ {LongAssignment.class, (long) NUMERIC_VALUE, new Object[]{(long) 0}, new Class<?>[]{long.class}},
+ {FloatAssignment.class, (float) NUMERIC_VALUE, new Object[]{(float) 0}, new Class<?>[]{float.class}},
+ {DoubleAssignment.class, (double) NUMERIC_VALUE, new Object[]{(double) 0}, new Class<?>[]{double.class}},
+ {ReferenceAssignment.class, FOO, new Object[]{null}, new Class<?>[]{String.class}},
+ {ReferenceAssignmentNoCast.class, FOO, new Object[]{null}, new Class<?>[]{Object.class}},
+ });
+ }
+
+ private final Class<?> type;
+
+ private final Object expected;
+
+ private final Object[] provided;
+
+ private final Class<?>[] parameterTypes;
+
+ public AdviceBoxedParameterAssignmentTest(Class<?> type, Object expected, Object[] provided, Class<?>[] parameterTypes) {
+ this.type = type;
+ this.expected = expected;
+ this.provided = provided;
+ this.parameterTypes = parameterTypes;
+ }
+
+ @Test
+ public void testAssignment() throws Exception {
+ Class<?> dynamicType = new ByteBuddy()
+ .redefine(type)
+ .visit(Advice.to(type).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(dynamicType.getDeclaredMethod(FOO, parameterTypes).invoke(dynamicType.getDeclaredConstructor().newInstance(), provided), is(expected));
+ }
+
+ @SuppressWarnings("all")
+ public static class VoidAssignment {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.AllArguments(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object[] value) {
+ value = new Object[0];
+ }
+ }
+
+ @SuppressWarnings("all")
+ public static class BooleanAssignment {
+
+ public boolean foo(boolean value) {
+ return value;
+ }
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.AllArguments(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object[] value) {
+ value = new Object[]{true};
+ }
+ }
+
+ @SuppressWarnings("all")
+ public static class ByteAssignment {
+
+ public byte foo(byte value) {
+ return value;
+ }
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.AllArguments(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object[] value) {
+ value = new Object[]{(byte) NUMERIC_VALUE};
+ }
+ }
+
+ @SuppressWarnings("all")
+ public static class ShortAssignment {
+
+ public short foo(short value) {
+ return value;
+ }
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.AllArguments(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object[] value) {
+ value = new Object[]{(short) NUMERIC_VALUE};
+ }
+ }
+
+ @SuppressWarnings("all")
+ public static class CharacterAssignment {
+
+ public char foo(char value) {
+ return value;
+ }
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.AllArguments(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object[] value) {
+ value = new Object[]{(char) NUMERIC_VALUE};
+ }
+ }
+
+ @SuppressWarnings("all")
+ public static class IntegerAssignment {
+
+ public int foo(int value) {
+ return value;
+ }
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.AllArguments(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object[] value) {
+ value = new Object[]{(int) NUMERIC_VALUE};
+ }
+ }
+
+ @SuppressWarnings("all")
+ public static class LongAssignment {
+
+ public long foo(long value) {
+ return value;
+ }
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.AllArguments(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object[] value) {
+ value = new Object[]{(long) NUMERIC_VALUE};
+ }
+ }
+
+ @SuppressWarnings("all")
+ public static class FloatAssignment {
+
+ public float foo(float value) {
+ return value;
+ }
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.AllArguments(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object[] value) {
+ value = new Object[]{(float) NUMERIC_VALUE};
+ }
+ }
+
+ @SuppressWarnings("all")
+ public static class DoubleAssignment {
+
+ public double foo(double value) {
+ return value;
+ }
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.AllArguments(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object[] value) {
+ value = new Object[]{(double) NUMERIC_VALUE};
+ }
+ }
+
+ @SuppressWarnings("all")
+ public static class ReferenceAssignment {
+
+ public String foo(String value) {
+ return value;
+ }
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.AllArguments(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object[] value) {
+ value = new Object[]{FOO};
+ }
+ }
+
+ @SuppressWarnings("all")
+ public static class ReferenceAssignmentNoCast {
+
+ public Object foo(Object value) {
+ return value;
+ }
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.AllArguments(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object[] value) {
+ value = new Object[]{FOO};
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceBoxedReturnAssignmentTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceBoxedReturnAssignmentTest.java
new file mode 100644
index 0000000..9d5c662
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceBoxedReturnAssignmentTest.java
@@ -0,0 +1,189 @@
+package net.bytebuddy.asm;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class AdviceBoxedReturnAssignmentTest {
+
+ private static final String FOO = "foo";
+
+ private static final byte NUMERIC_VALUE = 42;
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {VoidAssignment.class, null},
+ {BooleanAssignment.class, true},
+ {ByteAssignment.class, NUMERIC_VALUE},
+ {ShortAssignment.class, (short) NUMERIC_VALUE},
+ {CharacterAssignment.class, (char) NUMERIC_VALUE},
+ {IntegerAssignment.class, (int) NUMERIC_VALUE},
+ {LongAssignment.class, (long) NUMERIC_VALUE},
+ {FloatAssignment.class, (float) NUMERIC_VALUE},
+ {DoubleAssignment.class, (double) NUMERIC_VALUE},
+ {ReferenceAssignment.class, FOO},
+ });
+ }
+
+ private final Class<?> type;
+
+ private final Object expected;
+
+ public AdviceBoxedReturnAssignmentTest(Class<?> type, Object expected) {
+ this.type = type;
+ this.expected = expected;
+ }
+
+ @Test
+ public void testAssignment() throws Exception {
+ Class<?> dynamicType = new ByteBuddy()
+ .redefine(type)
+ .visit(Advice.to(type).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(dynamicType.getDeclaredMethod(FOO).invoke(dynamicType.getDeclaredConstructor().newInstance()), is(expected));
+ }
+
+ @SuppressWarnings("all")
+ public static class VoidAssignment {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object value) {
+ value = FOO;
+ }
+ }
+
+ @SuppressWarnings("all")
+ public static class BooleanAssignment {
+
+ public boolean foo() {
+ return false;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object value) {
+ value = true;
+ }
+ }
+
+ @SuppressWarnings("all")
+ public static class ByteAssignment {
+
+ public byte foo() {
+ return 0;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object value) {
+ value = NUMERIC_VALUE;
+ }
+ }
+
+ @SuppressWarnings("all")
+ public static class ShortAssignment {
+
+ public short foo() {
+ return 0;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object value) {
+ value = (short) NUMERIC_VALUE;
+ }
+ }
+
+ @SuppressWarnings("all")
+ public static class CharacterAssignment {
+
+ public char foo() {
+ return 0;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object value) {
+ value = (char) NUMERIC_VALUE;
+ }
+ }
+
+ @SuppressWarnings("all")
+ public static class IntegerAssignment {
+
+ public int foo() {
+ return 0;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object value) {
+ value = (int) NUMERIC_VALUE;
+ }
+ }
+
+ @SuppressWarnings("all")
+ public static class LongAssignment {
+
+ public long foo() {
+ return 0L;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object value) {
+ value = (long) NUMERIC_VALUE;
+ }
+ }
+
+ @SuppressWarnings("all")
+ public static class FloatAssignment {
+
+ public float foo() {
+ return 0f;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object value) {
+ value = (float) NUMERIC_VALUE;
+ }
+ }
+
+ @SuppressWarnings("all")
+ public static class DoubleAssignment {
+
+ public double foo() {
+ return 0d;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object value) {
+ value = (double) NUMERIC_VALUE;
+ }
+ }
+
+ @SuppressWarnings("all")
+ public static class ReferenceAssignment {
+
+ public String foo() {
+ return null;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object value) {
+ value = FOO;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceCustomAnnotationOnFieldTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceCustomAnnotationOnFieldTest.java
new file mode 100644
index 0000000..b46b484
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceCustomAnnotationOnFieldTest.java
@@ -0,0 +1,214 @@
+package net.bytebuddy.asm;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Collection;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class AdviceCustomAnnotationOnFieldTest {
+
+ private static final String FOO = "foo";
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {BooleanValue.class, false},
+ {ByteValue.class, (byte) 0},
+ {ShortValue.class, (short) 0},
+ {CharacterValue.class, (char) 0},
+ {IntegerValue.class, 0},
+ {LongValue.class, 0L},
+ {FloatValue.class, 0f},
+ {DoubleValue.class, 0d},
+ {ReferenceValue.class, FOO},
+ });
+ }
+
+ private final Class<?> target;
+
+ private final Object expected;
+
+ public AdviceCustomAnnotationOnFieldTest(Class<?> target, Object expected) {
+ this.target = target;
+ this.expected = expected;
+ }
+
+ @Test
+ public void testPrimitiveField() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(target)
+ .visit(Advice.withCustomMapping()
+ .bind(FieldValue.class, new FieldDescription.ForLoadedField(target.getDeclaredField(FOO)))
+ .to(target)
+ .on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is(expected));
+ }
+
+ @Test
+ public void testBoxedField() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(target)
+ .visit(Advice.withCustomMapping()
+ .bind(FieldValue.class, new FieldDescription.ForLoadedField(target.getDeclaredField(FOO)))
+ .to(BoxedFieldAdvice.class)
+ .on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is(expected));
+ }
+
+ public static class BoxedFieldAdvice {
+
+ @Advice.OnMethodExit
+ static void exit(@FieldValue Object value, @Advice.Return(readOnly = false) Object returned) {
+ returned = value;
+ }
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface FieldValue {
+ /* empty */
+ }
+
+ public static class BooleanValue {
+
+ boolean foo;
+
+ public Object foo() {
+ return null;
+ }
+
+ @Advice.OnMethodExit
+ static void exit(@FieldValue boolean value, @Advice.Return(readOnly = false) Object returned) {
+ returned = value;
+ }
+ }
+
+ public static class ByteValue {
+
+ byte foo;
+
+ public Object foo() {
+ return null;
+ }
+
+ @Advice.OnMethodExit
+ static void exit(@FieldValue byte value, @Advice.Return(readOnly = false) Object returned) {
+ returned = value;
+ }
+ }
+
+ public static class ShortValue {
+
+ short foo;
+
+ public Object foo() {
+ return null;
+ }
+
+ @Advice.OnMethodExit
+ static void exit(@FieldValue short value, @Advice.Return(readOnly = false) Object returned) {
+ returned = value;
+ }
+ }
+
+ public static class CharacterValue {
+
+ char foo;
+
+ public Object foo() {
+ return null;
+ }
+
+ @Advice.OnMethodExit
+ static void exit(@FieldValue char value, @Advice.Return(readOnly = false) Object returned) {
+ returned = value;
+ }
+ }
+
+ public static class IntegerValue {
+
+ int foo;
+
+ public Object foo() {
+ return null;
+ }
+
+ @Advice.OnMethodExit
+ static void exit(@FieldValue int value, @Advice.Return(readOnly = false) Object returned) {
+ returned = value;
+ }
+ }
+
+ public static class LongValue {
+
+ long foo;
+
+ public Object foo() {
+ return null;
+ }
+
+ @Advice.OnMethodExit
+ static void exit(@FieldValue long value, @Advice.Return(readOnly = false) Object returned) {
+ returned = value;
+ }
+ }
+
+ public static class FloatValue {
+
+ float foo;
+
+ public Object foo() {
+ return null;
+ }
+
+ @Advice.OnMethodExit
+ static void exit(@FieldValue float value, @Advice.Return(readOnly = false) Object returned) {
+ returned = value;
+ }
+ }
+
+ public static class DoubleValue {
+
+ double foo;
+
+ public Object foo() {
+ return null;
+ }
+
+ @Advice.OnMethodExit
+ static void exit(@FieldValue double value, @Advice.Return(readOnly = false) Object returned) {
+ returned = value;
+ }
+ }
+
+ public static class ReferenceValue {
+
+ String foo = FOO;
+
+ public Object foo() {
+ return null;
+ }
+
+ @Advice.OnMethodExit
+ static void exit(@FieldValue String value, @Advice.Return(readOnly = false) Object returned) {
+ returned = value;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceCustomAnnotationOnParameterTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceCustomAnnotationOnParameterTest.java
new file mode 100644
index 0000000..0052d46
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceCustomAnnotationOnParameterTest.java
@@ -0,0 +1,199 @@
+package net.bytebuddy.asm;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Collection;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class AdviceCustomAnnotationOnParameterTest {
+
+ private static final String FOO = "foo";
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {BooleanValue.class, boolean.class, false},
+ {ByteValue.class, byte.class, (byte) 0},
+ {ShortValue.class, short.class, (short) 0},
+ {CharacterValue.class, char.class, (char) 0},
+ {IntegerValue.class, int.class, 0},
+ {LongValue.class, long.class, 0L},
+ {FloatValue.class, float.class, 0f},
+ {DoubleValue.class, double.class, 0d},
+ {ReferenceValue.class, String.class, FOO},
+ });
+ }
+
+ private final Class<?> target;
+
+ private final Class<?> argumentType;
+
+ private final Object expected;
+
+ public AdviceCustomAnnotationOnParameterTest(Class<?> target, Class<?> argumentType, Object expected) {
+ this.target = target;
+ this.argumentType = argumentType;
+ this.expected = expected;
+ }
+
+ @Test
+ public void testPrimitiveField() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(target)
+ .visit(Advice.withCustomMapping()
+ .bind(ArgumentValue.class, new MethodDescription.ForLoadedMethod(target.getDeclaredMethod(FOO, argumentType)).getParameters().getOnly())
+ .to(target)
+ .on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO, argumentType).invoke(type.getDeclaredConstructor().newInstance(), expected), is(expected));
+ }
+
+ @Test
+ public void testBoxedField() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(target)
+ .visit(Advice.withCustomMapping()
+ .bind(ArgumentValue.class, new MethodDescription.ForLoadedMethod(target.getDeclaredMethod(FOO, argumentType)).getParameters().getOnly())
+ .to(BoxedFieldAdvice.class)
+ .on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO, argumentType).invoke(type.getDeclaredConstructor().newInstance(), expected), is(expected));
+ }
+
+ public static class BoxedFieldAdvice {
+
+ @Advice.OnMethodExit
+ static void exit(@ArgumentValue Object value, @Advice.Return(readOnly = false) Object returned) {
+ returned = value;
+ }
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface ArgumentValue {
+ /* empty */
+ }
+
+ public static class BooleanValue {
+
+ public Object foo(boolean value) {
+ return null;
+ }
+
+ @Advice.OnMethodExit
+ static void exit(@ArgumentValue boolean value, @Advice.Return(readOnly = false) Object returned) {
+ returned = value;
+ }
+ }
+
+ public static class ByteValue {
+
+ public Object foo(byte value) {
+ return null;
+ }
+
+ @Advice.OnMethodExit
+ static void exit(@ArgumentValue byte value, @Advice.Return(readOnly = false) Object returned) {
+ returned = value;
+ }
+ }
+
+ public static class ShortValue {
+
+ public Object foo(short value) {
+ return null;
+ }
+
+ @Advice.OnMethodExit
+ static void exit(@ArgumentValue short value, @Advice.Return(readOnly = false) Object returned) {
+ returned = value;
+ }
+ }
+
+ public static class CharacterValue {
+
+ public Object foo(char value) {
+ return null;
+ }
+
+ @Advice.OnMethodExit
+ static void exit(@ArgumentValue char value, @Advice.Return(readOnly = false) Object returned) {
+ returned = value;
+ }
+ }
+
+ public static class IntegerValue {
+
+ public Object foo(int value) {
+ return null;
+ }
+
+ @Advice.OnMethodExit
+ static void exit(@ArgumentValue int value, @Advice.Return(readOnly = false) Object returned) {
+ returned = value;
+ }
+ }
+
+ public static class LongValue {
+
+ public Object foo(long value) {
+ return null;
+ }
+
+ @Advice.OnMethodExit
+ static void exit(@ArgumentValue long value, @Advice.Return(readOnly = false) Object returned) {
+ returned = value;
+ }
+ }
+
+ public static class FloatValue {
+
+ public Object foo(float value) {
+ return null;
+ }
+
+ @Advice.OnMethodExit
+ static void exit(@ArgumentValue float value, @Advice.Return(readOnly = false) Object returned) {
+ returned = value;
+ }
+ }
+
+ public static class DoubleValue {
+
+ public Object foo(double value) {
+ return null;
+ }
+
+ @Advice.OnMethodExit
+ static void exit(@ArgumentValue double value, @Advice.Return(readOnly = false) Object returned) {
+ returned = value;
+ }
+ }
+
+ public static class ReferenceValue {
+
+ public Object foo(String value) {
+ return null;
+ }
+
+ @Advice.OnMethodExit
+ static void exit(@ArgumentValue String value, @Advice.Return(readOnly = false) Object returned) {
+ returned = value;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceDeadCodeTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceDeadCodeTest.java
new file mode 100644
index 0000000..de8bd8a
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceDeadCodeTest.java
@@ -0,0 +1,184 @@
+package net.bytebuddy.asm;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.modifier.Ownership;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.implementation.FixedValue;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class AdviceDeadCodeTest {
+
+ private static final String FOO = "foo";
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {ClassFileVersion.JAVA_V5},
+ {ClassFileVersion.JAVA_V6}
+ });
+ }
+
+ private final ClassFileVersion classFileVersion;
+
+ public AdviceDeadCodeTest(ClassFileVersion classFileVersion) {
+ this.classFileVersion = classFileVersion;
+ }
+
+ @Test
+ public void testAdviceProcessesDeadCode() throws Exception {
+ Class<?> type = new ByteBuddy(classFileVersion)
+ .subclass(Object.class)
+ .defineMethod(FOO, String.class, Visibility.PUBLIC)
+ .intercept(new DeadStringAppender())
+ .make()
+ .load(null, ClassLoadingStrategy.Default.WRAPPER_PERSISTENT)
+ .getLoaded();
+ Class<?> redefined = new ByteBuddy()
+ .redefine(type)
+ .visit(Advice.to(ExitAdvice.class).on(named(FOO)))
+ .make()
+ .load(null, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(redefined.getDeclaredMethod(FOO).invoke(redefined.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ }
+
+ @Test
+ public void testAdviceContainsDeadCode() throws Exception {
+ Class<?> advice = new ByteBuddy(classFileVersion)
+ .subclass(Object.class)
+ .defineMethod(FOO, void.class, Ownership.STATIC)
+ .intercept(new DeadVoidAppender())
+ .annotateMethod(AnnotationDescription.Builder.ofType(Advice.OnMethodEnter.class).define("suppress", RuntimeException.class).build())
+ .annotateMethod(AnnotationDescription.Builder.ofType(Advice.OnMethodExit.class).define("suppress", RuntimeException.class).build())
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER_PERSISTENT)
+ .getLoaded();
+ Class<?> foo = new ByteBuddy(classFileVersion)
+ .subclass(Object.class)
+ .defineMethod("foo", String.class, Visibility.PUBLIC)
+ .intercept(FixedValue.value(FOO))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER_PERSISTENT)
+ .getLoaded();
+ Class<?> redefined = new ByteBuddy()
+ .redefine(foo)
+ .visit(Advice.to(advice).on(named(FOO)))
+ .make()
+ .load(null, ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded();
+ assertThat(redefined, not(sameInstance((Object) foo)));
+ assertThat(redefined.getDeclaredMethod(FOO).invoke(redefined.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ }
+
+ @Test
+ public void testAdviceWithExchangeDuplicationDeadCode() throws Exception {
+ Class<?> type = new ByteBuddy(classFileVersion)
+ .subclass(Object.class)
+ .defineMethod(FOO, String.class, Visibility.PUBLIC)
+ .intercept(new DeadExchangeAppender())
+ .make()
+ .load(null, ClassLoadingStrategy.Default.WRAPPER_PERSISTENT)
+ .getLoaded();
+ Class<?> redefined = new ByteBuddy()
+ .redefine(type)
+ .visit(Advice.to(ExitAdvice.class).on(named(FOO)))
+ .make()
+ .load(null, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(redefined.getDeclaredMethod(FOO).invoke(redefined.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ }
+
+ @SuppressWarnings("all")
+ private static class ExitAdvice {
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return(readOnly = false) String value) {
+ value = FOO;
+ }
+ }
+
+ private static class DeadStringAppender implements Implementation, ByteCodeAppender {
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return this;
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ methodVisitor.visitInsn(Opcodes.ACONST_NULL);
+ methodVisitor.visitInsn(Opcodes.ARETURN);
+ methodVisitor.visitInsn(Opcodes.POP); // dead code
+ methodVisitor.visitInsn(Opcodes.ACONST_NULL);
+ methodVisitor.visitInsn(Opcodes.ARETURN);
+ return new Size(1, instrumentedMethod.getStackSize());
+ }
+ }
+
+ private static class DeadVoidAppender implements Implementation, ByteCodeAppender {
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return this;
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ methodVisitor.visitInsn(Opcodes.RETURN);
+ methodVisitor.visitInsn(Opcodes.POP); // dead code
+ methodVisitor.visitInsn(Opcodes.RETURN);
+ return new Size(1, instrumentedMethod.getStackSize());
+ }
+ }
+
+ private static class DeadExchangeAppender implements Implementation, ByteCodeAppender {
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return this;
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ methodVisitor.visitInsn(Opcodes.ACONST_NULL);
+ methodVisitor.visitInsn(Opcodes.ARETURN);
+ methodVisitor.visitInsn(Opcodes.DUP_X1); // dead code
+ methodVisitor.visitInsn(Opcodes.ARETURN);
+ return new Size(1, instrumentedMethod.getStackSize());
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceExchangedDuplicationTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceExchangedDuplicationTest.java
new file mode 100644
index 0000000..3efcd66
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceExchangedDuplicationTest.java
@@ -0,0 +1,104 @@
+package net.bytebuddy.asm;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class AdviceExchangedDuplicationTest {
+
+ private static final String FOO = "foo";
+
+ private static final int NUMERIC_VALUE = 42;
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(
+ new Object[][]{
+ {Opcodes.DUP_X1, int.class, int.class, NUMERIC_VALUE, NUMERIC_VALUE},
+ {Opcodes.DUP_X2, int.class, long.class, NUMERIC_VALUE, (long) NUMERIC_VALUE},
+ {Opcodes.DUP2_X1, long.class, int.class, (long) NUMERIC_VALUE, NUMERIC_VALUE},
+ {Opcodes.DUP2_X2, long.class, long.class, (long) NUMERIC_VALUE, (long) NUMERIC_VALUE}
+ }
+ );
+ }
+
+ private final int duplication;
+
+ private final Class<?> valueType, ignoredValueType;
+
+ private final Object value, ignoredValue;
+
+ public AdviceExchangedDuplicationTest(int duplication, Class<?> valueType, Class<?> ignoredValueType, Object value, Object ignoredValue) {
+ this.duplication = duplication;
+ this.valueType = valueType;
+ this.ignoredValueType = ignoredValueType;
+ this.value = value;
+ this.ignoredValue = ignoredValue;
+ }
+
+ @Test
+ public void testAdvice() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .subclass(Object.class)
+ .defineMethod(FOO, valueType, Visibility.PUBLIC)
+ .intercept(new DuplicationImplementation())
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER_PERSISTENT)
+ .getLoaded();
+ Class<?> redefined = new ByteBuddy()
+ .redefine(type)
+ .visit(Advice.to(AdviceExchangedDuplicationTest.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(redefined.getDeclaredMethod(FOO).invoke(redefined.getDeclaredConstructor().newInstance()), is(value));
+ }
+
+ @Advice.OnMethodExit
+ @SuppressWarnings("unused")
+ private static void exit() {
+ /* empty */
+ }
+
+ private class DuplicationImplementation implements Implementation, ByteCodeAppender {
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return this;
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ methodVisitor.visitLdcInsn(ignoredValue);
+ methodVisitor.visitLdcInsn(value);
+ methodVisitor.visitInsn(duplication);
+ methodVisitor.visitInsn(Type.getType(valueType).getSize() == 2 ? Opcodes.POP2 : Opcodes.POP);
+ methodVisitor.visitInsn(Type.getType(ignoredValueType).getSize() == 2 ? Opcodes.POP2 : Opcodes.POP);
+ methodVisitor.visitInsn(Type.getType(valueType).getOpcode(Opcodes.IRETURN));
+ return new Size(Type.getType(valueType).getSize() * 2 + Type.getType(ignoredValueType).getSize(), instrumentedMethod.getStackSize());
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceFrameTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceFrameTest.java
new file mode 100644
index 0000000..c8090b8
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceFrameTest.java
@@ -0,0 +1,501 @@
+package net.bytebuddy.asm;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassWriter;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class AdviceFrameTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux", COUNT = "count";
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {FrameAdvice.class, 2},
+ {FrameAdviceWithoutThrowable.class, 2},
+ {FrameAdviceWithSuppression.class, 2},
+ {FrameAdviceEntryOnly.class, 1},
+ {FrameAdviceEntryOnlyWithSuppression.class, 1},
+ {FrameAdviceExitOnly.class, 1},
+ {FrameAdviceExitOnlyWithSuppression.class, 1},
+ {FrameAdviceExitOnlyWithSuppressionAndNonExceptionHandling.class, 1},
+ {FrameReturnAdvice.class, 2}
+ });
+ }
+
+ private final Class<?> advice;
+
+ private final int count;
+
+ public AdviceFrameTest(Class<?> advice, int count) {
+ this.advice = advice;
+ this.count = count;
+ }
+
+ @Test
+ public void testFrameAdvice() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(FrameSample.class)
+ .visit(Advice.to(advice).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO, String.class).invoke(type.getDeclaredConstructor().newInstance(), FOO), is((Object) FOO));
+ assertThat(type.getField(COUNT).getInt(null), is((Object) count));
+ }
+
+ @Test
+ public void testFrameAdviceStaticMethod() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(FrameSample.class)
+ .visit(Advice.to(advice).on(named(BAR)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(BAR, String.class).invoke(null, FOO), is((Object) FOO));
+ assertThat(type.getField(COUNT).getInt(null), is((Object) count));
+ }
+
+ @Test
+ public void testFrameAdviceExpanded() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(FrameSample.class)
+ .visit(Advice.to(advice).on(named(FOO)).readerFlags(ClassReader.EXPAND_FRAMES))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO, String.class).invoke(type.getDeclaredConstructor().newInstance(), FOO), is((Object) FOO));
+ assertThat(type.getField(COUNT).getInt(null), is((Object) count));
+ }
+
+ @Test
+ public void testFrameAdviceStaticMethodExpanded() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(FrameSample.class)
+ .visit(Advice.to(advice).on(named(BAR)).readerFlags(ClassReader.EXPAND_FRAMES))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(BAR, String.class).invoke(null, FOO), is((Object) FOO));
+ assertThat(type.getField(COUNT).getInt(null), is((Object) count));
+ }
+
+ @Test
+ public void testFrameAdviceComputedMaxima() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(FrameSample.class)
+ .visit(Advice.to(advice).on(named(FOO)).writerFlags(ClassWriter.COMPUTE_MAXS))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO, String.class).invoke(type.getDeclaredConstructor().newInstance(), FOO), is((Object) FOO));
+ assertThat(type.getField(COUNT).getInt(null), is((Object) count));
+ }
+
+ @Test
+ public void testFrameAdviceStaticMethodComputedMaxima() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(FrameSample.class)
+ .visit(Advice.to(advice).on(named(BAR)).writerFlags(ClassWriter.COMPUTE_MAXS))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(BAR, String.class).invoke(null, FOO), is((Object) FOO));
+ assertThat(type.getField(COUNT).getInt(null), is((Object) count));
+ }
+
+ @Test
+ public void testFrameAdviceComputedFrames() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(FrameSample.class)
+ .visit(Advice.to(advice).on(named(FOO)).writerFlags(ClassWriter.COMPUTE_FRAMES))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO, String.class).invoke(type.getDeclaredConstructor().newInstance(), FOO), is((Object) FOO));
+ assertThat(type.getField(COUNT).getInt(null), is((Object) count));
+ }
+
+ @Test
+ public void testFrameAdviceStaticMethodComputedFrames() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(FrameSample.class)
+ .visit(Advice.to(advice).on(named(BAR)).writerFlags(ClassWriter.COMPUTE_FRAMES))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(BAR, String.class).invoke(null, FOO), is((Object) FOO));
+ assertThat(type.getField(COUNT).getInt(null), is((Object) count));
+ }
+
+ @SuppressWarnings("all")
+ public static class FrameSample {
+
+ public static int count;
+
+ public String foo(String value) {
+ int ignored = 0;
+ {
+ long v1 = 1L, v2 = 2L;
+ if (ignored == 1) {
+ throw new AssertionError();
+ } else if (ignored == 2) {
+ if (v1 + v2 == 0L) {
+ throw new AssertionError();
+ }
+ }
+ }
+ long v3 = 3L, v4 = 4L, v5 = 5L, v6 = 6L;
+ if (ignored == 3) {
+ throw new AssertionError();
+ } else if (ignored == 4) {
+ if (v3 + v4 + v5 + v6 == 0L) {
+ throw new AssertionError();
+ }
+ }
+ try {
+ long v7 = 7L;
+ } catch (Exception exception) {
+ long v8 = 8L;
+ }
+ return value;
+ }
+
+ public static String bar(String value) {
+ int ignored = 0;
+ {
+ long v1 = 1L, v2 = 2L;
+ if (ignored == 1) {
+ throw new AssertionError();
+ } else if (ignored == 2) {
+ if (v1 + v2 == 0L) {
+ throw new AssertionError();
+ }
+ }
+ }
+ long v3 = 3L, v4 = 4L, v5 = 5L, v6 = 6L;
+ if (ignored == 3) {
+ throw new AssertionError();
+ } else if (ignored == 4) {
+ if (v3 + v4 + v5 + v6 == 0L) {
+ throw new AssertionError();
+ }
+ }
+ try {
+ long v7 = 7L;
+ } catch (Exception exception) {
+ long v8 = 8L;
+ }
+ return value;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FrameAdvice {
+
+ @Advice.OnMethodEnter
+ @Advice.OnMethodExit(onThrowable = Exception.class)
+ private static String advice(@Advice.Unused int ignored, @Advice.Argument(0) String value) {
+ int v0 = 0;
+ {
+ long v1 = 1L, v2 = 2L;
+ if (ignored == 1) {
+ throw new AssertionError();
+ } else if (ignored == 2) {
+ if (v1 + v2 == 0L) {
+ throw new AssertionError();
+ }
+ }
+ }
+ long v3 = 3L, v4 = 4L, v5 = 5L, v6 = 6L;
+ if (ignored == 3) {
+ throw new AssertionError();
+ } else if (ignored == 4) {
+ if (v3 + v4 + v5 + v6 == 0L) {
+ throw new AssertionError();
+ }
+ }
+ try {
+ long v7 = 7L;
+ } catch (Exception exception) {
+ long v8 = 8L;
+ }
+ FrameSample.count++;
+ return value;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FrameAdviceWithoutThrowable {
+
+ @Advice.OnMethodEnter
+ @Advice.OnMethodExit
+ private static String advice(@Advice.Unused int ignored, @Advice.Argument(0) String value) {
+ int v0 = 0;
+ {
+ long v1 = 1L, v2 = 2L;
+ if (ignored == 1) {
+ throw new AssertionError();
+ } else if (ignored == 2) {
+ if (v1 + v2 == 0L) {
+ throw new AssertionError();
+ }
+ }
+ }
+ long v3 = 3L, v4 = 4L, v5 = 5L, v6 = 6L;
+ if (ignored == 3) {
+ throw new AssertionError();
+ } else if (ignored == 4) {
+ if (v3 + v4 + v5 + v6 == 0L) {
+ throw new AssertionError();
+ }
+ }
+ try {
+ long v7 = 7L;
+ } catch (Exception exception) {
+ long v8 = 8L;
+ }
+ FrameSample.count++;
+ return value;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FrameAdviceWithSuppression {
+
+ @Advice.OnMethodEnter(suppress = Exception.class)
+ @Advice.OnMethodExit(suppress = Exception.class, onThrowable = Exception.class)
+ private static String advice(@Advice.Unused int ignored, @Advice.Argument(0) String value) {
+ int v0 = 0;
+ {
+ long v1 = 1L, v2 = 2L;
+ if (ignored == 1) {
+ throw new AssertionError();
+ } else if (ignored == 2) {
+ if (v1 + v2 == 0L) {
+ throw new AssertionError();
+ }
+ }
+ }
+ long v3 = 3L, v4 = 4L, v5 = 5L, v6 = 6L;
+ if (ignored == 3) {
+ throw new AssertionError();
+ } else if (ignored == 4) {
+ if (v3 + v4 + v5 + v6 == 0L) {
+ throw new AssertionError();
+ }
+ }
+ try {
+ long v7 = 7L;
+ } catch (Exception exception) {
+ long v8 = 8L;
+ }
+ FrameSample.count++;
+ return value;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FrameAdviceEntryOnly {
+
+ @Advice.OnMethodEnter
+ private static String advice(@Advice.Unused int ignored, @Advice.Argument(0) String value) {
+ int v0 = 0;
+ {
+ long v1 = 1L, v2 = 2L;
+ if (ignored == 1) {
+ throw new AssertionError();
+ } else if (ignored == 2) {
+ if (v1 + v2 == 0L) {
+ throw new AssertionError();
+ }
+ }
+ }
+ long v3 = 3L, v4 = 4L, v5 = 5L, v6 = 6L;
+ if (ignored == 3) {
+ throw new AssertionError();
+ } else if (ignored == 4) {
+ if (v3 + v4 + v5 + v6 == 0L) {
+ throw new AssertionError();
+ }
+ }
+ try {
+ long v7 = 7L;
+ } catch (Exception exception) {
+ long v8 = 8L;
+ }
+ FrameSample.count++;
+ return value;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FrameAdviceEntryOnlyWithSuppression {
+
+ @Advice.OnMethodEnter(suppress = Exception.class)
+ private static String advice(@Advice.Unused int ignored, @Advice.Argument(0) String value) {
+ int v0 = 0;
+ {
+ long v1 = 1L, v2 = 2L;
+ if (ignored == 1) {
+ throw new AssertionError();
+ } else if (ignored == 2) {
+ if (v1 + v2 == 0L) {
+ throw new AssertionError();
+ }
+ }
+ }
+ long v3 = 3L, v4 = 4L, v5 = 5L, v6 = 6L;
+ if (ignored == 3) {
+ throw new AssertionError();
+ } else if (ignored == 4) {
+ if (v3 + v4 + v5 + v6 == 0L) {
+ throw new AssertionError();
+ }
+ }
+ try {
+ long v7 = 7L;
+ } catch (Exception exception) {
+ long v8 = 8L;
+ }
+ FrameSample.count++;
+ return value;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FrameAdviceExitOnly {
+
+ @Advice.OnMethodExit(onThrowable = Exception.class)
+ private static String advice(@Advice.Unused int ignored, @Advice.Argument(0) String value) {
+ int v0 = 0;
+ {
+ long v1 = 1L, v2 = 2L;
+ if (ignored == 1) {
+ throw new AssertionError();
+ } else if (ignored == 2) {
+ if (v1 + v2 == 0L) {
+ throw new AssertionError();
+ }
+ }
+ }
+ long v3 = 3L, v4 = 4L, v5 = 5L, v6 = 6L;
+ if (ignored == 3) {
+ throw new AssertionError();
+ } else if (ignored == 4) {
+ if (v3 + v4 + v5 + v6 == 0L) {
+ throw new AssertionError();
+ }
+ }
+ try {
+ long v7 = 7L;
+ } catch (Exception exception) {
+ long v8 = 8L;
+ }
+ FrameSample.count++;
+ return value;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FrameAdviceExitOnlyWithSuppression {
+
+ @Advice.OnMethodExit(suppress = Exception.class, onThrowable = Exception.class)
+ private static String advice(@Advice.Unused int ignored, @Advice.Argument(0) String value) {
+ int v0 = 0;
+ {
+ long v1 = 1L, v2 = 2L;
+ if (ignored == 1) {
+ throw new AssertionError();
+ } else if (ignored == 2) {
+ if (v1 + v2 == 0L) {
+ throw new AssertionError();
+ }
+ }
+ }
+ long v3 = 3L, v4 = 4L, v5 = 5L, v6 = 6L;
+ if (ignored == 3) {
+ throw new AssertionError();
+ } else if (ignored == 4) {
+ if (v3 + v4 + v5 + v6 == 0L) {
+ throw new AssertionError();
+ }
+ }
+ try {
+ long v7 = 7L;
+ } catch (Exception exception) {
+ long v8 = 8L;
+ }
+ FrameSample.count++;
+ return value;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FrameAdviceExitOnlyWithSuppressionAndNonExceptionHandling {
+
+ @Advice.OnMethodExit(suppress = Exception.class, onThrowable = Exception.class)
+ private static String advice(@Advice.Unused int ignored, @Advice.Argument(0) String value) {
+ int v0 = 0;
+ {
+ long v1 = 1L, v2 = 2L;
+ if (ignored == 1) {
+ throw new AssertionError();
+ } else if (ignored == 2) {
+ if (v1 + v2 == 0L) {
+ throw new AssertionError();
+ }
+ }
+ }
+ long v3 = 3L, v4 = 4L, v5 = 5L, v6 = 6L;
+ if (ignored == 3) {
+ throw new AssertionError();
+ } else if (ignored == 4) {
+ if (v3 + v4 + v5 + v6 == 0L) {
+ throw new AssertionError();
+ }
+ }
+ try {
+ long v7 = 7L;
+ } catch (Exception exception) {
+ long v8 = 8L;
+ }
+ FrameSample.count++;
+ return value;
+ }
+ }
+
+ @SuppressWarnings("all")
+ public static class FrameReturnAdvice {
+
+ @Advice.OnMethodEnter(suppress = RuntimeException.class)
+ @Advice.OnMethodExit(suppress = RuntimeException.class)
+ private static String advice() {
+ try {
+ int ignored = 0;
+ if (ignored != 0) {
+ return BAR;
+ }
+ } catch (Exception e) {
+ int ignored = 0;
+ if (ignored != 0) {
+ return QUX;
+ }
+ }
+ FrameSample.count++;
+ return FOO;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceIllegalTypeTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceIllegalTypeTest.java
new file mode 100644
index 0000000..5f102e5
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceIllegalTypeTest.java
@@ -0,0 +1,158 @@
+package net.bytebuddy.asm;
+
+import net.bytebuddy.ByteBuddy;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+
+ at RunWith(Parameterized.class)
+public class AdviceIllegalTypeTest {
+
+ private static final String FOO = "foo";
+
+ private static final byte VALUE = 42;
+
+ private static final boolean BOOLEAN = true;
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {BooleanAdvice.class},
+ {ByteAdvice.class},
+ {ShortAdvice.class},
+ {CharacterAdvice.class},
+ {IntegerAdvice.class},
+ {LongAdvice.class},
+ {FloatAdvice.class},
+ {DoubleAdvice.class},
+ {ReferenceAdvice.class}
+ });
+ }
+
+ private final Class<?> type;
+
+ public AdviceIllegalTypeTest(Class<?> type) {
+ this.type = type;
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIllegalAssignment() throws Exception {
+ new ByteBuddy()
+ .redefine(type)
+ .visit(Advice.to(type).on(named(FOO)))
+ .make();
+ }
+
+ public static class BooleanAdvice {
+
+ void foo(boolean value) {
+ /* empty */
+ }
+
+ @Advice.OnMethodEnter
+ public static void enter(@Advice.Argument(0) boolean value) {
+ value = BOOLEAN;
+ }
+ }
+
+ public static class ByteAdvice {
+
+ void foo(byte value) {
+ /* empty */
+ }
+
+ @Advice.OnMethodEnter
+ public static void enter(@Advice.Argument(0) byte value) {
+ value = VALUE;
+ }
+ }
+
+ public static class ShortAdvice {
+
+ void foo(short value) {
+ /* empty */
+ }
+
+ @Advice.OnMethodEnter
+ public static void enter(@Advice.Argument(0) short value) {
+ value = VALUE;
+ }
+ }
+
+ public static class CharacterAdvice {
+
+ void foo(char value) {
+ /* empty */
+ }
+
+ @Advice.OnMethodEnter
+ public static void enter(@Advice.Argument(0) char value) {
+ value = VALUE;
+ }
+ }
+
+ public static class IntegerAdvice {
+
+ void foo(int value) {
+ /* empty */
+ }
+
+ @Advice.OnMethodEnter
+ public static void enter(@Advice.Argument(0) int value) {
+ value = VALUE;
+ }
+ }
+
+ public static class LongAdvice {
+
+ void foo(long value) {
+ /* empty */
+ }
+
+ @Advice.OnMethodEnter
+ public static void enter(@Advice.Argument(0) long value) {
+ value = VALUE;
+ }
+ }
+
+ public static class FloatAdvice {
+
+ void foo(float value) {
+ /* empty */
+ }
+
+ @Advice.OnMethodEnter
+ public static void enter(@Advice.Argument(0) float value) {
+ value = VALUE;
+ }
+ }
+
+ public static class DoubleAdvice {
+
+ void foo(double value) {
+ /* empty */
+ }
+
+ @Advice.OnMethodEnter
+ public static void enter(@Advice.Argument(0) double value) {
+ value = VALUE;
+ }
+ }
+
+ public static class ReferenceAdvice {
+
+ void foo(Object value) {
+ /* empty */
+ }
+
+ @Advice.OnMethodEnter
+ public static void enter(@Advice.Argument(0) Object value) {
+ value = FOO;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceImplementationTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceImplementationTest.java
new file mode 100644
index 0000000..116a275
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceImplementationTest.java
@@ -0,0 +1,122 @@
+package net.bytebuddy.asm;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.StubMethod;
+import org.junit.Test;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class AdviceImplementationTest {
+
+ private static final String FOO = "foo";
+
+ @Test(expected = IllegalStateException.class)
+ public void testAbstractMethod() throws Exception {
+ new ByteBuddy()
+ .subclass(Foo.class)
+ .method(named(FOO))
+ .intercept(Advice.to(Foo.class))
+ .make();
+ }
+
+ @Test
+ public void testActualMethod() throws Exception {
+ assertThat(new ByteBuddy()
+ .subclass(Bar.class)
+ .method(named(FOO))
+ .intercept(Advice.to(Bar.class))
+ .make()
+ .load(Bar.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance()
+ .foo(), is((Object) FOO));
+ }
+
+ @Test
+ public void testExplicitWrap() throws Exception {
+ assertThat(new ByteBuddy()
+ .subclass(Qux.class)
+ .method(named(FOO))
+ .intercept(Advice.to(Qux.class).wrap(StubMethod.INSTANCE))
+ .make()
+ .load(Qux.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance()
+ .foo(), is(FOO));
+ }
+
+ @Test
+ public void testExplicitWrapMultiple() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Baz.class)
+ .method(named(FOO))
+ .intercept(Advice.to(Baz.class).wrap(Advice.to(Baz.class).wrap(StubMethod.INSTANCE)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Object baz = type.getDeclaredConstructor().newInstance();
+ assertThat(type.getDeclaredMethod(FOO).invoke(baz), nullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).getInt(null), is(2));
+ }
+
+ public abstract static class Foo {
+
+ public abstract String foo();
+
+ @Advice.OnMethodExit
+ public static void exit(@Advice.Return(readOnly = false) String returned) {
+ returned = FOO;
+ }
+ }
+
+ public static class Bar {
+
+ public Object foo() {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodExit(onThrowable = RuntimeException.class)
+ public static void exit(@Advice.Thrown(readOnly = false) Throwable throwable, @Advice.Return(readOnly = false) Object returned) {
+ if (!(throwable instanceof RuntimeException)) {
+ throw new AssertionError();
+ }
+ throwable = null;
+ returned = FOO;
+ }
+ }
+
+ public static class Qux {
+
+ public String foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodExit
+ public static void exit(@Advice.Return(readOnly = false) String returned) {
+ if (returned != null) {
+ throw new AssertionError();
+ }
+ returned = FOO;
+ }
+ }
+
+ public static class Baz {
+
+ public static int foo;
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodExit
+ public static void exit() {
+ foo += 1;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceInconsistentFrameTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceInconsistentFrameTest.java
new file mode 100644
index 0000000..a8c4bb8
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceInconsistentFrameTest.java
@@ -0,0 +1,154 @@
+package net.bytebuddy.asm;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.modifier.Ownership;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class AdviceInconsistentFrameTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ @Test(expected = IllegalStateException.class)
+ @JavaVersionRule.Enforce(7)
+ public void testFrameTooShort() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .subclass(Object.class)
+ .defineMethod(FOO, String.class, Visibility.PUBLIC)
+ .intercept(new TooShortMethod())
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER_PERSISTENT)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) BAR));
+ new ByteBuddy()
+ .redefine(type)
+ .visit(Advice.to(TrivialAdvice.class).on(named(FOO)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ @JavaVersionRule.Enforce(7)
+ public void testFrameInconsistentThisParameter() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .subclass(Object.class)
+ .defineMethod(FOO, String.class, Visibility.PUBLIC)
+ .intercept(new InconsistentThisReferenceMethod())
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER_PERSISTENT)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) BAR));
+ new ByteBuddy()
+ .redefine(type)
+ .visit(Advice.to(TrivialAdvice.class).on(named(FOO)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ @JavaVersionRule.Enforce(7)
+ public void testFrameInconsistentParameter() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .subclass(Object.class)
+ .defineMethod(FOO, String.class, Visibility.PUBLIC, Ownership.STATIC)
+ .withParameters(Void.class)
+ .intercept(new InconsistentParameterReferenceMethod())
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER_PERSISTENT)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO, Void.class).invoke(null, (Object) null), is((Object) BAR));
+ new ByteBuddy()
+ .redefine(type)
+ .visit(Advice.to(TrivialAdvice.class).on(named(FOO)))
+ .make();
+ }
+
+
+ @SuppressWarnings("all")
+ private static class TrivialAdvice {
+
+ @Advice.OnMethodEnter
+ private static void exit() {
+ /* empty */
+ }
+ }
+
+ private static class TooShortMethod implements Implementation, ByteCodeAppender {
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return this;
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ methodVisitor.visitFrame(Opcodes.F_FULL, 0, new Object[0], 0, new Object[0]);
+ methodVisitor.visitLdcInsn(BAR);
+ methodVisitor.visitInsn(Opcodes.ARETURN);
+ return new Size(1, 2);
+ }
+ }
+
+ private static class InconsistentThisReferenceMethod implements Implementation, ByteCodeAppender {
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return this;
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ methodVisitor.visitFrame(Opcodes.F_FULL, 1, new Object[] {TypeDescription.OBJECT.getInternalName()}, 0, new Object[0]);
+ methodVisitor.visitLdcInsn(BAR);
+ methodVisitor.visitInsn(Opcodes.ARETURN);
+ return new Size(1, 2);
+ }
+ }
+
+ private static class InconsistentParameterReferenceMethod implements Implementation, ByteCodeAppender {
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return this;
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ methodVisitor.visitFrame(Opcodes.F_FULL, 1, new Object[] {TypeDescription.OBJECT.getInternalName()}, 0, new Object[0]);
+ methodVisitor.visitLdcInsn(BAR);
+ methodVisitor.visitInsn(Opcodes.ARETURN);
+ return new Size(1, 2);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceInconsistentStackSizeTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceInconsistentStackSizeTest.java
new file mode 100644
index 0000000..2fa5e91
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceInconsistentStackSizeTest.java
@@ -0,0 +1,153 @@
+package net.bytebuddy.asm;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.modifier.Ownership;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.implementation.FixedValue;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.test.utility.DebuggingWrapper;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.io.Serializable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Collection;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class AdviceInconsistentStackSizeTest {
+
+ private static final String FOO = "foo";
+
+ private final Class<?> type;
+
+ private final Serializable original, replaced;
+
+ private final int opcode;
+
+ public AdviceInconsistentStackSizeTest(Class<?> type, Serializable original, Serializable replaced, int opcode) {
+ this.type = type;
+ this.original = original;
+ this.replaced = replaced;
+ this.opcode = opcode;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {String.class, "foo", "bar", Opcodes.ARETURN},
+ {boolean.class, 0, false, Opcodes.IRETURN},
+ {byte.class, 0, (byte) 42, Opcodes.IRETURN},
+ {short.class, 0, (short) 42, Opcodes.IRETURN},
+ {char.class, 0, (char) 42, Opcodes.IRETURN},
+ {int.class, 0, 42, Opcodes.IRETURN},
+ {long.class, 0L, 42L, Opcodes.LRETURN},
+ {float.class, 0f, 42f, Opcodes.FRETURN},
+ {double.class, 0d, 42d, Opcodes.DRETURN},
+ {void.class, null, null, Opcodes.RETURN},
+ });
+ }
+
+ @Test
+ public void testInconsistentStackSize() throws Exception {
+ Class<?> atypical = new ByteBuddy()
+ .subclass(Object.class)
+ .defineMethod(FOO, type, Visibility.PUBLIC)
+ .intercept(new InconsistentSizeAppender())
+ .make()
+ .load(null, ClassLoadingStrategy.Default.WRAPPER_PERSISTENT)
+ .getLoaded();
+ Class<?> adviced = new ByteBuddy()
+ .redefine(atypical)
+ .visit(Advice.withCustomMapping().bind(Value.class, replaced).to(ExitAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(adviced.getDeclaredMethod(FOO).invoke(adviced.getDeclaredConstructor().newInstance()), is((Object) replaced));
+ }
+
+ @Test
+ public void testInconsistentStackSizeAdvice() throws Exception {
+ Class<?> advice = new ByteBuddy()
+ .subclass(Object.class)
+ .defineMethod(FOO, type, Ownership.STATIC)
+ .intercept(new InconsistentSizeAppender())
+ .annotateMethod(AnnotationDescription.Builder.ofType(Advice.OnMethodEnter.class).define("suppress", RuntimeException.class).build())
+ .annotateMethod(AnnotationDescription.Builder.ofType(Advice.OnMethodExit.class).define("suppress", RuntimeException.class).build())
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER_PERSISTENT)
+ .getLoaded();
+ Class<?> foo = new ByteBuddy()
+ .subclass(Object.class)
+ .defineMethod("foo", String.class, Visibility.PUBLIC)
+ .intercept(FixedValue.value(FOO))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER_PERSISTENT)
+ .getLoaded();
+ Class<?> redefined = new ByteBuddy()
+ .redefine(foo)
+ .visit(Advice.to(advice).on(named(FOO)))
+ .make()
+ .load(null, ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded();
+ assertThat(redefined, not(sameInstance((Object) foo)));
+ assertThat(redefined.getDeclaredMethod(FOO).invoke(redefined.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ }
+
+ @SuppressWarnings("all")
+ private static class ExitAdvice {
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object returned, @Value Object value) {
+ returned = value;
+ }
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ private @interface Value {
+ /* empty */
+ }
+
+ private class InconsistentSizeAppender implements Implementation, ByteCodeAppender {
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return this;
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ if (original != null) {
+ methodVisitor.visitLdcInsn(original);
+ }
+ methodVisitor.visitInsn(opcode);
+ methodVisitor.visitFrame(Opcodes.F_SAME, 0, new Object[0], 0, new Object[0]);
+ if (original != null) {
+ methodVisitor.visitLdcInsn(original);
+ methodVisitor.visitLdcInsn(original);
+ }
+ methodVisitor.visitInsn(opcode);
+ return new Size(StackSize.of(type).getSize() * 2, instrumentedMethod.getStackSize());
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceJsrRetTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceJsrRetTest.java
new file mode 100644
index 0000000..4abd733
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceJsrRetTest.java
@@ -0,0 +1,78 @@
+package net.bytebuddy.asm;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import org.junit.Test;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class AdviceJsrRetTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ @Test
+ public void testJsrRetByteCodes() throws Exception {
+ Class<?> type = new ByteBuddy(ClassFileVersion.JAVA_V4)
+ .subclass(Object.class)
+ .defineMethod(FOO, String.class, Visibility.PUBLIC)
+ .intercept(new JsrRetMethod())
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER_PERSISTENT)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ Class<?> advised = new ByteBuddy()
+ .redefine(type)
+ .visit(Advice.to(JsrAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(advised.getDeclaredMethod(FOO).invoke(advised.getDeclaredConstructor().newInstance()), is((Object) BAR));
+ }
+
+ @SuppressWarnings("all")
+ private static class JsrAdvice {
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return(readOnly = false) String value) {
+ value = BAR;
+ }
+ }
+
+ private static class JsrRetMethod implements Implementation, ByteCodeAppender {
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return this;
+ }
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
+ Label target = new Label();
+ methodVisitor.visitJumpInsn(Opcodes.JSR, target);
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
+ methodVisitor.visitInsn(Opcodes.ARETURN);
+ methodVisitor.visitLabel(target);
+ methodVisitor.visitVarInsn(Opcodes.ASTORE, 2);
+ methodVisitor.visitLdcInsn(FOO);
+ methodVisitor.visitVarInsn(Opcodes.ASTORE, 1);
+ methodVisitor.visitVarInsn(Opcodes.RET, 2);
+ return new Size(1, 3);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceNoRegularReturnTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceNoRegularReturnTest.java
new file mode 100644
index 0000000..c845e87
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceNoRegularReturnTest.java
@@ -0,0 +1,186 @@
+package net.bytebuddy.asm;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
+import java.util.Collection;
+
+import static junit.framework.TestCase.fail;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class AdviceNoRegularReturnTest {
+
+ private static final String FOO = "foo";
+
+ private final Class<?> type;
+
+ public AdviceNoRegularReturnTest(Class<?> type) {
+ this.type = type;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {VoidSample.class},
+ {BooleanSample.class},
+ {ByteSample.class},
+ {ShortSample.class},
+ {CharacterSample.class},
+ {IntegerSample.class},
+ {LongSample.class},
+ {FloatSample.class},
+ {DoubleSample.class},
+ {ReferenceSample.class}
+ });
+ }
+
+ @Test
+ public void testNoRegularReturnWithSkip() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(this.type)
+ .visit(Advice.to(EnterAdviceSkip.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ try {
+ type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance());
+ fail();
+ } catch (InvocationTargetException exception) {
+ assertThat(exception.getCause(), instanceOf(RuntimeException.class));
+ }
+ }
+
+ @Test
+ public void testNoRegularReturnWithoutHandler() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(this.type)
+ .visit(Advice.to(ExitAdviceWithoutHandler.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ try {
+ type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance());
+ fail();
+ } catch (InvocationTargetException exception) {
+ assertThat(exception.getCause(), instanceOf(RuntimeException.class));
+ }
+ }
+
+ @Test
+ public void testNoRegularReturnWithHandler() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(this.type)
+ .visit(Advice.to(ExitAdviceWithHandler.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ try {
+ type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance());
+ fail();
+ } catch (InvocationTargetException exception) {
+ assertThat(exception.getCause(), instanceOf(RuntimeException.class));
+ }
+ }
+
+ private static class EnterAdviceSkip {
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static boolean enter() {
+ return false;
+ }
+ }
+
+ private static class ExitAdviceWithoutHandler {
+
+ @Advice.OnMethodExit
+ private static void exit() {
+ /* empty */
+ }
+ }
+
+ private static class ExitAdviceWithHandler {
+
+ @Advice.OnMethodExit(onThrowable = RuntimeException.class)
+ private static void exit() {
+ /* empty */
+ }
+ }
+
+ public static class VoidSample {
+
+ public void foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ public static class BooleanSample {
+
+ public boolean foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ public static class ByteSample {
+
+ public byte foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ public static class ShortSample {
+
+ public short foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ public static class CharacterSample {
+
+ public char foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ public static class IntegerSample {
+
+ public int foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ public static class LongSample {
+
+ public long foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ public static class FloatSample {
+
+ public float foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ public static class DoubleSample {
+
+ public double foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ public static class ReferenceSample {
+
+ public Object foo() {
+ throw new RuntimeException();
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceNoRegularReturnWithinAdviceTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceNoRegularReturnWithinAdviceTest.java
new file mode 100644
index 0000000..33c00e3
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceNoRegularReturnWithinAdviceTest.java
@@ -0,0 +1,327 @@
+package net.bytebuddy.asm;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
+import java.util.Collection;
+
+import static junit.framework.TestCase.fail;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class AdviceNoRegularReturnWithinAdviceTest {
+
+ private static final String FOO = "foo";
+
+ private final Class<?> type;
+
+ public AdviceNoRegularReturnWithinAdviceTest(Class<?> type) {
+ this.type = type;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {VoidEnterAdvice.class},
+ {BooleanEnterAdvice.class},
+ {ByteEnterAdvice.class},
+ {ShortEnterAdvice.class},
+ {CharacterEnterAdvice.class},
+ {IntegerEnterAdvice.class},
+ {LongEnterAdvice.class},
+ {FloatEnterAdvice.class},
+ {DoubleEnterAdvice.class},
+ {ReferenceEnterAdvice.class},
+ {VoidExitAdvice.class},
+ {BooleanExitAdvice.class},
+ {ByteExitAdvice.class},
+ {ShortExitAdvice.class},
+ {CharacterExitAdvice.class},
+ {IntegerExitAdvice.class},
+ {LongExitAdvice.class},
+ {FloatExitAdvice.class},
+ {DoubleExitAdvice.class},
+ {ReferenceExitAdvice.class},
+ {VoidExitHandlerAdvice.class},
+ {BooleanExitHandlerAdvice.class},
+ {ByteExitHandlerAdvice.class},
+ {ShortExitHandlerAdvice.class},
+ {CharacterExitHandlerAdvice.class},
+ {IntegerExitHandlerAdvice.class},
+ {LongExitHandlerAdvice.class},
+ {FloatExitHandlerAdvice.class},
+ {DoubleExitHandlerAdvice.class},
+ {ReferenceExitHandlerAdvice.class}
+ });
+ }
+
+ @Test
+ public void testNoRegularReturn() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(this.type).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ try {
+ type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance());
+ fail();
+ } catch (InvocationTargetException exception) {
+ assertThat(exception.getCause(), instanceOf(RuntimeException.class));
+ }
+ }
+
+ public static class Sample {
+
+ public void foo() {
+ /* empty */
+ }
+ }
+
+ private static class VoidEnterAdvice {
+
+ @Advice.OnMethodEnter
+ public static void foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ private static class BooleanEnterAdvice {
+
+ @Advice.OnMethodEnter
+ public static boolean foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ private static class ByteEnterAdvice {
+
+ @Advice.OnMethodEnter
+ public static byte foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ private static class ShortEnterAdvice {
+
+ @Advice.OnMethodEnter
+ public static short foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ private static class CharacterEnterAdvice {
+
+ @Advice.OnMethodEnter
+ public static char foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ private static class IntegerEnterAdvice {
+
+ @Advice.OnMethodEnter
+ public static int foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ private static class LongEnterAdvice {
+
+ @Advice.OnMethodEnter
+ public static long foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ private static class FloatEnterAdvice {
+
+ @Advice.OnMethodEnter
+ public static float foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ private static class DoubleEnterAdvice {
+
+ @Advice.OnMethodEnter
+ public static double foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ private static class ReferenceEnterAdvice {
+
+ @Advice.OnMethodEnter
+ public static Object foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ private static class VoidExitAdvice {
+
+ @Advice.OnMethodExit
+ public static void foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ private static class BooleanExitAdvice {
+
+ @Advice.OnMethodExit
+ public static boolean foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ private static class ByteExitAdvice {
+
+ @Advice.OnMethodExit
+ public static byte foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ private static class ShortExitAdvice {
+
+ @Advice.OnMethodExit
+ public static short foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ private static class CharacterExitAdvice {
+
+ @Advice.OnMethodExit
+ public static char foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ private static class IntegerExitAdvice {
+
+ @Advice.OnMethodExit
+ public static int foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ private static class LongExitAdvice {
+
+ @Advice.OnMethodExit
+ public static long foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ private static class FloatExitAdvice {
+
+ @Advice.OnMethodExit
+ public static float foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ private static class DoubleExitAdvice {
+
+ @Advice.OnMethodExit
+ public static double foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ private static class ReferenceExitAdvice {
+
+ @Advice.OnMethodExit
+ public static Object foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ private static class VoidExitHandlerAdvice {
+
+ @Advice.OnMethodExit(onThrowable = RuntimeException.class)
+ public static void foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ private static class BooleanExitHandlerAdvice {
+
+ @Advice.OnMethodExit(onThrowable = RuntimeException.class)
+ public static boolean foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ private static class ByteExitHandlerAdvice {
+
+ @Advice.OnMethodExit(onThrowable = RuntimeException.class)
+ public static byte foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ private static class ShortExitHandlerAdvice {
+
+ @Advice.OnMethodExit(onThrowable = RuntimeException.class)
+ public static short foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ private static class CharacterExitHandlerAdvice {
+
+ @Advice.OnMethodExit(onThrowable = RuntimeException.class)
+ public static char foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ private static class IntegerExitHandlerAdvice {
+
+ @Advice.OnMethodExit(onThrowable = RuntimeException.class)
+ public static int foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ private static class LongExitHandlerAdvice {
+
+ @Advice.OnMethodExit(onThrowable = RuntimeException.class)
+ public static long foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ private static class FloatExitHandlerAdvice {
+
+ @Advice.OnMethodExit(onThrowable = RuntimeException.class)
+ public static float foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ private static class DoubleExitHandlerAdvice {
+
+ @Advice.OnMethodExit(onThrowable = RuntimeException.class)
+ public static double foo() {
+ throw new RuntimeException();
+ }
+ }
+
+ private static class ReferenceExitHandlerAdvice {
+
+ @Advice.OnMethodExit(onThrowable = RuntimeException.class)
+ public static Object foo() {
+ throw new RuntimeException();
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceSizeConversionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceSizeConversionTest.java
new file mode 100644
index 0000000..78a42db
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceSizeConversionTest.java
@@ -0,0 +1,152 @@
+package net.bytebuddy.asm;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class AdviceSizeConversionTest {
+
+ private static final String FOO = "foo";
+
+ private static final int NUMERIC = 42;
+
+ private final Class<?> target, parameter;
+
+ private final Object input, output;
+
+ public AdviceSizeConversionTest(Class<?> target, Class<?> parameter, Object input, Object output) {
+ this.target = target;
+ this.parameter = parameter;
+ this.input = input;
+ this.output = output;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {IntToFloat.class, int.class, NUMERIC, (float) NUMERIC},
+ {IntToLong.class, int.class, NUMERIC, (long) NUMERIC},
+ {IntToDouble.class, int.class, NUMERIC, (double) NUMERIC},
+ {FloatToInt.class, float.class, (float) NUMERIC, NUMERIC},
+ {FloatToLong.class, float.class, (float) NUMERIC, (long) NUMERIC},
+ {FloatToDouble.class, float.class, (float) NUMERIC, (double) NUMERIC},
+ {LongToInt.class, long.class, (long) NUMERIC, NUMERIC},
+ {LongToFloat.class, long.class, (long) NUMERIC, (float) NUMERIC},
+ {LongToDouble.class, long.class, (long) NUMERIC, (double) NUMERIC},
+ {DoubleToInt.class, double.class, (double) NUMERIC, NUMERIC},
+ {DoubleToLong.class, double.class, (double) NUMERIC, (long) NUMERIC},
+ {DoubleToFloat.class, double.class, (double) NUMERIC, (float) NUMERIC},
+ });
+ }
+
+ @Test
+ public void testAdvice() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(target)
+ .visit(Advice.to(AdviceSizeConversionTest.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO, parameter).invoke(type.getDeclaredConstructor().newInstance(), input), is(output));
+ }
+
+ @Advice.OnMethodExit
+ private static void exit() {
+ /* empty */
+ }
+
+ public static class IntToFloat {
+
+ public float foo(int value) {
+ return (float) value;
+ }
+ }
+
+ public static class IntToLong {
+
+ public long foo(int value) {
+ return (long) value;
+ }
+ }
+
+ public static class IntToDouble {
+
+ public double foo(int value) {
+ return (double) value;
+ }
+ }
+
+ public static class FloatToInt {
+
+ public int foo(float value) {
+ return (int) value;
+ }
+ }
+
+ public static class FloatToLong {
+
+ public long foo(float value) {
+ return (long) value;
+ }
+ }
+
+ public static class FloatToDouble {
+
+ public double foo(float value) {
+ return (double) value;
+ }
+ }
+
+ public static class LongToInt {
+
+ public int foo(long value) {
+ return (int) value;
+ }
+ }
+
+ public static class LongToFloat {
+
+ public float foo(long value) {
+ return (float) value;
+ }
+ }
+
+ public static class LongToDouble {
+
+ public double foo(long value) {
+ return (double) value;
+ }
+ }
+
+ public static class DoubleToInt {
+
+ public int foo(double value) {
+ return (int) value;
+ }
+ }
+
+ public static class DoubleToFloat {
+
+ public float foo(double value) {
+ return (float) value;
+ }
+ }
+
+ public static class DoubleToLong {
+
+ public long foo(double value) {
+ return (long) value;
+ }
+ }
+
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceSkipOnDefaultValueTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceSkipOnDefaultValueTest.java
new file mode 100644
index 0000000..8aad5c5
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceSkipOnDefaultValueTest.java
@@ -0,0 +1,1438 @@
+package net.bytebuddy.asm;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class AdviceSkipOnDefaultValueTest {
+
+ private static final String FOO = "foo";
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {BooleanAdvice.class, false},
+ {ByteAdvice.class, (byte) 0},
+ {ShortAdvice.class, (short) 0},
+ {CharacterAdvice.class, (char) 0},
+ {IntegerAdvice.class, 0},
+ {LongAdvice.class, 0L},
+ {FloatAdvice.class, 0f},
+ {DoubleAdvice.class, 0d},
+ {ReferenceAdvice.class, null},
+ {VoidAdvice.class, null},
+ {BooleanDelegateAdvice.class, false},
+ {ByteDelegateAdvice.class, (byte) 0},
+ {ShortDelegateAdvice.class, (short) 0},
+ {CharacterDelegateAdvice.class, (char) 0},
+ {IntegerDelegateAdvice.class, 0},
+ {LongDelegateAdvice.class, 0L},
+ {FloatDelegateAdvice.class, 0f},
+ {DoubleDelegateAdvice.class, 0d},
+ {ReferenceDelegateAdvice.class, null},
+ {VoidDelegateAdvice.class, null},
+ {BooleanWithOutExitAdvice.class, false},
+ {ByteWithOutExitAdvice.class, (byte) 0},
+ {ShortWithOutExitAdvice.class, (short) 0},
+ {CharacterWithOutExitAdvice.class, (char) 0},
+ {IntegerWithOutExitAdvice.class, 0},
+ {LongWithOutExitAdvice.class, 0L},
+ {FloatWithOutExitAdvice.class, 0f},
+ {DoubleWithOutExitAdvice.class, 0d},
+ {ReferenceWithOutExitAdvice.class, null},
+ {VoidWithOutExitAdvice.class, null},
+ {BooleanDelegateWithOutExitAdvice.class, false},
+ {ByteDelegateWithOutExitAdvice.class, (byte) 0},
+ {ShortDelegateWithOutExitAdvice.class, (short) 0},
+ {CharacterDelegateWithOutExitAdvice.class, (char) 0},
+ {IntegerDelegateWithOutExitAdvice.class, 0},
+ {LongDelegateWithOutExitAdvice.class, 0L},
+ {FloatDelegateWithOutExitAdvice.class, 0f},
+ {DoubleDelegateWithOutExitAdvice.class, 0d},
+ {ReferenceDelegateWithOutExitAdvice.class, null},
+ {VoidDelegateWithOutExitAdvice.class, null},
+ {BooleanNoSkipAdvice.class, true},
+ {ByteNoSkipAdvice.class, (byte) 42},
+ {ShortNoSkipAdvice.class, (short) 42},
+ {CharacterNoSkipAdvice.class, (char) 42},
+ {IntegerNoSkipAdvice.class, 42},
+ {LongNoSkipAdvice.class, 42L},
+ {FloatNoSkipAdvice.class, 42f},
+ {DoubleNoSkipAdvice.class, 42d},
+ {ReferenceNoSkipAdvice.class, FOO},
+ {VoidNoSkipAdvice.class, null},
+ {BooleanDelegateNoSkipAdvice.class, true},
+ {ByteDelegateNoSkipAdvice.class, (byte) 42},
+ {ShortDelegateNoSkipAdvice.class, (short) 42},
+ {CharacterDelegateNoSkipAdvice.class, (char) 42},
+ {IntegerDelegateNoSkipAdvice.class, 42},
+ {LongDelegateNoSkipAdvice.class, 42L},
+ {FloatDelegateNoSkipAdvice.class, 42f},
+ {DoubleDelegateNoSkipAdvice.class, 42d},
+ {ReferenceDelegateNoSkipAdvice.class, FOO},
+ {VoidDelegateNoSkipAdvice.class, null},
+ {BooleanWithOutExitNoSkipAdvice.class, true},
+ {ByteWithOutExitNoSkipAdvice.class, (byte) 42},
+ {ShortWithOutExitNoSkipAdvice.class, (short) 42},
+ {CharacterWithOutExitNoSkipAdvice.class, (char) 42},
+ {IntegerWithOutExitNoSkipAdvice.class, 42},
+ {LongWithOutExitNoSkipAdvice.class, 42L},
+ {FloatWithOutExitNoSkipAdvice.class, 42f},
+ {DoubleWithOutExitNoSkipAdvice.class, 42d},
+ {ReferenceWithOutExitNoSkipAdvice.class, FOO},
+ {VoidWithOutExitNoSkipAdvice.class, null},
+ {BooleanDelegateWithOutExitNoSkipAdvice.class, true},
+ {ByteDelegateWithOutExitNoSkipAdvice.class, (byte) 42},
+ {ShortDelegateWithOutExitNoSkipAdvice.class, (short) 42},
+ {CharacterDelegateWithOutExitNoSkipAdvice.class, (char) 42},
+ {IntegerDelegateWithOutExitNoSkipAdvice.class, 42},
+ {LongDelegateWithOutExitNoSkipAdvice.class, 42L},
+ {FloatDelegateWithOutExitNoSkipAdvice.class, 42f},
+ {DoubleDelegateWithOutExitNoSkipAdvice.class, 42d},
+ {ReferenceDelegateWithOutExitNoSkipAdvice.class, FOO},
+ {VoidDelegateWithOutExitNoSkipAdvice.class, null}
+ });
+ }
+
+ private final Class<?> type;
+
+ private final Object value;
+
+ public AdviceSkipOnDefaultValueTest(Class<?> type, Object value) {
+ this.type = type;
+ this.value = value;
+ }
+
+ @Test
+ public void testAdvice() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(this.type)
+ .visit(Advice.to(this.type).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is(value));
+ }
+
+ @SuppressWarnings("unused")
+ public static class BooleanAdvice {
+
+ public boolean foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static boolean enter() {
+ return false;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return boolean value) {
+ if (value) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ByteAdvice {
+
+ public byte foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static byte enter() {
+ return 0;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return byte value) {
+ if (value != 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ShortAdvice {
+
+ public short foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static short enter() {
+ return 0;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return short value) {
+ if (value != 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class CharacterAdvice {
+
+ public char foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static char enter() {
+ return 0;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return char value) {
+ if (value != 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IntegerAdvice {
+
+ public int foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static int enter() {
+ return 0;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return int value) {
+ if (value != 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class LongAdvice {
+
+ public long foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static long enter() {
+ return 0L;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return long value) {
+ if (value != 0L) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FloatAdvice {
+
+ public float foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static float enter() {
+ return 0f;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return float value) {
+ if (value != 0f) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class DoubleAdvice {
+
+ public double foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static double enter() {
+ return 0d;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return double value) {
+ if (value != 0d) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ReferenceAdvice {
+
+ public Object foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static Object enter() {
+ return null;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return Object value) {
+ if (value != null) {
+ throw new AssertionError("Equality");
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class VoidAdvice {
+
+ public void foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static boolean enter() {
+ return false;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit() {
+ /* do nothing */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class BooleanDelegateAdvice {
+
+ public boolean foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static boolean enter() {
+ return false;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return boolean value) {
+ if (value) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ByteDelegateAdvice {
+
+ public byte foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static byte enter() {
+ return 0;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return byte value) {
+ if (value != 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ShortDelegateAdvice {
+
+ public short foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static short enter() {
+ return 0;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return short value) {
+ if (value != 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class CharacterDelegateAdvice {
+
+ public char foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static char enter() {
+ return 0;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return char value) {
+ if (value != 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IntegerDelegateAdvice {
+
+ public int foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static int enter() {
+ return 0;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return int value) {
+ if (value != 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class LongDelegateAdvice {
+
+ public long foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static long enter() {
+ return 0L;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return long value) {
+ if (value != 0L) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FloatDelegateAdvice {
+
+ public float foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static float enter() {
+ return 0f;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return float value) {
+ if (value != 0f) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class DoubleDelegateAdvice {
+
+ public double foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static double enter() {
+ return 0d;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return double value) {
+ if (value != 0d) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ReferenceDelegateAdvice {
+
+ public Object foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static Object enter() {
+ return null;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return Object value) {
+ if (value != null) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class VoidDelegateAdvice {
+
+ public void foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static boolean enter() {
+ return false;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit() {
+ /* do nothing */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class BooleanWithOutExitAdvice {
+
+ public boolean foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static boolean enter() {
+ return false;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ByteWithOutExitAdvice {
+
+ public byte foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static byte enter() {
+ return 0;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ShortWithOutExitAdvice {
+
+ public short foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static short enter() {
+ return 0;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class CharacterWithOutExitAdvice {
+
+ public char foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static char enter() {
+ return 0;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IntegerWithOutExitAdvice {
+
+ public int foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static int enter() {
+ return 0;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class LongWithOutExitAdvice {
+
+ public long foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static long enter() {
+ return 0L;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FloatWithOutExitAdvice {
+
+ public float foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static float enter() {
+ return 0f;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class DoubleWithOutExitAdvice {
+
+ public double foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static double enter() {
+ return 0d;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ReferenceWithOutExitAdvice {
+
+ public Object foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static Object enter() {
+ return null;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class VoidWithOutExitAdvice {
+
+ public void foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static boolean enter() {
+ return false;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class BooleanDelegateWithOutExitAdvice {
+
+ public boolean foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static boolean enter() {
+ return false;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ByteDelegateWithOutExitAdvice {
+
+ public byte foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static byte enter() {
+ return 0;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ShortDelegateWithOutExitAdvice {
+
+ public short foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static short enter() {
+ return 0;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class CharacterDelegateWithOutExitAdvice {
+
+ public char foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static char enter() {
+ return 0;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IntegerDelegateWithOutExitAdvice {
+
+ public int foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static int enter() {
+ return 0;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class LongDelegateWithOutExitAdvice {
+
+ public long foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static long enter() {
+ return 0L;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FloatDelegateWithOutExitAdvice {
+
+ public float foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static float enter() {
+ return 0f;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class DoubleDelegateWithOutExitAdvice {
+
+ public double foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static double enter() {
+ return 0d;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ReferenceDelegateWithOutExitAdvice {
+
+ public Object foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static Object enter() {
+ return null;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class VoidDelegateWithOutExitAdvice {
+
+ public void foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static boolean enter() {
+ return false;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class BooleanNoSkipAdvice {
+
+ public boolean foo() {
+ return true;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static boolean enter() {
+ return true;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return boolean value) {
+ if (!value) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ByteNoSkipAdvice {
+
+ public byte foo() {
+ return 42;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static byte enter() {
+ return 42;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return byte value) {
+ if (value != 42) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ShortNoSkipAdvice {
+
+ public short foo() {
+ return 42;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static short enter() {
+ return 42;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return short value) {
+ if (value != 42) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class CharacterNoSkipAdvice {
+
+ public char foo() {
+ return 42;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static char enter() {
+ return 42;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return char value) {
+ if (value != 42) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IntegerNoSkipAdvice {
+
+ public int foo() {
+ return 42;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static int enter() {
+ return 42;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return int value) {
+ if (value != 42) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class LongNoSkipAdvice {
+
+ public long foo() {
+ return 42L;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static long enter() {
+ return 42L;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return long value) {
+ if (value != 42L) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FloatNoSkipAdvice {
+
+ public float foo() {
+ return 42f;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static float enter() {
+ return 42f;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return float value) {
+ if (value != 42f) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class DoubleNoSkipAdvice {
+
+ public double foo() {
+ return 42d;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static double enter() {
+ return 42d;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return double value) {
+ if (value != 42d) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ReferenceNoSkipAdvice {
+
+ public Object foo() {
+ return FOO;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static Object enter() {
+ return new Object();
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return Object value) {
+ if (!value.equals(FOO)) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class VoidNoSkipAdvice {
+
+ public void foo() {
+ /* do nothing */
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static boolean enter() {
+ return true;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit() {
+ /* do nothing */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class BooleanDelegateNoSkipAdvice {
+
+ public boolean foo() {
+ return true;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static boolean enter() {
+ return true;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return boolean value) {
+ if (!value) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ByteDelegateNoSkipAdvice {
+
+ public byte foo() {
+ return 42;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static byte enter() {
+ return 42;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return byte value) {
+ if (value != 42) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ShortDelegateNoSkipAdvice {
+
+ public short foo() {
+ return 42;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static short enter() {
+ return 42;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return short value) {
+ if (value != 42) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class CharacterDelegateNoSkipAdvice {
+
+ public char foo() {
+ return 42;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static char enter() {
+ return 42;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return char value) {
+ if (value != 42) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IntegerDelegateNoSkipAdvice {
+
+ public int foo() {
+ return 42;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static int enter() {
+ return 42;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return int value) {
+ if (value != 42) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class LongDelegateNoSkipAdvice {
+
+ public long foo() {
+ return 42L;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static long enter() {
+ return 42L;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return long value) {
+ if (value != 42L) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FloatDelegateNoSkipAdvice {
+
+ public float foo() {
+ return 42f;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static float enter() {
+ return 42f;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return float value) {
+ if (value != 42f) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class DoubleDelegateNoSkipAdvice {
+
+ public double foo() {
+ return 42d;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static double enter() {
+ return 42d;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return double value) {
+ if (value != 42d) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ReferenceDelegateNoSkipAdvice {
+
+ public Object foo() {
+ return FOO;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static Object enter() {
+ return new Object();
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return Object value) {
+ if (!value.equals(FOO)) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class VoidDelegateNoSkipAdvice {
+
+ public void foo() {
+ /* do nothing */
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static boolean enter() {
+ return true;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit() {
+ /* do nothing */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class BooleanWithOutExitNoSkipAdvice {
+
+ public boolean foo() {
+ return true;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static boolean enter() {
+ return true;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ByteWithOutExitNoSkipAdvice {
+
+ public byte foo() {
+ return 42;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static byte enter() {
+ return 42;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ShortWithOutExitNoSkipAdvice {
+
+ public short foo() {
+ return 42;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static short enter() {
+ return 42;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class CharacterWithOutExitNoSkipAdvice {
+
+ public char foo() {
+ return 42;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static char enter() {
+ return 42;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IntegerWithOutExitNoSkipAdvice {
+
+ public int foo() {
+ return 42;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static int enter() {
+ return 42;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class LongWithOutExitNoSkipAdvice {
+
+ public long foo() {
+ return 42L;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static long enter() {
+ return 42L;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FloatWithOutExitNoSkipAdvice {
+
+ public float foo() {
+ return 42f;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static float enter() {
+ return 42f;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class DoubleWithOutExitNoSkipAdvice {
+
+ public double foo() {
+ return 42d;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static double enter() {
+ return 42d;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ReferenceWithOutExitNoSkipAdvice {
+
+ public Object foo() {
+ return FOO;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static Object enter() {
+ return new Object();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class VoidWithOutExitNoSkipAdvice {
+
+ public void foo() {
+ /* do nothing */
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static boolean enter() {
+ return true;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class BooleanDelegateWithOutExitNoSkipAdvice {
+
+ public boolean foo() {
+ return true;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static boolean enter() {
+ return true;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ByteDelegateWithOutExitNoSkipAdvice {
+
+ public byte foo() {
+ return 42;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static byte enter() {
+ return 42;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ShortDelegateWithOutExitNoSkipAdvice {
+
+ public short foo() {
+ return 42;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static short enter() {
+ return 42;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class CharacterDelegateWithOutExitNoSkipAdvice {
+
+ public char foo() {
+ return 42;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static char enter() {
+ return 42;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IntegerDelegateWithOutExitNoSkipAdvice {
+
+ public int foo() {
+ return 42;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static int enter() {
+ return 42;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class LongDelegateWithOutExitNoSkipAdvice {
+
+ public long foo() {
+ return 42L;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static long enter() {
+ return 42L;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FloatDelegateWithOutExitNoSkipAdvice {
+
+ public float foo() {
+ return 42f;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static float enter() {
+ return 42f;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class DoubleDelegateWithOutExitNoSkipAdvice {
+
+ public double foo() {
+ return 42d;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static double enter() {
+ return 42d;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ReferenceDelegateWithOutExitNoSkipAdvice {
+
+ public Object foo() {
+ return FOO;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static Object enter() {
+ return new Object();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class VoidDelegateWithOutExitNoSkipAdvice {
+
+ public void foo() {
+ /* do nothing */
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, inline = false)
+ private static boolean enter() {
+ return true;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceSkipOnNonDefaultValueTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceSkipOnNonDefaultValueTest.java
new file mode 100644
index 0000000..9c1e296
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceSkipOnNonDefaultValueTest.java
@@ -0,0 +1,1438 @@
+package net.bytebuddy.asm;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class AdviceSkipOnNonDefaultValueTest {
+
+ private static final String FOO = "foo";
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {BooleanAdvice.class, false},
+ {ByteAdvice.class, (byte) 0},
+ {ShortAdvice.class, (short) 0},
+ {CharacterAdvice.class, (char) 0},
+ {IntegerAdvice.class, 0},
+ {LongAdvice.class, 0L},
+ {FloatAdvice.class, 0f},
+ {DoubleAdvice.class, 0d},
+ {ReferenceAdvice.class, null},
+ {VoidAdvice.class, null},
+ {BooleanDelegateAdvice.class, false},
+ {ByteDelegateAdvice.class, (byte) 0},
+ {ShortDelegateAdvice.class, (short) 0},
+ {CharacterDelegateAdvice.class, (char) 0},
+ {IntegerDelegateAdvice.class, 0},
+ {LongDelegateAdvice.class, 0L},
+ {FloatDelegateAdvice.class, 0f},
+ {DoubleDelegateAdvice.class, 0d},
+ {ReferenceDelegateAdvice.class, null},
+ {VoidDelegateAdvice.class, null},
+ {BooleanWithOutExitAdvice.class, false},
+ {ByteWithOutExitAdvice.class, (byte) 0},
+ {ShortWithOutExitAdvice.class, (short) 0},
+ {CharacterWithOutExitAdvice.class, (char) 0},
+ {IntegerWithOutExitAdvice.class, 0},
+ {LongWithOutExitAdvice.class, 0L},
+ {FloatWithOutExitAdvice.class, 0f},
+ {DoubleWithOutExitAdvice.class, 0d},
+ {ReferenceWithOutExitAdvice.class, null},
+ {VoidWithOutExitAdvice.class, null},
+ {BooleanDelegateWithOutExitAdvice.class, false},
+ {ByteDelegateWithOutExitAdvice.class, (byte) 0},
+ {ShortDelegateWithOutExitAdvice.class, (short) 0},
+ {CharacterDelegateWithOutExitAdvice.class, (char) 0},
+ {IntegerDelegateWithOutExitAdvice.class, 0},
+ {LongDelegateWithOutExitAdvice.class, 0L},
+ {FloatDelegateWithOutExitAdvice.class, 0f},
+ {DoubleDelegateWithOutExitAdvice.class, 0d},
+ {ReferenceDelegateWithOutExitAdvice.class, null},
+ {VoidDelegateWithOutExitAdvice.class, null},
+ {BooleanNoSkipAdvice.class, true},
+ {ByteNoSkipAdvice.class, (byte) 42},
+ {ShortNoSkipAdvice.class, (short) 42},
+ {CharacterNoSkipAdvice.class, (char) 42},
+ {IntegerNoSkipAdvice.class, 42},
+ {LongNoSkipAdvice.class, 42L},
+ {FloatNoSkipAdvice.class, 42f},
+ {DoubleNoSkipAdvice.class, 42d},
+ {ReferenceNoSkipAdvice.class, FOO},
+ {VoidNoSkipAdvice.class, null},
+ {BooleanDelegateNoSkipAdvice.class, true},
+ {ByteDelegateNoSkipAdvice.class, (byte) 42},
+ {ShortDelegateNoSkipAdvice.class, (short) 42},
+ {CharacterDelegateNoSkipAdvice.class, (char) 42},
+ {IntegerDelegateNoSkipAdvice.class, 42},
+ {LongDelegateNoSkipAdvice.class, 42L},
+ {FloatDelegateNoSkipAdvice.class, 42f},
+ {DoubleDelegateNoSkipAdvice.class, 42d},
+ {ReferenceDelegateNoSkipAdvice.class, FOO},
+ {VoidDelegateNoSkipAdvice.class, null},
+ {BooleanWithOutExitNoSkipAdvice.class, true},
+ {ByteWithOutExitNoSkipAdvice.class, (byte) 42},
+ {ShortWithOutExitNoSkipAdvice.class, (short) 42},
+ {CharacterWithOutExitNoSkipAdvice.class, (char) 42},
+ {IntegerWithOutExitNoSkipAdvice.class, 42},
+ {LongWithOutExitNoSkipAdvice.class, 42L},
+ {FloatWithOutExitNoSkipAdvice.class, 42f},
+ {DoubleWithOutExitNoSkipAdvice.class, 42d},
+ {ReferenceWithOutExitNoSkipAdvice.class, FOO},
+ {VoidWithOutExitNoSkipAdvice.class, null},
+ {BooleanDelegateWithOutExitNoSkipAdvice.class, true},
+ {ByteDelegateWithOutExitNoSkipAdvice.class, (byte) 42},
+ {ShortDelegateWithOutExitNoSkipAdvice.class, (short) 42},
+ {CharacterDelegateWithOutExitNoSkipAdvice.class, (char) 42},
+ {IntegerDelegateWithOutExitNoSkipAdvice.class, 42},
+ {LongDelegateWithOutExitNoSkipAdvice.class, 42L},
+ {FloatDelegateWithOutExitNoSkipAdvice.class, 42f},
+ {DoubleDelegateWithOutExitNoSkipAdvice.class, 42d},
+ {ReferenceDelegateWithOutExitNoSkipAdvice.class, FOO},
+ {VoidDelegateWithOutExitNoSkipAdvice.class, null}
+ });
+ }
+
+ private final Class<?> type;
+
+ private final Object value;
+
+ public AdviceSkipOnNonDefaultValueTest(Class<?> type, Object value) {
+ this.type = type;
+ this.value = value;
+ }
+
+ @Test
+ public void testAdvice() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(this.type)
+ .visit(Advice.to(this.type).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is(value));
+ }
+
+ @SuppressWarnings("unused")
+ public static class BooleanAdvice {
+
+ public boolean foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static boolean enter() {
+ return true;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return boolean value) {
+ if (value) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ByteAdvice {
+
+ public byte foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static byte enter() {
+ return 42;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return byte value) {
+ if (value != 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ShortAdvice {
+
+ public short foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static short enter() {
+ return 42;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return short value) {
+ if (value != 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class CharacterAdvice {
+
+ public char foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static char enter() {
+ return 42;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return char value) {
+ if (value != 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IntegerAdvice {
+
+ public int foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static int enter() {
+ return 42;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return int value) {
+ if (value != 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class LongAdvice {
+
+ public long foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static long enter() {
+ return 42L;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return long value) {
+ if (value != 0L) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FloatAdvice {
+
+ public float foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static float enter() {
+ return 42f;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return float value) {
+ if (value != 0f) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class DoubleAdvice {
+
+ public double foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static double enter() {
+ return 42d;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return double value) {
+ if (value != 0d) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ReferenceAdvice {
+
+ public Object foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static Object enter() {
+ return new Object();
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return Object value) {
+ if (value != null) {
+ throw new AssertionError("Equality");
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class VoidAdvice {
+
+ public void foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static boolean enter() {
+ return true;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit() {
+ /* do nothing */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class BooleanDelegateAdvice {
+
+ public boolean foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static boolean enter() {
+ return true;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return boolean value) {
+ if (value) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ByteDelegateAdvice {
+
+ public byte foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static byte enter() {
+ return 42;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return byte value) {
+ if (value != 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ShortDelegateAdvice {
+
+ public short foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static short enter() {
+ return 42;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return short value) {
+ if (value != 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class CharacterDelegateAdvice {
+
+ public char foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static char enter() {
+ return 42;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return char value) {
+ if (value != 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IntegerDelegateAdvice {
+
+ public int foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static int enter() {
+ return 42;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return int value) {
+ if (value != 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class LongDelegateAdvice {
+
+ public long foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static long enter() {
+ return 42L;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return long value) {
+ if (value != 0L) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FloatDelegateAdvice {
+
+ public float foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static float enter() {
+ return 42f;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return float value) {
+ if (value != 0f) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class DoubleDelegateAdvice {
+
+ public double foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static double enter() {
+ return 42d;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return double value) {
+ if (value != 0d) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ReferenceDelegateAdvice {
+
+ public Object foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static Object enter() {
+ return new Object();
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return Object value) {
+ if (value != null) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class VoidDelegateAdvice {
+
+ public void foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static boolean enter() {
+ return true;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit() {
+ /* do nothing */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class BooleanWithOutExitAdvice {
+
+ public boolean foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static boolean enter() {
+ return true;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ByteWithOutExitAdvice {
+
+ public byte foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static byte enter() {
+ return 42;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ShortWithOutExitAdvice {
+
+ public short foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static short enter() {
+ return 42;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class CharacterWithOutExitAdvice {
+
+ public char foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static char enter() {
+ return 42;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IntegerWithOutExitAdvice {
+
+ public int foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static int enter() {
+ return 42;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class LongWithOutExitAdvice {
+
+ public long foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static long enter() {
+ return 42L;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FloatWithOutExitAdvice {
+
+ public float foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static float enter() {
+ return 42f;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class DoubleWithOutExitAdvice {
+
+ public double foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static double enter() {
+ return 42d;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ReferenceWithOutExitAdvice {
+
+ public Object foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static Object enter() {
+ return new Object();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class VoidWithOutExitAdvice {
+
+ public void foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static boolean enter() {
+ return true;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class BooleanDelegateWithOutExitAdvice {
+
+ public boolean foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static boolean enter() {
+ return true;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ByteDelegateWithOutExitAdvice {
+
+ public byte foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static byte enter() {
+ return 42;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ShortDelegateWithOutExitAdvice {
+
+ public short foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static short enter() {
+ return 42;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class CharacterDelegateWithOutExitAdvice {
+
+ public char foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static char enter() {
+ return 42;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IntegerDelegateWithOutExitAdvice {
+
+ public int foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static int enter() {
+ return 42;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class LongDelegateWithOutExitAdvice {
+
+ public long foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static long enter() {
+ return 42L;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FloatDelegateWithOutExitAdvice {
+
+ public float foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static float enter() {
+ return 42f;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class DoubleDelegateWithOutExitAdvice {
+
+ public double foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static double enter() {
+ return 42d;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ReferenceDelegateWithOutExitAdvice {
+
+ public Object foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static Object enter() {
+ return new Object();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class VoidDelegateWithOutExitAdvice {
+
+ public void foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static boolean enter() {
+ return true;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class BooleanNoSkipAdvice {
+
+ public boolean foo() {
+ return true;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static boolean enter() {
+ return false;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return boolean value) {
+ if (!value) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ByteNoSkipAdvice {
+
+ public byte foo() {
+ return 42;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static byte enter() {
+ return 0;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return byte value) {
+ if (value != 42) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ShortNoSkipAdvice {
+
+ public short foo() {
+ return 42;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static short enter() {
+ return 0;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return short value) {
+ if (value != 42) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class CharacterNoSkipAdvice {
+
+ public char foo() {
+ return 42;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static char enter() {
+ return 0;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return char value) {
+ if (value != 42) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IntegerNoSkipAdvice {
+
+ public int foo() {
+ return 42;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static int enter() {
+ return 0;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return int value) {
+ if (value != 42) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class LongNoSkipAdvice {
+
+ public long foo() {
+ return 42L;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static long enter() {
+ return 0L;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return long value) {
+ if (value != 42L) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FloatNoSkipAdvice {
+
+ public float foo() {
+ return 42f;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static float enter() {
+ return 0f;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return float value) {
+ if (value != 42f) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class DoubleNoSkipAdvice {
+
+ public double foo() {
+ return 42d;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static double enter() {
+ return 0d;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return double value) {
+ if (value != 42d) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ReferenceNoSkipAdvice {
+
+ public Object foo() {
+ return FOO;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static Object enter() {
+ return null;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return Object value) {
+ if (!value.equals(FOO)) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class VoidNoSkipAdvice {
+
+ public void foo() {
+ /* do nothing */
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static boolean enter() {
+ return false;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit() {
+ /* do nothing */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class BooleanDelegateNoSkipAdvice {
+
+ public boolean foo() {
+ return true;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static boolean enter() {
+ return false;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return boolean value) {
+ if (!value) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ByteDelegateNoSkipAdvice {
+
+ public byte foo() {
+ return 42;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static byte enter() {
+ return 0;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return byte value) {
+ if (value != 42) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ShortDelegateNoSkipAdvice {
+
+ public short foo() {
+ return 42;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static short enter() {
+ return 0;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return short value) {
+ if (value != 42) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class CharacterDelegateNoSkipAdvice {
+
+ public char foo() {
+ return 42;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static char enter() {
+ return 0;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return char value) {
+ if (value != 42) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IntegerDelegateNoSkipAdvice {
+
+ public int foo() {
+ return 42;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static int enter() {
+ return 0;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return int value) {
+ if (value != 42) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class LongDelegateNoSkipAdvice {
+
+ public long foo() {
+ return 42L;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static long enter() {
+ return 0L;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return long value) {
+ if (value != 42L) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FloatDelegateNoSkipAdvice {
+
+ public float foo() {
+ return 42f;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static float enter() {
+ return 0f;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return float value) {
+ if (value != 42f) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class DoubleDelegateNoSkipAdvice {
+
+ public double foo() {
+ return 42d;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static double enter() {
+ return 0d;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return double value) {
+ if (value != 42d) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ReferenceDelegateNoSkipAdvice {
+
+ public Object foo() {
+ return FOO;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static Object enter() {
+ return null;
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return Object value) {
+ if (!value.equals(FOO)) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class VoidDelegateNoSkipAdvice {
+
+ public void foo() {
+ /* do nothing */
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static boolean enter() {
+ return false;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit() {
+ /* do nothing */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class BooleanWithOutExitNoSkipAdvice {
+
+ public boolean foo() {
+ return true;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static boolean enter() {
+ return false;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ByteWithOutExitNoSkipAdvice {
+
+ public byte foo() {
+ return 42;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static byte enter() {
+ return 0;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ShortWithOutExitNoSkipAdvice {
+
+ public short foo() {
+ return 42;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static short enter() {
+ return 0;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class CharacterWithOutExitNoSkipAdvice {
+
+ public char foo() {
+ return 42;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static char enter() {
+ return 0;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IntegerWithOutExitNoSkipAdvice {
+
+ public int foo() {
+ return 42;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static int enter() {
+ return 0;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class LongWithOutExitNoSkipAdvice {
+
+ public long foo() {
+ return 42L;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static long enter() {
+ return 0L;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FloatWithOutExitNoSkipAdvice {
+
+ public float foo() {
+ return 42f;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static float enter() {
+ return 0f;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class DoubleWithOutExitNoSkipAdvice {
+
+ public double foo() {
+ return 42d;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static double enter() {
+ return 0d;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ReferenceWithOutExitNoSkipAdvice {
+
+ public Object foo() {
+ return FOO;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static Object enter() {
+ return null;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class VoidWithOutExitNoSkipAdvice {
+
+ public void foo() {
+ /* do nothing */
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static boolean enter() {
+ return false;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class BooleanDelegateWithOutExitNoSkipAdvice {
+
+ public boolean foo() {
+ return true;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static boolean enter() {
+ return false;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ByteDelegateWithOutExitNoSkipAdvice {
+
+ public byte foo() {
+ return 42;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static byte enter() {
+ return 0;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ShortDelegateWithOutExitNoSkipAdvice {
+
+ public short foo() {
+ return 42;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static short enter() {
+ return 0;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class CharacterDelegateWithOutExitNoSkipAdvice {
+
+ public char foo() {
+ return 42;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static char enter() {
+ return 0;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IntegerDelegateWithOutExitNoSkipAdvice {
+
+ public int foo() {
+ return 42;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static int enter() {
+ return 0;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class LongDelegateWithOutExitNoSkipAdvice {
+
+ public long foo() {
+ return 42L;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static long enter() {
+ return 0L;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FloatDelegateWithOutExitNoSkipAdvice {
+
+ public float foo() {
+ return 42f;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static float enter() {
+ return 0f;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class DoubleDelegateWithOutExitNoSkipAdvice {
+
+ public double foo() {
+ return 42d;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static double enter() {
+ return 0d;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ReferenceDelegateWithOutExitNoSkipAdvice {
+
+ public Object foo() {
+ return FOO;
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static Object enter() {
+ return null;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class VoidDelegateWithOutExitNoSkipAdvice {
+
+ public void foo() {
+ /* do nothing */
+ }
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
+ private static boolean enter() {
+ return false;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceSuppressionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceSuppressionTest.java
new file mode 100644
index 0000000..8ee45e6
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceSuppressionTest.java
@@ -0,0 +1,731 @@
+package net.bytebuddy.asm;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class AdviceSuppressionTest {
+
+ private static final String FOO = "foo";
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {VoidInlineEnterAdvice.class},
+ {BooleanInlineEnterAdvice.class},
+ {ByteInlineEnterAdvice.class},
+ {ShortInlineEnterAdvice.class},
+ {CharacterInlineEnterAdvice.class},
+ {IntegerInlineEnterAdvice.class},
+ {LongInlineEnterAdvice.class},
+ {FloatInlineEnterAdvice.class},
+ {DoubleInlineEnterAdvice.class},
+ {ReferenceInlineEnterAdvice.class},
+ {VoidDelegationEnterAdvice.class},
+ {BooleanDelegationEnterAdvice.class},
+ {ByteDelegationEnterAdvice.class},
+ {ShortDelegationEnterAdvice.class},
+ {CharacterDelegationEnterAdvice.class},
+ {IntegerDelegationEnterAdvice.class},
+ {LongDelegationEnterAdvice.class},
+ {FloatDelegationEnterAdvice.class},
+ {DoubleDelegationEnterAdvice.class},
+ {ReferenceDelegationEnterAdvice.class},
+ {VoidInlineExitAdvice.class},
+ {BooleanInlineExitAdvice.class},
+ {ByteInlineExitAdvice.class},
+ {ShortInlineExitAdvice.class},
+ {CharacterInlineExitAdvice.class},
+ {IntegerInlineExitAdvice.class},
+ {LongInlineExitAdvice.class},
+ {FloatInlineExitAdvice.class},
+ {DoubleInlineExitAdvice.class},
+ {ReferenceInlineExitAdvice.class},
+ {VoidDelegationExitAdvice.class},
+ {BooleanDelegationExitAdvice.class},
+ {ByteDelegationExitAdvice.class},
+ {ShortDelegationExitAdvice.class},
+ {CharacterDelegationExitAdvice.class},
+ {IntegerDelegationExitAdvice.class},
+ {LongDelegationExitAdvice.class},
+ {FloatDelegationExitAdvice.class},
+ {DoubleDelegationExitAdvice.class},
+ {ReferenceDelegationExitAdvice.class}
+ });
+ }
+
+ private final Class<?> type;
+
+ public AdviceSuppressionTest(Class<?> type) {
+ this.type = type;
+ }
+
+ @Test
+ public void testIllegalAssignment() throws Exception {
+ Class<?> dynamicType = new ByteBuddy()
+ .redefine(type)
+ .visit(Advice.to(type).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(dynamicType.getDeclaredMethod(FOO).invoke(dynamicType.getDeclaredConstructor().newInstance()), nullValue(Object.class));
+ }
+
+ @SuppressWarnings("unused")
+ public static class VoidInlineEnterAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodExit(suppress = RuntimeException.class)
+ public static void exit() {
+ throw new RuntimeException();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class BooleanInlineEnterAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodExit(suppress = RuntimeException.class)
+ public static boolean exit() {
+ throw new RuntimeException();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ByteInlineEnterAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodExit(suppress = RuntimeException.class)
+ public static byte exit() {
+ throw new RuntimeException();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ShortInlineEnterAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodExit(suppress = RuntimeException.class)
+ public static short exit() {
+ throw new RuntimeException();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class CharacterInlineEnterAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodExit(suppress = RuntimeException.class)
+ public static byte exit() {
+ throw new RuntimeException();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IntegerInlineEnterAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodExit(suppress = RuntimeException.class)
+ public static int exit() {
+ throw new RuntimeException();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class LongInlineEnterAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodExit(suppress = RuntimeException.class)
+ public static long exit() {
+ throw new RuntimeException();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FloatInlineEnterAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodExit(suppress = RuntimeException.class)
+ public static float exit() {
+ throw new RuntimeException();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class DoubleInlineEnterAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodExit(suppress = RuntimeException.class)
+ public static double exit() {
+ throw new RuntimeException();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ReferenceInlineEnterAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodExit(suppress = RuntimeException.class)
+ public static Object exit() {
+ throw new RuntimeException();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class BooleanDelegationEnterAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodExit(suppress = RuntimeException.class, inline = false)
+ public static boolean exit() {
+ throw new RuntimeException();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class VoidDelegationEnterAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodExit(suppress = RuntimeException.class, inline = false)
+ public static void exit() {
+ throw new RuntimeException();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ByteDelegationEnterAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodExit(suppress = RuntimeException.class, inline = false)
+ public static byte exit() {
+ throw new RuntimeException();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ShortDelegationEnterAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodExit(suppress = RuntimeException.class, inline = false)
+ public static short exit() {
+ throw new RuntimeException();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class CharacterDelegationEnterAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodExit(suppress = RuntimeException.class, inline = false)
+ public static byte exit() {
+ throw new RuntimeException();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IntegerDelegationEnterAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodExit(suppress = RuntimeException.class, inline = false)
+ public static int exit() {
+ throw new RuntimeException();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class LongDelegationEnterAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodExit(suppress = RuntimeException.class, inline = false)
+ public static long exit() {
+ throw new RuntimeException();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FloatDelegationEnterAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodExit(suppress = RuntimeException.class, inline = false)
+ public static float exit() {
+ throw new RuntimeException();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class DoubleDelegationEnterAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodExit(suppress = RuntimeException.class, inline = false)
+ public static double exit() {
+ throw new RuntimeException();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ReferenceDelegationEnterAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodExit(suppress = RuntimeException.class, inline = false)
+ public static Object exit() {
+ throw new RuntimeException();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class VoidInlineExitAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodEnter(suppress = RuntimeException.class)
+ public static void enter() {
+ throw new RuntimeException();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class BooleanInlineExitAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodEnter(suppress = RuntimeException.class)
+ public static boolean enter() {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodExit
+ @SuppressWarnings("all")
+ public static void exit(@Advice.Enter boolean value) {
+ if (value) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ByteInlineExitAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodEnter(suppress = RuntimeException.class)
+ public static byte enter() {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodExit
+ public static void exit(@Advice.Enter byte value) {
+ if (value != 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ShortInlineExitAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodEnter(suppress = RuntimeException.class)
+ public static short enter() {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodExit
+ public static void exit(@Advice.Enter short value) {
+ if (value != 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class CharacterInlineExitAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodEnter(suppress = RuntimeException.class)
+ public static byte enter() {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodExit
+ public static void exit(@Advice.Enter byte value) {
+ if (value != 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IntegerInlineExitAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodEnter(suppress = RuntimeException.class)
+ public static int enter() {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodExit
+ public static void exit(@Advice.Enter int value) {
+ if (value != 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class LongInlineExitAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodEnter(suppress = RuntimeException.class)
+ public static long enter() {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodExit
+ public static void exit(@Advice.Enter long value) {
+ if (value != 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FloatInlineExitAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodEnter(suppress = RuntimeException.class)
+ public static float enter() {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodExit
+ public static void exit(@Advice.Enter float value) {
+ if (value != 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class DoubleInlineExitAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodEnter(suppress = RuntimeException.class)
+ public static double enter() {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodExit
+ public static void exit(@Advice.Enter double value) {
+ if (value != 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ReferenceInlineExitAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodEnter(suppress = RuntimeException.class)
+ public static Object enter() {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodExit
+ public static void exit(@Advice.Enter Object value) {
+ if (value != null) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class VoidDelegationExitAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodEnter(suppress = RuntimeException.class, inline = false)
+ public static void enter() {
+ throw new RuntimeException();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class BooleanDelegationExitAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodEnter(suppress = RuntimeException.class, inline = false)
+ public static boolean enter() {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ @SuppressWarnings("all")
+ public static void exit(@Advice.Enter boolean value) {
+ if (value) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ByteDelegationExitAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodEnter(suppress = RuntimeException.class, inline = false)
+ public static byte enter() {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ public static void exit(@Advice.Enter byte value) {
+ if (value != 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ShortDelegationExitAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodEnter(suppress = RuntimeException.class, inline = false)
+ public static short enter() {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ public static void exit(@Advice.Enter short value) {
+ if (value != 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class CharacterDelegationExitAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodEnter(suppress = RuntimeException.class, inline = false)
+ public static byte enter() {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ public static void exit(@Advice.Enter byte value) {
+ if (value != 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IntegerDelegationExitAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodEnter(suppress = RuntimeException.class, inline = false)
+ public static int enter() {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ public static void exit(@Advice.Enter int value) {
+ if (value != 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class LongDelegationExitAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodEnter(suppress = RuntimeException.class, inline = false)
+ public static long enter() {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ public static void exit(@Advice.Enter long value) {
+ if (value != 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FloatDelegationExitAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodEnter(suppress = RuntimeException.class, inline = false)
+ public static float enter() {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ public static void exit(@Advice.Enter float value) {
+ if (value != 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class DoubleDelegationExitAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodEnter(suppress = RuntimeException.class, inline = false)
+ public static double enter() {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ public static void exit(@Advice.Enter double value) {
+ if (value != 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ReferenceDelegationExitAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodEnter(suppress = RuntimeException.class, inline = false)
+ public static Object enter() {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ public static void exit(@Advice.Enter Object value) {
+ if (value != null) {
+ throw new AssertionError();
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceTest.java
new file mode 100644
index 0000000..cf16e8c
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceTest.java
@@ -0,0 +1,3673 @@
+package net.bytebuddy.asm;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.constant.ClassConstant;
+import net.bytebuddy.implementation.bytecode.constant.TextConstant;
+import net.bytebuddy.pool.TypePool;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.MethodVisitor;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.Serializable;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.*;
+
+import static junit.framework.TestCase.fail;
+import static net.bytebuddy.matcher.ElementMatchers.*;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class AdviceTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux", BAZ = "baz";
+
+ private static final String ENTER = "enter", EXIT = "exit", INSIDE = "inside", THROWABLE = "throwable";
+
+ private static final int VALUE = 42, IGNORED = 1;
+
+ @Test
+ public void testEmptyAdviceEntryAndExit() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(EmptyMethod.class)
+ .visit(Advice.to(EmptyAdvice.class).on(named(FOO)).readerFlags(ClassReader.SKIP_DEBUG))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), nullValue(Object.class));
+ }
+
+ @Test
+ public void testEmptyAdviceEntryAndExitWithEntrySuppression() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(EmptyMethod.class)
+ .visit(Advice.to(EmptyAdviceWithEntrySuppression.class).on(named(FOO)).readerFlags(ClassReader.SKIP_DEBUG))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), nullValue(Object.class));
+ }
+
+ @Test
+ public void testEmptyAdviceEntryAndExitWithExitSuppression() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(EmptyMethod.class)
+ .visit(Advice.to(EmptyAdviceWithEntrySuppression.class).on(named(FOO)).readerFlags(ClassReader.SKIP_DEBUG))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), nullValue(Object.class));
+ }
+
+ @Test
+ public void testEmptyAdviceEntryAndExitWithSuppression() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(EmptyMethod.class)
+ .visit(Advice.to(EmptyAdviceWithSuppression.class).on(named(FOO)).readerFlags(ClassReader.SKIP_DEBUG))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), nullValue(Object.class));
+ }
+
+ @Test
+ public void testEmptyAdviceEntryAndExitWithExceptionHandling() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(EmptyMethod.class)
+ .visit(Advice.to(EmptyAdviceWithExceptionHandling.class).on(named(FOO)).readerFlags(ClassReader.SKIP_DEBUG))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), nullValue(Object.class));
+ }
+
+ @Test
+ public void testEmptyAdviceEntryAndExitWithExceptionHandlingAndEntrySuppression() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(EmptyMethod.class)
+ .visit(Advice.to(EmptyAdviceWithExceptionHandlingAndEntrySuppression.class).on(named(FOO)).readerFlags(ClassReader.SKIP_DEBUG))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), nullValue(Object.class));
+ }
+
+ @Test
+ public void testEmptyAdviceEntryAndExitWithExceptionHandlingAndExitSuppression() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(EmptyMethod.class)
+ .visit(Advice.to(EmptyAdviceWithExceptionHandlingAndExitSuppression.class).on(named(FOO)).readerFlags(ClassReader.SKIP_DEBUG))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), nullValue(Object.class));
+ }
+
+ @Test
+ public void testEmptyAdviceEntryAndExitWithExceptionHandlingAndSuppression() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(EmptyMethod.class)
+ .visit(Advice.to(EmptyAdviceWithExceptionHandlingAndSuppression.class).on(named(FOO)).readerFlags(ClassReader.SKIP_DEBUG))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), nullValue(Object.class));
+ }
+
+ @Test
+ public void testEmptyAdviceEntry() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(EmptyMethod.class)
+ .visit(Advice.to(EmptyAdviceEntry.class).on(named(FOO)).readerFlags(ClassReader.SKIP_DEBUG))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), nullValue(Object.class));
+ }
+
+ @Test
+ public void testEmptyAdviceEntryWithSuppression() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(EmptyMethod.class)
+ .visit(Advice.to(EmptyAdviceEntryWithSuppression.class).on(named(FOO)).readerFlags(ClassReader.SKIP_DEBUG))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), nullValue(Object.class));
+ }
+
+ @Test
+ public void testEmptyAdviceExit() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(EmptyMethod.class)
+ .visit(Advice.to(EmptyAdviceExit.class).on(named(FOO)).readerFlags(ClassReader.SKIP_DEBUG))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), nullValue(Object.class));
+ }
+
+ @Test
+ public void testEmptyAdviceExitAndSuppression() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(EmptyMethod.class)
+ .visit(Advice.to(EmptyAdviceExitAndSuppression.class).on(named(FOO)).readerFlags(ClassReader.SKIP_DEBUG))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), nullValue(Object.class));
+ }
+
+ @Test
+ public void testEmptyAdviceExitWithExceptionHandling() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(EmptyMethod.class)
+ .visit(Advice.to(EmptyAdviceExitWithExceptionHandling.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), nullValue(Object.class));
+ }
+
+ @Test
+ public void testEmptyAdviceExitWithExceptionHandlingAndSuppression() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(EmptyMethod.class)
+ .visit(Advice.to(EmptyAdviceExitWithExceptionHandlingAndSuppression.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), nullValue(Object.class));
+ }
+
+ @Test
+ public void testTrivialDelegation() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(EmptyDelegationAdvice.class)
+ .visit(Advice.to(EmptyDelegationAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), nullValue(Object.class));
+ }
+
+ @Test
+ public void testTrivialAdvice() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(TrivialAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testTrivialAdviceWithDelegation() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(TrivialAdviceDelegation.class)
+ .visit(Advice.to(TrivialAdviceDelegation.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testTrivialAdviceWithSuppression() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(TrivialAdviceWithSuppression.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testTrivialAdviceDistributedEnterOnly() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(TrivialAdvice.class, EmptyAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 0));
+ }
+
+ @Test
+ public void testTrivialAdviceDistributedExitOnly() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(EmptyAdvice.class, TrivialAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 0));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testTrivialAdviceWithDelegationEnterOnly() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(TrivialAdviceDelegation.class)
+ .visit(Advice.to(TrivialAdviceDelegation.class, EmptyAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 0));
+ }
+
+ @Test
+ public void testTrivialAdviceWithDelegationExitOnly() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(TrivialAdviceDelegation.class)
+ .visit(Advice.to(EmptyAdvice.class, TrivialAdviceDelegation.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 0));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testTrivialAdviceMultipleMethods() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(TrivialAdvice.class).on(isMethod()))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ assertThat(type.getDeclaredMethod(BAZ).invoke(null), is((Object) FOO));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 2));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 2));
+ }
+
+ @Test
+ public void testTrivialAdviceMultipleMethodsWithSuppression() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(TrivialAdviceWithSuppression.class).on(isMethod()))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ assertThat(type.getDeclaredMethod(BAZ).invoke(null), is((Object) FOO));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 2));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 2));
+ }
+
+ @Test
+ public void testTrivialAdviceNested() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(TrivialAdvice.class).on(named(FOO)))
+ .visit(Advice.to(TrivialAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 2));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 2));
+ }
+
+ @Test
+ public void testTrivialAdviceNestedWithSuppression() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(TrivialAdviceWithSuppression.class).on(named(FOO)))
+ .visit(Advice.to(TrivialAdviceWithSuppression.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 2));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 2));
+ }
+
+ @Test
+ public void testTrivialAdviceWithHandler() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(TrivialAdvice.class).on(named(FOO + BAZ)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO + BAZ).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testTrivialAdviceWithHandlerAndSuppression() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(TrivialAdviceWithSuppression.class).on(named(FOO + BAZ)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO + BAZ).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testAdviceOnConstructor() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(TrivialAdviceSkipException.class).on(isConstructor()))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredConstructor().newInstance(), notNullValue(Object.class));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testAdviceOnConstructorExitAdviceWithSuppression() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(TrivialAdviceSkipExceptionWithSuppression.class).on(isConstructor()))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredConstructor().newInstance(), notNullValue(Object.class));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testFrameAdviceSimpleShift() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(FrameShiftAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ }
+
+ @Test
+ public void testFrameAdviceSimpleShiftExpanded() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(FrameShiftAdvice.class).on(named(FOO)).readerFlags(ClassReader.EXPAND_FRAMES))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAdviceOnConstructorWithSuppressionNotLegal() throws Exception {
+ new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(TrivialAdvice.class).on(isConstructor()))
+ .make();
+ }
+
+ @Test
+ public void testAdviceWithImplicitArgument() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(ArgumentAdvice.class).on(named(BAR)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(BAR, String.class).invoke(type.getDeclaredConstructor().newInstance(), BAR), is((Object) BAR));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testAdviceWithImplicitArgumentDelegation() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(ArgumentAdviceDelegationImplicit.class)
+ .visit(Advice.to(ArgumentAdviceDelegationImplicit.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO, String.class).invoke(type.getDeclaredConstructor().newInstance(), BAR), is((Object) BAR));
+ }
+
+ @Test
+ public void testAdviceWithExplicitArgument() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(ArgumentAdviceExplicit.class).on(named(QUX)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(QUX, String.class, String.class).invoke(type.getDeclaredConstructor().newInstance(), FOO, BAR), is((Object) (FOO + BAR)));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testAdviceWithIncrement() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(IncrementSample.class)
+ .visit(Advice.to(IncrementAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO, int.class).invoke(type.getDeclaredConstructor().newInstance(), 0), is((Object) 2));
+ }
+
+ @Test
+ public void testAdviceWithThisReference() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(ThisReferenceAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testAdviceWithOptionalThisReferenceNonOptional() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(OptionalThisReferenceAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testAdviceWithOptionalThisReferenceOptional() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(OptionalThisReferenceAdvice.class).on(named(BAZ)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(BAZ).invoke(null), is((Object) FOO));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testAdviceWithEntranceValue() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(EntranceValueAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testAdviceWithReturnValue() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(ReturnValueAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 0));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testAdviceWithExceptionHandler() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(ExceptionHandlerAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testAdviceWithExceptionHandlerNested() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(ExceptionHandlerAdvice.class).on(named(FOO)))
+ .visit(Advice.to(ExceptionHandlerAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 2));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 2));
+ }
+
+ @Test
+ public void testAdviceNotSkipExceptionImplicit() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(TrivialAdvice.class).on(named(FOO + BAR)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ try {
+ type.getDeclaredMethod(FOO + BAR).invoke(type.getDeclaredConstructor().newInstance());
+ fail();
+ } catch (InvocationTargetException exception) {
+ assertThat(exception.getCause(), instanceOf(RuntimeException.class));
+ }
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testAdviceNotSkipExceptionImplicitNested() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(TrivialAdvice.class).on(named(FOO + BAR)))
+ .visit(Advice.to(TrivialAdvice.class).on(named(FOO + BAR)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ try {
+ type.getDeclaredMethod(FOO + BAR).invoke(type.getDeclaredConstructor().newInstance());
+ fail();
+ } catch (InvocationTargetException exception) {
+ assertThat(exception.getCause(), instanceOf(RuntimeException.class));
+ }
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 2));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 2));
+ }
+
+ @Test
+ public void testAdviceSkipExceptionImplicit() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(TrivialAdviceSkipException.class).on(named(FOO + BAR)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ try {
+ type.getDeclaredMethod(FOO + BAR).invoke(type.getDeclaredConstructor().newInstance());
+ fail();
+ } catch (InvocationTargetException exception) {
+ assertThat(exception.getCause(), instanceOf(RuntimeException.class));
+ }
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 0));
+ }
+
+ @Test
+ public void testAdviceNotSkipExceptionExplicit() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(TrivialAdvice.class).on(named(BAR + BAZ)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ try {
+ type.getDeclaredMethod(BAR + BAZ).invoke(type.getDeclaredConstructor().newInstance());
+ fail();
+ } catch (InvocationTargetException exception) {
+ assertThat(exception.getCause(), instanceOf(NullPointerException.class));
+ }
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testAdviceNotSkipExceptionExplicitNested() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(TrivialAdvice.class).on(named(BAR + BAZ)))
+ .visit(Advice.to(TrivialAdvice.class).on(named(BAR + BAZ)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ try {
+ type.getDeclaredMethod(BAR + BAZ).invoke(type.getDeclaredConstructor().newInstance());
+ fail();
+ } catch (InvocationTargetException exception) {
+ assertThat(exception.getCause(), instanceOf(NullPointerException.class));
+ }
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 2));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 2));
+ }
+
+ @Test
+ public void testAdviceSkipExceptionExplicit() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(TrivialAdviceSkipException.class).on(named(BAR + BAZ)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ try {
+ type.getDeclaredMethod(BAR + BAZ).invoke(type.getDeclaredConstructor().newInstance());
+ fail();
+ } catch (InvocationTargetException exception) {
+ assertThat(exception.getCause(), instanceOf(NullPointerException.class));
+ }
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 0));
+ }
+
+ @Test
+ public void testAdviceSkipExceptionDoesNotSkipNonException() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(TrivialAdviceSkipException.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testObsoleteReturnValue() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(ObsoleteReturnValueAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 0));
+ }
+
+ @Test
+ public void testUnusedReturnValue() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(UnusedReturnValueAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testVariableMappingAdviceLarger() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(AdviceWithVariableValues.class).on(named(BAR)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(BAR, String.class).invoke(type.getDeclaredConstructor().newInstance(), FOO + BAR + QUX + BAZ), is((Object) (FOO + BAR + QUX + BAZ)));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testVariableMappingInstrumentedLarger() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(AdviceWithVariableValues.class).on(named(QUX + BAZ)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(QUX + BAZ).invoke(type.getDeclaredConstructor().newInstance()), is((Object) (FOO + BAR + QUX + BAZ)));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testExceptionWhenNotThrown() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(ThrowableAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) (FOO)));
+ assertThat(type.getDeclaredField(THROWABLE).get(null), nullValue(Object.class));
+ }
+
+ @Test
+ public void testExceptionWhenThrown() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(ThrowableAdvice.class).on(named(FOO + BAR)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ try {
+ type.getDeclaredMethod(FOO + BAR).invoke(type.getDeclaredConstructor().newInstance());
+ fail();
+ } catch (InvocationTargetException exception) {
+ assertThat(exception.getCause(), instanceOf(RuntimeException.class));
+ }
+ assertThat(type.getDeclaredField(THROWABLE).get(null), instanceOf(RuntimeException.class));
+ }
+
+ @Test
+ public void testAdviceThrowOnEnter() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(TracableSample.class)
+ .visit(Advice.to(ThrowOnEnter.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ try {
+ type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance());
+ fail();
+ } catch (InvocationTargetException exception) {
+ assertThat(exception.getCause(), instanceOf(RuntimeException.class));
+ }
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(INSIDE).get(null), is((Object) 0));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 0));
+ }
+
+ @Test
+ public void testAdviceThrowOnExit() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(TracableSample.class)
+ .visit(Advice.to(ThrowOnExit.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ try {
+ type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance());
+ fail();
+ } catch (InvocationTargetException exception) {
+ assertThat(exception.getCause(), instanceOf(RuntimeException.class));
+ }
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(INSIDE).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testAdviceThrowSuppressed() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(TracableSample.class)
+ .visit(Advice.to(ThrowSuppressed.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance());
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(INSIDE).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testAdviceThrowNotSuppressedOnEnter() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(TracableSample.class)
+ .visit(Advice.to(ThrowNotSuppressedOnEnter.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ try {
+ type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance());
+ fail();
+ } catch (InvocationTargetException exception) {
+ assertThat(exception.getCause(), instanceOf(Exception.class));
+ }
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(INSIDE).get(null), is((Object) 0));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 0));
+ }
+
+ @Test
+ public void testAdviceThrowNotSuppressedOnExit() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(TracableSample.class)
+ .visit(Advice.to(ThrowNotSuppressedOnExit.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ try {
+ type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance());
+ fail();
+ } catch (InvocationTargetException exception) {
+ assertThat(exception.getCause(), instanceOf(Exception.class));
+ }
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(INSIDE).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testThisValueSubstitution() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Box.class)
+ .visit(Advice.to(ThisSubstitutionAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor(String.class).newInstance(FOO)), is((Object) BAR));
+ }
+
+ @Test
+ public void testThisValueSubstitutionOptional() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Box.class)
+ .visit(Advice.to(ThisOptionalSubstitutionAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor(String.class).newInstance(FOO)), is((Object) BAR));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIllegalThisValueSubstitution() throws Exception {
+ new ByteBuddy()
+ .redefine(Box.class)
+ .visit(Advice.to(IllegalThisSubstitutionAdvice.class).on(named(FOO)))
+ .make();
+ }
+
+ @Test
+ public void testParameterValueSubstitution() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Box.class)
+ .visit(Advice.to(ParameterSubstitutionAdvice.class).on(named(BAR)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(BAR, String.class).invoke(null, FOO), is((Object) BAR));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIllegalParameterValueSubstitution() throws Exception {
+ new ByteBuddy()
+ .redefine(Box.class)
+ .visit(Advice.to(IllegalParameterSubstitutionAdvice.class).on(named(BAR)))
+ .make();
+ }
+
+ @Test
+ public void testReturnValueSubstitution() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(ReturnSubstitutionAdvice.class).on(named(BAR)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(BAR, String.class).invoke(type.getDeclaredConstructor().newInstance(), FOO), is((Object) BAR));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIllegalReturnValueSubstitution() throws Exception {
+ new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(IllegalReturnSubstitutionAdvice.class).on(named(FOO)))
+ .make();
+ }
+
+ @Test
+ public void testEnterValueSubstitution() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(EnterSubstitutionAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIllegalEnterValueSubstitution() throws Exception {
+ new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(IllegalEnterSubstitutionAdvice.class).on(named(BAR)))
+ .make();
+ }
+
+ @Test
+ public void testFieldAdviceImplicit() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(FieldSample.class)
+ .visit(Advice.to(FieldAdviceImplicit.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testFieldAdviceExplicit() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(FieldSample.class)
+ .visit(Advice.to(FieldAdviceExplicit.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testAllArgumentsStackSizeAdvice() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(BoxedArgumentsStackSizeAdvice.class).on(named(BAR)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(BAR, String.class).invoke(type.getDeclaredConstructor().newInstance(), FOO), is((Object) FOO));
+ }
+
+ @Test
+ public void testAllArgumentsObjectTypeAdvice() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(BoxedArgumentsObjectTypeAdvice.class).on(named(BAR)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(BAR, String.class).invoke(type.getDeclaredConstructor().newInstance(), FOO), is((Object) FOO));
+ }
+
+ @Test
+ public void testAllArgumentsStackSizeEmptyAdvice() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(BoxedArgumentsStackSizeAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ }
+
+ @Test
+ public void testOriginAdvice() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(OriginAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testOriginCustomAdvice() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(OriginCustomAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testOriginMethodStackSizeAdvice() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(OriginMethodStackSizeAdvice.class).on(named(BAR)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(BAR, String.class).invoke(type.getDeclaredConstructor().newInstance(), FOO), is((Object) FOO));
+ }
+
+ @Test
+ public void testOriginMethodStackSizeEmptyAdvice() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(OriginMethodStackSizeAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ }
+
+ @Test
+ public void testOriginConstructorStackSizeAdvice() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(OriginConstructorStackSizeAdvice.class).on(isConstructor()))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ }
+
+ @Test
+ public void testOriginMethodAdvice() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(OriginMethodAdvice.class).on(named(BAR)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(BAR, String.class).invoke(type.getDeclaredConstructor().newInstance(), FOO), is((Object) FOO));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testOriginMethodNonAssignableAdvice() throws Exception {
+ new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(OriginMethodAdvice.class).on(isConstructor()))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testOriginConstructorNonAssignableAdvice() throws Exception {
+ new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(OriginConstructorAdvice.class).on(named(BAR)))
+ .make();
+ }
+
+ @Test
+ public void testOriginConstructorAdvice() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(OriginConstructorAdvice.class).on(isConstructor()))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testExceptionSuppressionAdvice() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(ExceptionSuppressionAdvice.class).on(named(FOO + BAR)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO + BAR).invoke(type.getDeclaredConstructor().newInstance()), nullValue(Object.class));
+ }
+
+ @Test
+ public void testExceptionTypeAdvice() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(ExceptionTypeAdvice.class)
+ .visit(Advice.to(ExceptionTypeAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ try {
+ type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance());
+ fail();
+ } catch (InvocationTargetException exception) {
+ assertThat(exception.getCause(), instanceOf(IllegalStateException.class));
+ }
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testExceptionNotCatchedAdvice() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(ExceptionNotCatchedAdvice.class)
+ .visit(Advice.to(ExceptionNotCatchedAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ try {
+ type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance());
+ fail();
+ } catch (InvocationTargetException exception) {
+ assertThat(exception.getCause(), instanceOf(Exception.class));
+ }
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 0));
+ }
+
+ @Test
+ public void testExceptionCatchedAdvice() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(ExceptionCatchedAdvice.class)
+ .visit(Advice.to(ExceptionCatchedAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ try {
+ type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance());
+ fail();
+ } catch (InvocationTargetException exception) {
+ assertThat(exception.getCause(), instanceOf(RuntimeException.class));
+ }
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testExceptionCatchedWithExchangeAdvice() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(ExceptionCatchedWithExchangeAdvice.class)
+ .visit(Advice.to(ExceptionCatchedWithExchangeAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ try {
+ type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance());
+ fail();
+ } catch (InvocationTargetException exception) {
+ assertThat(exception.getCause(), instanceOf(IOException.class));
+ }
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testNonAssignableCasting() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(NonAssignableReturnTypeAdvice.class)
+ .visit(Advice.to(NonAssignableReturnTypeAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ try {
+ type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance());
+ fail();
+ } catch (InvocationTargetException exception) {
+ assertThat(exception.getCause(), instanceOf(ClassCastException.class));
+ }
+ }
+
+ @Test
+ public void testTrivialAssignableCasting() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(TrivialReturnTypeAdvice.class)
+ .visit(Advice.to(TrivialReturnTypeAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) BAR));
+ }
+
+ @Test
+ public void testPrimitiveNonAssignableCasting() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(NonAssignablePrimitiveReturnTypeAdvice.class)
+ .visit(Advice.to(NonAssignablePrimitiveReturnTypeAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ try {
+ type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance());
+ fail();
+ } catch (InvocationTargetException exception) {
+ assertThat(exception.getCause(), instanceOf(ClassCastException.class));
+ }
+ }
+
+ @Test
+ public void testUserTypeValue() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.withCustomMapping().bind(Custom.class, Object.class).to(CustomAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ }
+
+ @Test
+ public void testUserEnumValue() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.withCustomMapping().bind(Custom.class, RetentionPolicy.CLASS).to(CustomAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ }
+
+ @Test
+ public void testUserSerializableTypeValue() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.withCustomMapping()
+ .bindSerialized(Custom.class, (Serializable) Collections.singletonMap(FOO, BAR))
+ .to(CustomSerializableAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ }
+
+ @Test
+ public void testUserStackManipulation() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.withCustomMapping().bind(Custom.class, ClassConstant.of(TypeDescription.OBJECT), Object.class).to(CustomAdvice.class).on(named(BAR)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(BAR, String.class).invoke(type.getDeclaredConstructor().newInstance(), BAR), is((Object) BAR));
+ }
+
+ @Test
+ public void testUserOffsetMapping() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.withCustomMapping().bind(Custom.class, new Advice.OffsetMapping.ForStackManipulation(ClassConstant.of(TypeDescription.OBJECT),
+ TypeDescription.STRING.asGenericType(),
+ TypeDescription.STRING.asGenericType(),
+ Assigner.Typing.STATIC)).to(CustomAdvice.class).on(named(BAR)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(BAR, String.class).invoke(type.getDeclaredConstructor().newInstance(), BAR), is((Object) BAR));
+ }
+
+ @Test
+ public void testLineNumberPrepend() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(LineNumberAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ }
+
+ @Test
+ public void testLineNumberNoPrepend() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(NoLineNumberAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ }
+
+ @Test
+ public void testLineNumberPrependDelegation() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(LineNumberDelegatingAdvice.class)
+ .visit(Advice.to(LineNumberDelegatingAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ }
+
+ @Test
+ public void testLineNumberNoPrependDelegation() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(NoLineNumberDelegatingAdvice.class)
+ .visit(Advice.to(NoLineNumberDelegatingAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ }
+
+ @Test
+ public void testInstanceOfSkip() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(InstanceOfSkip.class)
+ .visit(Advice.to(InstanceOfSkip.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), nullValue(Object.class));
+ }
+
+ @Test
+ public void testInstanceOfNoSkip() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(InstanceOfNoSkip.class)
+ .visit(Advice.to(InstanceOfNoSkip.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ }
+
+ @Test
+ public void testExceptionPrinting() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(ExceptionWriterTest.class).withExceptionPrinting().on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ PrintStream printStream = mock(PrintStream.class);
+ PrintStream err = System.err;
+ synchronized (System.err) {
+ System.setErr(printStream);
+ try {
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ } finally {
+ System.setErr(err);
+ }
+ }
+ verify(printStream, times(2)).println(Mockito.any(RuntimeException.class));
+ }
+
+ @Test
+ public void testOptionalArgument() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(OptionalArgumentAdvice.class)
+ .visit(Advice.to(OptionalArgumentAdvice.class).on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testInstanceOfPrimitiveSkip() throws Exception {
+ Advice.to(InstanceOfIllegalPrimitiveSkip.class);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testInstanceOfPrimitiveInstanceOfSkip() throws Exception {
+ Advice.to(InstanceOfIllegalPrimitiveInstanceOfSkip.class);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testDefaultValuePrimitiveSkip() throws Exception {
+ Advice.to(DefaultValueIllegalPrimitiveSkip.class);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonDefaultValuePrimitiveSkip() throws Exception {
+ Advice.to(NonDefaultValueIllegalPrimitiveSkip.class);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUserSerializableTypeValueNonAssignable() throws Exception {
+ new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.withCustomMapping().bind(Custom.class, Collections.singletonList(FOO)).to(CustomSerializableAdvice.class).on(named(FOO)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIllegalUserValue() throws Exception {
+ new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.withCustomMapping().bind(Custom.class, new Object()).to(CustomAdvice.class).on(named(FOO)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonAssignableStringValue() throws Exception {
+ new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.withCustomMapping().bind(Custom.class, FOO).to(CustomPrimitiveAdvice.class).on(named(FOO)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonAssignableTypeValue() throws Exception {
+ new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.withCustomMapping().bind(Custom.class, Object.class).to(CustomPrimitiveAdvice.class).on(named(FOO)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonAssignableTypeDescriptionValue() throws Exception {
+ new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.withCustomMapping().bind(Custom.class, TypeDescription.OBJECT).to(CustomPrimitiveAdvice.class).on(named(FOO)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonAssignableSerializableValue() throws Exception {
+ new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.withCustomMapping().bind(Custom.class, new ArrayList<String>()).to(CustomPrimitiveAdvice.class).on(named(FOO)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testInvisibleField() throws Exception {
+ new ByteBuddy()
+ .redefine(SampleExtension.class)
+ .visit(Advice.withCustomMapping().bind(Custom.class, Sample.class.getDeclaredField("object")).to(CustomAdvice.class).on(named(FOO)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonRelatedField() throws Exception {
+ new ByteBuddy()
+ .redefine(TracableSample.class)
+ .visit(Advice.withCustomMapping().bind(Custom.class, Sample.class.getDeclaredField("object")).to(CustomAdvice.class).on(named(FOO)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonAssignableField() throws Exception {
+ new ByteBuddy()
+ .redefine(SampleExtension.class)
+ .visit(Advice.withCustomMapping().bind(Custom.class, SampleExtension.class.getDeclaredField(FOO)).to(CustomPrimitiveAdvice.class).on(named(FOO)))
+ .make();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testMethodNegativeIndex() throws Exception {
+ Advice.withCustomMapping().bind(Custom.class, Sample.class.getDeclaredMethod(BAR, String.class), -1);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testMethodOverflowIndex() throws Exception {
+ Advice.withCustomMapping().bind(Custom.class, Sample.class.getDeclaredMethod(BAR, String.class), 1);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testConstrcutorNegativeIndex() throws Exception {
+ Advice.withCustomMapping().bind(Custom.class, Sample.class.getDeclaredConstructor(), -1);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testConstructorOverflowIndex() throws Exception {
+ Advice.withCustomMapping().bind(Custom.class, Sample.class.getDeclaredConstructor(), 0);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonAssignableParameter() throws Exception {
+ new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.withCustomMapping().bind(Custom.class, Sample.class.getDeclaredMethod(BAR, String.class), 0).to(CustomPrimitiveAdvice.class).on(named(FOO)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUnrelatedMethodParameter() throws Exception {
+ new ByteBuddy()
+ .redefine(SampleExtension.class)
+ .visit(Advice.withCustomMapping().bind(Custom.class, Sample.class.getDeclaredMethod(BAR, String.class), 0).to(CustomPrimitiveAdvice.class).on(named(FOO)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAdviceWithThisReferenceOnConstructor() throws Exception {
+ new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(ThisReferenceAdvice.class).on(isConstructor()))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAdviceWithFieldOnConstructor() throws Exception {
+ new ByteBuddy()
+ .redefine(FieldSample.class)
+ .visit(Advice.to(FieldAdviceExplicit.class).on(isConstructor()))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAdviceWithExceptionCatchOnConstructor() throws Exception {
+ new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(EmptyAdviceExitWithExceptionHandling.class).on(isConstructor()))
+ .make();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testAdviceWithoutAnnotations() throws Exception {
+ Advice.to(Object.class);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testDuplicateAdvice() throws Exception {
+ Advice.to(DuplicateAdvice.class);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIOExceptionOnRead() throws Exception {
+ ClassFileLocator classFileLocator = mock(ClassFileLocator.class);
+ when(classFileLocator.locate(TrivialAdvice.class.getName())).thenThrow(new IOException());
+ Advice.to(TrivialAdvice.class, classFileLocator);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonStaticAdvice() throws Exception {
+ Advice.to(NonStaticAdvice.class);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAmbiguousAdvice() throws Exception {
+ Advice.to(AmbiguousAdvice.class);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAmbiguousAdviceDelegation() throws Exception {
+ Advice.to(AmbiguousAdviceDelegation.class);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testCannotBindEnterToEnter() throws Exception {
+ Advice.to(EnterToEnterAdvice.class);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testCannotBindEnterToReturn() throws Exception {
+ Advice.to(EnterToReturnAdvice.class);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAdviceWithNonExistentArgument() throws Exception {
+ new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(IllegalArgumentAdvice.class).on(named(FOO)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAdviceWithNonAssignableParameterImplicit() throws Exception {
+ new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(IllegalArgumentAdvice.class).on(named(BAR)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAdviceWithNonAssignableParameter() throws Exception {
+ new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(IllegalArgumentWritableAdvice.class).on(named(BAR)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAdviceWithNonEqualParameter() throws Exception {
+ new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(IllegalArgumentReadOnlyAdvice.class).on(named(BAR)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAdviceThisReferenceNonExistent() throws Exception {
+ new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(ThisReferenceAdvice.class).on(named(BAZ)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAdviceWithNonAssignableThisReference() throws Exception {
+ new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(IllegalThisReferenceAdvice.class).on(named(FOO)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAdviceWithNonEqualThisReference() throws Exception {
+ new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(IllegalThisReferenceWritableAdvice.class).on(named(FOO)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAdviceWithNonAssignableReturnValue() throws Exception {
+ new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(NonAssignableReturnAdvice.class).on(named(FOO + QUX)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAdviceWithNonAssignableReturnValueWritable() throws Exception {
+ new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(NonEqualReturnWritableAdvice.class).on(named(FOO + QUX)))
+ .make();
+ }
+
+ @Test
+ public void testAdviceAbstractMethodIsSkipped() throws Exception {
+ Advice.Dispatcher.Resolved.ForMethodEnter methodEnter = mock(Advice.Dispatcher.Resolved.ForMethodEnter.class);
+ Advice.Dispatcher.Resolved.ForMethodExit methodExit = mock(Advice.Dispatcher.Resolved.ForMethodExit.class);
+ MethodDescription.InDefinedShape methodDescription = mock(MethodDescription.InDefinedShape.class);
+ when(methodDescription.isAbstract()).thenReturn(true);
+ MethodVisitor methodVisitor = mock(MethodVisitor.class);
+ assertThat(new Advice(methodEnter, methodExit).wrap(mock(TypeDescription.class),
+ methodDescription,
+ methodVisitor,
+ mock(Implementation.Context.class),
+ mock(TypePool.class),
+ IGNORED,
+ IGNORED), sameInstance(methodVisitor));
+ }
+
+ @Test
+ public void testAdviceNativeMethodIsSkipped() throws Exception {
+ Advice.Dispatcher.Resolved.ForMethodEnter methodEnter = mock(Advice.Dispatcher.Resolved.ForMethodEnter.class);
+ Advice.Dispatcher.Resolved.ForMethodExit methodExit = mock(Advice.Dispatcher.Resolved.ForMethodExit.class);
+ MethodDescription.InDefinedShape methodDescription = mock(MethodDescription.InDefinedShape.class);
+ when(methodDescription.isNative()).thenReturn(true);
+ MethodVisitor methodVisitor = mock(MethodVisitor.class);
+ assertThat(new Advice(methodEnter, methodExit).wrap(mock(TypeDescription.class),
+ methodDescription,
+ methodVisitor,
+ mock(Implementation.Context.class),
+ mock(TypePool.class),
+ IGNORED,
+ IGNORED), sameInstance(methodVisitor));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAdviceWithNonAssignableEnterValue() throws Exception {
+ new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(NonAssignableEnterAdvice.class).on(named(FOO)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAdviceWithNonAssignableEnterValueWritable() throws Exception {
+ new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(NonAssignableEnterWriteAdvice.class).on(named(FOO)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIllegalThrowableRequest() throws Exception {
+ Advice.to(IllegalThrowableRequestAdvice.class);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIllegalThrowableType() throws Exception {
+ Advice.to(IllegalThrowableTypeAdvice.class);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testFieldIllegalExplicit() throws Exception {
+ new ByteBuddy()
+ .redefine(FieldSample.class)
+ .visit(Advice.to(FieldAdviceIllegalExplicit.class).on(named(FOO)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testFieldNonExistent() throws Exception {
+ new ByteBuddy()
+ .redefine(FieldSample.class)
+ .visit(Advice.to(FieldAdviceNonExistent.class).on(named(FOO)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testFieldNonAssignable() throws Exception {
+ new ByteBuddy()
+ .redefine(FieldSample.class)
+ .visit(Advice.to(FieldAdviceNonAssignable.class).on(named(FOO)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testFieldWrite() throws Exception {
+ new ByteBuddy()
+ .redefine(FieldSample.class)
+ .visit(Advice.to(FieldAdviceWrite.class).on(named(FOO)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIllegalOriginType() throws Exception {
+ Advice.to(IllegalOriginType.class);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIllegalOriginPattern() throws Exception {
+ Advice.to(IllegalOriginPattern.class);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIllegalOriginPatternEnd() throws Exception {
+ Advice.to(IllegalOriginPatternEnd.class);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testCannotWriteOrigin() throws Exception {
+ new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(IllegalOriginWriteAdvice.class).on(named(FOO)))
+ .make();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testDuplicateRegistration() throws Exception {
+ Advice.withCustomMapping().bind(Custom.class, FOO).bind(Custom.class, FOO);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNotAnnotationType() throws Exception {
+ Advice.withCustomMapping().bind(Annotation.class, (Serializable) null);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testInlineAdviceCannotWriteParameter() throws Exception {
+ Advice.to(IllegalArgumentWritableInlineAdvice.class);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testInlineAdviceCannotWriteThis() throws Exception {
+ Advice.to(IllegalThisWritableInlineAdvice.class);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testInlineAdviceCannotWriteField() throws Exception {
+ Advice.to(IllegalFieldWritableInlineAdvice.class);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testInlineAdviceCannotWriteThrow() throws Exception {
+ Advice.to(IllegalThrowWritableInlineAdvice.class);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testInlineAdviceCannotWriteReturn() throws Exception {
+ Advice.to(IllegalThrowWritableInlineAdvice.class);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testInvisibleDelegationAdvice() throws Exception {
+ new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(NonVisibleAdvice.class).on(named(FOO)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonResolvedAdvice() throws Exception {
+ Advice.to(new TypeDescription.ForLoadedType(TrivialAdvice.class));
+ }
+
+ @Test
+ public void testCannotInstantiateSuppressionMarker() throws Exception {
+ Class<?> type = Class.forName(Advice.class.getName() + "$NoExceptionHandler");
+ assertThat(Modifier.isPrivate(type.getModifiers()), is(true));
+ try {
+ Constructor<?> constructor = type.getDeclaredConstructor();
+ assertThat(Modifier.isPrivate(constructor.getModifiers()), is(true));
+ constructor.setAccessible(true);
+ constructor.newInstance();
+ fail();
+ } catch (InvocationTargetException exception) {
+ assertThat(exception.getCause(), instanceOf(UnsupportedOperationException.class));
+ }
+ }
+
+ @Test
+ public void testCannotInstantiateSkipDefaultValueMarker() throws Exception {
+ try {
+ Constructor<?> constructor = Advice.OnDefaultValue.class.getDeclaredConstructor();
+ assertThat(Modifier.isPrivate(constructor.getModifiers()), is(true));
+ constructor.setAccessible(true);
+ constructor.newInstance();
+ fail();
+ } catch (InvocationTargetException exception) {
+ assertThat(exception.getCause(), instanceOf(UnsupportedOperationException.class));
+ }
+ }
+
+ @Test
+ public void testCannotInstantiateSkipNonDefaultValueMarker() throws Exception {
+ try {
+ Constructor<?> constructor = Advice.OnNonDefaultValue.class.getDeclaredConstructor();
+ assertThat(Modifier.isPrivate(constructor.getModifiers()), is(true));
+ constructor.setAccessible(true);
+ constructor.newInstance();
+ fail();
+ } catch (InvocationTargetException exception) {
+ assertThat(exception.getCause(), instanceOf(UnsupportedOperationException.class));
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testDistributedAdviceNoEnterAdvice() throws Exception {
+ Advice.to(Object.class, EmptyAdvice.class);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testDistributedAdviceNoExitAdvice() throws Exception {
+ Advice.to(EmptyAdvice.class, Object.class);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIllegalBoxedReturn() throws Exception {
+ Advice.to(IllegalBoxedReturnType.class);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testBoxedArgumentsWriteDelegateEntry() throws Exception {
+ Advice.to(BoxedArgumentsWriteDelegateEntry.class);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testBoxedArgumentsWriteDelegateExit() throws Exception {
+ Advice.to(BoxedArgumentsWriteDelegateExit.class);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testBoxedArgumentsCannotWrite() throws Exception {
+ new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(Advice.to(BoxedArgumentsCannotWrite.class).on(named(FOO)))
+ .make();
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(Advice.class).apply();
+ ObjectPropertyAssertion.of(Advice.WithCustomMapping.class).apply();
+ ObjectPropertyAssertion.of(Advice.MethodSizeHandler.NoOp.class).apply();
+ ObjectPropertyAssertion.of(Advice.StackMapFrameHandler.NoOp.class).apply();
+ ObjectPropertyAssertion.of(Advice.StackMapFrameHandler.Default.TranslationMode.class).apply();
+ ObjectPropertyAssertion.of(Advice.Dispatcher.SuppressionHandler.NoOp.class).apply();
+ ObjectPropertyAssertion.of(Advice.Dispatcher.SuppressionHandler.Suppressing.class).apply();
+ ObjectPropertyAssertion.of(Advice.Dispatcher.Inactive.class).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.Context.ForMethodEntry.class).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.Context.ForMethodExit.class).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.Target.ForVariable.ReadOnly.class).refine(new ObjectPropertyAssertion.Refinement<ParameterDescription>() {
+ @Override
+ public void apply(ParameterDescription mock) {
+ when(mock.getType()).thenReturn(mock(TypeDescription.Generic.class));
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.Target.ForVariable.ReadWrite.class).refine(new ObjectPropertyAssertion.Refinement<ParameterDescription>() {
+ @Override
+ public void apply(ParameterDescription mock) {
+ when(mock.getType()).thenReturn(mock(TypeDescription.Generic.class));
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.Target.ForArray.ReadOnly.class).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.Target.ForArray.ReadWrite.class).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.Target.ForField.ReadOnly.class).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.Target.ForField.ReadWrite.class).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.Target.ForDefaultValue.ReadOnly.class).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.Target.ForDefaultValue.ReadWrite.class).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.Target.ForStackManipulation.class).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.ForArgument.Unresolved.class).refine(new ObjectPropertyAssertion.Refinement<ParameterDescription>() {
+ @Override
+ public void apply(ParameterDescription mock) {
+ when(mock.getType()).thenReturn(mock(TypeDescription.Generic.class));
+ }
+ }).refine(new ObjectPropertyAssertion.Refinement<Advice.Argument>() {
+ @Override
+ public void apply(Advice.Argument mock) {
+ when(mock.value()).thenReturn(new Random().nextInt());
+ when(mock.typing()).thenReturn(Assigner.Typing.DYNAMIC);
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.ForArgument.Resolved.class).refine(new ObjectPropertyAssertion.Refinement<ParameterDescription>() {
+ @Override
+ public void apply(ParameterDescription mock) {
+ when(mock.getType()).thenReturn(mock(TypeDescription.Generic.class));
+ }
+ }).refine(new ObjectPropertyAssertion.Refinement<Advice.Argument>() {
+ @Override
+ public void apply(Advice.Argument mock) {
+ when(mock.value()).thenReturn(new Random().nextInt());
+ when(mock.typing()).thenReturn(Assigner.Typing.DYNAMIC);
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.ForArgument.Factory.class).apply();
+ final Iterator<Boolean> allArguments = Arrays.<Boolean>asList(true, false).iterator();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.ForAllArguments.class).refine(new ObjectPropertyAssertion.Refinement<Advice.AllArguments>() {
+ @Override
+ public void apply(Advice.AllArguments mock) {
+ when(mock.readOnly()).thenReturn(allArguments.next());
+ when(mock.typing()).thenReturn(Assigner.Typing.DYNAMIC);
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.ForAllArguments.Factory.class).apply();
+ final Iterator<Boolean> enter = Arrays.<Boolean>asList(true, false).iterator();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.ForEnterValue.class).refine(new ObjectPropertyAssertion.Refinement<Advice.Enter>() {
+ @Override
+ public void apply(Advice.Enter mock) {
+ when(mock.readOnly()).thenReturn(enter.next());
+ when(mock.typing()).thenReturn(Assigner.Typing.DYNAMIC);
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.ForEnterValue.Factory.class).apply();
+// ObjectPropertyAssertion.of(Advice.OffsetMapping.ForField.Unresolved.WithImplicitType.class).apply();
+// ObjectPropertyAssertion.of(Advice.OffsetMapping.ForField.Unresolved.WithExplicitType.class).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.ForField.Resolved.class).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.ForField.Factory.class).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.ForInstrumentedMethod.class).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.ForInstrumentedType.class).apply();
+ final Iterator<Boolean> returned = Arrays.<Boolean>asList(true, false).iterator();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.ForReturnValue.class).refine(new ObjectPropertyAssertion.Refinement<Advice.Return>() {
+ @Override
+ public void apply(Advice.Return mock) {
+ when(mock.readOnly()).thenReturn(returned.next());
+ when(mock.typing()).thenReturn(Assigner.Typing.DYNAMIC);
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.ForStubValue.class).apply();
+ final Iterator<Boolean> self = Arrays.<Boolean>asList(true, false).iterator();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.ForThisReference.class).refine(new ObjectPropertyAssertion.Refinement<Advice.This>() {
+ @Override
+ public void apply(Advice.This mock) {
+ when(mock.readOnly()).thenReturn(self.next());
+ when(mock.typing()).thenReturn(Assigner.Typing.DYNAMIC);
+ }
+ }).apply();
+ final Iterator<Boolean> thrown = Arrays.<Boolean>asList(true, false).iterator();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.ForThrowable.class).refine(new ObjectPropertyAssertion.Refinement<Advice.Thrown>() {
+ @Override
+ public void apply(Advice.Thrown mock) {
+ when(mock.readOnly()).thenReturn(thrown.next());
+ when(mock.typing()).thenReturn(Assigner.Typing.DYNAMIC);
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.ForThrowable.Factory.class).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.ForUnusedValue.class).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.ForUnusedValue.Factory.class).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.ForOrigin.class).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.ForOrigin.Factory.class).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.ForOrigin.Renderer.ForConstantValue.class).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.ForOrigin.Renderer.ForDescriptor.class).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.ForOrigin.Renderer.ForMethodName.class).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.ForOrigin.Renderer.ForStringRepresentation.class).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.ForOrigin.Renderer.ForTypeName.class).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.ForOrigin.Renderer.ForReturnTypeName.class).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.ForOrigin.Renderer.ForJavaSignature.class).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.ForStackManipulation.class).apply();
+ final Iterator<Class<?>> types = Arrays.<Class<?>>asList(Object.class, String.class, Integer.class, Long.class,
+ Character.class, Float.class, Double.class, Short.class, Void.class, Byte.class, int.class, double.class).iterator();
+// ObjectPropertyAssertion.of(Advice.OffsetMapping.ForStackManipulation.Factory.class).create(new ObjectPropertyAssertion.Creator<Class<?>>() {
+// @Override
+// public Class<?> create() {
+// return types.next();
+// }
+// }).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.ForStackManipulation.OfAnnotationProperty.class).create(new ObjectPropertyAssertion.Creator<Class<?>>() {
+ @Override
+ public Class<?> create() {
+ return types.next();
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.ForStackManipulation.OfDefaultValue.class).create(new ObjectPropertyAssertion.Creator<Class<?>>() {
+ @Override
+ public Class<?> create() {
+ return types.next();
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.ForSerializedValue.class).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.ForSerializedValue.Factory.class).create(new ObjectPropertyAssertion.Creator<Class<?>>() {
+ @Override
+ public Class<?> create() {
+ return types.next();
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.Factory.Simple.class).create(new ObjectPropertyAssertion.Creator<Class<?>>() {
+ @Override
+ public Class<?> create() {
+ return types.next();
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(Advice.OffsetMapping.Factory.Illegal.class).create(new ObjectPropertyAssertion.Creator<Class<?>>() {
+ @Override
+ public Class<?> create() {
+ return types.next();
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(Advice.Dispatcher.Inlining.class).apply();
+ ObjectPropertyAssertion.of(Advice.Dispatcher.Resolved.ForMethodEnter.SkipDispatcher.ForValue.class).apply();
+ ObjectPropertyAssertion.of(Advice.Dispatcher.Resolved.ForMethodEnter.SkipDispatcher.ForType.class).apply();
+ ObjectPropertyAssertion.of(Advice.Dispatcher.Resolved.ForMethodEnter.SkipDispatcher.Disabled.class).apply();
+ ObjectPropertyAssertion.of(Advice.Appender.class).apply();
+ }
+
+ @SuppressWarnings("unused")
+ public static class Sample {
+
+ private Object object;
+
+ public static int enter, exit;
+
+ public static Throwable throwable;
+
+ public String foo() {
+ return FOO;
+ }
+
+ public String foobar() {
+ throw new RuntimeException();
+ }
+
+ public String bar(String argument) {
+ return argument;
+ }
+
+ public String qux(String first, String second) {
+ return first + second;
+ }
+
+ public static String baz() {
+ return FOO;
+ }
+
+ public String quxbaz() {
+ String foo = FOO, bar = BAR, qux = QUX, baz = BAZ;
+ return foo + bar + qux + baz;
+ }
+
+ public void fooqux() {
+ /* do nothing */
+ }
+
+ public void barbaz() {
+ object.getClass(); // implicit null pointer
+ }
+
+ public String foobaz() {
+ try {
+ throw new Exception();
+ } catch (Exception ignored) {
+ return FOO;
+ }
+ }
+ }
+
+ public static class SampleExtension extends Sample {
+
+ public Object foo;
+
+ @Override
+ public String foo() {
+ return null;
+ }
+ }
+
+ public static class TracableSample {
+
+ public static int enter, exit, inside;
+
+ public void foo() {
+ inside++;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class EmptyDelegationAdvice {
+
+ public void foo() {
+ /* empty */
+ }
+
+ @Advice.OnMethodEnter
+ private static void enter() {
+ /* empty */
+ }
+
+ @Advice.OnMethodExit
+ private static void exit() {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class TrivialAdvice {
+
+ @Advice.OnMethodEnter
+ private static void enter() {
+ Sample.enter++;
+ }
+
+ @Advice.OnMethodExit(onThrowable = Exception.class)
+ private static void exit() {
+ Sample.exit++;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class TrivialAdviceWithSuppression {
+
+ @Advice.OnMethodEnter(suppress = Exception.class)
+ private static void enter() {
+ Sample.enter++;
+ }
+
+ @Advice.OnMethodExit(suppress = Exception.class)
+ private static void exit() {
+ Sample.exit++;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class TrivialAdviceDelegation {
+
+ public static int enter, exit;
+
+ public String foo() {
+ return FOO;
+ }
+
+ @Advice.OnMethodEnter(inline = false)
+ private static void enter() {
+ enter++;
+ }
+
+ @Advice.OnMethodExit(inline = false, onThrowable = Exception.class)
+ private static void exit() {
+ exit++;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class EmptyMethod {
+
+ public void foo() {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class EmptyAdvice {
+
+ @Advice.OnMethodEnter
+ @Advice.OnMethodExit
+ private static void advice() {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class EmptyAdviceDelegation {
+
+ public void foo() {
+ /* do nothing */
+ }
+
+ @Advice.OnMethodEnter(inline = false)
+ @Advice.OnMethodExit(inline = false)
+ private static void advice() {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class EmptyAdviceWithEntrySuppression {
+
+ @Advice.OnMethodEnter(suppress = Throwable.class)
+ @Advice.OnMethodExit
+ private static void advice() {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class EmptyAdviceWithExitSuppression {
+
+ @Advice.OnMethodEnter
+ @Advice.OnMethodExit(suppress = Throwable.class)
+ private static void advice() {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class EmptyAdviceWithSuppression {
+
+ @Advice.OnMethodEnter
+ @Advice.OnMethodExit(suppress = Throwable.class)
+ private static void advice() {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class EmptyAdviceWithExceptionHandling {
+
+ @Advice.OnMethodEnter
+ @Advice.OnMethodExit(onThrowable = Exception.class)
+ private static void advice() {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class EmptyAdviceWithExceptionHandlingAndEntrySuppression {
+
+ @Advice.OnMethodEnter(suppress = Throwable.class)
+ @Advice.OnMethodExit(onThrowable = Exception.class)
+ private static void advice() {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class EmptyAdviceWithExceptionHandlingAndExitSuppression {
+
+ @Advice.OnMethodEnter
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Exception.class)
+ private static void advice() {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class EmptyAdviceWithExceptionHandlingAndSuppression {
+
+ @Advice.OnMethodEnter(suppress = Throwable.class)
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Exception.class)
+ private static void advice() {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class EmptyAdviceEntry {
+
+ @Advice.OnMethodEnter
+ private static void advice() {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class EmptyAdviceEntryWithSuppression {
+
+ @Advice.OnMethodEnter(suppress = Throwable.class)
+ private static void advice() {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class EmptyAdviceExit {
+
+ @Advice.OnMethodExit
+ private static void advice() {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class EmptyAdviceExitAndSuppression {
+
+ @Advice.OnMethodExit(suppress = Throwable.class)
+ private static void advice() {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class EmptyAdviceExitWithExceptionHandling {
+
+ @Advice.OnMethodExit(onThrowable = Exception.class)
+ private static void advice() {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class EmptyAdviceExitWithExceptionHandlingAndSuppression {
+
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Exception.class)
+ private static void advice() {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class AdviceWithVariableValues {
+
+ @Advice.OnMethodEnter
+ private static int enter() {
+ int foo = VALUE, bar = VALUE * 2;
+ Sample.enter++;
+ return foo + bar;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Enter int enter, @Advice.Return String value) {
+ int foo = VALUE, bar = VALUE * 2;
+ if (foo + bar != enter || !value.equals(FOO + BAR + QUX + BAZ)) {
+ throw new AssertionError();
+ }
+ Sample.exit++;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class TrivialAdviceSkipException {
+
+ @Advice.OnMethodEnter
+ private static void enter() {
+ Sample.enter++;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit() {
+ Sample.exit++;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class TrivialAdviceSkipExceptionWithSuppression {
+
+ @Advice.OnMethodEnter(suppress = Exception.class)
+ private static void enter() {
+ Sample.enter++;
+ }
+
+ @Advice.OnMethodExit(suppress = Exception.class)
+ private static void exit() {
+ Sample.exit++;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ArgumentAdvice {
+
+ public static int enter, exit;
+
+ @Advice.OnMethodEnter
+ private static void enter(String argument) {
+ if (!argument.equals(BAR)) {
+ throw new AssertionError();
+ }
+ Sample.enter++;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(String argument) {
+ if (!argument.equals(BAR)) {
+ throw new AssertionError();
+ }
+ Sample.exit++;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ArgumentAdviceExplicit {
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.Argument(1) String argument) {
+ if (!argument.equals(BAR)) {
+ throw new AssertionError();
+ }
+ Sample.enter++;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Argument(1) String argument) {
+ if (!argument.equals(BAR)) {
+ throw new AssertionError();
+ }
+ Sample.exit++;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ArgumentAdviceDelegationImplicit {
+
+ public String foo(String value) {
+ return value;
+ }
+
+ @Advice.OnMethodEnter(inline = false)
+ private static void enter(String argument) {
+ if (!argument.equals(BAR)) {
+ throw new AssertionError();
+ }
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(String argument) {
+ if (!argument.equals(BAR)) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IncrementSample {
+
+ public int foo(int value) {
+ return ++value;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IncrementAdvice {
+
+ @Advice.OnMethodEnter
+ private static int enter(@Advice.Argument(value = 0, readOnly = false) int argument, @Advice.Unused int ignored) {
+ if (++argument != 1) {
+ throw new AssertionError();
+ }
+ if (++ignored != 0) {
+ throw new AssertionError();
+ }
+ int value = 0;
+ if (++value != 1) {
+ throw new AssertionError();
+ }
+ return value;
+ }
+
+ @Advice.OnMethodExit
+ private static int exit(@Advice.Argument(value = 0, readOnly = false) int argument, @Advice.Unused int ignored) {
+ if (++argument != 3) {
+ throw new AssertionError();
+ }
+ if (++ignored != 0) {
+ throw new AssertionError();
+ }
+ int value = 0;
+ if (++value != 1) {
+ throw new AssertionError();
+ }
+ return value;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ThisReferenceAdvice {
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.This Sample thiz) {
+ if (thiz == null) {
+ throw new AssertionError();
+ }
+ Sample.enter++;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.This Sample thiz) {
+ if (thiz == null) {
+ throw new AssertionError();
+ }
+ Sample.exit++;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class OptionalThisReferenceAdvice {
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.This(optional = true) Sample thiz) {
+ Sample.enter++;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.This(optional = true) Sample thiz) {
+ Sample.exit++;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class EntranceValueAdvice {
+
+ @Advice.OnMethodEnter
+ private static int enter() {
+ Sample.enter++;
+ return VALUE;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Enter int value) {
+ if (value != VALUE) {
+ throw new AssertionError();
+ }
+ Sample.exit++;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ReturnValueAdvice {
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return String value) {
+ if (!value.equals(FOO)) {
+ throw new AssertionError();
+ }
+ Sample.exit++;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ExceptionHandlerAdvice {
+
+ @Advice.OnMethodEnter(suppress = Throwable.class)
+ private static void enter() {
+ try {
+ throw new Exception();
+ } catch (Exception ignored) {
+ Sample.enter++;
+ }
+ }
+
+ @Advice.OnMethodExit(suppress = Throwable.class)
+ private static void exit() {
+ try {
+ throw new Exception();
+ } catch (Exception ignored) {
+ Sample.exit++;
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ObsoleteReturnValueAdvice {
+
+ @Advice.OnMethodEnter
+ private static int enter() {
+ Sample.enter++;
+ return VALUE;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class UnusedReturnValueAdvice {
+
+ @Advice.OnMethodEnter
+ private static int enter() {
+ Sample.enter++;
+ return VALUE;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit() {
+ Sample.exit++;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ThrowableAdvice {
+
+ @Advice.OnMethodExit(onThrowable = Exception.class)
+ private static void exit(@Advice.Thrown Throwable throwable) {
+ Sample.throwable = throwable;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ThrowOnEnter {
+
+ @Advice.OnMethodEnter
+ private static void enter() {
+ TracableSample.enter++;
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodExit
+ private static void exit() {
+ TracableSample.exit++;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ThrowOnExit {
+
+ @Advice.OnMethodEnter
+ private static void enter() {
+ TracableSample.enter++;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit() {
+ TracableSample.exit++;
+ throw new RuntimeException();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ThrowSuppressed {
+
+ @Advice.OnMethodEnter(suppress = RuntimeException.class)
+ private static void enter() {
+ TracableSample.enter++;
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodExit(suppress = RuntimeException.class)
+ private static void exit() {
+ TracableSample.exit++;
+ throw new RuntimeException();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ThrowNotSuppressedOnEnter {
+
+ @Advice.OnMethodEnter(suppress = RuntimeException.class)
+ private static void enter() throws Exception {
+ TracableSample.enter++;
+ throw new Exception();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ThrowNotSuppressedOnExit {
+
+ @Advice.OnMethodEnter(suppress = RuntimeException.class)
+ private static void enter() {
+ TracableSample.enter++;
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodExit(suppress = RuntimeException.class)
+ private static void exit() throws Exception {
+ TracableSample.exit++;
+ throw new Exception();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ThisSubstitutionAdvice {
+
+ @Advice.OnMethodEnter
+ @SuppressWarnings("all")
+ private static void enter(@Advice.This(readOnly = false) Box box) {
+ box = new Box(BAR);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ThisOptionalSubstitutionAdvice {
+
+ @Advice.OnMethodEnter
+ @SuppressWarnings("all")
+ private static void enter(@Advice.This(readOnly = false, optional = true) Box box) {
+ box = new Box(BAR);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IllegalThisSubstitutionAdvice {
+
+ @Advice.OnMethodEnter
+ @SuppressWarnings("all")
+ private static void enter(@Advice.This Box box) {
+ box = new Box(BAR);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ParameterSubstitutionAdvice {
+
+ @Advice.OnMethodEnter
+ @SuppressWarnings("all")
+ private static void enter(@Advice.Argument(value = 0, readOnly = false) String value) {
+ value = BAR;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IllegalParameterSubstitutionAdvice {
+
+ @Advice.OnMethodEnter
+ @SuppressWarnings("all")
+ private static void enter(@Advice.Argument(0) String value) {
+ value = BAR;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class EnterSubstitutionAdvice {
+
+ @Advice.OnMethodEnter
+ private static String enter() {
+ return FOO;
+ }
+
+ @Advice.OnMethodExit
+ @SuppressWarnings("all")
+ private static void exit(@Advice.Enter(readOnly = false) String value) {
+ value = BAR;
+ if (!value.equals(BAR)) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IllegalEnterSubstitutionAdvice {
+
+ @Advice.OnMethodEnter
+ private static String enter() {
+ return FOO;
+ }
+
+ @Advice.OnMethodExit
+ @SuppressWarnings("all")
+ private static void exit(@Advice.Enter String value) {
+ value = BAR;
+ throw new AssertionError();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ReturnSubstitutionAdvice {
+
+ @Advice.OnMethodExit
+ @SuppressWarnings("all")
+ private static void exit(@Advice.Return(readOnly = false) String value) {
+ value = BAR;
+ if (!value.equals(BAR)) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IllegalReturnSubstitutionAdvice {
+
+ @Advice.OnMethodExit
+ @SuppressWarnings("all")
+ private static void exit(@Advice.Return String value) {
+ value = BAR;
+ throw new AssertionError();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class Box {
+
+ public final String value;
+
+ public Box(String value) {
+ this.value = value;
+ }
+
+ public String foo() {
+ return value;
+ }
+
+ public static String bar(String value) {
+ return value;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FieldSample {
+
+ public static int enter, exit;
+
+ private String foo = FOO;
+
+ public String foo() {
+ return foo;
+ }
+
+ public static String bar() {
+ return BAR;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FieldAdviceImplicit {
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.FieldValue("foo") String foo) {
+ FieldSample.enter++;
+ if (!foo.equals(FOO)) {
+ throw new AssertionError();
+ }
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.FieldValue("foo") String foo) {
+ FieldSample.exit++;
+ if (!foo.equals(FOO)) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FieldAdviceExplicit {
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.FieldValue(value = "foo", declaringType = FieldSample.class) String foo) {
+ FieldSample.enter++;
+ if (!foo.equals(FOO)) {
+ throw new AssertionError();
+ }
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.FieldValue(value = "foo", declaringType = FieldSample.class) String foo) {
+ FieldSample.exit++;
+ if (!foo.equals(FOO)) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class OriginAdvice {
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.Origin String origin) throws Exception {
+ if (!origin.equals(Sample.class.getDeclaredMethod(FOO).toString())) {
+ throw new AssertionError();
+ }
+ Sample.enter++;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Origin String origin) throws Exception {
+ if (!origin.equals(Sample.class.getDeclaredMethod(FOO).toString())) {
+ throw new AssertionError();
+ }
+ Sample.exit++;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class OriginCustomAdvice {
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.Origin("#t #m #d #r #s") String origin, @Advice.Origin Class<?> type) throws Exception {
+ if (!origin.equals(Sample.class.getName() + " "
+ + FOO
+ + " ()L" + String.class.getName().replace('.', '/') + "; "
+ + String.class.getName()
+ + " ()")) {
+ throw new AssertionError();
+ }
+ if (type != Sample.class) {
+ throw new AssertionError();
+ }
+ Sample.enter++;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Origin("\\#\\#\\\\#m") String origin, @Advice.Origin Class<?> type) throws Exception {
+ if (!origin.equals("##\\" + FOO)) {
+ throw new AssertionError();
+ }
+ if (type != Sample.class) {
+ throw new AssertionError();
+ }
+ Sample.exit++;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class OriginMethodStackSizeAdvice {
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.Origin Method origin) throws Exception {
+ Object ignored = origin;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class OriginConstructorStackSizeAdvice {
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.Origin Constructor<?> origin) throws Exception {
+ Object ignored = origin;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class OriginMethodAdvice {
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.Origin Method origin) throws Exception {
+ if (!origin.equals(Sample.class.getDeclaredMethod(BAR, String.class))) {
+ throw new AssertionError();
+ }
+ Sample.enter++;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Origin Method origin) throws Exception {
+ if (!origin.equals(Sample.class.getDeclaredMethod(BAR, String.class))) {
+ throw new AssertionError();
+ }
+ Sample.exit++;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class OriginConstructorAdvice {
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.Origin Constructor<?> origin) throws Exception {
+ if (!origin.equals(Sample.class.getDeclaredConstructor())) {
+ throw new AssertionError();
+ }
+ Sample.enter++;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Origin Constructor<?> origin) throws Exception {
+ if (!origin.equals(Sample.class.getDeclaredConstructor())) {
+ throw new AssertionError();
+ }
+ Sample.exit++;
+ }
+ }
+
+ @SuppressWarnings("all")
+ public static class FrameShiftAdvice {
+
+ @Advice.OnMethodEnter
+ private static String enter() {
+ int v0 = 0;
+ if (v0 != 0) {
+ // do nothing
+ }
+ return BAR;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Enter String value) {
+ if (!value.equals(BAR)) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ExceptionTypeAdvice {
+
+ public static int enter, exit;
+
+ public void foo() {
+ throw new IllegalStateException();
+ }
+
+ @Advice.OnMethodEnter(suppress = UnsupportedOperationException.class)
+ private static void enter() {
+ enter++;
+ throw new UnsupportedOperationException();
+ }
+
+ @Advice.OnMethodExit(suppress = ArrayIndexOutOfBoundsException.class, onThrowable = IllegalStateException.class)
+ private static void exit() {
+ exit++;
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ExceptionNotCatchedAdvice {
+
+ public static int enter, exit;
+
+ public void foo() throws Exception {
+ throw new Exception();
+ }
+
+ @Advice.OnMethodEnter
+ private static void enter() {
+ enter++;
+ }
+
+ @Advice.OnMethodExit(onThrowable = RuntimeException.class)
+ private static void exit() {
+ exit++;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ExceptionCatchedAdvice {
+
+ public static int enter, exit;
+
+ public void foo() {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodEnter
+ private static void enter() {
+ enter++;
+ }
+
+ @Advice.OnMethodExit(onThrowable = Exception.class)
+ private static void exit() {
+ exit++;
+ }
+ }
+
+ @SuppressWarnings("all")
+ public static class ExceptionCatchedWithExchangeAdvice {
+
+ public static int enter, exit;
+
+ public void foo() {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodEnter
+ private static void enter() {
+ enter++;
+ }
+
+ @Advice.OnMethodExit(onThrowable = RuntimeException.class)
+ private static void exit(@Advice.Thrown(readOnly = false) Throwable throwable) {
+ exit++;
+ throwable = new IOException();
+ }
+ }
+
+ @SuppressWarnings("all")
+ public static class NonAssignableReturnTypeAdvice {
+
+ public String foo() {
+ return FOO;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object value) {
+ value = new Object();
+ }
+ }
+
+ @SuppressWarnings("all")
+ public static class TrivialReturnTypeAdvice {
+
+ public Object foo() {
+ return FOO;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object value) {
+ value = BAR;
+ }
+ }
+
+ @SuppressWarnings("all")
+ public static class NonAssignablePrimitiveReturnTypeAdvice {
+
+ public int foo() {
+ return 0;
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object value) {
+ value = new Object();
+ }
+ }
+
+ public static class OptionalArgumentAdvice {
+
+ public String foo() {
+ return FOO;
+ }
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.Argument(value = 0, optional = true) String value) {
+ if (value != null) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FieldAdviceIllegalExplicit {
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.FieldValue(value = "bar", declaringType = Void.class) String bar) {
+ throw new AssertionError();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FieldAdviceNonExistent {
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.FieldValue("bar") String bar) {
+ throw new AssertionError();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FieldAdviceNonAssignable {
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.FieldValue("foo") Void foo) {
+ throw new AssertionError();
+ }
+ }
+
+ @SuppressWarnings("all")
+ public static class FieldAdviceWrite {
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.FieldValue("foo") String foo) {
+ foo = BAR;
+ }
+ }
+
+ @SuppressWarnings("all")
+ public static class ExceptionSuppressionAdvice {
+
+ @Advice.OnMethodExit(onThrowable = Exception.class)
+ private static void exit(@Advice.Thrown(readOnly = false) Throwable throwable) {
+ throwable = null;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IllegalArgumentAdvice {
+
+ @Advice.OnMethodEnter
+ private static void enter(Void argument) {
+ throw new AssertionError();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IllegalArgumentReadOnlyAdvice {
+
+ @SuppressWarnings("unused")
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.Argument(0) Void argument) {
+ throw new AssertionError();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IllegalArgumentWritableAdvice {
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.Argument(value = 0, readOnly = false) Object argument) {
+ throw new AssertionError();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class DuplicateAdvice {
+
+ @Advice.OnMethodEnter
+ private static void enter1() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter
+ private static void enter2() {
+ throw new AssertionError();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class NonStaticAdvice {
+
+ @Advice.OnMethodEnter
+ private void enter() {
+ throw new AssertionError();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IllegalThisReferenceAdvice {
+
+ @Advice.OnMethodExit
+ private static void enter(@Advice.This Void thiz) {
+ throw new AssertionError();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IllegalThisReferenceWritableAdvice {
+
+ @Advice.OnMethodExit
+ private static void enter(@Advice.This(readOnly = false) Object thiz) {
+ throw new AssertionError();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class AmbiguousAdvice {
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.Argument(0) @Advice.This Object thiz) {
+ throw new AssertionError();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class AmbiguousAdviceDelegation {
+
+ @Advice.OnMethodEnter(inline = false)
+ private static void enter(@Advice.Argument(0) @Advice.This Object thiz) {
+ throw new AssertionError();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class EnterToEnterAdvice {
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.Enter Object value) {
+ throw new AssertionError();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class EnterToReturnAdvice {
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.Return Object value) {
+ throw new AssertionError();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class NonAssignableEnterAdvice {
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Enter Object value) {
+ throw new AssertionError();
+ }
+ }
+
+ public static class NonAssignableEnterWriteAdvice {
+
+ @Advice.OnMethodEnter
+ private static String enter() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Enter(readOnly = false) Object value) {
+ throw new AssertionError();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class NonEqualEnterAdvice {
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Enter(readOnly = false) Object value) {
+ throw new AssertionError();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class NonAssignableReturnAdvice {
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return Void value) {
+ throw new AssertionError();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class NonEqualReturnWritableAdvice {
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Return(readOnly = false) Object value) {
+ throw new AssertionError();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IllegalThrowableRequestAdvice {
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Thrown Throwable value) {
+ throw new AssertionError();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IllegalThrowableTypeAdvice {
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Thrown Object value) {
+ throw new AssertionError();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IllegalOriginType {
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Origin Void value) {
+ throw new AssertionError();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IllegalOriginPattern {
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Origin("#x") String value) {
+ throw new AssertionError();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IllegalOriginPatternEnd {
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Origin("#") String value) {
+ throw new AssertionError();
+ }
+ }
+
+ @SuppressWarnings("all")
+ public static class IllegalOriginWriteAdvice {
+
+ @Advice.OnMethodExit
+ private static void exit(@Advice.Origin String value) {
+ value = null;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IllegalArgumentWritableInlineAdvice {
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Argument(value = 0, readOnly = false) Object argument) {
+ throw new AssertionError();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IllegalThisWritableInlineAdvice {
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.This(readOnly = false) Object argument) {
+ throw new AssertionError();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IllegalThrowWritableInlineAdvice {
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Thrown(readOnly = false) Object argument) {
+ throw new AssertionError();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IllegalReturnWritableInlineAdvice {
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Return(readOnly = false) Object argument) {
+ throw new AssertionError();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IllegalFieldWritableInlineAdvice {
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.Argument(value = 0, readOnly = false) Object argument) {
+ throw new AssertionError();
+ }
+ }
+
+ public static class IllegalBoxedReturnType {
+
+ @Advice.OnMethodEnter
+ private static void advice(@Advice.StubValue int value) {
+ throw new AssertionError();
+ }
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface Custom {
+ /* empty */
+ }
+
+ @SuppressWarnings("unused")
+ public static class CustomAdvice {
+
+ @Advice.OnMethodEnter
+ @Advice.OnMethodExit
+ private static void advice(@Custom Object value) {
+ if (value != Object.class && value != RetentionPolicy.CLASS) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class CustomPrimitiveAdvice {
+
+ @Advice.OnMethodEnter
+ @Advice.OnMethodExit
+ private static void advice(@Custom int value) {
+ if (value == 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class CustomSerializableAdvice {
+
+ @Advice.OnMethodEnter
+ @Advice.OnMethodExit
+ private static void advice(@Custom Map<String, String> value) {
+ if (value.size() != 1 && !value.get(FOO).equals(BAR)) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class NonVisibleAdvice {
+
+ @Advice.OnMethodEnter(inline = false)
+ private static void enter() {
+ /* empty */
+ }
+ }
+
+ public static class LineNumberAdvice {
+
+ @Advice.OnMethodEnter
+ private static void enter() {
+ StackTraceElement top = new Throwable().getStackTrace()[0];
+ if (top.getLineNumber() < 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ public static class NoLineNumberAdvice {
+
+ @Advice.OnMethodEnter(prependLineNumber = false)
+ private static void enter() {
+ StackTraceElement top = new Throwable().getStackTrace()[0];
+ if (top.getLineNumber() >= 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ public static class LineNumberDelegatingAdvice {
+
+ public String foo() {
+ return FOO;
+ }
+
+ @Advice.OnMethodEnter(inline = false)
+ private static void enter() {
+ StackTraceElement top = new Throwable().getStackTrace()[1];
+ if (top.getLineNumber() < 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ public static class NoLineNumberDelegatingAdvice {
+
+ public String foo() {
+ return FOO;
+ }
+
+ @Advice.OnMethodEnter(inline = false, prependLineNumber = false)
+ private static void enter() {
+ StackTraceElement top = new Throwable().getStackTrace()[1];
+ if (top.getLineNumber() >= 0) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ public static class InstanceOfSkip {
+
+ public String foo() {
+ throw new AssertionError();
+ }
+
+ @Advice.OnMethodEnter(skipOn = InstanceOfSkip.class)
+ private static Object enter() {
+ return new InstanceOfSkip();
+ }
+ }
+
+ public static class InstanceOfNoSkip {
+
+ public String foo() {
+ return FOO;
+ }
+
+ @Advice.OnMethodEnter(skipOn = InstanceOfSkip.class)
+ private static Object enter() {
+ return null;
+ }
+ }
+
+ public static class InstanceOfIllegalPrimitiveSkip {
+
+ @Advice.OnMethodEnter(skipOn = InstanceOfSkip.class)
+ private static void enter() {
+ /* empty */
+ }
+ }
+
+ public static class DefaultValueIllegalPrimitiveSkip {
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
+ private static void enter() {
+ /* empty */
+ }
+ }
+
+ public static class NonDefaultValueIllegalPrimitiveSkip {
+
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ private static void enter() {
+ /* empty */
+ }
+ }
+
+ public static class InstanceOfIllegalPrimitiveInstanceOfSkip {
+
+ @Advice.OnMethodEnter(skipOn = int.class)
+ private static void enter() {
+ /* empty */
+ }
+ }
+
+ public static class BoxedArgumentsStackSizeAdvice {
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.AllArguments Object[] value) {
+ Object ignored = value;
+ }
+ }
+
+ public static class BoxedArgumentsObjectTypeAdvice {
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.AllArguments Object value) {
+ Object ignored = value;
+ }
+ }
+
+ public static class BoxedArgumentsWriteDelegateEntry {
+
+ @Advice.OnMethodEnter(inline = false)
+ private static void enter(@Advice.AllArguments(readOnly = false) Object[] value) {
+ throw new AssertionError();
+ }
+ }
+
+ public static class BoxedArgumentsWriteDelegateExit {
+
+ @Advice.OnMethodExit(inline = false)
+ private static void exit(@Advice.AllArguments(readOnly = false) Object[] value) {
+ throw new AssertionError();
+ }
+ }
+
+ public static class BoxedArgumentsCannotWrite {
+
+ @Advice.OnMethodEnter
+ private static void enter(@Advice.AllArguments Object[] value) {
+ value = new Object[0];
+ }
+ }
+
+ public static class ExceptionWriterTest {
+
+ @Advice.OnMethodEnter(suppress = RuntimeException.class)
+ @Advice.OnMethodExit(suppress = RuntimeException.class)
+ private static void advice() {
+ RuntimeException exception = new RuntimeException();
+ exception.setStackTrace(new StackTraceElement[0]);
+ throw exception;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceTypeTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceTypeTest.java
new file mode 100644
index 0000000..f93c675
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceTypeTest.java
@@ -0,0 +1,1962 @@
+package net.bytebuddy.asm;
+
+import net.bytebuddy.ByteBuddy;
+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.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.pool.TypePool;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Locale;
+
+import static junit.framework.TestCase.fail;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class AdviceTypeTest {
+
+ private static final String FOO = "foo", BAR = "bar", ENTER = "enter", EXIT = "exit", exception = "exception";
+
+ private static final String FIELD = "field", MUTATED = "mutated", STATIC_FIELD = "staticField", MUTATED_STATIC_FIELD = "mutatedStatic";
+
+ private static final byte VALUE = 42;
+
+ private static final boolean BOOLEAN = true;
+
+ private static final byte NUMERIC_DEFAULT = 0;
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {VoidInlineAdvice.class, Void.class, null},
+ {VoidDelegatingAdvice.class, Void.class, null},
+ {BooleanInlineAdvice.class, boolean.class, BOOLEAN},
+ {BooleanDelegationAdvice.class, boolean.class, BOOLEAN},
+ {ByteInlineAdvice.class, byte.class, VALUE},
+ {ByteDelegationAdvice.class, byte.class, VALUE},
+ {ShortInlineAdvice.class, short.class, (short) VALUE},
+ {ShortDelegationAdvice.class, short.class, (short) VALUE},
+ {CharacterInlineAdvice.class, char.class, (char) VALUE},
+ {CharacterDelegationAdvice.class, char.class, (char) VALUE},
+ {IntegerInlineAdvice.class, int.class, (int) VALUE},
+ {IntegerDelegationAdvice.class, int.class, (int) VALUE},
+ {LongInlineAdvice.class, long.class, (long) VALUE},
+ {LongDelegationAdvice.class, long.class, (long) VALUE},
+ {FloatInlineAdvice.class, float.class, (float) VALUE},
+ {FloatDelegationAdvice.class, float.class, (float) VALUE},
+ {DoubleInlineAdvice.class, double.class, (double) VALUE},
+ {DoubleDelegationAdvice.class, double.class, (double) VALUE},
+ {ReferenceInlineAdvice.class, Object.class, FOO},
+ {ReferenceDelegationAdvice.class, Object.class, FOO}
+ });
+ }
+
+ private final Class<?> advice;
+
+ private final Class<?> type;
+
+ private final Serializable value;
+
+ public AdviceTypeTest(Class<?> advice, Class<?> type, Serializable value) {
+ this.advice = advice;
+ this.type = type;
+ this.value = value;
+ }
+
+ @Test
+ public void testAdvice() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(advice)
+ .visit(new SerializationAssertion())
+ .visit(Advice.withCustomMapping()
+ .bind(CustomAnnotation.class, value)
+ .to(advice)
+ .on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO, this.type, this.type).invoke(type.getDeclaredConstructor().newInstance(), value, value), is((Object) value));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testAdviceWithException() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(advice)
+ .visit(new SerializationAssertion())
+ .visit(Advice.withCustomMapping()
+ .bind(CustomAnnotation.class, value)
+ .to(advice)
+ .on(named(BAR)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ type.getDeclaredField(exception).set(null, true);
+ try {
+ assertThat(type.getDeclaredMethod(BAR, this.type, this.type).invoke(type.getDeclaredConstructor().newInstance(), value, value), is((Object) value));
+ fail();
+ } catch (InvocationTargetException exception) {
+ assertThat(exception.getCause(), instanceOf(RuntimeException.class));
+ }
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @Test
+ public void testAdviceWithProperty() throws Exception {
+ if (type == Void.class) {
+ return; // No void property on annotations.
+ }
+ Class<?> type = new ByteBuddy()
+ .redefine(advice)
+ .visit(new SerializationAssertion())
+ .visit(Advice.withCustomMapping()
+ .bindProperty(CustomAnnotation.class, this.type.getSimpleName().toLowerCase(Locale.US) + "Value")
+ .to(advice)
+ .on(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO, this.type, this.type).invoke(type.getDeclaredConstructor().newInstance(), value, value), is((Object) value));
+ assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
+ assertThat(type.getDeclaredField(EXIT).get(null), is((Object) 1));
+ }
+
+ @SuppressWarnings("unused")
+ public static class VoidInlineAdvice {
+
+ public static int enter, exit;
+
+ public static boolean exception;
+
+ public void foo(Void ignoredArgument, Void ignoredMutableArgument) {
+ /* empty */
+ }
+
+ public void bar(Void ignoredArgument, Void ignoredMutableArgument) {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodEnter
+ public static void enter(@Advice.AllArguments Object[] boxed,
+ @Advice.StubValue Object stubValue,
+ @CustomAnnotation Void custom) {
+ if (boxed.length != 2 || boxed[0] != null || boxed[1] != null) {
+ throw new AssertionError();
+ }
+ if (stubValue != null) {
+ throw new AssertionError();
+ }
+ if (custom != null) {
+ throw new AssertionError();
+ }
+ enter++;
+ }
+
+ @Advice.OnMethodExit(onThrowable = Exception.class)
+ public static void exit(@Advice.Thrown Throwable throwable,
+ @Advice.Return(typing = Assigner.Typing.DYNAMIC) Object boxedReturn,
+ @Advice.AllArguments Object[] boxed,
+ @CustomAnnotation Void custom) {
+ if (!(exception ? throwable instanceof RuntimeException : throwable == null)) {
+ throw new AssertionError();
+ }
+ if (boxedReturn != null) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || boxed[0] != null || boxed[1] != null) {
+ throw new AssertionError();
+ }
+ if (custom != null) {
+ throw new AssertionError();
+ }
+ exit++;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class VoidDelegatingAdvice {
+
+ public static int enter, exit;
+
+ public static boolean exception;
+
+ public void foo(Void ignoredArgument, Void ignored) {
+ /* empty */
+ }
+
+ public void bar(Void ignoredArgument, Void ignored) {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodEnter(inline = false)
+ public static void enter(@Advice.AllArguments Object[] boxed,
+ @Advice.StubValue Object stubValue,
+ @CustomAnnotation Void custom) {
+ if (boxed.length != 2 || boxed[0] != null || boxed[1] != null) {
+ throw new AssertionError();
+ }
+ if (stubValue != null) {
+ throw new AssertionError();
+ }
+ if (custom != null) {
+ throw new AssertionError();
+ }
+ enter++;
+ }
+
+ @Advice.OnMethodExit(inline = false, onThrowable = Exception.class)
+ public static void exit(@Advice.Thrown Throwable throwable,
+ @Advice.Return(typing = Assigner.Typing.DYNAMIC) Object boxedReturn,
+ @Advice.AllArguments Object[] boxed,
+ @CustomAnnotation Void custom) {
+ if (!(exception ? throwable instanceof RuntimeException : throwable == null)) {
+ throw new AssertionError();
+ }
+ if (boxedReturn != null) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || boxed[0] != null || boxed[1] != null) {
+ throw new AssertionError();
+ }
+ if (custom != null) {
+ throw new AssertionError();
+ }
+ exit++;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class BooleanInlineAdvice {
+
+ public static int enter, exit;
+
+ public static boolean exception;
+
+ private boolean field = BOOLEAN, mutated = BOOLEAN;
+
+ private static boolean staticField = BOOLEAN, mutatedStatic = BOOLEAN;
+
+ public boolean foo(boolean argument, boolean mutableArgument) {
+ return argument;
+ }
+
+ public boolean bar(boolean argument, boolean mutableArgument) {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodEnter
+ public static boolean enter(@Advice.Unused boolean value,
+ @Advice.StubValue Object stubValue,
+ @Advice.Argument(0) boolean argument,
+ @Advice.Argument(value = 1, readOnly = false) boolean mutableArgument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) boolean field,
+ @Advice.FieldValue(STATIC_FIELD) boolean staticField,
+ @Advice.FieldValue(value = MUTATED, readOnly = false) boolean mutated,
+ @Advice.FieldValue(value = MUTATED_STATIC_FIELD, readOnly = false) boolean mutatedStatic,
+ @CustomAnnotation boolean custom) {
+ if (value) {
+ throw new AssertionError();
+ }
+ value = BOOLEAN;
+ if (value) {
+ throw new AssertionError();
+ }
+ if ((Boolean) stubValue) {
+ throw new AssertionError();
+ }
+ if (!argument || !mutableArgument) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals(BOOLEAN) || !boxed[1].equals(BOOLEAN)) {
+ throw new AssertionError();
+ }
+ mutableArgument = false;
+ if (boxed.length != 2 || !boxed[0].equals(BOOLEAN) || !boxed[1].equals(false)) {
+ throw new AssertionError();
+ }
+ if (!field || !mutated || !staticField || !mutatedStatic) {
+ throw new AssertionError();
+ }
+ mutated = mutatedStatic = false;
+ if (!custom) {
+ throw new AssertionError();
+ }
+ enter++;
+ return BOOLEAN;
+ }
+
+ @Advice.OnMethodExit(onThrowable = Exception.class)
+ public static boolean exit(@Advice.Return boolean result,
+ @Advice.StubValue Object stubValue,
+ @Advice.Enter boolean enter,
+ @Advice.Thrown Throwable throwable,
+ @Advice.Return Object boxedReturn,
+ @Advice.Argument(0) boolean argument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.Argument(value = 1, readOnly = false) boolean mutableArgument,
+ @Advice.FieldValue(FIELD) boolean field,
+ @Advice.FieldValue(STATIC_FIELD) boolean staticField,
+ @Advice.FieldValue(value = MUTATED, readOnly = false) boolean mutated,
+ @Advice.FieldValue(value = MUTATED_STATIC_FIELD, readOnly = false) boolean mutatedStatic,
+ @CustomAnnotation boolean custom) {
+ if (result == exception
+ || !enter
+ || !(exception ? throwable instanceof RuntimeException : throwable == null)) {
+ throw new AssertionError();
+ }
+ if (boxedReturn.equals(exception)) {
+ throw new AssertionError();
+ }
+ if (!argument || mutableArgument) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals(BOOLEAN) || !boxed[1].equals(false)) {
+ throw new AssertionError();
+ }
+ if (!field || mutated || !staticField || mutatedStatic) {
+ throw new AssertionError();
+ }
+ if (!custom) {
+ throw new AssertionError();
+ }
+ exit++;
+ return BOOLEAN;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class BooleanDelegationAdvice {
+
+ public static int enter, exit;
+
+ public static boolean exception;
+
+ private boolean field = BOOLEAN;
+
+ private static boolean staticField = BOOLEAN;
+
+ public boolean foo(boolean argument, boolean ignored) {
+ return argument;
+ }
+
+ public boolean bar(boolean argument, boolean ignored) {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodEnter(inline = false)
+ public static boolean enter(@Advice.Unused boolean value,
+ @Advice.StubValue Object stubValue,
+ @Advice.Argument(0) boolean argument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) boolean field,
+ @Advice.FieldValue(STATIC_FIELD) boolean staticField,
+ @CustomAnnotation boolean custom) {
+ if (value) {
+ throw new AssertionError();
+ }
+ if ((Boolean) stubValue) {
+ throw new AssertionError();
+ }
+ if (!argument) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals(BOOLEAN) || !boxed[1].equals(BOOLEAN)) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals(BOOLEAN) || !boxed[1].equals(BOOLEAN)) {
+ throw new AssertionError();
+ }
+ if (!field || !staticField) {
+ throw new AssertionError();
+ }
+ if (!custom) {
+ throw new AssertionError();
+ }
+ enter++;
+ return BOOLEAN;
+ }
+
+ @Advice.OnMethodExit(inline = false, onThrowable = Exception.class)
+ public static boolean exit(@Advice.Return boolean result,
+ @Advice.Enter boolean enter,
+ @Advice.Thrown Throwable throwable,
+ @Advice.Return Object boxedReturn,
+ @Advice.Argument(0) boolean argument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) boolean field,
+ @Advice.FieldValue(STATIC_FIELD) boolean staticField,
+ @CustomAnnotation boolean custom) {
+ if (result == exception
+ || !enter
+ || !(exception ? throwable instanceof RuntimeException : throwable == null)) {
+ throw new AssertionError();
+ }
+ if (boxedReturn.equals(exception)) {
+ throw new AssertionError();
+ }
+ if (!argument) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals(BOOLEAN) || !boxed[1].equals(BOOLEAN)) {
+ throw new AssertionError();
+ }
+ if (!field || !staticField) {
+ throw new AssertionError();
+ }
+ if (!custom) {
+ throw new AssertionError();
+ }
+ exit++;
+ return BOOLEAN;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ByteInlineAdvice {
+
+ public static int enter, exit;
+
+ public static boolean exception;
+
+ private byte field = VALUE, mutated = VALUE;
+
+ private static byte staticField = VALUE, mutatedStatic = VALUE;
+
+ public byte foo(byte argument, byte mutableArgument) {
+ return argument;
+ }
+
+ public byte bar(byte argument, byte mutableArgument) {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodEnter
+ public static byte enter(@Advice.Unused byte value,
+ @Advice.StubValue Object stubValue,
+ @Advice.Argument(0) byte argument,
+ @Advice.Argument(value = 1, readOnly = false) byte mutableArgument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) byte field,
+ @Advice.FieldValue(STATIC_FIELD) byte staticField,
+ @Advice.FieldValue(value = MUTATED, readOnly = false) byte mutated,
+ @Advice.FieldValue(value = MUTATED_STATIC_FIELD, readOnly = false) byte mutatedStatic,
+ @CustomAnnotation byte custom) {
+ if (value != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ value = VALUE;
+ if (value != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ if ((Byte) stubValue != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ if (argument != VALUE || mutableArgument != VALUE) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals(VALUE) || !boxed[1].equals(VALUE)) {
+ throw new AssertionError();
+ }
+ mutableArgument = VALUE * 2;
+ if (boxed.length != 2 || !boxed[0].equals(VALUE) || !boxed[1].equals((byte) (VALUE * 2))) {
+ throw new AssertionError();
+ }
+ if (field != VALUE || mutated != VALUE || staticField != VALUE || mutatedStatic != VALUE) {
+ throw new AssertionError();
+ }
+ mutated = mutatedStatic = VALUE * 2;
+ if (custom != VALUE) {
+ throw new AssertionError();
+ }
+ enter++;
+ return VALUE * 2;
+ }
+
+ @Advice.OnMethodExit(onThrowable = Exception.class)
+ public static byte exit(@Advice.Return byte result,
+ @Advice.Enter byte enter,
+ @Advice.Thrown Throwable throwable,
+ @Advice.Return Object boxedReturn,
+ @Advice.Argument(0) byte argument,
+ @Advice.Argument(value = 1, readOnly = false) byte mutableArgument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) byte field,
+ @Advice.FieldValue(STATIC_FIELD) byte staticField,
+ @Advice.FieldValue(value = MUTATED, readOnly = false) byte mutated,
+ @Advice.FieldValue(value = MUTATED_STATIC_FIELD, readOnly = false) byte mutatedStatic,
+ @CustomAnnotation byte custom) {
+ if (result != (exception ? 0 : VALUE)
+ || enter != VALUE * 2
+ || !(exception ? throwable instanceof RuntimeException : throwable == null)) {
+ throw new AssertionError();
+ }
+ if (!boxedReturn.equals(exception ? NUMERIC_DEFAULT : VALUE)) {
+ throw new AssertionError();
+ }
+ if (argument != VALUE || mutableArgument != VALUE * 2) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals(VALUE) || !boxed[1].equals((byte) (VALUE * 2))) {
+ throw new AssertionError();
+ }
+ if (field != VALUE || mutated != VALUE * 2 || staticField != VALUE || mutatedStatic != VALUE * 2) {
+ throw new AssertionError();
+ }
+ if (custom != VALUE) {
+ throw new AssertionError();
+ }
+ exit++;
+ return VALUE;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ByteDelegationAdvice {
+
+ public static int enter, exit;
+
+ public static boolean exception;
+
+ private byte field = VALUE;
+
+ private static byte staticField = VALUE;
+
+ public byte foo(byte argument, byte ignored) {
+ return argument;
+ }
+
+ public byte bar(byte argument, byte ignored) {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodEnter(inline = false)
+ public static byte enter(@Advice.Unused byte value,
+ @Advice.StubValue Object stubValue,
+ @Advice.Argument(0) byte argument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) byte field,
+ @Advice.FieldValue(STATIC_FIELD) byte staticField,
+ @CustomAnnotation byte custom) {
+ if (value != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ if ((Byte) stubValue != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ if (argument != VALUE) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals(VALUE) || !boxed[1].equals(VALUE)) {
+ throw new AssertionError();
+ }
+ if (field != VALUE || staticField != VALUE) {
+ throw new AssertionError();
+ }
+ if (custom != VALUE) {
+ throw new AssertionError();
+ }
+ enter++;
+ return VALUE * 2;
+ }
+
+ @Advice.OnMethodExit(inline = false, onThrowable = Exception.class)
+ public static byte exit(@Advice.Return byte result,
+ @Advice.Enter byte enter,
+ @Advice.Thrown Throwable throwable,
+ @Advice.Return Object boxedReturn,
+ @Advice.Argument(0) byte argument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) byte field,
+ @Advice.FieldValue(STATIC_FIELD) byte staticField,
+ @CustomAnnotation byte custom) {
+ if (result != (exception ? 0 : VALUE)
+ || enter != VALUE * 2
+ || !(exception ? throwable instanceof RuntimeException : throwable == null)) {
+ throw new AssertionError();
+ }
+ if (!boxedReturn.equals(exception ? NUMERIC_DEFAULT : VALUE)) {
+ throw new AssertionError();
+ }
+ if (argument != VALUE) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals(VALUE) || !boxed[1].equals(VALUE)) {
+ throw new AssertionError();
+ }
+ if (field != VALUE || staticField != VALUE) {
+ throw new AssertionError();
+ }
+ if (custom != VALUE) {
+ throw new AssertionError();
+ }
+ exit++;
+ return VALUE;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ShortInlineAdvice {
+
+ public static int enter, exit;
+
+ public static boolean exception;
+
+ private short field = VALUE, mutated = VALUE;
+
+ private static short staticField = VALUE, mutatedStatic = VALUE;
+
+ public short foo(short argument, short mutableArgument) {
+ return argument;
+ }
+
+ public short bar(short argument, short mutableArgument) {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodEnter
+ public static short enter(@Advice.Unused short value,
+ @Advice.StubValue Object stubValue,
+ @Advice.Argument(0) short argument,
+ @Advice.Argument(value = 1, readOnly = false) short mutableArgument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) short field,
+ @Advice.FieldValue(STATIC_FIELD) short staticField,
+ @Advice.FieldValue(value = MUTATED, readOnly = false) short mutated,
+ @Advice.FieldValue(value = MUTATED_STATIC_FIELD, readOnly = false) short mutatedStatic,
+ @CustomAnnotation short custom) {
+ if (value != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ value = VALUE;
+ if (value != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ if ((Short) stubValue != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ if (argument != VALUE || mutableArgument != VALUE) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals((short) VALUE) || !boxed[1].equals((short) VALUE)) {
+ throw new AssertionError();
+ }
+ mutableArgument = VALUE * 2;
+ if (boxed.length != 2 || !boxed[0].equals((short) VALUE) || !boxed[1].equals((short) (VALUE * 2))) {
+ throw new AssertionError();
+ }
+ if (field != VALUE || mutated != VALUE || staticField != VALUE || mutatedStatic != VALUE) {
+ throw new AssertionError();
+ }
+ mutated = mutatedStatic = VALUE * 2;
+ if (custom != VALUE) {
+ throw new AssertionError();
+ }
+ enter++;
+ return VALUE * 2;
+ }
+
+ @Advice.OnMethodExit(onThrowable = Exception.class)
+ public static short exit(@Advice.Return short result,
+ @Advice.Enter short enter,
+ @Advice.Thrown Throwable throwable,
+ @Advice.Return Object boxedReturn,
+ @Advice.Argument(0) short argument,
+ @Advice.Argument(value = 1, readOnly = false) short mutableArgument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) short field,
+ @Advice.FieldValue(STATIC_FIELD) short staticField,
+ @Advice.FieldValue(value = MUTATED, readOnly = false) short mutated,
+ @Advice.FieldValue(value = MUTATED_STATIC_FIELD, readOnly = false) short mutatedStatic,
+ @CustomAnnotation short custom) {
+ if (result != (exception ? 0 : VALUE)
+ || enter != VALUE * 2
+ || !(exception ? throwable instanceof RuntimeException : throwable == null)) {
+ throw new AssertionError();
+ }
+ if (!boxedReturn.equals((short) (exception ? NUMERIC_DEFAULT : VALUE))) {
+ throw new AssertionError();
+ }
+ if (argument != VALUE || mutableArgument != VALUE * 2) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals((short) VALUE) || !boxed[1].equals((short) (VALUE * 2))) {
+ throw new AssertionError();
+ }
+ if (field != VALUE || mutated != VALUE * 2 || staticField != VALUE || mutatedStatic != VALUE * 2) {
+ throw new AssertionError();
+ }
+ if (custom != VALUE) {
+ throw new AssertionError();
+ }
+ exit++;
+ return VALUE;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ShortDelegationAdvice {
+
+ public static int enter, exit;
+
+ public static boolean exception;
+
+ private short field = VALUE;
+
+ private static short staticField = VALUE;
+
+ public short foo(short argument, short ignored) {
+ return argument;
+ }
+
+ public short bar(short argument, short ignored) {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodEnter(inline = false)
+ public static short enter(@Advice.Unused short value,
+ @Advice.StubValue Object stubValue,
+ @Advice.Argument(0) short argument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) short field,
+ @Advice.FieldValue(STATIC_FIELD) short staticField,
+ @CustomAnnotation short custom) {
+ if (value != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ if ((Short) stubValue != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ if (argument != VALUE) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals((short) VALUE) || !boxed[1].equals((short) VALUE)) {
+ throw new AssertionError();
+ }
+ if (field != VALUE || staticField != VALUE) {
+ throw new AssertionError();
+ }
+ if (custom != VALUE) {
+ throw new AssertionError();
+ }
+ enter++;
+ return VALUE * 2;
+ }
+
+ @Advice.OnMethodExit(inline = false, onThrowable = Exception.class)
+ public static short exit(@Advice.Return short result,
+ @Advice.Enter short enter,
+ @Advice.Thrown Throwable throwable,
+ @Advice.Return Object boxedReturn,
+ @Advice.Argument(0) short argument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) short field,
+ @Advice.FieldValue(STATIC_FIELD) short staticField,
+ @CustomAnnotation short custom) {
+ if (result != (exception ? 0 : VALUE)
+ || enter != VALUE * 2
+ || !(exception ? throwable instanceof RuntimeException : throwable == null)) {
+ throw new AssertionError();
+ }
+ if (!boxedReturn.equals((short) (exception ? NUMERIC_DEFAULT : VALUE))) {
+ throw new AssertionError();
+ }
+ if (argument != VALUE) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals((short) VALUE) || !boxed[1].equals((short) VALUE)) {
+ throw new AssertionError();
+ }
+ if (custom != VALUE) {
+ throw new AssertionError();
+ }
+ exit++;
+ return VALUE;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class CharacterInlineAdvice {
+
+ public static int enter, exit;
+
+ public static boolean exception;
+
+ private char field = VALUE, mutated = VALUE;
+
+ private static char staticField = VALUE, mutatedStatic = VALUE;
+
+ public char foo(char argument, char mutableArgument) {
+ return argument;
+ }
+
+ public char bar(char argument, char mutableArgument) {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodEnter
+ public static char enter(@Advice.Unused char value,
+ @Advice.StubValue Object stubValue,
+ @Advice.Argument(0) char argument,
+ @Advice.Argument(value = 1, readOnly = false) char mutableArgument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) char field,
+ @Advice.FieldValue(STATIC_FIELD) char staticField,
+ @Advice.FieldValue(value = MUTATED, readOnly = false) char mutated,
+ @Advice.FieldValue(value = MUTATED_STATIC_FIELD, readOnly = false) char mutatedStatic,
+ @CustomAnnotation char custom) {
+ if (value != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ value = VALUE;
+ if (value != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ if ((Character) stubValue != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ if (argument != VALUE || mutableArgument != VALUE) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals((char) VALUE) || !boxed[1].equals((char) VALUE)) {
+ throw new AssertionError();
+ }
+ mutableArgument = VALUE * 2;
+ if (boxed.length != 2 || !boxed[0].equals((char) VALUE) || !boxed[1].equals((char) (VALUE * 2))) {
+ throw new AssertionError();
+ }
+ if (field != VALUE || mutated != VALUE || staticField != VALUE || mutatedStatic != VALUE) {
+ throw new AssertionError();
+ }
+ mutated = mutatedStatic = VALUE * 2;
+ if (custom != VALUE) {
+ throw new AssertionError();
+ }
+ enter++;
+ return VALUE * 2;
+ }
+
+ @Advice.OnMethodExit(onThrowable = Exception.class)
+ public static char exit(@Advice.Return char result,
+ @Advice.Enter char enter,
+ @Advice.Thrown Throwable throwable,
+ @Advice.Return Object boxedReturn,
+ @Advice.Argument(0) char argument,
+ @Advice.Argument(value = 1, readOnly = false) char mutableArgument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) char field,
+ @Advice.FieldValue(STATIC_FIELD) char staticField,
+ @Advice.FieldValue(value = MUTATED, readOnly = false) char mutated,
+ @Advice.FieldValue(value = MUTATED_STATIC_FIELD, readOnly = false) char mutatedStatic,
+ @CustomAnnotation char custom) {
+ if (result != (exception ? 0 : VALUE)
+ || enter != VALUE * 2
+ || !(exception ? throwable instanceof RuntimeException : throwable == null)) {
+ throw new AssertionError();
+ }
+ if (!boxedReturn.equals((char) (exception ? NUMERIC_DEFAULT : VALUE))) {
+ throw new AssertionError();
+ }
+ if (argument != VALUE || mutableArgument != VALUE * 2) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals((char) VALUE) || !boxed[1].equals((char) (VALUE * 2))) {
+ throw new AssertionError();
+ }
+ if (field != VALUE || mutated != VALUE * 2 || staticField != VALUE || mutatedStatic != VALUE * 2) {
+ throw new AssertionError();
+ }
+ if (custom != VALUE) {
+ throw new AssertionError();
+ }
+ exit++;
+ return VALUE;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class CharacterDelegationAdvice {
+
+ public static int enter, exit;
+
+ public static boolean exception;
+
+ private char field = VALUE;
+
+ private static char staticField = VALUE;
+
+ public char foo(char argument, char ignored) {
+ return argument;
+ }
+
+ public char bar(char argument, char ignored) {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodEnter(inline = false)
+ public static char enter(@Advice.Unused char value,
+ @Advice.StubValue Object stubValue,
+ @Advice.Argument(0) char argument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) char field,
+ @Advice.FieldValue(STATIC_FIELD) char staticField,
+ @CustomAnnotation char custom) {
+ if (value != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ if ((Character) stubValue != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ if (argument != VALUE) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals((char) VALUE) || !boxed[1].equals((char) VALUE)) {
+ throw new AssertionError();
+ }
+ if (field != VALUE || staticField != VALUE) {
+ throw new AssertionError();
+ }
+ if (custom != VALUE) {
+ throw new AssertionError();
+ }
+ enter++;
+ return VALUE * 2;
+ }
+
+ @Advice.OnMethodExit(inline = false, onThrowable = Exception.class)
+ public static char exit(@Advice.Return char result,
+ @Advice.Enter char enter,
+ @Advice.Thrown Throwable throwable,
+ @Advice.Return Object boxedReturn,
+ @Advice.Argument(0) char argument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) char field,
+ @Advice.FieldValue(STATIC_FIELD) char staticField,
+ @CustomAnnotation char custom) {
+ if (result != (exception ? 0 : VALUE)
+ || enter != VALUE * 2
+ || !(exception ? throwable instanceof RuntimeException : throwable == null)) {
+ throw new AssertionError();
+ }
+ if (!boxedReturn.equals((char) (exception ? NUMERIC_DEFAULT : VALUE))) {
+ throw new AssertionError();
+ }
+ if (argument != VALUE) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals((char) VALUE) || !boxed[1].equals((char) VALUE)) {
+ throw new AssertionError();
+ }
+ if (field != VALUE || staticField != VALUE) {
+ throw new AssertionError();
+ }
+ if (custom != VALUE) {
+ throw new AssertionError();
+ }
+ exit++;
+ return VALUE;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IntegerInlineAdvice {
+
+ public static int enter, exit;
+
+ public static boolean exception;
+
+ private int field = VALUE, mutated = VALUE;
+
+ private static int staticField = VALUE, mutatedStatic = VALUE;
+
+ public int foo(int argument, int mutableArgument) {
+ return argument;
+ }
+
+ public int bar(int argument, int mutableArgument) {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodEnter
+ public static int enter(@Advice.Unused int value,
+ @Advice.StubValue Object stubValue,
+ @Advice.Argument(0) int argument,
+ @Advice.Argument(value = 1, readOnly = false) int mutableArgument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) int field,
+ @Advice.FieldValue(STATIC_FIELD) int staticField,
+ @Advice.FieldValue(value = MUTATED, readOnly = false) int mutated,
+ @Advice.FieldValue(value = MUTATED_STATIC_FIELD, readOnly = false) int mutatedStatic,
+ @CustomAnnotation int custom) {
+ if (value != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ value = VALUE;
+ if (value != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ if ((Integer) stubValue != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ if (argument != VALUE || mutableArgument != VALUE) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals((int) VALUE) || !boxed[1].equals((int) (VALUE))) {
+ throw new AssertionError();
+ }
+ mutableArgument = VALUE * 2;
+ mutableArgument++;
+ if (boxed.length != 2 || !boxed[0].equals((int) VALUE) || !boxed[1].equals(VALUE * 2 + 1)) {
+ throw new AssertionError();
+ }
+ if (field != VALUE || mutated != VALUE || staticField != VALUE || mutatedStatic != VALUE) {
+ throw new AssertionError();
+ }
+ mutated = mutatedStatic = VALUE * 2;
+ mutated++;
+ mutatedStatic++;
+ if (custom != VALUE) {
+ throw new AssertionError();
+ }
+ enter++;
+ return VALUE * 2;
+ }
+
+ @Advice.OnMethodExit(onThrowable = Exception.class)
+ public static int exit(@Advice.Return int result,
+ @Advice.Enter int enter,
+ @Advice.Thrown Throwable throwable,
+ @Advice.Return Object boxedReturn,
+ @Advice.Argument(0) int argument,
+ @Advice.Argument(value = 1, readOnly = false) int mutableArgument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) int field,
+ @Advice.FieldValue(STATIC_FIELD) int staticField,
+ @Advice.FieldValue(value = MUTATED, readOnly = false) int mutated,
+ @Advice.FieldValue(value = MUTATED_STATIC_FIELD, readOnly = false) int mutatedStatic,
+ @CustomAnnotation int custom) {
+ if (result != (exception ? 0 : VALUE)
+ || enter != VALUE * 2
+ || !(exception ? throwable instanceof RuntimeException : throwable == null)) {
+ throw new AssertionError();
+ }
+ if (!boxedReturn.equals((int) (exception ? NUMERIC_DEFAULT : VALUE))) {
+ throw new AssertionError();
+ }
+ if (argument != VALUE || mutableArgument != VALUE * 2 + 1) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals((int) VALUE) || !boxed[1].equals(VALUE * 2 + 1)) {
+ throw new AssertionError();
+ }
+ if (field != VALUE || mutated != VALUE * 2 + 1 || staticField != VALUE || mutatedStatic != VALUE * 2 + 1) {
+ throw new AssertionError();
+ }
+ if (custom != VALUE) {
+ throw new AssertionError();
+ }
+ exit++;
+ return VALUE;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IntegerDelegationAdvice {
+
+ public static int enter, exit;
+
+ public static boolean exception;
+
+ private int field = VALUE;
+
+ private static int staticField = VALUE;
+
+ public int foo(int argument, int ignored) {
+ return argument;
+ }
+
+ public int bar(int argument, int ignored) {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodEnter(inline = false)
+ public static int enter(@Advice.Unused int value,
+ @Advice.StubValue Object stubValue,
+ @Advice.Argument(0) int argument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) int field,
+ @Advice.FieldValue(STATIC_FIELD) int staticField,
+ @CustomAnnotation int custom) {
+ if (value != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ if ((Integer) stubValue != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ if (argument != VALUE) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals((int) VALUE) || !boxed[1].equals((int) (VALUE))) {
+ throw new AssertionError();
+ }
+ if (field != VALUE || staticField != VALUE) {
+ throw new AssertionError();
+ }
+ if (custom != VALUE) {
+ throw new AssertionError();
+ }
+ enter++;
+ return VALUE * 2;
+ }
+
+ @Advice.OnMethodExit(inline = false, onThrowable = Exception.class)
+ public static int exit(@Advice.Return int result,
+ @Advice.Enter int enter,
+ @Advice.Thrown Throwable throwable,
+ @Advice.Return Object boxedReturn,
+ @Advice.Argument(0) int argument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) int field,
+ @Advice.FieldValue(STATIC_FIELD) int staticField,
+ @CustomAnnotation int custom) {
+ if (result != (exception ? 0 : VALUE)
+ || enter != VALUE * 2
+ || !(exception ? throwable instanceof RuntimeException : throwable == null)) {
+ throw new AssertionError();
+ }
+ if (!boxedReturn.equals((int) (exception ? NUMERIC_DEFAULT : VALUE))) {
+ throw new AssertionError();
+ }
+ if (argument != VALUE) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals((int) VALUE) || !boxed[1].equals((int) VALUE)) {
+ throw new AssertionError();
+ }
+ if (field != VALUE || staticField != VALUE) {
+ throw new AssertionError();
+ }
+ if (custom != VALUE) {
+ throw new AssertionError();
+ }
+ exit++;
+ return VALUE;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class LongInlineAdvice {
+
+ public static int enter, exit;
+
+ public static boolean exception;
+
+ private long field = VALUE, mutated = VALUE;
+
+ private static long staticField = VALUE, mutatedStatic = VALUE;
+
+ public long foo(long argument, long mutableArgument) {
+ return argument;
+ }
+
+ public long bar(long argument, long mutableArgument) {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodEnter
+ public static long enter(@Advice.Unused long value,
+ @Advice.StubValue Object stubValue,
+ @Advice.Argument(0) long argument,
+ @Advice.Argument(value = 1, readOnly = false) long mutableArgument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) long field,
+ @Advice.FieldValue(STATIC_FIELD) long staticField,
+ @Advice.FieldValue(value = MUTATED, readOnly = false) long mutated,
+ @Advice.FieldValue(value = MUTATED_STATIC_FIELD, readOnly = false) long mutatedStatic,
+ @CustomAnnotation long custom) {
+ if (value != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ value = VALUE;
+ if (value != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ if ((Long) stubValue != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ if (argument != VALUE || mutableArgument != VALUE) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals((long) VALUE) || !boxed[1].equals((long) (VALUE))) {
+ throw new AssertionError();
+ }
+ mutableArgument = VALUE * 2;
+ if (boxed.length != 2 || !boxed[0].equals((long) VALUE) || !boxed[1].equals((long) (VALUE * 2))) {
+ throw new AssertionError();
+ }
+ if (field != VALUE || mutated != VALUE || staticField != VALUE || mutatedStatic != VALUE) {
+ throw new AssertionError();
+ }
+ mutated = mutatedStatic = VALUE * 2;
+ if (custom != VALUE) {
+ throw new AssertionError();
+ }
+ enter++;
+ return VALUE * 2;
+ }
+
+ @Advice.OnMethodExit(onThrowable = Exception.class)
+ public static long exit(@Advice.Return long result,
+ @Advice.Enter long enter,
+ @Advice.Thrown Throwable throwable,
+ @Advice.Return Object boxedReturn,
+ @Advice.Argument(0) long argument,
+ @Advice.Argument(value = 1, readOnly = false) long mutableArgument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) long field,
+ @Advice.FieldValue(STATIC_FIELD) long staticField,
+ @Advice.FieldValue(value = MUTATED, readOnly = false) long mutated,
+ @Advice.FieldValue(value = MUTATED_STATIC_FIELD, readOnly = false) long mutatedStatic,
+ @CustomAnnotation long custom) {
+ if (result != (exception ? 0 : VALUE)
+ || enter != VALUE * 2
+ || !(exception ? throwable instanceof RuntimeException : throwable == null)) {
+ throw new AssertionError();
+ }
+ if (!boxedReturn.equals((long) (exception ? NUMERIC_DEFAULT : VALUE))) {
+ throw new AssertionError();
+ }
+ if (argument != VALUE || mutableArgument != VALUE * 2) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals((long) VALUE) || !boxed[1].equals((long) (VALUE * 2))) {
+ throw new AssertionError();
+ }
+ if (field != VALUE || mutated != VALUE * 2 || staticField != VALUE || mutatedStatic != VALUE * 2) {
+ throw new AssertionError();
+ }
+ if (custom != VALUE) {
+ throw new AssertionError();
+ }
+ exit++;
+ return VALUE;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class LongDelegationAdvice {
+
+ public static int enter, exit;
+
+ public static boolean exception;
+
+ private long field = VALUE;
+
+ private static long staticField = VALUE;
+
+ public long foo(long argument, long ignored) {
+ return argument;
+ }
+
+ public long bar(long argument, long ignored) {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodEnter(inline = false)
+ public static long enter(@Advice.Unused long value,
+ @Advice.StubValue Object stubValue,
+ @Advice.Argument(0) long argument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) long field,
+ @Advice.FieldValue(STATIC_FIELD) long staticField,
+ @CustomAnnotation long custom) {
+ if (value != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ if ((Long) stubValue != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ if (argument != VALUE) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals((long) VALUE) || !boxed[1].equals((long) (VALUE))) {
+ throw new AssertionError();
+ }
+ if (field != VALUE || staticField != VALUE) {
+ throw new AssertionError();
+ }
+ if (custom != VALUE) {
+ throw new AssertionError();
+ }
+ enter++;
+ return VALUE * 2;
+ }
+
+ @Advice.OnMethodExit(inline = false, onThrowable = Exception.class)
+ public static long exit(@Advice.Return long result,
+ @Advice.Enter long enter,
+ @Advice.Thrown Throwable throwable,
+ @Advice.Return Object boxedReturn,
+ @Advice.Argument(0) long argument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) long field,
+ @Advice.FieldValue(STATIC_FIELD) long staticField,
+ @CustomAnnotation long custom) {
+ if (result != (exception ? 0 : VALUE)
+ || enter != VALUE * 2
+ || !(exception ? throwable instanceof RuntimeException : throwable == null)) {
+ throw new AssertionError();
+ }
+ if (!boxedReturn.equals((long) (exception ? NUMERIC_DEFAULT : VALUE))) {
+ throw new AssertionError();
+ }
+ if (argument != VALUE) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals((long) VALUE) || !boxed[1].equals((long) VALUE)) {
+ throw new AssertionError();
+ }
+ if (field != VALUE || staticField != VALUE) {
+ throw new AssertionError();
+ }
+ if (custom != VALUE) {
+ throw new AssertionError();
+ }
+ exit++;
+ return VALUE;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FloatInlineAdvice {
+
+ public static int enter, exit;
+
+ public static boolean exception;
+
+ private float field = VALUE, mutated = VALUE;
+
+ private static float staticField = VALUE, mutatedStatic = VALUE;
+
+ public float foo(float argument, float mutableArgument) {
+ return argument;
+ }
+
+ public float bar(float argument, float mutableArgument) {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodEnter
+ public static float enter(@Advice.Unused float value,
+ @Advice.StubValue Object stubValue,
+ @Advice.Argument(0) float argument,
+ @Advice.Argument(value = 1, readOnly = false) float mutableArgument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) float field,
+ @Advice.FieldValue(STATIC_FIELD) float staticField,
+ @Advice.FieldValue(value = MUTATED, readOnly = false) float mutated,
+ @Advice.FieldValue(value = MUTATED_STATIC_FIELD, readOnly = false) float mutatedStatic,
+ @CustomAnnotation float custom) {
+ if (value != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ value = VALUE;
+ if (value != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ if ((Float) stubValue != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ if (argument != VALUE || mutableArgument != VALUE) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals((float) VALUE) || !boxed[1].equals((float) (VALUE))) {
+ throw new AssertionError();
+ }
+ mutableArgument = VALUE * 2;
+ if (boxed.length != 2 || !boxed[0].equals((float) VALUE) || !boxed[1].equals((float) (VALUE * 2))) {
+ throw new AssertionError();
+ }
+ if (field != VALUE || mutated != VALUE || staticField != VALUE || mutatedStatic != VALUE) {
+ throw new AssertionError();
+ }
+ mutated = mutatedStatic = VALUE * 2;
+ if (custom != VALUE) {
+ throw new AssertionError();
+ }
+ enter++;
+ return VALUE * 2;
+ }
+
+ @Advice.OnMethodExit(onThrowable = Exception.class)
+ public static float exit(@Advice.Return float result,
+ @Advice.Enter float enter,
+ @Advice.Thrown Throwable throwable,
+ @Advice.Return Object boxedReturn,
+ @Advice.Argument(0) float argument,
+ @Advice.Argument(value = 1, readOnly = false) float mutableArgument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) float field,
+ @Advice.FieldValue(STATIC_FIELD) float staticField,
+ @Advice.FieldValue(value = MUTATED, readOnly = false) float mutated,
+ @Advice.FieldValue(value = MUTATED_STATIC_FIELD, readOnly = false) float mutatedStatic,
+ @CustomAnnotation float custom) {
+ if (result != (exception ? 0 : VALUE)
+ || enter != VALUE * 2
+ || !(exception ? throwable instanceof RuntimeException : throwable == null)) {
+ throw new AssertionError();
+ }
+ if (!boxedReturn.equals((float) (exception ? NUMERIC_DEFAULT : VALUE))) {
+ throw new AssertionError();
+ }
+ if (argument != VALUE || mutableArgument != VALUE * 2) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals((float) VALUE) || !boxed[1].equals((float) (VALUE * 2))) {
+ throw new AssertionError();
+ }
+ if (field != VALUE || mutated != VALUE * 2 || staticField != VALUE || mutatedStatic != VALUE * 2) {
+ throw new AssertionError();
+ }
+ if (custom != VALUE) {
+ throw new AssertionError();
+ }
+ exit++;
+ return VALUE;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FloatDelegationAdvice {
+
+ public static int enter, exit;
+
+ public static boolean exception;
+
+ private float field = VALUE;
+
+ private static float staticField = VALUE;
+
+ public float foo(float argument, float ignored) {
+ return argument;
+ }
+
+ public float bar(float argument, float ignored) {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodEnter(inline = false)
+ public static float enter(@Advice.Unused float value,
+ @Advice.StubValue Object stubValue,
+ @Advice.Argument(0) float argument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) float field,
+ @Advice.FieldValue(STATIC_FIELD) float staticField,
+ @CustomAnnotation float custom) {
+ if (value != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ if ((Float) stubValue != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ if (argument != VALUE) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals((float) VALUE) || !boxed[1].equals((float) (VALUE))) {
+ throw new AssertionError();
+ }
+ if (field != VALUE || staticField != VALUE) {
+ throw new AssertionError();
+ }
+ if (custom != VALUE) {
+ throw new AssertionError();
+ }
+ enter++;
+ return VALUE * 2;
+ }
+
+ @Advice.OnMethodExit(inline = false, onThrowable = Exception.class)
+ public static float exit(@Advice.Return float result,
+ @Advice.Enter float enter,
+ @Advice.Thrown Throwable throwable,
+ @Advice.Return Object boxedReturn,
+ @Advice.Argument(0) float argument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) float field,
+ @Advice.FieldValue(STATIC_FIELD) float staticField,
+ @CustomAnnotation float custom) {
+ if (result != (exception ? 0 : VALUE)
+ || enter != VALUE * 2
+ || !(exception ? throwable instanceof RuntimeException : throwable == null)) {
+ throw new AssertionError();
+ }
+ if (!boxedReturn.equals((float) (exception ? NUMERIC_DEFAULT : VALUE))) {
+ throw new AssertionError();
+ }
+ if (argument != VALUE) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals((float) VALUE) || !boxed[1].equals((float) VALUE)) {
+ throw new AssertionError();
+ }
+ if (field != VALUE || staticField != VALUE) {
+ throw new AssertionError();
+ }
+ if (custom != VALUE) {
+ throw new AssertionError();
+ }
+ exit++;
+ return VALUE;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class DoubleInlineAdvice {
+
+ public static int enter, exit;
+
+ public static boolean exception;
+
+ private double field = VALUE, mutated = VALUE;
+
+ private static double staticField = VALUE, mutatedStatic = VALUE;
+
+ public double foo(double argument, double mutableArgument) {
+ return argument;
+ }
+
+ public double bar(double argument, double mutableArgument) {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodEnter
+ public static double enter(@Advice.Unused double value,
+ @Advice.StubValue Object stubValue,
+ @Advice.Argument(0) double argument,
+ @Advice.Argument(value = 1, readOnly = false) double mutableArgument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) double field,
+ @Advice.FieldValue(STATIC_FIELD) double staticField,
+ @Advice.FieldValue(value = MUTATED, readOnly = false) double mutated,
+ @Advice.FieldValue(value = MUTATED_STATIC_FIELD, readOnly = false) double mutatedStatic,
+ @CustomAnnotation double custom) {
+ if (value != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ value = VALUE;
+ if (value != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ if ((Double) stubValue != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ if (argument != VALUE || mutableArgument != VALUE) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals((double) VALUE) || !boxed[1].equals((double) (VALUE))) {
+ throw new AssertionError();
+ }
+ mutableArgument = VALUE * 2;
+ if (boxed.length != 2 || !boxed[0].equals((double) VALUE) || !boxed[1].equals((double) (VALUE * 2))) {
+ throw new AssertionError();
+ }
+ if (field != VALUE || mutated != VALUE || staticField != VALUE || mutatedStatic != VALUE) {
+ throw new AssertionError();
+ }
+ mutated = mutatedStatic = VALUE * 2;
+ if (custom != VALUE) {
+ throw new AssertionError();
+ }
+ enter++;
+ return VALUE * 2;
+ }
+
+ @Advice.OnMethodExit(onThrowable = Exception.class)
+ public static double exit(@Advice.Return double result,
+ @Advice.Enter double enter,
+ @Advice.Thrown Throwable throwable,
+ @Advice.Return Object boxedReturn,
+ @Advice.Argument(0) double argument,
+ @Advice.Argument(value = 1, readOnly = false) double mutableArgument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) double field,
+ @Advice.FieldValue(STATIC_FIELD) double staticField,
+ @Advice.FieldValue(value = MUTATED, readOnly = false) double mutated,
+ @Advice.FieldValue(value = MUTATED_STATIC_FIELD, readOnly = false) double mutatedStatic,
+ @CustomAnnotation double custom) {
+ if (result != (exception ? 0 : VALUE)
+ || enter != VALUE * 2
+ || !(exception ? throwable instanceof RuntimeException : throwable == null)) {
+ throw new AssertionError();
+ }
+ if (!boxedReturn.equals((double) (exception ? NUMERIC_DEFAULT : VALUE))) {
+ throw new AssertionError();
+ }
+ if (argument != VALUE || mutableArgument != VALUE * 2) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals((double) VALUE) || !boxed[1].equals((double) (VALUE * 2))) {
+ throw new AssertionError();
+ }
+ if (field != VALUE || mutated != VALUE * 2 || staticField != VALUE || mutatedStatic != VALUE * 2) {
+ throw new AssertionError();
+ }
+ if (custom != VALUE) {
+ throw new AssertionError();
+ }
+ exit++;
+ return VALUE;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class DoubleDelegationAdvice {
+
+ public static int enter, exit;
+
+ public static boolean exception;
+
+ private double field = VALUE;
+
+ private static double staticField = VALUE;
+
+ public double foo(double argument, double ignored) {
+ return argument;
+ }
+
+ public double bar(double argument, double ignored) {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodEnter(inline = false)
+ public static double enter(@Advice.Unused double value,
+ @Advice.StubValue Object stubValue,
+ @Advice.Argument(0) double argument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) double field,
+ @Advice.FieldValue(STATIC_FIELD) double staticField,
+ @CustomAnnotation double custom) {
+ if (value != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ if ((Double) stubValue != NUMERIC_DEFAULT) {
+ throw new AssertionError();
+ }
+ if (argument != VALUE) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals((double) VALUE) || !boxed[1].equals((double) (VALUE))) {
+ throw new AssertionError();
+ }
+ if (field != VALUE || staticField != VALUE) {
+ throw new AssertionError();
+ }
+ if (custom != VALUE) {
+ throw new AssertionError();
+ }
+ enter++;
+ return VALUE * 2;
+ }
+
+ @Advice.OnMethodExit(inline = false, onThrowable = Exception.class)
+ public static double exit(@Advice.Return double result,
+ @Advice.Enter double enter,
+ @Advice.Thrown Throwable throwable,
+ @Advice.Return Object boxedReturn,
+ @Advice.Argument(0) double argument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) double field,
+ @Advice.FieldValue(STATIC_FIELD) double staticField,
+ @CustomAnnotation double custom) {
+ if (result != (exception ? 0 : VALUE)
+ || enter != VALUE * 2
+ || !(exception ? throwable instanceof RuntimeException : throwable == null)) {
+ throw new AssertionError();
+ }
+ if (!boxedReturn.equals((double) (exception ? NUMERIC_DEFAULT : VALUE))) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals((double) VALUE) || !boxed[1].equals((double) VALUE)) {
+ throw new AssertionError();
+ }
+ if (custom != VALUE) {
+ throw new AssertionError();
+ }
+ exit++;
+ return VALUE;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ReferenceInlineAdvice {
+
+ public static int enter, exit;
+
+ public static boolean exception;
+
+ private Object field = FOO, mutated = FOO;
+
+ private static Object staticField = FOO, mutatedStatic = FOO;
+
+ public Object foo(Object argument, Object mutableArgument) {
+ return argument;
+ }
+
+ public Object bar(Object argument, Object mutableArgument) {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodEnter
+ public static Object enter(@Advice.Unused Object value,
+ @Advice.StubValue Object stubValue,
+ @Advice.Argument(0) Object argument,
+ @Advice.Argument(value = 1, readOnly = false) Object mutableArgument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) Object field,
+ @Advice.FieldValue(STATIC_FIELD) Object staticField,
+ @Advice.FieldValue(value = MUTATED, readOnly = false) Object mutated,
+ @Advice.FieldValue(value = MUTATED_STATIC_FIELD, readOnly = false) Object mutatedStatic,
+ @CustomAnnotation String custom) {
+ if (value != null) {
+ throw new AssertionError();
+ }
+ value = FOO;
+ if (value != null) {
+ throw new AssertionError();
+ }
+ if (stubValue != null) {
+ throw new AssertionError();
+ }
+ if (!argument.equals(FOO) || !mutableArgument.equals(FOO)) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals(FOO) || !boxed[1].equals(FOO)) {
+ throw new AssertionError();
+ }
+ mutableArgument = BAR;
+ if (boxed.length != 2 || !boxed[0].equals(FOO) || !boxed[1].equals(BAR)) {
+ throw new AssertionError();
+ }
+ if (!field.equals(FOO) || !mutated.equals(FOO) || !staticField.equals(FOO) || !mutatedStatic.equals(FOO)) {
+ throw new AssertionError();
+ }
+ mutated = mutatedStatic = BAR;
+ if (!custom.equals(FOO)) {
+ throw new AssertionError();
+ }
+ enter++;
+ return FOO + BAR;
+ }
+
+ @Advice.OnMethodExit(onThrowable = Exception.class)
+ public static Object exit(@Advice.Return Object result,
+ @Advice.Enter Object enter,
+ @Advice.Thrown Throwable throwable,
+ @Advice.Return Object boxedReturn,
+ @Advice.Argument(0) Object argument,
+ @Advice.Argument(value = 1, readOnly = false) Object mutableArgument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) Object field,
+ @Advice.FieldValue(STATIC_FIELD) Object staticField,
+ @Advice.FieldValue(value = MUTATED, readOnly = false) Object mutated,
+ @Advice.FieldValue(value = MUTATED_STATIC_FIELD, readOnly = false) Object mutatedStatic,
+ @CustomAnnotation String custom) {
+ if ((exception ? result != null : !result.equals(FOO))
+ || !enter.equals(FOO + BAR)
+ || !(exception ? throwable instanceof RuntimeException : throwable == null)) {
+ throw new AssertionError();
+ }
+ if (exception ? boxedReturn != null : !boxedReturn.equals(FOO)) {
+ throw new AssertionError();
+ }
+ if (!argument.equals(FOO) || !mutableArgument.equals(BAR)) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals(FOO) || !boxed[1].equals(BAR)) {
+ throw new AssertionError();
+ }
+ if (!field.equals(FOO) || !mutated.equals(BAR) || !staticField.equals(FOO) || !mutatedStatic.equals(BAR)) {
+ throw new AssertionError();
+ }
+ if (!custom.equals(FOO)) {
+ throw new AssertionError();
+ }
+ exit++;
+ return FOO;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ReferenceDelegationAdvice {
+
+ public static int enter, exit;
+
+ public static boolean exception;
+
+ private Object field = FOO;
+
+ private static Object staticField = FOO;
+
+ public Object foo(Object argument, Object ignored) {
+ return argument;
+ }
+
+ public Object bar(Object argument, Object ignored) {
+ throw new RuntimeException();
+ }
+
+ @Advice.OnMethodEnter(inline = false)
+ public static Object enter(@Advice.Unused Object value,
+ @Advice.StubValue Object stubValue,
+ @Advice.Argument(0) Object argument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) Object field,
+ @Advice.FieldValue(STATIC_FIELD) Object staticField,
+ @CustomAnnotation String custom) {
+ if (value != null) {
+ throw new AssertionError();
+ }
+ if (stubValue != null) {
+ throw new AssertionError();
+ }
+ if (!argument.equals(FOO)) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals(FOO) || !boxed[1].equals(FOO)) {
+ throw new AssertionError();
+ }
+ if (!field.equals(FOO) || !staticField.equals(FOO)) {
+ throw new AssertionError();
+ }
+ if (!custom.equals(FOO)) {
+ throw new AssertionError();
+ }
+ enter++;
+ return FOO + BAR;
+ }
+
+ @Advice.OnMethodExit(inline = false, onThrowable = Exception.class)
+ public static Object exit(@Advice.Return Object result,
+ @Advice.Enter Object enter,
+ @Advice.Thrown Throwable throwable,
+ @Advice.Return Object boxedReturn,
+ @Advice.Argument(0) Object argument,
+ @Advice.AllArguments Object[] boxed,
+ @Advice.FieldValue(FIELD) Object field,
+ @Advice.FieldValue(STATIC_FIELD) Object staticField,
+ @CustomAnnotation String custom) {
+ if ((exception ? result != null : !result.equals(FOO))
+ || !enter.equals(FOO + BAR)
+ || !(exception ? throwable instanceof RuntimeException : throwable == null)) {
+ throw new AssertionError();
+ }
+ if (exception ? boxedReturn != null : !boxedReturn.equals(FOO)) {
+ throw new AssertionError();
+ }
+ if (!argument.equals(FOO)) {
+ throw new AssertionError();
+ }
+ if (boxed.length != 2 || !boxed[0].equals(FOO) || !boxed[1].equals(FOO)) {
+ throw new AssertionError();
+ }
+ if (!field.equals(FOO) || !staticField.equals(FOO)) {
+ throw new AssertionError();
+ }
+ if (!custom.equals(FOO)) {
+ throw new AssertionError();
+ }
+ exit++;
+ return FOO;
+ }
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.PARAMETER)
+ @SuppressWarnings("unused")
+ private @interface CustomAnnotation {
+
+ boolean booleanValue() default BOOLEAN;
+
+ byte byteValue() default VALUE;
+
+ short shortValue() default VALUE;
+
+ char charValue() default VALUE;
+
+ int intValue() default VALUE;
+
+ long longValue() default VALUE;
+
+ float floatValue() default VALUE;
+
+ double doubleValue() default VALUE;
+
+ String objectValue() default FOO;
+ }
+
+ private static class SerializationAssertion extends AsmVisitorWrapper.AbstractBase {
+
+ @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 SerializationClassVisitor(classVisitor);
+ }
+
+ private static class SerializationClassVisitor extends ClassVisitor {
+
+ public SerializationClassVisitor(ClassVisitor classVisitor) {
+ super(Opcodes.ASM5, classVisitor);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+ return new SerializationMethodVisitor(super.visitMethod(access, name, desc, signature, exceptions));
+ }
+ }
+
+ private static class SerializationMethodVisitor extends MethodVisitor {
+
+ public SerializationMethodVisitor(MethodVisitor methodVisitor) {
+ super(Opcodes.ASM5, methodVisitor);
+ }
+
+ @Override
+ public void visitTypeInsn(int opcode, String type) {
+ if (type.equals(Type.getInternalName(ObjectInputStream.class))) {
+ throw new AssertionError("Unexpected deserialization");
+ }
+ super.visitTypeInsn(opcode, type);
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceVariableAccessTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceVariableAccessTest.java
new file mode 100644
index 0000000..d4e613a
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceVariableAccessTest.java
@@ -0,0 +1,181 @@
+package net.bytebuddy.asm;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class AdviceVariableAccessTest {
+
+ private static final String READ = "read", WRITE = "write";
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {BooleanSample.class, true, boolean.class},
+ {ByteSample.class, (byte) 42, byte.class},
+ {ShortSample.class, (short) 42, short.class},
+ {CharacterSample.class, (char) 42, char.class},
+ {IntegerSample.class, 42, int.class},
+ {LongSample.class, 42L, long.class},
+ {FloatSample.class, 42f, float.class},
+ {DoubleSample.class, 42d, double.class},
+ {ReferenceSample.class, "foo", Object.class},
+ });
+ }
+
+ private final Class<?> sample, type;
+
+ private final Object value;
+
+ public AdviceVariableAccessTest(Class<?> sample, Object value, Class<?> type) {
+ this.sample = sample;
+ this.value = value;
+ this.type = type;
+ }
+
+ @Test
+ public void testArray() throws Exception {
+ Class<?> dynamicType = new ByteBuddy()
+ .redefine(sample)
+ .visit(Advice.to(AdviceVariableAccessTest.class).on(named(READ).or(named(WRITE))))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Object instance = dynamicType.getDeclaredConstructor().newInstance();
+ assertThat(dynamicType.getDeclaredMethod(WRITE, type).invoke(instance, value), nullValue(Object.class));
+ assertThat(dynamicType.getDeclaredMethod(READ).invoke(instance), is(value));
+ }
+
+ @Advice.OnMethodExit
+ private static void advice() {
+ /* empty */
+ }
+
+ public static class BooleanSample {
+
+ private final boolean[] array = new boolean[1];
+
+ public void write(boolean value) {
+ array[0] = value;
+ }
+
+ public boolean read() {
+ return array[0];
+ }
+ }
+
+ public static class ByteSample {
+
+ private final byte[] array = new byte[1];
+
+ public void write(byte value) {
+ array[0] = value;
+ }
+
+ public byte read() {
+ return array[0];
+ }
+ }
+
+ public static class ShortSample {
+
+ private final short[] array = new short[1];
+
+ public void write(short value) {
+ array[0] = value;
+ }
+
+ public short read() {
+ return array[0];
+ }
+ }
+
+ public static class CharacterSample {
+
+ private final char[] array = new char[1];
+
+ public void write(char value) {
+ array[0] = value;
+ }
+
+ public char read() {
+ return array[0];
+ }
+ }
+
+ public static class IntegerSample {
+
+ private final int[] array = new int[1];
+
+ public void write(int value) {
+ array[0] = value;
+ }
+
+ public int read() {
+ return array[0];
+ }
+ }
+
+ public static class LongSample {
+
+ private final long[] array = new long[1];
+
+ public void write(long value) {
+ array[0] = value;
+ }
+
+ public long read() {
+ return array[0];
+ }
+ }
+
+ public static class FloatSample {
+
+ private final float[] array = new float[1];
+
+ public void write(float value) {
+ array[0] = value;
+ }
+
+ public float read() {
+ return array[0];
+ }
+ }
+
+ public static class DoubleSample {
+
+ private final double[] array = new double[1];
+
+ public void write(double value) {
+ array[0] = value;
+ }
+
+ public double read() {
+ return array[0];
+ }
+ }
+
+ public static class ReferenceSample {
+
+ private final Object[] array = new Object[1];
+
+ public void write(Object value) {
+ array[0] = value;
+ }
+
+ public Object read() {
+ return array[0];
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AsmVisitorWrapperCompoundTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AsmVisitorWrapperCompoundTest.java
new file mode 100644
index 0000000..92c3e6a
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AsmVisitorWrapperCompoundTest.java
@@ -0,0 +1,111 @@
+package net.bytebuddy.asm;
+
+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.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.ClassVisitor;
+
+import java.util.Collections;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class AsmVisitorWrapperCompoundTest {
+
+ private static final int FOO = 1, BAR = 2, QUX = 3, BAZ = 4, FLAGS = 42;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription instrumentedType;
+
+ @Mock
+ private AsmVisitorWrapper wrapper, prepend, append;
+
+ @Mock
+ private ClassVisitor wrapperVisitor, prependVisitor, appendVisitor, resultVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @Mock
+ private TypePool typePool;
+
+ @Mock
+ private FieldList<FieldDescription.InDefinedShape> fields;
+
+ @Mock
+ private MethodList<?> methods;
+
+ @Before
+ public void setUp() throws Exception {
+ when(prepend.wrap(instrumentedType, prependVisitor, implementationContext, typePool, fields, methods, FLAGS, FLAGS * 2)).thenReturn(wrapperVisitor);
+ when(wrapper.wrap(instrumentedType, wrapperVisitor, implementationContext, typePool, fields, methods, FLAGS, FLAGS * 2)).thenReturn(appendVisitor);
+ when(append.wrap(instrumentedType, appendVisitor, implementationContext, typePool, fields, methods, FLAGS, FLAGS * 2)).thenReturn(resultVisitor);
+ when(prepend.mergeReader(FOO)).thenReturn(BAR);
+ when(wrapper.mergeReader(BAR)).thenReturn(QUX);
+ when(append.mergeReader(QUX)).thenReturn(BAZ);
+ when(prepend.mergeWriter(FOO)).thenReturn(BAR);
+ when(wrapper.mergeWriter(BAR)).thenReturn(QUX);
+ when(append.mergeWriter(QUX)).thenReturn(BAZ);
+ }
+
+ @Test
+ public void testWrapperChain() throws Exception {
+ AsmVisitorWrapper.Compound compound = new AsmVisitorWrapper.Compound(prepend, wrapper, append);
+ assertThat(compound.wrap(instrumentedType, prependVisitor, implementationContext, typePool, fields, methods, FLAGS, FLAGS * 2), is(resultVisitor));
+ verify(prepend).wrap(instrumentedType, prependVisitor, implementationContext, typePool, fields, methods, FLAGS, FLAGS * 2);
+ verifyNoMoreInteractions(prepend);
+ verify(wrapper).wrap(instrumentedType, wrapperVisitor, implementationContext, typePool, fields, methods, FLAGS, FLAGS * 2);
+ verifyNoMoreInteractions(wrapper);
+ verify(append).wrap(instrumentedType, appendVisitor, implementationContext, typePool, fields, methods, FLAGS, FLAGS * 2);
+ verifyNoMoreInteractions(append);
+ }
+
+ @Test
+ public void testReaderFlags() throws Exception {
+ AsmVisitorWrapper.Compound compound = new AsmVisitorWrapper.Compound(prepend, wrapper, append);
+ assertThat(compound.mergeReader(FOO), is(BAZ));
+ verify(prepend).mergeReader(FOO);
+ verifyNoMoreInteractions(prepend);
+ verify(wrapper).mergeReader(BAR);
+ verifyNoMoreInteractions(wrapper);
+ verify(append).mergeReader(QUX);
+ verifyNoMoreInteractions(append);
+ }
+
+ @Test
+ public void testWriterFlags() throws Exception {
+ AsmVisitorWrapper.Compound compound = new AsmVisitorWrapper.Compound(prepend, wrapper, append);
+ assertThat(compound.mergeWriter(FOO), is(BAZ));
+ verify(prepend).mergeWriter(FOO);
+ verifyNoMoreInteractions(prepend);
+ verify(wrapper).mergeWriter(BAR);
+ verifyNoMoreInteractions(wrapper);
+ verify(append).mergeWriter(QUX);
+ verifyNoMoreInteractions(append);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AsmVisitorWrapper.Compound.class).create(new ObjectPropertyAssertion.Creator<List<?>>() {
+ @Override
+ public List<?> create() {
+ return Collections.singletonList(mock(AsmVisitorWrapper.class));
+ }
+ }).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AsmVisitorWrapperForDeclaredFieldsTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AsmVisitorWrapperForDeclaredFieldsTest.java
new file mode 100644
index 0000000..b76b508
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AsmVisitorWrapperForDeclaredFieldsTest.java
@@ -0,0 +1,128 @@
+package net.bytebuddy.asm;
+
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.pool.TypePool;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class AsmVisitorWrapperForDeclaredFieldsTest {
+
+ private static final int MODIFIERS = 42, IRRELEVANT = -1;
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux", BAZ = "baz";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private ElementMatcher<? super FieldDescription.InDefinedShape> matcher;
+
+ @Mock
+ private AsmVisitorWrapper.ForDeclaredFields.FieldVisitorWrapper fieldVisitorWrapper;
+
+ @Mock
+ private TypeDescription instrumentedType;
+
+ @Mock
+ private FieldDescription.InDefinedShape foo, bar;
+
+ @Mock
+ private ClassVisitor classVisitor;
+
+ @Mock
+ private TypePool typePool;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @Mock
+ private FieldVisitor fieldVisitor, wrappedVisitor;
+
+ @Before
+ public void setUp() throws Exception {
+ when(foo.getInternalName()).thenReturn(FOO);
+ when(foo.getDescriptor()).thenReturn(QUX);
+ when(bar.getInternalName()).thenReturn(BAR);
+ when(bar.getDescriptor()).thenReturn(QUX);
+ when(classVisitor.visitField(eq(MODIFIERS), any(String.class), eq(QUX), eq(BAZ), eq(QUX + BAZ))).thenReturn(fieldVisitor);
+ when(fieldVisitorWrapper.wrap(instrumentedType, foo, fieldVisitor)).thenReturn(wrappedVisitor);
+ when(matcher.matches(foo)).thenReturn(true);
+ }
+
+ @Test
+ public void testMatched() throws Exception {
+ assertThat(new AsmVisitorWrapper.ForDeclaredFields()
+ .field(matcher, fieldVisitorWrapper)
+ .wrap(instrumentedType,
+ classVisitor,
+ implementationContext,
+ typePool,
+ new FieldList.Explicit<FieldDescription.InDefinedShape>(foo, bar),
+ new MethodList.Empty<MethodDescription>(),
+ IRRELEVANT,
+ IRRELEVANT)
+ .visitField(MODIFIERS, FOO, QUX, BAZ, QUX + BAZ), is(wrappedVisitor));
+ verify(matcher).matches(foo);
+ verifyNoMoreInteractions(matcher);
+ verify(fieldVisitorWrapper).wrap(instrumentedType, foo, fieldVisitor);
+ verifyNoMoreInteractions(fieldVisitorWrapper);
+ }
+
+ @Test
+ public void testNotMatched() throws Exception {
+ assertThat(new AsmVisitorWrapper.ForDeclaredFields()
+ .field(matcher, fieldVisitorWrapper)
+ .wrap(instrumentedType,
+ classVisitor,
+ implementationContext,
+ typePool,
+ new FieldList.Explicit<FieldDescription.InDefinedShape>(foo, bar),
+ new MethodList.Empty<MethodDescription>(),
+ IRRELEVANT,
+ IRRELEVANT)
+ .visitField(MODIFIERS, BAR, QUX, BAZ, QUX + BAZ), is(fieldVisitor));
+ verify(matcher).matches(bar);
+ verifyNoMoreInteractions(matcher);
+ verifyZeroInteractions(fieldVisitorWrapper);
+ }
+
+ @Test
+ public void testUnknown() throws Exception {
+ assertThat(new AsmVisitorWrapper.ForDeclaredFields()
+ .field(matcher, fieldVisitorWrapper)
+ .wrap(instrumentedType,
+ classVisitor,
+ implementationContext,
+ typePool,
+ new FieldList.Explicit<FieldDescription.InDefinedShape>(foo, bar),
+ new MethodList.Empty<MethodDescription>(),
+ IRRELEVANT,
+ IRRELEVANT)
+ .visitField(MODIFIERS, FOO + BAR, QUX, BAZ, QUX + BAZ), is(fieldVisitor));
+ verifyZeroInteractions(matcher);
+ verifyZeroInteractions(fieldVisitorWrapper);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AsmVisitorWrapper.ForDeclaredFields.class).apply();
+ ObjectPropertyAssertion.of(AsmVisitorWrapper.ForDeclaredFields.Entry.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AsmVisitorWrapperForDeclaredMethodsTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AsmVisitorWrapperForDeclaredMethodsTest.java
new file mode 100644
index 0000000..353b839
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AsmVisitorWrapperForDeclaredMethodsTest.java
@@ -0,0 +1,141 @@
+package net.bytebuddy.asm;
+
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.pool.TypePool;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class AsmVisitorWrapperForDeclaredMethodsTest {
+
+ private static final int MODIFIERS = 42, FLAGS = 42;
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux", BAZ = "baz";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private ElementMatcher<? super MethodDescription> matcher;
+
+ @Mock
+ private AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper methodVisitorWrapper;
+
+ @Mock
+ private TypeDescription instrumentedType;
+
+ @Mock
+ private MethodDescription.InDefinedShape foo, bar;
+
+ @Mock
+ private ClassVisitor classVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @Mock
+ private TypePool typePool;
+
+ @Mock
+ private MethodVisitor methodVisitor, wrappedVisitor;
+
+ @Before
+ public void setUp() throws Exception {
+ when(foo.getInternalName()).thenReturn(FOO);
+ when(foo.getDescriptor()).thenReturn(QUX);
+ when(bar.getInternalName()).thenReturn(BAR);
+ when(bar.getDescriptor()).thenReturn(BAZ);
+ when(classVisitor.visitMethod(eq(MODIFIERS), any(String.class), any(String.class), eq(BAZ), eq(new String[]{QUX + BAZ}))).thenReturn(methodVisitor);
+ when(methodVisitorWrapper.wrap(instrumentedType, foo, methodVisitor, implementationContext, typePool, FLAGS, FLAGS * 2)).thenReturn(wrappedVisitor);
+ when(matcher.matches(foo)).thenReturn(true);
+ }
+
+ @Test
+ public void testMatched() throws Exception {
+ assertThat(new AsmVisitorWrapper.ForDeclaredMethods()
+ .method(matcher, methodVisitorWrapper)
+ .wrap(instrumentedType,
+ classVisitor,
+ implementationContext,
+ typePool,
+ new FieldList.Empty<FieldDescription.InDefinedShape>(),
+ new MethodList.Explicit<MethodDescription>(foo, bar),
+ FLAGS,
+ FLAGS * 2)
+ .visitMethod(MODIFIERS, FOO, QUX, BAZ, new String[]{QUX + BAZ}), is(wrappedVisitor));
+ verify(matcher).matches(foo);
+ verifyNoMoreInteractions(matcher);
+ verify(methodVisitorWrapper).wrap(instrumentedType, foo, methodVisitor, implementationContext, typePool, FLAGS, FLAGS * 2);
+ verifyNoMoreInteractions(methodVisitorWrapper);
+ verifyZeroInteractions(typePool);
+ }
+
+ @Test
+ public void testNotMatched() throws Exception {
+ assertThat(new AsmVisitorWrapper.ForDeclaredMethods()
+ .method(matcher, methodVisitorWrapper)
+ .wrap(instrumentedType,
+ classVisitor,
+ implementationContext,
+ typePool,
+ new FieldList.Empty<FieldDescription.InDefinedShape>(),
+ new MethodList.Explicit<MethodDescription>(foo, bar),
+ FLAGS,
+ FLAGS * 2)
+ .visitMethod(MODIFIERS, BAR, BAZ, BAZ, new String[]{QUX + BAZ}), is(methodVisitor));
+ verify(matcher).matches(bar);
+ verifyNoMoreInteractions(matcher);
+ verifyZeroInteractions(methodVisitorWrapper);
+ verifyZeroInteractions(typePool);
+ }
+
+ @Test
+ public void testUnknown() throws Exception {
+ assertThat(new AsmVisitorWrapper.ForDeclaredMethods()
+ .method(matcher, methodVisitorWrapper)
+ .wrap(instrumentedType,
+ classVisitor,
+ implementationContext,
+ typePool,
+ new FieldList.Empty<FieldDescription.InDefinedShape>(),
+ new MethodList.Explicit<MethodDescription>(foo, bar),
+ FLAGS,
+ FLAGS * 2)
+ .visitMethod(MODIFIERS, FOO + BAR, QUX, BAZ, new String[]{QUX + BAZ}), is(methodVisitor));
+ verifyZeroInteractions(matcher);
+ verifyZeroInteractions(methodVisitorWrapper);
+ verifyZeroInteractions(typePool);
+ }
+
+ @Test
+ public void testWriterFlags() throws Exception {
+ assertThat(new AsmVisitorWrapper.ForDeclaredMethods().writerFlags(FLAGS).mergeWriter(0), is(FLAGS));
+ }
+
+ @Test
+ public void testReaderFlags() throws Exception {
+ assertThat(new AsmVisitorWrapper.ForDeclaredMethods().readerFlags(FLAGS).mergeReader(0), is(FLAGS));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AsmVisitorWrapper.ForDeclaredMethods.class).apply();
+ ObjectPropertyAssertion.of(AsmVisitorWrapper.ForDeclaredMethods.Entry.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AsmVisitorWrapperNoOpTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AsmVisitorWrapperNoOpTest.java
new file mode 100644
index 0000000..f7252e6
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/AsmVisitorWrapperNoOpTest.java
@@ -0,0 +1,51 @@
+package net.bytebuddy.asm;
+
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.method.MethodDescription;
+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.ObjectPropertyAssertion;
+import org.junit.Test;
+import org.objectweb.asm.ClassVisitor;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+public class AsmVisitorWrapperNoOpTest {
+
+ private static final int FOO = 42, IGNORED = -1;
+
+ @Test
+ public void testWrapperChain() throws Exception {
+ ClassVisitor classVisitor = mock(ClassVisitor.class);
+ assertThat(AsmVisitorWrapper.NoOp.INSTANCE.wrap(mock(TypeDescription.class),
+ classVisitor,
+ mock(Implementation.Context.class),
+ mock(TypePool.class),
+ new FieldList.Empty<FieldDescription.InDefinedShape>(),
+ new MethodList.Empty<MethodDescription>(),
+ IGNORED,
+ IGNORED), is(classVisitor));
+ verifyZeroInteractions(classVisitor);
+ }
+
+ @Test
+ public void testReaderFlags() throws Exception {
+ assertThat(AsmVisitorWrapper.NoOp.INSTANCE.mergeReader(FOO), is(FOO));
+ }
+
+ @Test
+ public void testWriterFlags() throws Exception {
+ assertThat(AsmVisitorWrapper.NoOp.INSTANCE.mergeWriter(FOO), is(FOO));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AsmVisitorWrapper.NoOp.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/asm/MemberRemovalTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/MemberRemovalTest.java
new file mode 100644
index 0000000..d647fc6
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/MemberRemovalTest.java
@@ -0,0 +1,96 @@
+package net.bytebuddy.asm;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+import static net.bytebuddy.matcher.ElementMatchers.*;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+public class MemberRemovalTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ @Test
+ public void testFieldRemoval() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(new MemberRemoval().stripFields(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ try {
+ type.getDeclaredField(FOO);
+ fail();
+ } catch (NoSuchFieldException ignored) {
+ }
+ assertThat(type.getDeclaredField(BAR), notNullValue(Field.class));
+ }
+
+ @Test
+ public void testMethodRemoval() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(new MemberRemoval().stripMethods(named(FOO)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ try {
+ type.getDeclaredMethod(FOO);
+ fail();
+ } catch (NoSuchMethodException ignored) {
+ }
+ assertThat(type.getDeclaredMethod(BAR), notNullValue(Method.class));
+ }
+
+ @Test
+ public void testConstructorRemoval() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(new MemberRemoval().stripConstructors(takesArguments(0)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ try {
+ type.getDeclaredConstructor();
+ fail();
+ } catch (NoSuchMethodException ignored) {
+ }
+ assertThat(type.getDeclaredConstructor(Void.class), notNullValue(Constructor.class));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MemberRemoval.class).apply();
+ }
+
+ private static class Sample {
+
+ Void foo;
+
+ Void bar;
+
+ Sample() {
+ /* empty */
+ }
+
+ Sample(Void ignored) {
+ /* empty */
+ }
+
+ void foo() {
+ /* empty*/
+ }
+
+ void bar() {
+ /* empty*/
+ }
+ }
+}
\ No newline at end of file
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/asm/MemberSubstitutionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/MemberSubstitutionTest.java
new file mode 100644
index 0000000..899fdbb
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/MemberSubstitutionTest.java
@@ -0,0 +1,828 @@
+package net.bytebuddy.asm;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ByteArrayClassLoader;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.pool.TypePool;
+import net.bytebuddy.test.utility.ClassFileExtraction;
+import org.junit.Test;
+
+import static net.bytebuddy.matcher.ElementMatchers.any;
+import static net.bytebuddy.matcher.ElementMatchers.*;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.assertThat;
+
+public class MemberSubstitutionTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux", BAZ = "baz", RUN = "run";
+
+ @Test
+ public void testFieldReadStub() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(FieldAccessSample.class)
+ .visit(MemberSubstitution.strict().field(named(FOO)).stub().on(named(RUN)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Object instance = type.getDeclaredConstructor().newInstance();
+ assertThat(type.getDeclaredField(FOO).get(instance), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(instance), is((Object) BAR));
+ assertThat(type.getDeclaredMethod(RUN).invoke(instance), nullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).get(instance), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(instance), nullValue(Object.class));
+ }
+
+ @Test
+ public void testStaticFieldReadStub() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(StaticFieldAccessSample.class)
+ .visit(MemberSubstitution.strict().field(named(FOO)).stub().on(named(RUN)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Object instance = type.getDeclaredConstructor().newInstance();
+ assertThat(type.getDeclaredField(FOO).get(null), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(null), is((Object) BAR));
+ assertThat(type.getDeclaredMethod(RUN).invoke(instance), nullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).get(null), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(null), nullValue(Object.class));
+ }
+
+ @Test
+ public void testFieldReadWithFieldSubstitution() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(FieldAccessSample.class)
+ .visit(MemberSubstitution.strict().field(named(FOO)).replaceWith(FieldAccessSample.class.getDeclaredField(QUX)).on(named(RUN)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Object instance = type.getDeclaredConstructor().newInstance();
+ assertThat(type.getDeclaredField(FOO).get(instance), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(instance), is((Object) BAR));
+ assertThat(type.getDeclaredField(QUX).get(instance), is((Object) QUX));
+ assertThat(type.getDeclaredMethod(RUN).invoke(instance), nullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).get(instance), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(instance), is((Object) QUX));
+ assertThat(type.getDeclaredField(QUX).get(instance), is((Object) QUX));
+ }
+
+ @Test
+ public void testStaticFieldReadWithFieldSubstitution() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(StaticFieldAccessSample.class)
+ .visit(MemberSubstitution.strict().field(named(FOO)).replaceWith(StaticFieldAccessSample.class.getDeclaredField(QUX)).on(named(RUN)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Object instance = type.getDeclaredConstructor().newInstance();
+ assertThat(type.getDeclaredField(FOO).get(null), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(null), is((Object) BAR));
+ assertThat(type.getDeclaredField(QUX).get(instance), is((Object) QUX));
+ assertThat(type.getDeclaredMethod(RUN).invoke(instance), nullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).get(null), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(null), is((Object) QUX));
+ assertThat(type.getDeclaredField(QUX).get(instance), is((Object) QUX));
+ }
+
+ @Test
+ public void testFieldReadWithMethodSubstitution() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(FieldAccessSample.class)
+ .visit(MemberSubstitution.strict().field(named(FOO)).replaceWith(FieldAccessSample.class.getDeclaredMethod(BAZ)).on(named(RUN)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Object instance = type.getDeclaredConstructor().newInstance();
+ assertThat(type.getDeclaredField(FOO).get(instance), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(instance), is((Object) BAR));
+ assertThat(type.getDeclaredField(BAZ).get(instance), is((Object) BAZ));
+ assertThat(type.getDeclaredMethod(RUN).invoke(instance), nullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).get(instance), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(instance), is((Object) BAZ));
+ assertThat(type.getDeclaredField(BAZ).get(instance), is((Object) BAZ));
+ }
+
+ @Test
+ public void testStaticFieldReadWithMethodSubstitution() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(StaticFieldAccessSample.class)
+ .visit(MemberSubstitution.strict().field(named(FOO)).replaceWith(StaticFieldAccessSample.class.getDeclaredMethod(BAZ)).on(named(RUN)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Object instance = type.getDeclaredConstructor().newInstance();
+ assertThat(type.getDeclaredField(FOO).get(null), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(null), is((Object) BAR));
+ assertThat(type.getDeclaredField(BAZ).get(null), is((Object) BAZ));
+ assertThat(type.getDeclaredMethod(RUN).invoke(instance), nullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).get(null), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(null), is((Object) BAZ));
+ assertThat(type.getDeclaredField(BAZ).get(null), is((Object) BAZ));
+ }
+
+ @Test
+ public void testFieldWriteStub() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(FieldAccessSample.class)
+ .visit(MemberSubstitution.strict().field(named(BAR)).stub().on(named(RUN)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Object instance = type.getDeclaredConstructor().newInstance();
+ assertThat(type.getDeclaredField(FOO).get(instance), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(instance), is((Object) BAR));
+ assertThat(type.getDeclaredMethod(RUN).invoke(instance), nullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).get(instance), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(instance), is((Object) BAR));
+ }
+
+ @Test
+ public void testStaticFieldWriteStub() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(StaticFieldAccessSample.class)
+ .visit(MemberSubstitution.strict().field(named(BAR)).stub().on(named(RUN)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Object instance = type.getDeclaredConstructor().newInstance();
+ assertThat(type.getDeclaredField(FOO).get(null), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(null), is((Object) BAR));
+ assertThat(type.getDeclaredMethod(RUN).invoke(instance), nullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).get(null), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(null), is((Object) BAR));
+ }
+
+ @Test
+ public void testFieldWriteWithFieldSubstitution() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(FieldAccessSample.class)
+ .visit(MemberSubstitution.strict().field(named(BAR)).replaceWith(FieldAccessSample.class.getDeclaredField(QUX)).on(named(RUN)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Object instance = type.getDeclaredConstructor().newInstance();
+ assertThat(type.getDeclaredField(FOO).get(instance), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(instance), is((Object) BAR));
+ assertThat(type.getDeclaredField(QUX).get(instance), is((Object) QUX));
+ assertThat(type.getDeclaredMethod(RUN).invoke(instance), nullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).get(instance), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(instance), is((Object) BAR));
+ assertThat(type.getDeclaredField(QUX).get(instance), is((Object) FOO));
+ }
+
+ @Test
+ public void testStaticFieldWriteWithFieldSubstitution() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(StaticFieldAccessSample.class)
+ .visit(MemberSubstitution.strict().field(named(BAR)).replaceWith(StaticFieldAccessSample.class.getDeclaredField(QUX)).on(named(RUN)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Object instance = type.getDeclaredConstructor().newInstance();
+ assertThat(type.getDeclaredField(FOO).get(null), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(null), is((Object) BAR));
+ assertThat(type.getDeclaredField(QUX).get(instance), is((Object) QUX));
+ assertThat(type.getDeclaredMethod(RUN).invoke(instance), nullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).get(null), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(null), is((Object) BAR));
+ assertThat(type.getDeclaredField(QUX).get(instance), is((Object) FOO));
+ }
+
+ @Test
+ public void testFieldWriteWithMethodSubstitution() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(FieldAccessSample.class)
+ .visit(MemberSubstitution.strict().field(named(BAR)).replaceWith(FieldAccessSample.class.getDeclaredMethod(BAZ, String.class)).on(named(RUN)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Object instance = type.getDeclaredConstructor().newInstance();
+ assertThat(type.getDeclaredField(FOO).get(instance), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(instance), is((Object) BAR));
+ assertThat(type.getDeclaredField(BAZ).get(instance), is((Object) BAZ));
+ assertThat(type.getDeclaredMethod(RUN).invoke(instance), nullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).get(instance), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(instance), is((Object) BAR));
+ assertThat(type.getDeclaredField(BAZ).get(instance), is((Object) FOO));
+ }
+
+ @Test
+ public void testStaticFieldWriteWithMethodSubstitution() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(StaticFieldAccessSample.class)
+ .visit(MemberSubstitution.strict().field(named(BAR)).replaceWith(StaticFieldAccessSample.class.getDeclaredMethod(BAZ, String.class)).on(named(RUN)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Object instance = type.getDeclaredConstructor().newInstance();
+ assertThat(type.getDeclaredField(FOO).get(null), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(null), is((Object) BAR));
+ assertThat(type.getDeclaredField(BAZ).get(null), is((Object) BAZ));
+ assertThat(type.getDeclaredMethod(RUN).invoke(instance), nullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).get(null), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(null), is((Object) BAR));
+ assertThat(type.getDeclaredField(BAZ).get(null), is((Object) FOO));
+ }
+
+ @Test
+ public void testFieldReadStubWithMatchedConstraint() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(FieldAccessSample.class)
+ .visit(MemberSubstitution.strict().field(named(FOO)).onRead().stub().on(named(RUN)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Object instance = type.getDeclaredConstructor().newInstance();
+ assertThat(type.getDeclaredField(FOO).get(instance), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(instance), is((Object) BAR));
+ assertThat(type.getDeclaredMethod(RUN).invoke(instance), nullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).get(instance), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(instance), nullValue(Object.class));
+ }
+
+ @Test
+ public void testFieldReadStubWithNonMatchedConstraint() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(FieldAccessSample.class)
+ .visit(MemberSubstitution.strict().field(named(FOO)).onWrite().stub().on(named(RUN)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Object instance = type.getDeclaredConstructor().newInstance();
+ assertThat(type.getDeclaredField(FOO).get(instance), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(instance), is((Object) BAR));
+ assertThat(type.getDeclaredMethod(RUN).invoke(instance), nullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).get(instance), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(instance), is((Object) FOO));
+ }
+
+ @Test
+ public void testFieldWriteStubWithMatchedConstraint() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(FieldAccessSample.class)
+ .visit(MemberSubstitution.strict().field(named(BAR)).onWrite().stub().on(named(RUN)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Object instance = type.getDeclaredConstructor().newInstance();
+ assertThat(type.getDeclaredField(FOO).get(instance), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(instance), is((Object) BAR));
+ assertThat(type.getDeclaredMethod(RUN).invoke(instance), nullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).get(instance), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(instance), is((Object) BAR));
+ }
+
+ @Test
+ public void testFieldWriteStubWithNonMatchedConstraint() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(FieldAccessSample.class)
+ .visit(MemberSubstitution.strict().field(named(BAR)).onRead().stub().on(named(RUN)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Object instance = type.getDeclaredConstructor().newInstance();
+ assertThat(type.getDeclaredField(FOO).get(instance), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(instance), is((Object) BAR));
+ assertThat(type.getDeclaredMethod(RUN).invoke(instance), nullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).get(instance), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(instance), is((Object) FOO));
+ }
+
+ @Test
+ public void testMethodReadStub() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(MethodInvokeSample.class)
+ .visit(MemberSubstitution.strict().method(named(FOO)).stub().on(named(RUN)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Object instance = type.getDeclaredConstructor().newInstance();
+ assertThat(type.getDeclaredField(FOO).get(instance), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(instance), is((Object) BAR));
+ assertThat(type.getDeclaredMethod(RUN).invoke(instance), nullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).get(instance), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(instance), nullValue(Object.class));
+ }
+
+ @Test
+ public void testStaticMethodReadStub() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(StaticMethodInvokeSample.class)
+ .visit(MemberSubstitution.strict().method(named(FOO)).stub().on(named(RUN)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Object instance = type.getDeclaredConstructor().newInstance();
+ assertThat(type.getDeclaredField(FOO).get(null), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(null), is((Object) BAR));
+ assertThat(type.getDeclaredMethod(RUN).invoke(instance), nullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).get(null), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(null), nullValue(Object.class));
+ }
+
+ @Test
+ public void testMethodReadWithFieldSubstitution() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(MethodInvokeSample.class)
+ .visit(MemberSubstitution.strict().method(named(FOO)).replaceWith(MethodInvokeSample.class.getDeclaredField(QUX)).on(named(RUN)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Object instance = type.getDeclaredConstructor().newInstance();
+ assertThat(type.getDeclaredField(FOO).get(instance), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(instance), is((Object) BAR));
+ assertThat(type.getDeclaredField(QUX).get(instance), is((Object) QUX));
+ assertThat(type.getDeclaredMethod(RUN).invoke(instance), nullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).get(instance), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(instance), is((Object) QUX));
+ assertThat(type.getDeclaredField(QUX).get(instance), is((Object) QUX));
+ }
+
+ @Test
+ public void testStaticMethodReadWithFieldSubstitution() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(StaticMethodInvokeSample.class)
+ .visit(MemberSubstitution.strict().method(named(FOO)).replaceWith(StaticMethodInvokeSample.class.getDeclaredField(QUX)).on(named(RUN)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Object instance = type.getDeclaredConstructor().newInstance();
+ assertThat(type.getDeclaredField(FOO).get(null), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(null), is((Object) BAR));
+ assertThat(type.getDeclaredField(QUX).get(instance), is((Object) QUX));
+ assertThat(type.getDeclaredMethod(RUN).invoke(instance), nullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).get(null), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(null), is((Object) QUX));
+ assertThat(type.getDeclaredField(QUX).get(instance), is((Object) QUX));
+ }
+
+ @Test
+ public void testMethodReadWithMethodSubstitution() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(MethodInvokeSample.class)
+ .visit(MemberSubstitution.strict().method(named(FOO)).replaceWith(MethodInvokeSample.class.getDeclaredMethod(BAZ)).on(named(RUN)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Object instance = type.getDeclaredConstructor().newInstance();
+ assertThat(type.getDeclaredField(FOO).get(instance), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(instance), is((Object) BAR));
+ assertThat(type.getDeclaredField(BAZ).get(instance), is((Object) BAZ));
+ assertThat(type.getDeclaredMethod(RUN).invoke(instance), nullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).get(instance), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(instance), is((Object) BAZ));
+ assertThat(type.getDeclaredField(BAZ).get(instance), is((Object) BAZ));
+ }
+
+ @Test
+ public void testStaticMethodReadWithMethodSubstitution() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(StaticMethodInvokeSample.class)
+ .visit(MemberSubstitution.strict().method(named(FOO)).replaceWith(StaticMethodInvokeSample.class.getDeclaredMethod(BAZ)).on(named(RUN)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Object instance = type.getDeclaredConstructor().newInstance();
+ assertThat(type.getDeclaredField(FOO).get(null), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(null), is((Object) BAR));
+ assertThat(type.getDeclaredField(BAZ).get(null), is((Object) BAZ));
+ assertThat(type.getDeclaredMethod(RUN).invoke(instance), nullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).get(null), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(null), is((Object) BAZ));
+ assertThat(type.getDeclaredField(BAZ).get(null), is((Object) BAZ));
+ }
+
+ @Test
+ public void testMethodWriteStub() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(MethodInvokeSample.class)
+ .visit(MemberSubstitution.strict().method(named(BAR)).stub().on(named(RUN)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Object instance = type.getDeclaredConstructor().newInstance();
+ assertThat(type.getDeclaredField(FOO).get(instance), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(instance), is((Object) BAR));
+ assertThat(type.getDeclaredMethod(RUN).invoke(instance), nullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).get(instance), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(instance), is((Object) BAR));
+ }
+
+ @Test
+ public void testStaticMethodWriteStub() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(StaticMethodInvokeSample.class)
+ .visit(MemberSubstitution.strict().method(named(BAR)).stub().on(named(RUN)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Object instance = type.getDeclaredConstructor().newInstance();
+ assertThat(type.getDeclaredField(FOO).get(null), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(null), is((Object) BAR));
+ assertThat(type.getDeclaredMethod(RUN).invoke(instance), nullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).get(null), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(null), is((Object) BAR));
+ }
+
+ @Test
+ public void testMethodWriteWithFieldSubstitution() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(MethodInvokeSample.class)
+ .visit(MemberSubstitution.strict().method(named(BAR)).replaceWith(MethodInvokeSample.class.getDeclaredField(QUX)).on(named(RUN)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Object instance = type.getDeclaredConstructor().newInstance();
+ assertThat(type.getDeclaredField(FOO).get(instance), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(instance), is((Object) BAR));
+ assertThat(type.getDeclaredField(QUX).get(instance), is((Object) QUX));
+ assertThat(type.getDeclaredMethod(RUN).invoke(instance), nullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).get(instance), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(instance), is((Object) BAR));
+ assertThat(type.getDeclaredField(QUX).get(instance), is((Object) FOO));
+ }
+
+ @Test
+ public void testStaticMethodWriteWithFieldSubstitution() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(StaticMethodInvokeSample.class)
+ .visit(MemberSubstitution.strict().method(named(BAR)).replaceWith(StaticMethodInvokeSample.class.getDeclaredField(QUX)).on(named(RUN)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Object instance = type.getDeclaredConstructor().newInstance();
+ assertThat(type.getDeclaredField(FOO).get(null), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(null), is((Object) BAR));
+ assertThat(type.getDeclaredField(QUX).get(instance), is((Object) QUX));
+ assertThat(type.getDeclaredMethod(RUN).invoke(instance), nullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).get(null), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(null), is((Object) BAR));
+ assertThat(type.getDeclaredField(QUX).get(instance), is((Object) FOO));
+ }
+
+ @Test
+ public void testMethodWriteWithMethodSubstitution() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(MethodInvokeSample.class)
+ .visit(MemberSubstitution.strict().method(named(BAR)).replaceWith(MethodInvokeSample.class.getDeclaredMethod(BAZ, String.class)).on(named(RUN)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Object instance = type.getDeclaredConstructor().newInstance();
+ assertThat(type.getDeclaredField(FOO).get(instance), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(instance), is((Object) BAR));
+ assertThat(type.getDeclaredField(BAZ).get(instance), is((Object) BAZ));
+ assertThat(type.getDeclaredMethod(RUN).invoke(instance), nullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).get(instance), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(instance), is((Object) BAR));
+ assertThat(type.getDeclaredField(BAZ).get(instance), is((Object) FOO));
+ }
+
+ @Test
+ public void testStaticMethodWriteWithMethodSubstitution() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(StaticMethodInvokeSample.class)
+ .visit(MemberSubstitution.strict().method(named(BAR)).replaceWith(StaticMethodInvokeSample.class.getDeclaredMethod(BAZ, String.class)).on(named(RUN)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Object instance = type.getDeclaredConstructor().newInstance();
+ assertThat(type.getDeclaredField(FOO).get(null), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(null), is((Object) BAR));
+ assertThat(type.getDeclaredField(BAZ).get(null), is((Object) BAZ));
+ assertThat(type.getDeclaredMethod(RUN).invoke(instance), nullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).get(null), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(null), is((Object) BAR));
+ assertThat(type.getDeclaredField(BAZ).get(null), is((Object) FOO));
+ }
+
+ @Test
+ public void testConstructorSubstitution() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(ConstructorSubstitutionSample.class)
+ .visit(MemberSubstitution.strict().constructor(isDeclaredBy(Object.class)).stub().on(named(RUN)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Object instance = type.getDeclaredConstructor().newInstance();
+ assertThat(type.getDeclaredMethod(RUN).invoke(instance), nullValue(Object.class));
+ }
+
+ @Test
+ public void testVirtualMethodSubstitution() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(VirtualMethodSubstitutionSample.class)
+ .visit(MemberSubstitution.strict().method(named(FOO)).stub().on(named(RUN)))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Object instance = type.getDeclaredConstructor().newInstance();
+ assertThat(type.getDeclaredMethod(RUN).invoke(instance), nullValue(Object.class));
+ }
+
+ @Test
+ public void testVirtualMethodVirtualCallSubstitution() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(VirtualMethodCallSubstitutionSample.Extension.class)
+ .visit(MemberSubstitution.strict().method(named(FOO)).onVirtualCall().stub().on(named(RUN)))
+ .make()
+ .load(new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassFileExtraction.of(VirtualMethodCallSubstitutionSample.class)),
+ ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded();
+ Object instance = type.getDeclaredConstructor().newInstance();
+ assertThat(type.getDeclaredMethod(RUN).invoke(instance), is((Object) 1));
+ }
+
+ @Test
+ public void testVirtualMethodSuperCallSubstitution() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(VirtualMethodCallSubstitutionSample.Extension.class)
+ .visit(MemberSubstitution.strict().method(named(FOO)).onSuperCall().stub().on(named(RUN)))
+ .make()
+ .load(new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassFileExtraction.of(VirtualMethodCallSubstitutionSample.class)),
+ ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded();
+ Object instance = type.getDeclaredConstructor().newInstance();
+ assertThat(type.getDeclaredMethod(RUN).invoke(instance), is((Object) 2));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testFieldNotAccessible() throws Exception {
+ new ByteBuddy()
+ .redefine(StaticFieldAccessSample.class)
+ .visit(MemberSubstitution.strict().field(named(FOO)).replaceWith(ValidationTarget.class.getDeclaredField(FOO)).on(named(RUN)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testFieldReadNotAssignable() throws Exception {
+ new ByteBuddy()
+ .redefine(StaticFieldAccessSample.class)
+ .visit(MemberSubstitution.strict().field(named(FOO)).replaceWith(ValidationTarget.class.getDeclaredField(BAR)).on(named(RUN)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testFieldWriteNotAssignable() throws Exception {
+ new ByteBuddy()
+ .redefine(StaticFieldAccessSample.class)
+ .visit(MemberSubstitution.strict().field(named(BAR)).replaceWith(ValidationTarget.class.getDeclaredField(BAR)).on(named(RUN)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testFieldReadNotCompatible() throws Exception {
+ new ByteBuddy()
+ .redefine(StaticFieldAccessSample.class)
+ .visit(MemberSubstitution.strict().field(named(FOO)).replaceWith(ValidationTarget.class.getDeclaredField(QUX)).on(named(RUN)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testFieldWriteNotCompatible() throws Exception {
+ new ByteBuddy()
+ .redefine(StaticFieldAccessSample.class)
+ .visit(MemberSubstitution.strict().field(named(BAR)).replaceWith(ValidationTarget.class.getDeclaredField(QUX)).on(named(RUN)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodNotAccessible() throws Exception {
+ new ByteBuddy()
+ .redefine(StaticFieldAccessSample.class)
+ .visit(MemberSubstitution.strict().field(named(BAR)).replaceWith(ValidationTarget.class.getDeclaredMethod(FOO)).on(named(RUN)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodArgumentsNotAssignable() throws Exception {
+ new ByteBuddy()
+ .redefine(StaticFieldAccessSample.class)
+ .visit(MemberSubstitution.strict().field(named(FOO)).replaceWith(ValidationTarget.class.getDeclaredMethod(BAR, Void.class)).on(named(RUN)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodReturnNotAssignable() throws Exception {
+ new ByteBuddy()
+ .redefine(StaticFieldAccessSample.class)
+ .visit(MemberSubstitution.strict().field(named(BAR)).replaceWith(ValidationTarget.class.getDeclaredMethod(BAR)).on(named(RUN)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodNotCompatible() throws Exception {
+ new ByteBuddy()
+ .redefine(StaticFieldAccessSample.class)
+ .visit(MemberSubstitution.strict().field(named(BAR)).replaceWith(ValidationTarget.class.getDeclaredMethod(QUX)).on(named(RUN)))
+ .make();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testConstructorReplacement() throws Exception {
+ MemberSubstitution.strict().field(any()).replaceWith(new MethodDescription.ForLoadedConstructor(Object.class.getDeclaredConstructor()));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testOptionalField() throws Exception {
+ new ByteBuddy()
+ .redefine(OptionalTarget.class)
+ .visit(MemberSubstitution.strict().field(named(BAR)).stub().on(named(RUN)))
+ .make(TypePool.Empty.INSTANCE);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testOptionalMethod() throws Exception {
+ new ByteBuddy()
+ .redefine(OptionalTarget.class)
+ .visit(MemberSubstitution.strict().method(named(BAR)).stub().on(named(RUN)))
+ .make(TypePool.Empty.INSTANCE);
+ }
+
+ @Test
+ public void testOptionalFieldRelaxed() throws Exception {
+ assertThat(new ByteBuddy()
+ .redefine(OptionalTarget.class)
+ .visit(MemberSubstitution.relaxed().field(named(BAR)).stub().on(named(RUN)))
+ .make(TypePool.Empty.INSTANCE), notNullValue(DynamicType.class));
+ }
+
+ @Test
+ public void testOptionalMethodRelaxed() throws Exception {
+ assertThat(new ByteBuddy()
+ .redefine(OptionalTarget.class)
+ .visit(MemberSubstitution.relaxed().method(named(BAR)).stub().on(named(RUN)))
+ .make(TypePool.Empty.INSTANCE), notNullValue(DynamicType.class));
+ }
+
+ public static class FieldAccessSample {
+
+ public String foo = FOO, bar = BAR, qux = QUX, baz = BAZ;
+
+ public void run() {
+ bar = foo;
+ }
+
+ @SuppressWarnings("unused")
+ private String baz() {
+ return baz;
+ }
+
+ @SuppressWarnings("unused")
+ private void baz(String baz) {
+ this.baz = baz;
+ }
+ }
+
+ public static class StaticFieldAccessSample {
+
+ public static String foo = FOO, bar = BAR, qux = QUX, baz = BAZ;
+
+ public void run() {
+ bar = foo;
+ }
+
+ @SuppressWarnings("unused")
+ private static String baz() {
+ return baz;
+ }
+
+ @SuppressWarnings("unused")
+ private static void baz(String baz) {
+ StaticFieldAccessSample.baz = baz;
+ }
+ }
+
+ public static class MethodInvokeSample {
+
+ public String foo = FOO, bar = BAR, qux = QUX, baz = BAZ;
+
+ public void run() {
+ bar(foo());
+ }
+
+ @SuppressWarnings("unused")
+ private String foo() {
+ return foo;
+ }
+
+ @SuppressWarnings("unused")
+ private void bar(String bar) {
+ this.bar = bar;
+ }
+
+ @SuppressWarnings("unused")
+ private String baz() {
+ return baz;
+ }
+
+ @SuppressWarnings("unused")
+ private void baz(String baz) {
+ this.baz = baz;
+ }
+ }
+
+ public static class StaticMethodInvokeSample {
+
+ public static String foo = FOO, bar = BAR, qux = QUX, baz = BAZ;
+
+ public void run() {
+ bar(foo());
+ }
+
+ @SuppressWarnings("unused")
+ private static String foo() {
+ return foo;
+ }
+
+ @SuppressWarnings("unused")
+ private static void bar(String bar) {
+ StaticMethodInvokeSample.bar = bar;
+ }
+
+ @SuppressWarnings("unused")
+ private static String baz() {
+ return baz;
+ }
+
+ @SuppressWarnings("unused")
+ private static void baz(String baz) {
+ StaticMethodInvokeSample.baz = baz;
+ }
+ }
+
+ public static class ConstructorSubstitutionSample {
+
+ public Object run() {
+ return new Object();
+ }
+ }
+
+ public static class VirtualMethodSubstitutionSample {
+
+ public Object run() {
+ return foo();
+ }
+
+ public Object foo() {
+ return FOO;
+ }
+ }
+
+ public static class VirtualMethodCallSubstitutionSample {
+
+ public int foo() {
+ return 1;
+ }
+
+ public static class Extension extends VirtualMethodCallSubstitutionSample {
+
+ @Override
+ public int foo() {
+ return 2;
+ }
+
+ public int run() {
+ return foo() + super.foo();
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ValidationTarget {
+
+ private static String foo;
+
+ public static Void bar;
+
+ public String qux;
+
+ private static String foo() {
+ return null;
+ }
+
+ public static Void bar() {
+ return null;
+ }
+
+ public static void bar(Void bar) {
+ /* empty */
+ }
+
+ public String qux() {
+ return null;
+ }
+ }
+
+ public static class OptionalTarget {
+
+ public static void run() {
+ ValidationTarget.bar = null;
+ ValidationTarget.bar();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/asm/ModifierAdjustmentTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/ModifierAdjustmentTest.java
new file mode 100644
index 0000000..82f921e
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/ModifierAdjustmentTest.java
@@ -0,0 +1,180 @@
+package net.bytebuddy.asm;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+import org.objectweb.asm.Opcodes;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class ModifierAdjustmentTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ @Test
+ public void testTypeModifier() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(new ModifierAdjustment().withTypeModifiers(named(Sample.class.getName()), Visibility.PUBLIC))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getModifiers(), is(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC));
+ }
+
+ @Test
+ public void testTypeModifierNotApplied() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(new ModifierAdjustment().withTypeModifiers(named(FOO), Visibility.PUBLIC))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getModifiers(), is(Sample.class.getModifiers()));
+ }
+
+ @Test
+ public void testTypeModifierUnqualified() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(new ModifierAdjustment().withTypeModifiers(Visibility.PUBLIC))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getModifiers(), is(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC));
+ }
+
+ @Test
+ public void testFieldModifier() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(new ModifierAdjustment().withFieldModifiers(named(FOO), Visibility.PUBLIC))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredField(FOO).getModifiers(), is(Opcodes.ACC_PUBLIC));
+ assertThat(type.getDeclaredField(BAR).getModifiers(), is(0));
+ }
+
+ @Test
+ public void testFieldModifierUnqualified() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(new ModifierAdjustment().withFieldModifiers(Visibility.PUBLIC))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredField(FOO).getModifiers(), is(Opcodes.ACC_PUBLIC));
+ assertThat(type.getDeclaredField(BAR).getModifiers(), is(Opcodes.ACC_PUBLIC));
+ }
+
+ @Test
+ public void testMethodModifier() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(new ModifierAdjustment().withMethodModifiers(named(FOO), Visibility.PUBLIC))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).getModifiers(), is(Opcodes.ACC_PUBLIC));
+ assertThat(type.getDeclaredMethod(BAR).getModifiers(), is(0));
+ }
+
+ @Test
+ public void testMethodModifierUnqualified() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(new ModifierAdjustment().withMethodModifiers(Visibility.PUBLIC))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).getModifiers(), is(Opcodes.ACC_PUBLIC));
+ assertThat(type.getDeclaredMethod(BAR).getModifiers(), is(Opcodes.ACC_PUBLIC));
+ }
+
+ @Test
+ public void testConstructorModifier() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(new ModifierAdjustment().withConstructorModifiers(takesArgument(0, Void.class), Visibility.PUBLIC))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredConstructor(Void.class).getModifiers(), is(Opcodes.ACC_PUBLIC));
+ assertThat(type.getDeclaredConstructor().getModifiers(), is(0));
+ }
+
+ @Test
+ public void testConstructorModifierUnqualified() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(new ModifierAdjustment().withConstructorModifiers(Visibility.PUBLIC))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredConstructor(Void.class).getModifiers(), is(Opcodes.ACC_PUBLIC));
+ assertThat(type.getDeclaredConstructor().getModifiers(), is(Opcodes.ACC_PUBLIC));
+ }
+
+ @Test
+ public void testInvokableModifier() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(new ModifierAdjustment().withInvokableModifiers(named(FOO).or(takesArgument(0, Void.class)), Visibility.PUBLIC))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).getModifiers(), is(Opcodes.ACC_PUBLIC));
+ assertThat(type.getDeclaredMethod(BAR).getModifiers(), is(0));
+ assertThat(type.getDeclaredConstructor(Void.class).getModifiers(), is(Opcodes.ACC_PUBLIC));
+ assertThat(type.getDeclaredConstructor().getModifiers(), is(0));
+ }
+
+ @Test
+ public void testInvokableModifierUnqualified() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .redefine(Sample.class)
+ .visit(new ModifierAdjustment().withInvokableModifiers(Visibility.PUBLIC))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).getModifiers(), is(Opcodes.ACC_PUBLIC));
+ assertThat(type.getDeclaredMethod(BAR).getModifiers(), is(Opcodes.ACC_PUBLIC));
+ assertThat(type.getDeclaredConstructor(Void.class).getModifiers(), is(Opcodes.ACC_PUBLIC));
+ assertThat(type.getDeclaredConstructor().getModifiers(), is(Opcodes.ACC_PUBLIC));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ModifierAdjustment.class).apply();
+ ObjectPropertyAssertion.of(ModifierAdjustment.Adjustment.class).apply();
+ }
+
+ private static class Sample {
+
+ Void foo;
+
+ Void bar;
+
+ Sample() {
+ /* empty */
+ }
+
+ Sample(Void ignored) {
+ /* empty */
+ }
+
+ void foo() {
+ /* empty*/
+ }
+
+ void bar() {
+ /* empty*/
+ }
+ }
+}
\ No newline at end of file
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/asm/TypeConstantAdjustmentTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/TypeConstantAdjustmentTest.java
new file mode 100644
index 0000000..f9fd647
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/TypeConstantAdjustmentTest.java
@@ -0,0 +1,154 @@
+package net.bytebuddy.asm;
+
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.method.MethodDescription;
+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.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class TypeConstantAdjustmentTest {
+
+ private static final int FOOBAR = 42, IGNORED = -1;
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux", BAZ = "baz";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private ClassVisitor classVisitor;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Before
+ public void setUp() throws Exception {
+ when(classVisitor.visitMethod(anyInt(), any(String.class), any(String.class), any(String.class), any(String[].class)))
+ .thenReturn(methodVisitor);
+ }
+
+ @Test
+ public void testWriterFlags() throws Exception {
+ assertThat(TypeConstantAdjustment.INSTANCE.mergeWriter(FOOBAR), is(FOOBAR));
+ }
+
+ @Test
+ public void testReaderFlags() throws Exception {
+ assertThat(TypeConstantAdjustment.INSTANCE.mergeReader(FOOBAR), is(FOOBAR));
+ }
+
+ @Test
+ public void testInstrumentationModernClassFile() throws Exception {
+ ClassVisitor classVisitor = TypeConstantAdjustment.INSTANCE.wrap(mock(TypeDescription.class),
+ this.classVisitor,
+ mock(Implementation.Context.class),
+ mock(TypePool.class),
+ new FieldList.Empty<FieldDescription.InDefinedShape>(),
+ new MethodList.Empty<MethodDescription>(),
+ IGNORED,
+ IGNORED);
+ classVisitor.visit(ClassFileVersion.JAVA_V5.getMinorMajorVersion(), FOOBAR, FOO, BAR, QUX, new String[]{BAZ});
+ assertThat(classVisitor.visitMethod(FOOBAR, FOO, BAR, QUX, new String[]{BAZ}), is(methodVisitor));
+ verify(this.classVisitor).visit(ClassFileVersion.JAVA_V5.getMinorMajorVersion(), FOOBAR, FOO, BAR, QUX, new String[]{BAZ});
+ verify(this.classVisitor).visitMethod(FOOBAR, FOO, BAR, QUX, new String[]{BAZ});
+ verifyNoMoreInteractions(this.classVisitor);
+ verifyZeroInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testInstrumentationLegacyClassFileObjectType() throws Exception {
+ ClassVisitor classVisitor = TypeConstantAdjustment.INSTANCE.wrap(mock(TypeDescription.class),
+ this.classVisitor,
+ mock(Implementation.Context.class),
+ mock(TypePool.class),
+ new FieldList.Empty<FieldDescription.InDefinedShape>(),
+ new MethodList.Empty<MethodDescription>(),
+ IGNORED,
+ IGNORED);
+ classVisitor.visit(ClassFileVersion.JAVA_V4.getMinorMajorVersion(), FOOBAR, FOO, BAR, QUX, new String[]{BAZ});
+ MethodVisitor methodVisitor = classVisitor.visitMethod(FOOBAR, FOO, BAR, QUX, new String[]{BAZ});
+ assertThat(methodVisitor, not(this.methodVisitor));
+ methodVisitor.visitLdcInsn(Type.getType(Object.class));
+ verify(this.classVisitor).visit(ClassFileVersion.JAVA_V4.getMinorMajorVersion(), FOOBAR, FOO, BAR, QUX, new String[]{BAZ});
+ verify(this.classVisitor).visitMethod(FOOBAR, FOO, BAR, QUX, new String[]{BAZ});
+ verifyNoMoreInteractions(this.classVisitor);
+ verify(this.methodVisitor).visitLdcInsn(Type.getType(Object.class).getClassName());
+ verify(this.methodVisitor).visitMethodInsn(Opcodes.INVOKESTATIC,
+ Type.getType(Class.class).getInternalName(),
+ "forName",
+ Type.getType(Class.class.getDeclaredMethod("forName", String.class)).getDescriptor(),
+ false);
+ verifyNoMoreInteractions(this.methodVisitor);
+ }
+
+ @Test
+ public void testInstrumentationLegacyClassFileArrayType() throws Exception {
+ ClassVisitor classVisitor = TypeConstantAdjustment.INSTANCE.wrap(mock(TypeDescription.class),
+ this.classVisitor,
+ mock(Implementation.Context.class),
+ mock(TypePool.class),
+ new FieldList.Empty<FieldDescription.InDefinedShape>(),
+ new MethodList.Empty<MethodDescription>(),
+ IGNORED,
+ IGNORED);
+ classVisitor.visit(ClassFileVersion.JAVA_V4.getMinorMajorVersion(), FOOBAR, FOO, BAR, QUX, new String[]{BAZ});
+ MethodVisitor methodVisitor = classVisitor.visitMethod(FOOBAR, FOO, BAR, QUX, new String[]{BAZ});
+ assertThat(methodVisitor, not(this.methodVisitor));
+ methodVisitor.visitLdcInsn(Type.getType(Object[].class));
+ verify(this.classVisitor).visit(ClassFileVersion.JAVA_V4.getMinorMajorVersion(), FOOBAR, FOO, BAR, QUX, new String[]{BAZ});
+ verify(this.classVisitor).visitMethod(FOOBAR, FOO, BAR, QUX, new String[]{BAZ});
+ verifyNoMoreInteractions(this.classVisitor);
+ verify(this.methodVisitor).visitLdcInsn(Type.getType(Object[].class).getInternalName().replace('/', '.'));
+ verify(this.methodVisitor).visitMethodInsn(Opcodes.INVOKESTATIC,
+ Type.getType(Class.class).getInternalName(),
+ "forName",
+ Type.getType(Class.class.getDeclaredMethod("forName", String.class)).getDescriptor(),
+ false);
+ verifyNoMoreInteractions(this.methodVisitor);
+ }
+
+ @Test
+ public void testInstrumentationLegacyClassOtherType() throws Exception {
+ ClassVisitor classVisitor = TypeConstantAdjustment.INSTANCE.wrap(mock(TypeDescription.class),
+ this.classVisitor,
+ mock(Implementation.Context.class),
+ mock(TypePool.class),
+ new FieldList.Empty<FieldDescription.InDefinedShape>(),
+ new MethodList.Empty<MethodDescription>(),
+ IGNORED,
+ IGNORED);
+ classVisitor.visit(ClassFileVersion.JAVA_V4.getMinorMajorVersion(), FOOBAR, FOO, BAR, QUX, new String[]{BAZ});
+ MethodVisitor methodVisitor = classVisitor.visitMethod(FOOBAR, FOO, BAR, QUX, new String[]{BAZ});
+ assertThat(methodVisitor, not(this.methodVisitor));
+ methodVisitor.visitLdcInsn(FOO);
+ verify(this.classVisitor).visit(ClassFileVersion.JAVA_V4.getMinorMajorVersion(), FOOBAR, FOO, BAR, QUX, new String[]{BAZ});
+ verify(this.classVisitor).visitMethod(FOOBAR, FOO, BAR, QUX, new String[]{BAZ});
+ verifyNoMoreInteractions(this.classVisitor);
+ verify(this.methodVisitor).visitLdcInsn(FOO);
+ verifyNoMoreInteractions(this.methodVisitor);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeConstantAdjustment.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/build/EntryPointDefaultTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/build/EntryPointDefaultTest.java
new file mode 100644
index 0000000..f778d85
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/build/EntryPointDefaultTest.java
@@ -0,0 +1,71 @@
+package net.bytebuddy.build;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.scaffold.inline.MethodNameTransformer;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static net.bytebuddy.matcher.ElementMatchers.not;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class EntryPointDefaultTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Mock
+ private ByteBuddy byteBuddy;
+
+ @Mock
+ private ClassFileLocator classFileLocator;
+
+ @Mock
+ private MethodNameTransformer methodNameTransformer;
+
+ @Mock
+ private DynamicType.Builder<?> builder, otherBuilder;
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testRebase() throws Exception {
+ assertThat(EntryPoint.Default.REBASE.getByteBuddy(), is(new ByteBuddy()));
+ when(byteBuddy.rebase(typeDescription, classFileLocator, methodNameTransformer)).thenReturn((DynamicType.Builder) builder);
+ assertThat(EntryPoint.Default.REBASE.transform(typeDescription, byteBuddy, classFileLocator, methodNameTransformer), is((DynamicType.Builder) builder));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testRedefine() throws Exception {
+ assertThat(EntryPoint.Default.REDEFINE.getByteBuddy(), is(new ByteBuddy()));
+ when(byteBuddy.redefine(typeDescription, classFileLocator)).thenReturn((DynamicType.Builder) builder);
+ assertThat(EntryPoint.Default.REDEFINE.transform(typeDescription, byteBuddy, classFileLocator, methodNameTransformer), is((DynamicType.Builder) builder));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testRedefineLocal() throws Exception {
+ assertThat(EntryPoint.Default.REDEFINE_LOCAL.getByteBuddy(), is(new ByteBuddy().with(Implementation.Context.Disabled.Factory.INSTANCE)));
+ when(byteBuddy.redefine(typeDescription, classFileLocator)).thenReturn((DynamicType.Builder) builder);
+ when(builder.ignoreAlso(not(isDeclaredBy(typeDescription)))).thenReturn((DynamicType.Builder) otherBuilder);
+ assertThat(EntryPoint.Default.REDEFINE_LOCAL.transform(typeDescription, byteBuddy, classFileLocator, methodNameTransformer), is((DynamicType.Builder) otherBuilder));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(EntryPoint.Default.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/ByteCodeElementTokenListTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/ByteCodeElementTokenListTest.java
new file mode 100644
index 0000000..7fd4870
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/ByteCodeElementTokenListTest.java
@@ -0,0 +1,35 @@
+package net.bytebuddy.description;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class ByteCodeElementTokenListTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private ByteCodeElement.Token<?> original, transformed;
+
+ @Mock
+ private TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor;
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testTransformation() throws Exception {
+ when(original.accept(visitor)).thenReturn((ByteCodeElement.Token) transformed);
+ ByteCodeElement.Token.TokenList<?> tokenList = new ByteCodeElement.Token.TokenList(original).accept(visitor);
+ assertThat(tokenList.size(), is(1));
+ assertThat(tokenList.get(0), is((ByteCodeElement.Token) transformed));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/TypeVariableSourceVisitorNoOpTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/TypeVariableSourceVisitorNoOpTest.java
new file mode 100644
index 0000000..d417906
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/TypeVariableSourceVisitorNoOpTest.java
@@ -0,0 +1,31 @@
+package net.bytebuddy.description;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.hamcrest.MatcherAssert;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class TypeVariableSourceVisitorNoOpTest {
+
+ @Test
+ public void testVisitType() throws Exception {
+ TypeDescription typeDescription = mock(TypeDescription.class);
+ MatcherAssert.assertThat(TypeVariableSource.Visitor.NoOp.INSTANCE.onType(typeDescription), is((TypeVariableSource) typeDescription));
+ }
+
+ @Test
+ public void testVisitMethod() throws Exception {
+ MethodDescription.InDefinedShape methodDescription = mock(MethodDescription.InDefinedShape.class);
+ assertThat(TypeVariableSource.Visitor.NoOp.INSTANCE.onMethod(methodDescription), is((TypeVariableSource) methodDescription));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeVariableSource.Visitor.NoOp.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AbstractAnnotationDescriptionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AbstractAnnotationDescriptionTest.java
new file mode 100644
index 0000000..ecb6331
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AbstractAnnotationDescriptionTest.java
@@ -0,0 +1,746 @@
+package net.bytebuddy.description.annotation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.asm.AsmVisitorWrapper;
+import net.bytebuddy.description.enumeration.EnumerationDescription;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeVariableToken;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.pool.TypePool;
+import org.hamcrest.CoreMatchers;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.lang.annotation.*;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public abstract class AbstractAnnotationDescriptionTest {
+
+ private static final boolean BOOLEAN = true;
+
+ private static final boolean[] BOOLEAN_ARRAY = new boolean[]{BOOLEAN};
+
+ private static final byte BYTE = 42;
+
+ private static final byte[] BYTE_ARRAY = new byte[]{BYTE};
+
+ private static final short SHORT = 42;
+
+ private static final short[] SHORT_ARRAY = new short[]{SHORT};
+
+ private static final char CHARACTER = 42;
+
+ private static final char[] CHARACTER_ARRAY = new char[]{CHARACTER};
+
+ private static final int INTEGER = 42;
+
+ private static final int[] INTEGER_ARRAY = new int[]{INTEGER};
+
+ private static final long LONG = 42L;
+
+ private static final long[] LONG_ARRAY = new long[]{LONG};
+
+ private static final float FLOAT = 42f;
+
+ private static final float[] FLOAT_ARRAY = new float[]{FLOAT};
+
+ private static final double DOUBLE = 42d;
+
+ private static final double[] DOUBLE_ARRAY = new double[]{DOUBLE};
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ private static final String[] STRING_ARRAY = new String[]{FOO};
+
+ private static final SampleEnumeration ENUMERATION = SampleEnumeration.VALUE;
+
+ private static final SampleEnumeration[] ENUMERATION_ARRAY = new SampleEnumeration[]{ENUMERATION};
+
+ private static final Class<?> CLASS = Void.class;
+
+ private static final Class<?>[] CLASS_ARRAY = new Class<?>[]{CLASS};
+
+ private static final Class<?> ARRAY_CLASS = Void[].class;
+
+ private static final Other ANNOTATION = EnumerationCarrier.class.getAnnotation(Other.class);
+
+ private static final Other[] ANNOTATION_ARRAY = new Other[]{ANNOTATION};
+
+ private static final boolean OTHER_BOOLEAN = false;
+
+ private static final boolean[] OTHER_BOOLEAN_ARRAY = new boolean[]{OTHER_BOOLEAN};
+
+ private static final byte OTHER_BYTE = 42 * 2;
+
+ private static final byte[] OTHER_BYTE_ARRAY = new byte[]{OTHER_BYTE};
+
+ private static final short OTHER_SHORT = 42 * 2;
+
+ private static final short[] OTHER_SHORT_ARRAY = new short[]{OTHER_SHORT};
+
+ private static final char OTHER_CHARACTER = 42 * 2;
+
+ private static final char[] OTHER_CHARACTER_ARRAY = new char[]{OTHER_CHARACTER};
+
+ private static final int OTHER_INTEGER = 42 * 2;
+
+ private static final int[] OTHER_INTEGER_ARRAY = new int[]{OTHER_INTEGER};
+
+ private static final long OTHER_LONG = 42L * 2;
+
+ private static final long[] OTHER_LONG_ARRAY = new long[]{OTHER_LONG};
+
+ private static final float OTHER_FLOAT = 42f * 2;
+
+ private static final float[] OTHER_FLOAT_ARRAY = new float[]{OTHER_FLOAT};
+
+ private static final double OTHER_DOUBLE = 42d * 2;
+
+ private static final double[] OTHER_DOUBLE_ARRAY = new double[]{OTHER_DOUBLE};
+
+ private static final SampleEnumeration OTHER_ENUMERATION = SampleEnumeration.OTHER;
+
+ private static final SampleEnumeration[] OTHER_ENUMERATION_ARRAY = new SampleEnumeration[]{OTHER_ENUMERATION};
+
+ private static final Class<?> OTHER_CLASS = Object.class;
+
+ private static final Class<?>[] OTHER_CLASS_ARRAY = new Class<?>[]{OTHER_CLASS};
+
+ private static final Class<?> OTHER_ARRAY_CLASS = Object[].class;
+
+ private static final Other OTHER_ANNOTATION = OtherEnumerationCarrier.class.getAnnotation(Other.class);
+
+ private static final Other[] OTHER_ANNOTATION_ARRAY = new Other[]{OTHER_ANNOTATION};
+
+ private static final String[] OTHER_STRING_ARRAY = new String[]{BAR};
+
+ private Annotation first, second, defaultFirst, defaultSecond, explicitTarget, broken;
+
+ private Class<?> brokenCarrier;
+
+ protected abstract AnnotationDescription describe(Annotation annotation, Class<?> declaringType);
+
+ private AnnotationDescription describe(Annotation annotation) {
+ Class<?> carrier;
+ if (annotation == first) {
+ carrier = FooSample.class;
+ } else if (annotation == second) {
+ carrier = BarSample.class;
+ } else if (annotation == defaultFirst) {
+ carrier = DefaultSample.class;
+ } else if (annotation == defaultSecond) {
+ carrier = NonDefaultSample.class;
+ } else if (annotation == explicitTarget) {
+ carrier = ExplicitTarget.Carrier.class;
+ } else if (annotation == broken) {
+ carrier = brokenCarrier;
+ } else {
+ throw new AssertionError();
+ }
+ return describe(annotation, carrier);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ first = FooSample.class.getAnnotation(Sample.class);
+ second = BarSample.class.getAnnotation(Sample.class);
+ defaultFirst = DefaultSample.class.getAnnotation(SampleDefault.class);
+ defaultSecond = NonDefaultSample.class.getAnnotation(SampleDefault.class);
+ explicitTarget = ExplicitTarget.Carrier.class.getAnnotation(ExplicitTarget.class);
+ brokenCarrier = new ByteBuddy()
+ .subclass(Object.class)
+ .visit(new AnnotationValueBreaker())
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER_PERSISTENT)
+ .getLoaded();
+ broken = brokenCarrier.getAnnotations()[0];
+ }
+
+ @Test
+ public void testPrecondition() throws Exception {
+ assertThat(describe(first), is(describe(first)));
+ assertThat(describe(second), is(describe(second)));
+ assertThat(describe(first), not(describe(second)));
+ assertThat(describe(first).getAnnotationType(), is(describe(second).getAnnotationType()));
+ assertThat(describe(first).getAnnotationType(), not((TypeDescription) new TypeDescription.ForLoadedType(Other.class)));
+ assertThat(describe(second).getAnnotationType(), not((TypeDescription) new TypeDescription.ForLoadedType(Other.class)));
+ assertThat(describe(first).getAnnotationType().represents(first.annotationType()), is(true));
+ assertThat(describe(second).getAnnotationType().represents(second.annotationType()), is(true));
+ }
+
+ @Test
+ public void assertToString() throws Exception {
+ assertToString(describe(first).toString(), first);
+ assertToString(describe(second).toString(), second);
+ }
+
+ private void assertToString(String actual, Annotation loaded) throws Exception {
+ assertThat(actual, startsWith("@" + loaded.annotationType().getName()));
+ String loadedString = loaded.toString();
+ if (loadedString.length() - loadedString.replace(",", "").length() != loaded.annotationType().getDeclaredMethods().length - 1) {
+ throw new AssertionError("Unexpected amount of commas for " + loaded); // Expect tested annotations not to contain commas in values.
+ }
+ for (Method method : loaded.annotationType().getDeclaredMethods()) {
+ assertThat(loadedString.split(method.getName() + "=", -1).length - 1, is(1)); // Expect property delimiter not to exist as value.
+ int start = loadedString.indexOf(method.getName() + "="), end = loadedString.indexOf(',', start);
+ assertThat(actual, containsString(loadedString.substring(start, end == -1 ? loadedString.length() - 1 : end)));
+ }
+ }
+
+ @Test
+ public void testHashCode() throws Exception {
+ assertThat(describe(first).hashCode(), is(describe(first).hashCode()));
+ assertThat(describe(second).hashCode(), is(describe(second).hashCode()));
+ assertThat(describe(first).hashCode(), not(describe(second).hashCode()));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testEquals() throws Exception {
+ AnnotationDescription identical = describe(first);
+ assertThat(identical, is(identical));
+ AnnotationDescription equalFirst = mock(AnnotationDescription.class);
+ when(equalFirst.getAnnotationType()).thenReturn(new TypeDescription.ForLoadedType(first.annotationType()));
+ when(equalFirst.getValue(Mockito.any(MethodDescription.InDefinedShape.class))).then(new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ MethodDescription.InDefinedShape method = (MethodDescription.InDefinedShape) invocation.getArguments()[0];
+ return AnnotationDescription.ForLoadedAnnotation.of(first).getValue(method);
+ }
+ });
+ assertThat(describe(first), is(equalFirst));
+ AnnotationDescription equalSecond = mock(AnnotationDescription.class);
+ when(equalSecond.getAnnotationType()).thenReturn(new TypeDescription.ForLoadedType(first.annotationType()));
+ when(equalSecond.getValue(Mockito.any(MethodDescription.InDefinedShape.class))).then(new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ MethodDescription.InDefinedShape method = (MethodDescription.InDefinedShape) invocation.getArguments()[0];
+ return AnnotationDescription.ForLoadedAnnotation.of(second).getValue(method);
+ }
+ });
+ assertThat(describe(second), is(equalSecond));
+ AnnotationDescription equalFirstTypeOnly = mock(AnnotationDescription.class);
+ when(equalFirstTypeOnly.getAnnotationType()).thenReturn(new TypeDescription.ForLoadedType(Other.class));
+ when(equalFirstTypeOnly.getValue(Mockito.any(MethodDescription.InDefinedShape.class))).then(new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ MethodDescription.InDefinedShape method = (MethodDescription.InDefinedShape) invocation.getArguments()[0];
+ return AnnotationDescription.ForLoadedAnnotation.of(first).getValue(method);
+ }
+ });
+ assertThat(describe(first), not(equalFirstTypeOnly));
+ AnnotationDescription equalFirstNameOnly = mock(AnnotationDescription.class);
+ when(equalFirstNameOnly.getAnnotationType()).thenReturn(new TypeDescription.ForLoadedType(first.annotationType()));
+ AnnotationValue<?, ?> annotationValue = mock(AnnotationValue.class);
+ when(annotationValue.resolve()).thenReturn(null);
+ when(equalFirstNameOnly.getValue(Mockito.any(MethodDescription.InDefinedShape.class))).thenReturn((AnnotationValue) annotationValue);
+ assertThat(describe(first), not(equalFirstNameOnly));
+ assertThat(describe(first), not(equalSecond));
+ assertThat(describe(first), not(new Object()));
+ assertThat(describe(first), not(equalTo(null)));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testIllegalMethod() throws Exception {
+ describe(first).getValue(new MethodDescription.ForLoadedMethod(Object.class.getMethod("toString")));
+ }
+
+ @Test
+ public void testLoadedEquals() throws Exception {
+ assertThat(describe(first).prepare(Sample.class).load(), is(first));
+ assertThat(describe(first).prepare(Sample.class).load(), is(describe(first).prepare(Sample.class).load()));
+ assertThat(describe(first).prepare(Sample.class).load(), not(describe(second).prepare(Sample.class).load()));
+ assertThat(describe(second).prepare(Sample.class).load(), is(second));
+ assertThat(describe(first).prepare(Sample.class).load(), not(second));
+ }
+
+ @Test
+ public void testLoadedHashCode() throws Exception {
+ assertThat(describe(first).prepare(Sample.class).load().hashCode(), is(first.hashCode()));
+ assertThat(describe(second).prepare(Sample.class).load().hashCode(), is(second.hashCode()));
+ assertThat(describe(first).prepare(Sample.class).load().hashCode(), not(second.hashCode()));
+ }
+
+ @Test
+ public void testLoadedToString() throws Exception {
+ assertToString(describe(first).prepare(Sample.class).load().toString(), first);
+ assertToString(describe(second).prepare(Sample.class).load().toString(), second);
+ }
+
+ @Test
+ public void testToString() throws Exception {
+ assertToString(describe(first).prepare(Sample.class).toString(), first);
+ assertToString(describe(second).prepare(Sample.class).toString(), second);
+ }
+
+ @Test
+ @Ignore("Add better handling for annotations with inconsistent values")
+ public void testBrokenAnnotation() throws Exception {
+ describe(broken);
+ }
+
+ @Test
+ public void testLoadedAnnotationType() throws Exception {
+ assertThat(describe(first).prepare(Sample.class).load().annotationType(), CoreMatchers.<Class<?>>is(Sample.class));
+ assertThat(describe(second).prepare(Sample.class).load().annotationType(), CoreMatchers.<Class<?>>is(Sample.class));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testIllegalPreparation() throws Exception {
+ describe(first).prepare(Other.class);
+ }
+
+ @Test
+ public void testValues() throws Exception {
+ assertValue(first, "booleanValue", BOOLEAN, BOOLEAN);
+ assertValue(second, "booleanValue", BOOLEAN, BOOLEAN);
+ assertValue(first, "byteValue", BYTE, BYTE);
+ assertValue(second, "byteValue", BYTE, BYTE);
+ assertValue(first, "shortValue", SHORT, SHORT);
+ assertValue(second, "shortValue", SHORT, SHORT);
+ assertValue(first, "charValue", CHARACTER, CHARACTER);
+ assertValue(second, "charValue", CHARACTER, CHARACTER);
+ assertValue(first, "intValue", INTEGER, INTEGER);
+ assertValue(second, "intValue", INTEGER, INTEGER);
+ assertValue(first, "longValue", LONG, LONG);
+ assertValue(second, "longValue", LONG, LONG);
+ assertValue(first, "floatValue", FLOAT, FLOAT);
+ assertValue(second, "floatValue", FLOAT, FLOAT);
+ assertValue(first, "doubleValue", DOUBLE, DOUBLE);
+ assertValue(second, "doubleValue", DOUBLE, DOUBLE);
+ assertValue(first, "stringValue", FOO, FOO);
+ assertValue(second, "stringValue", BAR, BAR);
+ assertValue(first, "classValue", new TypeDescription.ForLoadedType(CLASS), CLASS);
+ assertValue(second, "classValue", new TypeDescription.ForLoadedType(CLASS), CLASS);
+ assertValue(first, "arrayClassValue", new TypeDescription.ForLoadedType(ARRAY_CLASS), ARRAY_CLASS);
+ assertValue(second, "arrayClassValue", new TypeDescription.ForLoadedType(ARRAY_CLASS), ARRAY_CLASS);
+ assertValue(first, "enumValue", new EnumerationDescription.ForLoadedEnumeration(ENUMERATION), ENUMERATION);
+ assertValue(second, "enumValue", new EnumerationDescription.ForLoadedEnumeration(ENUMERATION), ENUMERATION);
+ assertValue(first, "annotationValue", AnnotationDescription.ForLoadedAnnotation.of(ANNOTATION), ANNOTATION);
+ assertValue(second, "annotationValue", AnnotationDescription.ForLoadedAnnotation.of(ANNOTATION), ANNOTATION);
+ assertValue(first, "booleanArrayValue", BOOLEAN_ARRAY, BOOLEAN_ARRAY);
+ assertValue(second, "booleanArrayValue", BOOLEAN_ARRAY, BOOLEAN_ARRAY);
+ assertValue(first, "byteArrayValue", BYTE_ARRAY, BYTE_ARRAY);
+ assertValue(second, "byteArrayValue", BYTE_ARRAY, BYTE_ARRAY);
+ assertValue(first, "shortArrayValue", SHORT_ARRAY, SHORT_ARRAY);
+ assertValue(second, "shortArrayValue", SHORT_ARRAY, SHORT_ARRAY);
+ assertValue(first, "charArrayValue", CHARACTER_ARRAY, CHARACTER_ARRAY);
+ assertValue(second, "charArrayValue", CHARACTER_ARRAY, CHARACTER_ARRAY);
+ assertValue(first, "intArrayValue", INTEGER_ARRAY, INTEGER_ARRAY);
+ assertValue(second, "intArrayValue", INTEGER_ARRAY, INTEGER_ARRAY);
+ assertValue(first, "longArrayValue", LONG_ARRAY, LONG_ARRAY);
+ assertValue(second, "longArrayValue", LONG_ARRAY, LONG_ARRAY);
+ assertValue(first, "floatArrayValue", FLOAT_ARRAY, FLOAT_ARRAY);
+ assertValue(second, "floatArrayValue", FLOAT_ARRAY, FLOAT_ARRAY);
+ assertValue(first, "doubleArrayValue", DOUBLE_ARRAY, DOUBLE_ARRAY);
+ assertValue(second, "doubleArrayValue", DOUBLE_ARRAY, DOUBLE_ARRAY);
+ assertValue(first, "stringArrayValue", STRING_ARRAY, STRING_ARRAY);
+ assertValue(second, "stringArrayValue", STRING_ARRAY, STRING_ARRAY);
+ assertValue(first, "classArrayValue", new TypeDescription[]{new TypeDescription.ForLoadedType(CLASS)}, CLASS_ARRAY);
+ assertValue(second, "classArrayValue", new TypeDescription[]{new TypeDescription.ForLoadedType(CLASS)}, CLASS_ARRAY);
+ assertValue(first, "enumArrayValue", new EnumerationDescription[]{new EnumerationDescription.ForLoadedEnumeration(ENUMERATION)}, ENUMERATION_ARRAY);
+ assertValue(second, "enumArrayValue", new EnumerationDescription[]{new EnumerationDescription.ForLoadedEnumeration(ENUMERATION)}, ENUMERATION_ARRAY);
+ assertValue(first, "annotationArrayValue", new AnnotationDescription[]{AnnotationDescription.ForLoadedAnnotation.of(ANNOTATION)}, ANNOTATION_ARRAY);
+ assertValue(second, "annotationArrayValue", new AnnotationDescription[]{AnnotationDescription.ForLoadedAnnotation.of(ANNOTATION)}, ANNOTATION_ARRAY);
+ }
+
+ @Test
+ public void testValuesDefaults() throws Exception {
+ assertValue(defaultFirst, "booleanValue", BOOLEAN, BOOLEAN);
+ assertValue(defaultSecond, "booleanValue", OTHER_BOOLEAN, OTHER_BOOLEAN);
+ assertValue(defaultFirst, "byteValue", BYTE, BYTE);
+ assertValue(defaultSecond, "byteValue", OTHER_BYTE, OTHER_BYTE);
+ assertValue(defaultFirst, "shortValue", SHORT, SHORT);
+ assertValue(defaultSecond, "shortValue", OTHER_SHORT, OTHER_SHORT);
+ assertValue(defaultFirst, "charValue", CHARACTER, CHARACTER);
+ assertValue(defaultSecond, "charValue", OTHER_CHARACTER, OTHER_CHARACTER);
+ assertValue(defaultFirst, "intValue", INTEGER, INTEGER);
+ assertValue(defaultSecond, "intValue", OTHER_INTEGER, OTHER_INTEGER);
+ assertValue(defaultFirst, "longValue", LONG, LONG);
+ assertValue(defaultSecond, "longValue", OTHER_LONG, OTHER_LONG);
+ assertValue(defaultFirst, "floatValue", FLOAT, FLOAT);
+ assertValue(defaultSecond, "floatValue", OTHER_FLOAT, OTHER_FLOAT);
+ assertValue(defaultFirst, "doubleValue", DOUBLE, DOUBLE);
+ assertValue(defaultSecond, "doubleValue", OTHER_DOUBLE, OTHER_DOUBLE);
+ assertValue(defaultFirst, "stringValue", FOO, FOO);
+ assertValue(defaultSecond, "stringValue", BAR, BAR);
+ assertValue(defaultFirst, "classValue", new TypeDescription.ForLoadedType(CLASS), CLASS);
+ assertValue(defaultSecond, "classValue", new TypeDescription.ForLoadedType(OTHER_CLASS), OTHER_CLASS);
+ assertValue(defaultFirst, "arrayClassValue", new TypeDescription.ForLoadedType(ARRAY_CLASS), ARRAY_CLASS);
+ assertValue(defaultSecond, "arrayClassValue", new TypeDescription.ForLoadedType(OTHER_ARRAY_CLASS), OTHER_ARRAY_CLASS);
+ assertValue(defaultFirst, "enumValue", new EnumerationDescription.ForLoadedEnumeration(ENUMERATION), ENUMERATION);
+ assertValue(defaultSecond, "enumValue", new EnumerationDescription.ForLoadedEnumeration(OTHER_ENUMERATION), OTHER_ENUMERATION);
+ assertValue(defaultFirst, "annotationValue", AnnotationDescription.ForLoadedAnnotation.of(ANNOTATION), ANNOTATION);
+ assertValue(defaultSecond, "annotationValue", AnnotationDescription.ForLoadedAnnotation.of(OTHER_ANNOTATION), OTHER_ANNOTATION);
+ assertValue(defaultFirst, "booleanArrayValue", BOOLEAN_ARRAY, BOOLEAN_ARRAY);
+ assertValue(defaultSecond, "booleanArrayValue", OTHER_BOOLEAN_ARRAY, OTHER_BOOLEAN_ARRAY);
+ assertValue(defaultFirst, "byteArrayValue", BYTE_ARRAY, BYTE_ARRAY);
+ assertValue(defaultSecond, "byteArrayValue", OTHER_BYTE_ARRAY, OTHER_BYTE_ARRAY);
+ assertValue(defaultFirst, "shortArrayValue", SHORT_ARRAY, SHORT_ARRAY);
+ assertValue(defaultSecond, "shortArrayValue", OTHER_SHORT_ARRAY, OTHER_SHORT_ARRAY);
+ assertValue(defaultFirst, "charArrayValue", CHARACTER_ARRAY, CHARACTER_ARRAY);
+ assertValue(defaultSecond, "charArrayValue", OTHER_CHARACTER_ARRAY, OTHER_CHARACTER_ARRAY);
+ assertValue(defaultFirst, "intArrayValue", INTEGER_ARRAY, INTEGER_ARRAY);
+ assertValue(defaultSecond, "intArrayValue", OTHER_INTEGER_ARRAY, OTHER_INTEGER_ARRAY);
+ assertValue(defaultFirst, "longArrayValue", LONG_ARRAY, LONG_ARRAY);
+ assertValue(defaultSecond, "longArrayValue", OTHER_LONG_ARRAY, OTHER_LONG_ARRAY);
+ assertValue(defaultFirst, "floatArrayValue", FLOAT_ARRAY, FLOAT_ARRAY);
+ assertValue(defaultSecond, "floatArrayValue", OTHER_FLOAT_ARRAY, OTHER_FLOAT_ARRAY);
+ assertValue(defaultFirst, "doubleArrayValue", DOUBLE_ARRAY, DOUBLE_ARRAY);
+ assertValue(defaultSecond, "doubleArrayValue", OTHER_DOUBLE_ARRAY, OTHER_DOUBLE_ARRAY);
+ assertValue(defaultFirst, "stringArrayValue", STRING_ARRAY, STRING_ARRAY);
+ assertValue(defaultSecond, "stringArrayValue", OTHER_STRING_ARRAY, OTHER_STRING_ARRAY);
+ assertValue(defaultFirst, "classArrayValue", new TypeDescription[]{new TypeDescription.ForLoadedType(CLASS)}, CLASS_ARRAY);
+ assertValue(defaultSecond, "classArrayValue", new TypeDescription[]{new TypeDescription.ForLoadedType(OTHER_CLASS)}, OTHER_CLASS_ARRAY);
+ assertValue(defaultFirst, "enumArrayValue", new EnumerationDescription[]{new EnumerationDescription.ForLoadedEnumeration(ENUMERATION)}, ENUMERATION_ARRAY);
+ assertValue(defaultSecond, "enumArrayValue", new EnumerationDescription[]{new EnumerationDescription.ForLoadedEnumeration(OTHER_ENUMERATION)}, OTHER_ENUMERATION_ARRAY);
+ assertValue(defaultFirst, "annotationArrayValue", new AnnotationDescription[]{AnnotationDescription.ForLoadedAnnotation.of(ANNOTATION)}, ANNOTATION_ARRAY);
+ assertValue(defaultSecond, "annotationArrayValue", new AnnotationDescription[]{AnnotationDescription.ForLoadedAnnotation.of(OTHER_ANNOTATION)}, OTHER_ANNOTATION_ARRAY);
+ }
+
+ @Test
+ public void testRetention() throws Exception {
+ assertThat(describe(first).getRetention(), is(RetentionPolicy.RUNTIME));
+ }
+
+ @Test
+ public void testAnnotationTarget() throws Exception {
+ assertThat(describe(first).getElementTypes(), is((Set<ElementType>) new HashSet<ElementType>(Arrays.asList(ElementType.ANNOTATION_TYPE,
+ ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.METHOD,
+ ElementType.PACKAGE, ElementType.PARAMETER, ElementType.TYPE))));
+ assertThat(describe(explicitTarget).getElementTypes(), is(Collections.singleton(ElementType.TYPE)));
+ }
+
+ @Test
+ public void testInheritance() throws Exception {
+ assertThat(describe(first).isInherited(), is(false));
+ assertThat(describe(defaultFirst).isInherited(), is(true));
+ }
+
+ @Test
+ public void testDocumented() throws Exception {
+ assertThat(describe(first).isDocumented(), is(false));
+ assertThat(describe(defaultFirst).isDocumented(), is(true));
+ }
+
+ private void assertValue(Annotation annotation, String methodName, Object unloadedValue, Object loadedValue) throws Exception {
+ assertThat(describe(annotation).getValue(new MethodDescription.ForLoadedMethod(annotation.annotationType().getDeclaredMethod(methodName))).resolve(),
+ is(unloadedValue));
+ assertThat(describe(annotation).getValue(new MethodDescription.Latent(new TypeDescription.ForLoadedType(annotation.annotationType()),
+ methodName,
+ Opcodes.ACC_PUBLIC,
+ Collections.<TypeVariableToken>emptyList(),
+ new TypeDescription.Generic.OfNonGenericType.ForLoadedType(annotation.annotationType().getDeclaredMethod(methodName).getReturnType()),
+ Collections.<ParameterDescription.Token>emptyList(),
+ Collections.<TypeDescription.Generic>emptyList(),
+ Collections.<AnnotationDescription>emptyList(),
+ AnnotationValue.UNDEFINED,
+ TypeDescription.Generic.UNDEFINED)).resolve(), is(unloadedValue));
+ assertThat(annotation.annotationType().getDeclaredMethod(methodName).invoke(describe(annotation).prepare(annotation.annotationType()).load()),
+ is(loadedValue));
+ }
+
+ public enum SampleEnumeration {
+ VALUE,
+ OTHER
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface Sample2 {
+
+ Class<?> foo();
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface Sample {
+
+ boolean booleanValue();
+
+ byte byteValue();
+
+ short shortValue();
+
+ char charValue();
+
+ int intValue();
+
+ long longValue();
+
+ float floatValue();
+
+ double doubleValue();
+
+ String stringValue();
+
+ Class<?> classValue();
+
+ Class<?> arrayClassValue();
+
+ SampleEnumeration enumValue();
+
+ Other annotationValue();
+
+ boolean[] booleanArrayValue();
+
+ byte[] byteArrayValue();
+
+ short[] shortArrayValue();
+
+ char[] charArrayValue();
+
+ int[] intArrayValue();
+
+ long[] longArrayValue();
+
+ float[] floatArrayValue();
+
+ double[] doubleArrayValue();
+
+ String[] stringArrayValue();
+
+ Class<?>[] classArrayValue();
+
+ SampleEnumeration[] enumArrayValue();
+
+ Other[] annotationArrayValue();
+ }
+
+ @Documented
+ @Inherited
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface SampleDefault {
+
+ boolean booleanValue() default BOOLEAN;
+
+ byte byteValue() default BYTE;
+
+ short shortValue() default SHORT;
+
+ char charValue() default CHARACTER;
+
+ int intValue() default INTEGER;
+
+ long longValue() default LONG;
+
+ float floatValue() default FLOAT;
+
+ double doubleValue() default DOUBLE;
+
+ String stringValue() default FOO;
+
+ Class<?> classValue() default Void.class;
+
+ Class<?> arrayClassValue() default Void[].class;
+
+ SampleEnumeration enumValue() default SampleEnumeration.VALUE;
+
+ Other annotationValue() default @Other;
+
+ boolean[] booleanArrayValue() default BOOLEAN;
+
+ byte[] byteArrayValue() default BYTE;
+
+ short[] shortArrayValue() default SHORT;
+
+ char[] charArrayValue() default CHARACTER;
+
+ int[] intArrayValue() default INTEGER;
+
+ long[] longArrayValue() default LONG;
+
+ float[] floatArrayValue() default FLOAT;
+
+ double[] doubleArrayValue() default DOUBLE;
+
+ String[] stringArrayValue() default FOO;
+
+ Class<?>[] classArrayValue() default Void.class;
+
+ SampleEnumeration[] enumArrayValue() default SampleEnumeration.VALUE;
+
+ Other[] annotationArrayValue() default @Other;
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface Other {
+
+ String value() default FOO;
+ }
+
+ @Sample(booleanValue = BOOLEAN,
+ byteValue = BYTE,
+ charValue = CHARACTER,
+ shortValue = SHORT,
+ intValue = INTEGER,
+ longValue = LONG,
+ floatValue = FLOAT,
+ doubleValue = DOUBLE,
+ stringValue = FOO,
+ classValue = Void.class,
+ arrayClassValue = Void[].class,
+ enumValue = SampleEnumeration.VALUE,
+ annotationValue = @Other,
+ booleanArrayValue = BOOLEAN,
+ byteArrayValue = BYTE,
+ shortArrayValue = SHORT,
+ charArrayValue = CHARACTER,
+ intArrayValue = INTEGER,
+ longArrayValue = LONG,
+ floatArrayValue = FLOAT,
+ doubleArrayValue = DOUBLE,
+ stringArrayValue = FOO,
+ classArrayValue = Void.class,
+ enumArrayValue = SampleEnumeration.VALUE,
+ annotationArrayValue = @Other)
+ private static class FooSample {
+ /* empty */
+ }
+
+ @Sample(booleanValue = BOOLEAN,
+ byteValue = BYTE,
+ charValue = CHARACTER,
+ shortValue = SHORT,
+ intValue = INTEGER,
+ longValue = LONG,
+ floatValue = FLOAT,
+ doubleValue = DOUBLE,
+ stringValue = BAR,
+ classValue = Void.class,
+ arrayClassValue = Void[].class,
+ enumValue = SampleEnumeration.VALUE,
+ annotationValue = @Other,
+ booleanArrayValue = BOOLEAN,
+ byteArrayValue = BYTE,
+ shortArrayValue = SHORT,
+ charArrayValue = CHARACTER,
+ intArrayValue = INTEGER,
+ longArrayValue = LONG,
+ floatArrayValue = FLOAT,
+ doubleArrayValue = DOUBLE,
+ stringArrayValue = FOO,
+ classArrayValue = Void.class,
+ enumArrayValue = SampleEnumeration.VALUE,
+ annotationArrayValue = @Other)
+ private static class BarSample {
+ /* empty */
+ }
+
+ @SampleDefault
+ private static class DefaultSample {
+ /* empty */
+ }
+
+ @SampleDefault(booleanValue = !BOOLEAN,
+ byteValue = BYTE * 2,
+ charValue = CHARACTER * 2,
+ shortValue = SHORT * 2,
+ intValue = INTEGER * 2,
+ longValue = LONG * 2,
+ floatValue = FLOAT * 2,
+ doubleValue = DOUBLE * 2,
+ stringValue = BAR,
+ classValue = Object.class,
+ arrayClassValue = Object[].class,
+ enumValue = SampleEnumeration.OTHER,
+ annotationValue = @Other(BAR),
+ booleanArrayValue = !BOOLEAN,
+ byteArrayValue = OTHER_BYTE,
+ shortArrayValue = OTHER_SHORT,
+ charArrayValue = OTHER_CHARACTER,
+ intArrayValue = OTHER_INTEGER,
+ longArrayValue = OTHER_LONG,
+ floatArrayValue = OTHER_FLOAT,
+ doubleArrayValue = OTHER_DOUBLE,
+ stringArrayValue = BAR,
+ classArrayValue = Object.class,
+ enumArrayValue = SampleEnumeration.OTHER,
+ annotationArrayValue = @Other(BAR))
+ private static class NonDefaultSample {
+ /* empty */
+ }
+
+ @Other
+ private static class EnumerationCarrier {
+ /* empty */
+ }
+
+ @Other(BAR)
+ private static class OtherEnumerationCarrier {
+ /* empty */
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ protected @interface ExplicitTarget {
+
+ @ExplicitTarget
+ class Carrier {
+ /* empty */
+ }
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface BrokenAnnotation {
+
+ String stringValue();
+
+ SampleEnumeration enumValue();
+
+ Class<?> classValue();
+ }
+
+ private static class AnnotationValueBreaker extends AsmVisitorWrapper.AbstractBase {
+
+ @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 BreakingClassVisitor(classVisitor);
+ }
+
+ private static class BreakingClassVisitor extends ClassVisitor {
+
+ public BreakingClassVisitor(ClassVisitor classVisitor) {
+ super(Opcodes.ASM5, classVisitor);
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+ super.visit(version, access, name, signature, superName, interfaces);
+ AnnotationVisitor annotationVisitor = visitAnnotation(Type.getDescriptor(BrokenAnnotation.class), true);
+ annotationVisitor.visit("stringValue", INTEGER);
+ annotationVisitor.visitEnum("enumValue", Type.getDescriptor(SampleEnumeration.class), FOO);
+ annotationVisitor.visit("classValue", Type.getType("Lnet/bytebuddy/inexistant/Foo;"));
+ annotationVisitor.visitEnd();
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AbstractAnnotationListTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AbstractAnnotationListTest.java
new file mode 100644
index 0000000..ad2af9f
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AbstractAnnotationListTest.java
@@ -0,0 +1,109 @@
+package net.bytebuddy.description.annotation;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.AbstractFilterableListTest;
+import net.bytebuddy.matcher.ElementMatchers;
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public abstract class AbstractAnnotationListTest<U> extends AbstractFilterableListTest<AnnotationDescription, AnnotationList, U> {
+
+ @Test
+ public void testAnnotationIsPresent() throws Exception {
+ assertThat(asList(getFirst()).isAnnotationPresent(Foo.class), is(true));
+ }
+
+ @Test
+ public void testAnnotationIsNotPresent() throws Exception {
+ assertThat(asList(getFirst()).isAnnotationPresent(Annotation.class), is(false));
+ }
+
+ @Test
+ public void testAnnotationIsPresentDescription() throws Exception {
+ assertThat(asList(getFirst()).isAnnotationPresent(new TypeDescription.ForLoadedType(Foo.class)), is(true));
+ }
+
+ @Test
+ public void testAnnotationIsNotPresentDescription() throws Exception {
+ assertThat(asList(getFirst()).isAnnotationPresent(new TypeDescription.ForLoadedType(Annotation.class)), is(false));
+ }
+
+ @Test
+ public void testAnnotationOfType() throws Exception {
+ assertThat(asList(getFirst()).ofType(Foo.class), is(AnnotationDescription.ForLoadedAnnotation.of(Holder.class.getAnnotation(Foo.class))));
+ }
+
+ @Test
+ public void testAnnotationOfTypeWrongType() throws Exception {
+ assertThat(asList(getFirst()).ofType(Annotation.class), nullValue(AnnotationDescription.Loadable.class));
+ }
+
+ @Test
+ public void testAnnotationOfTypeDescription() throws Exception {
+ assertThat(asList(getFirst()).ofType(new TypeDescription.ForLoadedType(Foo.class)),
+ is((AnnotationDescription) AnnotationDescription.ForLoadedAnnotation.of(Holder.class.getAnnotation(Foo.class))));
+ }
+
+ @Test
+ public void testAnnotationWrongTypeOfTypeDescription() throws Exception {
+ assertThat(asList(getFirst()).ofType(new TypeDescription.ForLoadedType(Annotation.class)), nullValue(AnnotationDescription.class));
+ }
+
+ @Test
+ public void testInherited() throws Exception {
+ assertThat(asList(getFirst()).inherited(Collections.<TypeDescription>emptySet()), is(asList(getFirst())));
+ }
+
+ @Test
+ public void testInheritedIgnoreType() throws Exception {
+ assertThat(asList(getFirst()).inherited(Collections.<TypeDescription>singleton(new TypeDescription.ForLoadedType(Foo.class))).size(), is(0));
+ }
+
+ @Test
+ public void testInheritedIgnoreNonInherited() throws Exception {
+ assertThat(asList(getSecond()).inherited(Collections.<TypeDescription>emptySet()).size(), is(0));
+ }
+
+ @Test
+ public void testVisible() throws Exception {
+ assertThat(asList(getFirst()).visibility(ElementMatchers.is(RetentionPolicy.RUNTIME)), is(asList(getFirst())));
+ }
+
+ @Test
+ public void testNotVisible() throws Exception {
+ assertThat(asList(getFirst()).visibility(ElementMatchers.is(RetentionPolicy.SOURCE)).size(), is(0));
+ }
+
+ @Test
+ public void testAsTypeList() throws Exception {
+ assertThat(asList(getFirst()).asTypeList(), is(Collections.singletonList(asElement(getFirst()).getAnnotationType())));
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Inherited
+ protected @interface Foo {
+ /* empty */
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ protected @interface Bar {
+ /* empty */
+ }
+
+ @Foo
+ @Bar
+ public static class Holder {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationDescriptionAbstractPreparedExceptionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationDescriptionAbstractPreparedExceptionTest.java
new file mode 100644
index 0000000..0feb25b
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationDescriptionAbstractPreparedExceptionTest.java
@@ -0,0 +1,38 @@
+package net.bytebuddy.description.annotation;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import org.junit.Test;
+
+import java.lang.annotation.Annotation;
+
+public class AnnotationDescriptionAbstractPreparedExceptionTest {
+
+ @Test(expected = IllegalStateException.class)
+ public void testThrowWithoutClassLoader() throws Exception {
+ new PseudoDescription().loadSilent();
+ }
+
+ private static class PseudoDescription extends AnnotationDescription.AbstractBase.ForPrepared<Annotation> {
+
+ @Override
+ public Annotation load() throws ClassNotFoundException {
+ throw new ClassNotFoundException();
+ }
+
+ @Override
+ public AnnotationValue<?, ?> getValue(MethodDescription.InDefinedShape property) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public TypeDescription getAnnotationType() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T extends Annotation> Loadable<T> prepare(Class<T> annotationType) {
+ throw new UnsupportedOperationException();
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationDescriptionAnnotationInvocationHandlerTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationDescriptionAnnotationInvocationHandlerTest.java
new file mode 100644
index 0000000..f67a658
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationDescriptionAnnotationInvocationHandlerTest.java
@@ -0,0 +1,305 @@
+package net.bytebuddy.description.annotation;
+
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.AnnotationTypeMismatchException;
+import java.lang.annotation.IncompleteAnnotationException;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.any;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class AnnotationDescriptionAnnotationInvocationHandlerTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private AnnotationValue<?, ?> annotationValue, otherAnnotationValue, freeAnnotationValue;
+
+ @Mock
+ private AnnotationValue.Loaded<?> loadedAnnotationValue, otherLoadedAnnotationValue;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ when(annotationValue.load(getClass().getClassLoader()))
+ .thenReturn((AnnotationValue.Loaded) loadedAnnotationValue);
+ when(otherAnnotationValue.load(getClass().getClassLoader()))
+ .thenReturn((AnnotationValue.Loaded) otherLoadedAnnotationValue);
+ }
+
+ @Test(expected = ClassNotFoundException.class)
+ public void testClassNotFoundExceptionIsTransparent() throws Throwable {
+ when(freeAnnotationValue.load(getClass().getClassLoader())).thenThrow(new ClassNotFoundException());
+ Proxy.getInvocationHandler(AnnotationDescription.AnnotationInvocationHandler.of(getClass().getClassLoader(),
+ Foo.class,
+ Collections.<String, AnnotationValue<?, ?>>singletonMap(FOO, freeAnnotationValue)))
+ .invoke(new Object(), Foo.class.getDeclaredMethod("foo"), new Object[0]);
+ }
+
+ @Test(expected = AnnotationTypeMismatchException.class)
+ @SuppressWarnings("unchecked")
+ public void testAnnotationTypeMismatchException() throws Throwable {
+ when(loadedAnnotationValue.resolve()).thenReturn(new Object());
+ Proxy.getInvocationHandler(AnnotationDescription.AnnotationInvocationHandler.of(getClass().getClassLoader(),
+ Foo.class,
+ Collections.<String, AnnotationValue<?, ?>>singletonMap(FOO, annotationValue)))
+ .invoke(new Object(), Foo.class.getDeclaredMethod("foo"), new Object[0]);
+ }
+
+ @Test(expected = IncompleteAnnotationException.class)
+ @SuppressWarnings("unchecked")
+ public void testIncompleteAnnotationException() throws Throwable {
+ when(freeAnnotationValue.load(getClass().getClassLoader())).thenReturn((AnnotationValue.Loaded)
+ new AnnotationDescription.AnnotationInvocationHandler.MissingValue(Foo.class, "foo"));
+ Proxy.getInvocationHandler(AnnotationDescription.AnnotationInvocationHandler.of(getClass().getClassLoader(),
+ Foo.class,
+ Collections.<String, AnnotationValue<?, ?>>singletonMap(FOO, freeAnnotationValue)))
+ .invoke(new Object(), Foo.class.getDeclaredMethod("foo"), new Object[0]);
+ }
+
+ @Test(expected = EnumConstantNotPresentException.class)
+ @SuppressWarnings("unchecked")
+ public void testEnumConstantNotPresentException() throws Throwable {
+ when(freeAnnotationValue.load(getClass().getClassLoader())).thenReturn((AnnotationValue.Loaded)
+ new AnnotationValue.ForEnumerationDescription.UnknownRuntimeEnumeration(Bar.class, FOO));
+ Proxy.getInvocationHandler(AnnotationDescription.AnnotationInvocationHandler.of(getClass().getClassLoader(),
+ Foo.class,
+ Collections.<String, AnnotationValue<?, ?>>singletonMap(FOO, freeAnnotationValue)))
+ .invoke(new Object(), Foo.class.getDeclaredMethod("foo"), new Object[0]);
+ }
+
+ @Test(expected = IncompatibleClassChangeError.class)
+ @SuppressWarnings("unchecked")
+ public void testEnumTypeIncompatible() throws Throwable {
+ when(freeAnnotationValue.load(getClass().getClassLoader())).thenReturn((AnnotationValue.Loaded)
+ new AnnotationValue.ForEnumerationDescription.IncompatibleRuntimeType(Foo.class));
+ Proxy.getInvocationHandler(AnnotationDescription.AnnotationInvocationHandler.of(getClass().getClassLoader(),
+ Foo.class,
+ Collections.<String, AnnotationValue<?, ?>>singletonMap(FOO, freeAnnotationValue)))
+ .invoke(new Object(), Foo.class.getDeclaredMethod("foo"), new Object[0]);
+ }
+
+ @Test(expected = IncompatibleClassChangeError.class)
+ @SuppressWarnings("unchecked")
+ public void testAnnotationTypeIncompatible() throws Throwable {
+ when(freeAnnotationValue.load(getClass().getClassLoader())).thenReturn((AnnotationValue.Loaded)
+ new AnnotationValue.ForEnumerationDescription.IncompatibleRuntimeType(Foo.class));
+ Proxy.getInvocationHandler(AnnotationDescription.AnnotationInvocationHandler.of(getClass().getClassLoader(),
+ Foo.class,
+ Collections.<String, AnnotationValue<?, ?>>singletonMap(FOO, freeAnnotationValue)))
+ .invoke(new Object(), Foo.class.getDeclaredMethod("foo"), new Object[0]);
+ }
+
+ @Test(expected = RuntimeException.class)
+ public void testOtherExceptionIsTransparent() throws Throwable {
+ when(freeAnnotationValue.load(getClass().getClassLoader())).thenThrow(new RuntimeException());
+ Proxy.getInvocationHandler(AnnotationDescription.AnnotationInvocationHandler.of(getClass().getClassLoader(),
+ Foo.class,
+ Collections.<String, AnnotationValue<?, ?>>singletonMap(FOO, freeAnnotationValue)))
+ .invoke(new Object(), Foo.class.getDeclaredMethod("foo"), new Object[0]);
+ }
+
+ @Test
+ public void testEqualsToDirectIsTrue() throws Throwable {
+ when(loadedAnnotationValue.getState()).thenReturn(AnnotationValue.Loaded.State.RESOLVED);
+ when(loadedAnnotationValue.represents(FOO)).thenReturn(true);
+ assertThat(Proxy.getInvocationHandler(AnnotationDescription.AnnotationInvocationHandler.of(getClass().getClassLoader(),
+ Foo.class,
+ Collections.<String, AnnotationValue<?, ?>>singletonMap(FOO, annotationValue)))
+ .invoke(new Object(), Object.class.getDeclaredMethod("equals", Object.class), new Object[]{new ExplicitFoo(FOO)}),
+ is((Object) true));
+ }
+
+ @Test
+ public void testEqualsToUnresolvedIsFalse() throws Throwable {
+ when(loadedAnnotationValue.getState()).thenReturn(AnnotationValue.Loaded.State.UNRESOLVED);
+ assertThat(Proxy.getInvocationHandler(AnnotationDescription.AnnotationInvocationHandler.of(getClass().getClassLoader(),
+ Foo.class,
+ Collections.<String, AnnotationValue<?, ?>>singletonMap(FOO, annotationValue)))
+ .invoke(new Object(), Object.class.getDeclaredMethod("equals", Object.class), new Object[]{new ExplicitFoo(FOO)}),
+ is((Object) false));
+ verify(loadedAnnotationValue, never()).resolve();
+ }
+
+ @Test
+ public void testEqualsToUndefinedIsFalse() throws Throwable {
+ when(loadedAnnotationValue.getState()).thenReturn(AnnotationValue.Loaded.State.UNDEFINED);
+ assertThat(Proxy.getInvocationHandler(AnnotationDescription.AnnotationInvocationHandler.of(getClass().getClassLoader(),
+ Foo.class,
+ Collections.<String, AnnotationValue<?, ?>>singletonMap(FOO, annotationValue)))
+ .invoke(new Object(), Object.class.getDeclaredMethod("equals", Object.class), new Object[]{new ExplicitFoo(FOO)}),
+ is((Object) false));
+ verify(loadedAnnotationValue, never()).resolve();
+ }
+
+ @Test
+ public void testEqualsToIndirectIsTrue() throws Throwable {
+ when(loadedAnnotationValue.getState()).thenReturn(AnnotationValue.Loaded.State.RESOLVED);
+ when(loadedAnnotationValue.represents(FOO)).thenReturn(true);
+ assertThat(Proxy.getInvocationHandler(AnnotationDescription.AnnotationInvocationHandler.of(getClass().getClassLoader(),
+ Foo.class,
+ Collections.<String, AnnotationValue<?, ?>>singletonMap(FOO, annotationValue)))
+ .invoke(new Object(), Object.class.getDeclaredMethod("equals", Object.class),
+ new Object[]{Proxy.newProxyInstance(getClass().getClassLoader(), new Class<?>[]{Foo.class}, new ExplicitFoo(FOO))}),
+ is((Object) true));
+ verify(loadedAnnotationValue).represents(FOO);
+ }
+
+ @Test
+ public void testEqualsToOtherHandlerIsTrue() throws Throwable {
+ when(loadedAnnotationValue.getState()).thenReturn(AnnotationValue.Loaded.State.RESOLVED);
+ when(loadedAnnotationValue.resolve()).thenReturn(FOO);
+ InvocationHandler invocationHandler = Proxy.getInvocationHandler(AnnotationDescription.AnnotationInvocationHandler.of(getClass().getClassLoader(),
+ Foo.class,
+ Collections.<String, AnnotationValue<?, ?>>singletonMap(FOO, annotationValue)));
+ assertThat(Proxy.getInvocationHandler(AnnotationDescription.AnnotationInvocationHandler.of(getClass().getClassLoader(),
+ Foo.class,
+ Collections.<String, AnnotationValue<?, ?>>singletonMap(FOO, annotationValue)))
+ .invoke(new Object(), Object.class.getDeclaredMethod("equals", Object.class),
+ new Object[]{Proxy.newProxyInstance(getClass().getClassLoader(), new Class<?>[]{Foo.class}, invocationHandler)}),
+ is((Object) true));
+ verify(loadedAnnotationValue, never()).resolve();
+ }
+
+ @Test
+ public void testEqualsToDirectIsFalse() throws Throwable {
+ when(loadedAnnotationValue.getState()).thenReturn(AnnotationValue.Loaded.State.RESOLVED);
+ when(loadedAnnotationValue.represents(BAR)).thenReturn(false);
+ assertThat(Proxy.getInvocationHandler(AnnotationDescription.AnnotationInvocationHandler.of(getClass().getClassLoader(),
+ Foo.class,
+ Collections.<String, AnnotationValue<?, ?>>singletonMap(FOO, annotationValue)))
+ .invoke(new Object(), Object.class.getDeclaredMethod("equals", Object.class), new Object[]{new ExplicitFoo(BAR)}),
+ is((Object) false));
+ verify(loadedAnnotationValue).represents(BAR);
+ }
+
+ @Test
+ public void testEqualsToIndirectIsFalse() throws Throwable {
+ when(loadedAnnotationValue.getState()).thenReturn(AnnotationValue.Loaded.State.RESOLVED);
+ when(loadedAnnotationValue.represents(BAR)).thenReturn(false);
+ assertThat(Proxy.getInvocationHandler(AnnotationDescription.AnnotationInvocationHandler.of(getClass().getClassLoader(),
+ Foo.class,
+ Collections.<String, AnnotationValue<?, ?>>singletonMap(FOO, annotationValue)))
+ .invoke(new Object(), Object.class.getDeclaredMethod("equals", Object.class),
+ new Object[]{Proxy.newProxyInstance(getClass().getClassLoader(), new Class<?>[]{Foo.class}, new ExplicitFoo(BAR))}),
+ is((Object) false));
+ verify(loadedAnnotationValue).represents(BAR);
+ }
+
+ @Test
+ public void testEqualsToOtherHandlerIsFalse() throws Throwable {
+ when(loadedAnnotationValue.getState()).thenReturn(AnnotationValue.Loaded.State.RESOLVED);
+ when(loadedAnnotationValue.resolve()).thenReturn(FOO);
+ when(otherLoadedAnnotationValue.getState()).thenReturn(AnnotationValue.Loaded.State.RESOLVED);
+ when(otherLoadedAnnotationValue.resolve()).thenReturn(BAR);
+ InvocationHandler invocationHandler = Proxy.getInvocationHandler(AnnotationDescription.AnnotationInvocationHandler.of(getClass().getClassLoader(),
+ Foo.class,
+ Collections.<String, AnnotationValue<?, ?>>singletonMap(FOO, otherAnnotationValue)));
+ assertThat(Proxy.getInvocationHandler(AnnotationDescription.AnnotationInvocationHandler.of(getClass().getClassLoader(),
+ Foo.class,
+ Collections.<String, AnnotationValue<?, ?>>singletonMap(FOO, annotationValue)))
+ .invoke(new Object(), Object.class.getDeclaredMethod("equals", Object.class),
+ new Object[]{Proxy.newProxyInstance(getClass().getClassLoader(), new Class<?>[]{Foo.class}, invocationHandler)}),
+ is((Object) false));
+ verify(loadedAnnotationValue, never()).resolve();
+ verify(otherLoadedAnnotationValue, never()).resolve();
+ }
+
+ @Test
+ public void testEqualsToObjectIsFalse() throws Throwable {
+ when(loadedAnnotationValue.getState()).thenReturn(AnnotationValue.Loaded.State.RESOLVED);
+ when(loadedAnnotationValue.resolve()).thenReturn(FOO);
+ assertThat(Proxy.getInvocationHandler(AnnotationDescription.AnnotationInvocationHandler.of(getClass().getClassLoader(),
+ Foo.class,
+ Collections.<String, AnnotationValue<?, ?>>singletonMap(FOO, annotationValue)))
+ .invoke(new Object(), Object.class.getDeclaredMethod("equals", Object.class), new Object[]{new Other()}),
+ is((Object) false));
+ verify(loadedAnnotationValue, never()).resolve();
+ }
+
+ @Test
+ public void testEqualsToInvocationExceptionIsFalse() throws Throwable {
+ when(loadedAnnotationValue.getState()).thenReturn(AnnotationValue.Loaded.State.RESOLVED);
+ when(loadedAnnotationValue.resolve()).thenReturn(FOO);
+ assertThat(Proxy.getInvocationHandler(AnnotationDescription.AnnotationInvocationHandler.of(getClass().getClassLoader(),
+ Foo.class,
+ Collections.<String, AnnotationValue<?, ?>>singletonMap(FOO, annotationValue)))
+ .invoke(new Object(), Object.class.getDeclaredMethod("equals", Object.class), new Object[]{new FooWithException()}),
+ is((Object) false));
+ verify(loadedAnnotationValue, never()).represents(any(String.class));
+ }
+
+ public enum Bar {
+ VALUE
+ }
+
+ public @interface Foo {
+
+ String foo();
+ }
+
+ public @interface DefaultFoo {
+
+ String foo() default FOO;
+ }
+
+ private static class FooWithException implements Foo {
+
+ @Override
+ public String foo() {
+ throw new RuntimeException();
+ }
+
+ @Override
+ public Class<? extends Annotation> annotationType() {
+ return Foo.class;
+ }
+ }
+
+ private static class ExplicitFoo implements Foo, InvocationHandler {
+
+ private final String value;
+
+ private ExplicitFoo(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public String foo() {
+ return value;
+ }
+
+ @Override
+ public Class<? extends Annotation> annotationType() {
+ return Foo.class;
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ return method.invoke(this, args);
+ }
+ }
+
+ private static class Other implements Annotation {
+
+ @Override
+ public Class<? extends Annotation> annotationType() {
+ return Annotation.class;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationDescriptionAnnotationValueLoadedStateTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationDescriptionAnnotationValueLoadedStateTest.java
new file mode 100644
index 0000000..fa8ff11
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationDescriptionAnnotationValueLoadedStateTest.java
@@ -0,0 +1,29 @@
+package net.bytebuddy.description.annotation;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class AnnotationDescriptionAnnotationValueLoadedStateTest {
+
+ @Test
+ public void testIsDefined() throws Exception {
+ assertThat(AnnotationValue.Loaded.State.RESOLVED.isResolved(), is(true));
+ assertThat(AnnotationValue.Loaded.State.UNRESOLVED.isResolved(), is(false));
+ assertThat(AnnotationValue.Loaded.State.UNDEFINED.isResolved(), is(false));
+ }
+
+ @Test
+ public void testIsResolved() throws Exception {
+ assertThat(AnnotationValue.Loaded.State.RESOLVED.isDefined(), is(true));
+ assertThat(AnnotationValue.Loaded.State.UNRESOLVED.isDefined(), is(true));
+ assertThat(AnnotationValue.Loaded.State.UNDEFINED.isDefined(), is(false));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AnnotationValue.Loaded.State.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationDescriptionBuilderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationDescriptionBuilderTest.java
new file mode 100644
index 0000000..b8cd618
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationDescriptionBuilderTest.java
@@ -0,0 +1,107 @@
+package net.bytebuddy.description.annotation;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.lang.annotation.Annotation;
+
+public class AnnotationDescriptionBuilderTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux", BAZ = "baz";
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNonMatchingEnumerationValue() throws Exception {
+ AnnotationDescription.Builder.ofType(Foo.class).define(FOO, FooBar.FIRST);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNonMatchingAnnotationValue() throws Exception {
+ AnnotationDescription.Builder.ofType(Qux.class).define(FOO, new QuxBaz.Instance());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ @SuppressWarnings("unchecked")
+ public void testNonMatchingEnumerationArrayValue() throws Exception {
+ AnnotationDescription.Builder.ofType(Foo.class).defineEnumerationArray(BAR, (Class) Bar.class, Bar.FIRST, FooBar.SECOND);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ @SuppressWarnings("unchecked")
+ public void testNonMatchingAnnotationArrayValue() throws Exception {
+ AnnotationDescription.Builder.ofType(Foo.class).defineAnnotationArray(BAZ, (Class) Qux.class, new Qux.Instance(), new QuxBaz.Instance());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ @SuppressWarnings("unchecked")
+ public void testNonAnnotationType() throws Exception {
+ AnnotationDescription.Builder.ofType((Class) Object.class);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIncompleteAnnotation() throws Exception {
+ AnnotationDescription.Builder.ofType(Foo.class).build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testUnknownProperty() throws Exception {
+ AnnotationDescription.Builder.ofType(Foo.class).define(FOO + BAR, FOO);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testIllegalProperty() throws Exception {
+ AnnotationDescription.Builder.ofType(Foo.class).define(FOO, FOO);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testDuplicateProperty() throws Exception {
+ AnnotationDescription.Builder.ofType(Foo.class).define(BAZ, FOO).define(BAZ, FOO);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AnnotationDescription.Builder.class).apply();
+ }
+
+ public enum Bar {
+ FIRST,
+ SECOND
+ }
+
+ public enum FooBar {
+ FIRST,
+ SECOND
+ }
+
+ public @interface Foo {
+
+ Bar foo();
+
+ Bar[] bar();
+
+ Qux qux();
+
+ String baz();
+ }
+
+ public @interface Qux {
+
+ class Instance implements Qux {
+
+ @Override
+ public Class<? extends Annotation> annotationType() {
+ return Qux.class;
+ }
+ }
+ }
+
+ public @interface QuxBaz {
+
+ class Instance implements QuxBaz {
+
+ @Override
+ public Class<? extends Annotation> annotationType() {
+ return QuxBaz.class;
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationDescriptionForLoadedAnnotationDifferentClassLoaderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationDescriptionForLoadedAnnotationDifferentClassLoaderTest.java
new file mode 100644
index 0000000..decbaaa
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationDescriptionForLoadedAnnotationDifferentClassLoaderTest.java
@@ -0,0 +1,34 @@
+package net.bytebuddy.description.annotation;
+
+import net.bytebuddy.dynamic.loading.ByteArrayClassLoader;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.dynamic.loading.PackageDefinitionStrategy;
+import net.bytebuddy.test.utility.ClassFileExtraction;
+import org.junit.Before;
+
+import java.lang.annotation.Annotation;
+
+public class AnnotationDescriptionForLoadedAnnotationDifferentClassLoaderTest extends AbstractAnnotationDescriptionTest {
+
+ private ClassLoader classLoader;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ classLoader = new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER,
+ ClassFileExtraction.of(Sample.class, SampleDefault.class, Other.class, SampleEnumeration.class, ExplicitTarget.class));
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ protected AnnotationDescription describe(Annotation annotation, Class<?> declaringType) {
+ try {
+ return AnnotationDescription.ForLoadedAnnotation.of(AnnotationDescription.ForLoadedAnnotation.of(annotation)
+ .prepare((Class<Annotation>) classLoader.loadClass(annotation.annotationType().getName()))
+ .load());
+ } catch (ClassNotFoundException exception) {
+ throw new AssertionError(exception);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationDescriptionForLoadedAnnotationTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationDescriptionForLoadedAnnotationTest.java
new file mode 100644
index 0000000..dd7b3e1
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationDescriptionForLoadedAnnotationTest.java
@@ -0,0 +1,68 @@
+package net.bytebuddy.description.annotation;
+
+import net.bytebuddy.description.method.MethodDescription;
+import org.junit.Test;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Method;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class AnnotationDescriptionForLoadedAnnotationTest extends AbstractAnnotationDescriptionTest {
+
+ private static final String FOO = "foo";
+
+ @Override
+ protected AnnotationDescription describe(Annotation annotation, Class<?> declaringType) {
+ return AnnotationDescription.ForLoadedAnnotation.of(annotation);
+ }
+
+ @Test
+ public void testAnnotationNonVisible() throws Exception {
+ assertThat(describe(Carrier.class.getAnnotation(PrivateAnnotation.class), Carrier.class)
+ .getValue(new MethodDescription.ForLoadedMethod(PrivateAnnotation.class.getDeclaredMethod("value"))).resolve(String.class), is(FOO));
+ }
+
+ @Test
+ public void testAnnotationNonVisibleAccessible() throws Exception {
+ Method method = PrivateAnnotation.class.getDeclaredMethod("value");
+ method.setAccessible(true);
+ assertThat(describe(Carrier.class.getAnnotation(PrivateAnnotation.class), Carrier.class)
+ .getValue(new MethodDescription.ForLoadedMethod(method)).resolve(String.class), is(FOO));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testInoperational() throws Exception {
+ describe(PrivateAnnotation.Defect.INSTANCE, Carrier.class)
+ .getValue(new MethodDescription.ForLoadedMethod(PrivateAnnotation.class.getDeclaredMethod("value")));
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ private @interface PrivateAnnotation {
+
+ String value();
+
+ enum Defect implements PrivateAnnotation {
+
+ INSTANCE;
+
+ @Override
+ public String value() {
+ throw new RuntimeException();
+ }
+
+ @Override
+ public Class<? extends Annotation> annotationType() {
+ return PrivateAnnotation.class;
+ }
+ }
+ }
+
+ @PrivateAnnotation(FOO)
+ private static class Carrier {
+
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationDescriptionLatentTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationDescriptionLatentTest.java
new file mode 100644
index 0000000..376ade3
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationDescriptionLatentTest.java
@@ -0,0 +1,76 @@
+package net.bytebuddy.description.annotation;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+
+public class AnnotationDescriptionLatentTest extends AbstractAnnotationDescriptionTest {
+
+ @SuppressWarnings("unchecked")
+ private static AnnotationDescription build(Annotation annotation) throws Exception {
+ AnnotationDescription.Builder builder = AnnotationDescription.Builder.ofType(annotation.annotationType());
+ for (Method method : annotation.annotationType().getDeclaredMethods()) {
+ Object value = method.invoke(annotation);
+ if (value instanceof Annotation) {
+ builder = builder.define(method.getName(), (Annotation) value);
+ } else if (value instanceof Annotation[]) {
+ builder = builder.defineAnnotationArray(method.getName(), (Class) method.getReturnType().getComponentType(), (Annotation[]) value);
+ } else if (value instanceof Enum<?>) {
+ builder = builder.define(method.getName(), (Enum<?>) value);
+ } else if (value instanceof Enum<?>[]) {
+ builder = builder.defineEnumerationArray(method.getName(), (Class) method.getReturnType().getComponentType(), (Enum[]) value);
+ } else if (value instanceof Class<?>) {
+ builder = builder.define(method.getName(), (Class<?>) value);
+ } else if (value instanceof Class<?>[]) {
+ builder = builder.defineTypeArray(method.getName(), (Class<?>[]) value);
+ } else if (value instanceof String) {
+ builder = builder.define(method.getName(), (String) value);
+ } else if (value instanceof String[]) {
+ builder = builder.defineArray(method.getName(), (String[]) value);
+ } else if (value instanceof Boolean) {
+ builder = builder.define(method.getName(), (Boolean) value);
+ } else if (value instanceof Byte) {
+ builder = builder.define(method.getName(), (Byte) value);
+ } else if (value instanceof Character) {
+ builder = builder.define(method.getName(), (Character) value);
+ } else if (value instanceof Short) {
+ builder = builder.define(method.getName(), (Short) value);
+ } else if (value instanceof Integer) {
+ builder = builder.define(method.getName(), (Integer) value);
+ } else if (value instanceof Long) {
+ builder = builder.define(method.getName(), (Long) value);
+ } else if (value instanceof Float) {
+ builder = builder.define(method.getName(), (Float) value);
+ } else if (value instanceof Double) {
+ builder = builder.define(method.getName(), (Double) value);
+ } else if (value instanceof boolean[]) {
+ builder = builder.defineArray(method.getName(), (boolean[]) value);
+ } else if (value instanceof byte[]) {
+ builder = builder.defineArray(method.getName(), (byte[]) value);
+ } else if (value instanceof char[]) {
+ builder = builder.defineArray(method.getName(), (char[]) value);
+ } else if (value instanceof short[]) {
+ builder = builder.defineArray(method.getName(), (short[]) value);
+ } else if (value instanceof int[]) {
+ builder = builder.defineArray(method.getName(), (int[]) value);
+ } else if (value instanceof long[]) {
+ builder = builder.defineArray(method.getName(), (long[]) value);
+ } else if (value instanceof float[]) {
+ builder = builder.defineArray(method.getName(), (float[]) value);
+ } else if (value instanceof double[]) {
+ builder = builder.defineArray(method.getName(), (double[]) value);
+ } else {
+ throw new IllegalArgumentException("Cannot handle: " + method);
+ }
+ }
+ return builder.build();
+ }
+
+ @Override
+ protected AnnotationDescription describe(Annotation annotation, Class<?> declaringType) {
+ try {
+ return build(annotation);
+ } catch (Exception exception) {
+ throw new AssertionError(exception);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationListEmptyTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationListEmptyTest.java
new file mode 100644
index 0000000..ef7d7d3
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationListEmptyTest.java
@@ -0,0 +1,45 @@
+package net.bytebuddy.description.annotation;
+
+import net.bytebuddy.description.type.TypeDescription;
+import org.junit.Test;
+
+import java.lang.annotation.Annotation;
+import java.util.Collections;
+
+import static net.bytebuddy.matcher.ElementMatchers.none;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class AnnotationListEmptyTest {
+
+ @Test
+ public void testAnnotationIsPresent() throws Exception {
+ assertThat(new AnnotationList.Empty().isAnnotationPresent(Annotation.class), is(false));
+ }
+
+ @Test
+ public void testAnnotationIsPresentDescription() throws Exception {
+ assertThat(new AnnotationList.Empty().isAnnotationPresent(new TypeDescription.ForLoadedType(Annotation.class)), is(false));
+ }
+
+ @Test
+ public void testAnnotationOfType() throws Exception {
+ assertThat(new AnnotationList.Empty().ofType(Annotation.class), nullValue(AnnotationDescription.Loadable.class));
+ }
+
+ @Test
+ public void testAnnotationInherited() throws Exception {
+ assertThat(new AnnotationList.Empty().inherited(Collections.<TypeDescription>emptySet()).size(), is(0));
+ }
+
+ @Test
+ public void testAnnotationVisibility() throws Exception {
+ assertThat(new AnnotationList.Empty().visibility(none()).size(), is(0));
+ }
+
+ @Test
+ public void testAsTypeList() throws Exception {
+ assertThat(new AnnotationList.Empty().asTypeList().size(), is(0));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationListExplicitTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationListExplicitTest.java
new file mode 100644
index 0000000..5001ce6
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationListExplicitTest.java
@@ -0,0 +1,26 @@
+package net.bytebuddy.description.annotation;
+
+import java.util.List;
+
+public class AnnotationListExplicitTest extends AbstractAnnotationListTest<AnnotationDescription> {
+
+ @Override
+ protected AnnotationDescription getFirst() throws Exception {
+ return new AnnotationDescription.ForLoadedAnnotation<Foo>(Holder.class.getAnnotation(Foo.class));
+ }
+
+ @Override
+ protected AnnotationDescription getSecond() throws Exception {
+ return new AnnotationDescription.ForLoadedAnnotation<Bar>(Holder.class.getAnnotation(Bar.class));
+ }
+
+ @Override
+ protected AnnotationList asList(List<AnnotationDescription> elements) {
+ return new AnnotationList.Explicit(elements);
+ }
+
+ @Override
+ protected AnnotationDescription asElement(AnnotationDescription element) {
+ return element;
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationListForLoadedAnnotationsTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationListForLoadedAnnotationsTest.java
new file mode 100644
index 0000000..705f8b6
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationListForLoadedAnnotationsTest.java
@@ -0,0 +1,27 @@
+package net.bytebuddy.description.annotation;
+
+import java.lang.annotation.Annotation;
+import java.util.List;
+
+public class AnnotationListForLoadedAnnotationsTest extends AbstractAnnotationListTest<Annotation> {
+
+ @Override
+ protected Annotation getFirst() throws Exception {
+ return Holder.class.getAnnotation(Foo.class);
+ }
+
+ @Override
+ protected Annotation getSecond() throws Exception {
+ return Holder.class.getAnnotation(Bar.class);
+ }
+
+ @Override
+ protected AnnotationList asList(List<Annotation> elements) {
+ return new AnnotationList.ForLoadedAnnotations(elements);
+ }
+
+ @Override
+ protected AnnotationDescription asElement(Annotation element) {
+ return new AnnotationDescription.ForLoadedAnnotation<Annotation>(element);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationSourceTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationSourceTest.java
new file mode 100644
index 0000000..ef3b8e9
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationSourceTest.java
@@ -0,0 +1,22 @@
+package net.bytebuddy.description.annotation;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.mock;
+
+public class AnnotationSourceTest {
+
+ @Test
+ public void testEmpty() throws Exception {
+ assertThat(AnnotationSource.Empty.INSTANCE.getDeclaredAnnotations().size(), is(0));
+ }
+
+ @Test
+ public void testExplicit() throws Exception {
+ AnnotationDescription annotationDescription = mock(AnnotationDescription.class);
+ assertThat(new AnnotationSource.Explicit(annotationDescription).getDeclaredAnnotations().size(), is(1));
+ assertThat(new AnnotationSource.Explicit(annotationDescription).getDeclaredAnnotations().getOnly(), is(annotationDescription));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationValueForConstantTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationValueForConstantTest.java
new file mode 100644
index 0000000..31a470c
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationValueForConstantTest.java
@@ -0,0 +1,18 @@
+package net.bytebuddy.description.annotation;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+public class AnnotationValueForConstantTest {
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testIllegalArgument() throws Exception {
+ AnnotationValue.ForConstant.of(new Object());
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AnnotationValue.ForConstant.PropertyDelegate.ForArrayType.class).apply();
+ ObjectPropertyAssertion.of(AnnotationValue.ForConstant.PropertyDelegate.ForNonArrayType.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationValueRenderingDispatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationValueRenderingDispatcherTest.java
new file mode 100644
index 0000000..9f203a5
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/annotation/AnnotationValueRenderingDispatcherTest.java
@@ -0,0 +1,96 @@
+package net.bytebuddy.description.annotation;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.util.Arrays;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class AnnotationValueRenderingDispatcherTest {
+
+ @Test
+ public void testBoolean() throws Exception {
+ assertThat(AnnotationValue.RenderingDispatcher.LEGACY_VM.toSourceString(true), is("true"));
+ assertThat(AnnotationValue.RenderingDispatcher.JAVA_9_CAPABLE_VM.toSourceString(true), is("true"));
+ }
+
+ @Test
+ public void testByte() throws Exception {
+ assertThat(AnnotationValue.RenderingDispatcher.LEGACY_VM.toSourceString((byte) 42), is("42"));
+ assertThat(AnnotationValue.RenderingDispatcher.JAVA_9_CAPABLE_VM.toSourceString((byte) 42), is("42"));
+ }
+
+ @Test
+ public void testShort() throws Exception {
+ assertThat(AnnotationValue.RenderingDispatcher.LEGACY_VM.toSourceString((short) 42), is("42"));
+ assertThat(AnnotationValue.RenderingDispatcher.JAVA_9_CAPABLE_VM.toSourceString((short) 42), is("42"));
+ }
+
+ @Test
+ public void testCharacter() throws Exception {
+ assertThat(AnnotationValue.RenderingDispatcher.LEGACY_VM.toSourceString('*'), is("*"));
+ assertThat(AnnotationValue.RenderingDispatcher.JAVA_9_CAPABLE_VM.toSourceString('*'), is("'*'"));
+ assertThat(AnnotationValue.RenderingDispatcher.LEGACY_VM.toSourceString('\''), is("'"));
+ assertThat(AnnotationValue.RenderingDispatcher.JAVA_9_CAPABLE_VM.toSourceString('\''), is("'\\''"));
+ }
+
+ @Test
+ public void testInteger() throws Exception {
+ assertThat(AnnotationValue.RenderingDispatcher.LEGACY_VM.toSourceString(42), is("42"));
+ assertThat(AnnotationValue.RenderingDispatcher.JAVA_9_CAPABLE_VM.toSourceString(42), is("42"));
+ }
+
+ @Test
+ public void testLong() throws Exception {
+ assertThat(AnnotationValue.RenderingDispatcher.LEGACY_VM.toSourceString(42L), is("42"));
+ assertThat(AnnotationValue.RenderingDispatcher.JAVA_9_CAPABLE_VM.toSourceString(42L), is("42"));
+ assertThat(AnnotationValue.RenderingDispatcher.LEGACY_VM.toSourceString(1L + Integer.MAX_VALUE), is(Long.toString(Integer.MAX_VALUE + 1L)));
+ assertThat(AnnotationValue.RenderingDispatcher.JAVA_9_CAPABLE_VM.toSourceString(1L + Integer.MAX_VALUE), is(Long.toString(Integer.MAX_VALUE + 1L) + "L"));
+ }
+
+ @Test
+ public void testFloat() throws Exception {
+ assertThat(AnnotationValue.RenderingDispatcher.LEGACY_VM.toSourceString(42f), is("42.0"));
+ assertThat(AnnotationValue.RenderingDispatcher.JAVA_9_CAPABLE_VM.toSourceString(42f), is("42.0f"));
+ assertThat(AnnotationValue.RenderingDispatcher.JAVA_9_CAPABLE_VM.toSourceString(Float.POSITIVE_INFINITY), is("1.0f/0.0f"));
+ assertThat(AnnotationValue.RenderingDispatcher.JAVA_9_CAPABLE_VM.toSourceString(Float.NEGATIVE_INFINITY), is("-1.0f/0.0f"));
+ assertThat(AnnotationValue.RenderingDispatcher.JAVA_9_CAPABLE_VM.toSourceString(0f / 0f), is("0.0f/0.0f"));
+ }
+
+ @Test
+ public void testDouble() throws Exception {
+ assertThat(AnnotationValue.RenderingDispatcher.LEGACY_VM.toSourceString(42d), is("42.0"));
+ assertThat(AnnotationValue.RenderingDispatcher.JAVA_9_CAPABLE_VM.toSourceString(42d), is("42.0"));
+ assertThat(AnnotationValue.RenderingDispatcher.JAVA_9_CAPABLE_VM.toSourceString(Double.POSITIVE_INFINITY), is("1.0/0.0"));
+ assertThat(AnnotationValue.RenderingDispatcher.JAVA_9_CAPABLE_VM.toSourceString(Double.NEGATIVE_INFINITY), is("-1.0/0.0"));
+ assertThat(AnnotationValue.RenderingDispatcher.JAVA_9_CAPABLE_VM.toSourceString(0d / 0d), is("0.0/0.0"));
+ }
+
+ @Test
+ public void testString() throws Exception {
+ assertThat(AnnotationValue.RenderingDispatcher.LEGACY_VM.toSourceString("foo"), is("foo"));
+ assertThat(AnnotationValue.RenderingDispatcher.JAVA_9_CAPABLE_VM.toSourceString("foo"), is("\"foo\""));
+ assertThat(AnnotationValue.RenderingDispatcher.LEGACY_VM.toSourceString("\"foo\""), is("\"foo\""));
+ assertThat(AnnotationValue.RenderingDispatcher.JAVA_9_CAPABLE_VM.toSourceString("\"foo\""), is("\"\\\"foo\\\"\""));
+ }
+
+ @Test
+ public void testTypeDescription() throws Exception {
+ assertThat(AnnotationValue.RenderingDispatcher.LEGACY_VM.toSourceString(TypeDescription.OBJECT), is("class java.lang.Object"));
+ assertThat(AnnotationValue.RenderingDispatcher.JAVA_9_CAPABLE_VM.toSourceString(TypeDescription.OBJECT), is("java.lang.Object.class"));
+ }
+
+ @Test
+ public void testArray() throws Exception {
+ assertThat(AnnotationValue.RenderingDispatcher.LEGACY_VM.toSourceString(Arrays.asList("foo", "bar")), is("[foo, bar]"));
+ assertThat(AnnotationValue.RenderingDispatcher.JAVA_9_CAPABLE_VM.toSourceString(Arrays.asList("foo", "bar")), is("{foo, bar}"));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AnnotationValue.RenderingDispatcher.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/enumeration/AbstractEnumerationDescriptionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/enumeration/AbstractEnumerationDescriptionTest.java
new file mode 100644
index 0000000..cdca7df
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/enumeration/AbstractEnumerationDescriptionTest.java
@@ -0,0 +1,132 @@
+package net.bytebuddy.description.enumeration;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.lang.reflect.Method;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public abstract class AbstractEnumerationDescriptionTest {
+
+ private Method annotationMethod;
+
+ private EnumerationDescription describe(Enum<?> enumeration) {
+ if (enumeration == Sample.FIRST) {
+ return describe(enumeration, FirstCarrier.class, new MethodDescription.ForLoadedMethod(annotationMethod));
+ } else {
+ return describe(enumeration, SecondCarrier.class, new MethodDescription.ForLoadedMethod(annotationMethod));
+ }
+ }
+
+ protected abstract EnumerationDescription describe(Enum<?> enumeration,
+ Class<?> carrierType,
+ MethodDescription.InDefinedShape annotationMethod);
+
+ @Before
+ public void setUp() throws Exception {
+ annotationMethod = Carrier.class.getDeclaredMethod("value");
+ }
+
+ @Test
+ public void testPrecondition() throws Exception {
+ assertThat(describe(Sample.FIRST).getEnumerationType().represents(Sample.class), is(true));
+ assertThat(describe(Sample.SECOND).getEnumerationType().represents(Sample.class), is(true));
+ assertThat(describe(Sample.FIRST).getEnumerationType().represents(Other.class), is(false));
+ }
+
+ @Test
+ public void testValue() throws Exception {
+ assertThat(describe(Sample.FIRST).getValue(), is(Sample.FIRST.name()));
+ assertThat(describe(Sample.SECOND).getValue(), is(Sample.SECOND.name()));
+ }
+
+ @Test
+ public void testName() throws Exception {
+ assertThat(describe(Sample.FIRST).getActualName(), is(Sample.FIRST.name()));
+ assertThat(describe(Sample.SECOND).getActualName(), is(Sample.SECOND.name()));
+ }
+
+ @Test
+ public void testToString() throws Exception {
+ assertThat(describe(Sample.FIRST).toString(), is(Sample.FIRST.toString()));
+ assertThat(describe(Sample.SECOND).toString(), is(Sample.SECOND.toString()));
+ }
+
+ @Test
+ public void testType() throws Exception {
+ assertThat(describe(Sample.FIRST).getEnumerationType(), is((TypeDescription) new TypeDescription.ForLoadedType(Sample.class)));
+ assertThat(describe(Sample.SECOND).getEnumerationType(), is((TypeDescription) new TypeDescription.ForLoadedType(Sample.class)));
+ }
+
+ @Test
+ public void testHashCode() throws Exception {
+ assertThat(describe(Sample.FIRST).hashCode(), is(Sample.FIRST.name().hashCode() + 31 * new TypeDescription.ForLoadedType(Sample.class).hashCode()));
+ assertThat(describe(Sample.SECOND).hashCode(), is(Sample.SECOND.name().hashCode() + 31 * new TypeDescription.ForLoadedType(Sample.class).hashCode()));
+ assertThat(describe(Sample.FIRST).hashCode(), not(describe(Sample.SECOND).hashCode()));
+ }
+
+ @Test
+ public void testEquals() throws Exception {
+ EnumerationDescription identical = describe(Sample.FIRST);
+ assertThat(identical, is(identical));
+ EnumerationDescription equalFirst = mock(EnumerationDescription.class);
+ when(equalFirst.getValue()).thenReturn(Sample.FIRST.name());
+ when(equalFirst.getEnumerationType()).thenReturn(new TypeDescription.ForLoadedType(Sample.class));
+ assertThat(describe(Sample.FIRST), is(equalFirst));
+ EnumerationDescription equalSecond = mock(EnumerationDescription.class);
+ when(equalSecond.getValue()).thenReturn(Sample.SECOND.name());
+ when(equalSecond.getEnumerationType()).thenReturn(new TypeDescription.ForLoadedType(Sample.class));
+ assertThat(describe(Sample.SECOND), is(equalSecond));
+ EnumerationDescription equalFirstTypeOnly = mock(EnumerationDescription.class);
+ when(equalFirstTypeOnly.getValue()).thenReturn(Sample.SECOND.name());
+ when(equalFirstTypeOnly.getEnumerationType()).thenReturn(new TypeDescription.ForLoadedType(Sample.class));
+ assertThat(describe(Sample.FIRST), not(equalFirstTypeOnly));
+ EnumerationDescription equalFirstNameOnly = mock(EnumerationDescription.class);
+ when(equalFirstNameOnly.getValue()).thenReturn(Sample.FIRST.name());
+ when(equalFirstNameOnly.getEnumerationType()).thenReturn(new TypeDescription.ForLoadedType(Other.class));
+ assertThat(describe(Sample.FIRST), not(equalFirstNameOnly));
+ assertThat(describe(Sample.FIRST), not(equalSecond));
+ assertThat(describe(Sample.FIRST), not(new Object()));
+ assertThat(describe(Sample.FIRST), not(equalTo(null)));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testIncompatible() throws Exception {
+ describe(Sample.FIRST).load(Other.class);
+ }
+
+ @Test
+ public void testLoad() throws Exception {
+ assertThat(describe(Sample.FIRST).load(Sample.class), is(Sample.FIRST));
+ }
+
+ public enum Sample {
+ FIRST,
+ SECOND
+ }
+
+ private enum Other {
+ INSTANCE
+ }
+
+ public @interface Carrier {
+
+ Sample value();
+ }
+
+ @Carrier(Sample.FIRST)
+ private static class FirstCarrier {
+
+ }
+
+ @Carrier(Sample.SECOND)
+ private static class SecondCarrier {
+
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/enumeration/EnumerationDescriptionForLoadedEnumerationTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/enumeration/EnumerationDescriptionForLoadedEnumerationTest.java
new file mode 100644
index 0000000..a0a74eb
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/enumeration/EnumerationDescriptionForLoadedEnumerationTest.java
@@ -0,0 +1,13 @@
+package net.bytebuddy.description.enumeration;
+
+import net.bytebuddy.description.method.MethodDescription;
+
+public class EnumerationDescriptionForLoadedEnumerationTest extends AbstractEnumerationDescriptionTest {
+
+ @Override
+ protected EnumerationDescription describe(Enum<?> enumeration,
+ Class<?> carrierType,
+ MethodDescription.InDefinedShape annotationMethod) {
+ return new EnumerationDescription.ForLoadedEnumeration(enumeration);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/enumeration/EnumerationDescriptionLatentTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/enumeration/EnumerationDescriptionLatentTest.java
new file mode 100644
index 0000000..15e78ec
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/enumeration/EnumerationDescriptionLatentTest.java
@@ -0,0 +1,14 @@
+package net.bytebuddy.description.enumeration;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+
+public class EnumerationDescriptionLatentTest extends AbstractEnumerationDescriptionTest {
+
+ @Override
+ protected EnumerationDescription describe(Enum<?> enumeration,
+ Class<?> carrierType,
+ MethodDescription.InDefinedShape annotationMethod) {
+ return new EnumerationDescription.Latent(new TypeDescription.ForLoadedType(enumeration.getDeclaringClass()), enumeration.name());
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/field/AbstractFieldDescriptionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/field/AbstractFieldDescriptionTest.java
new file mode 100644
index 0000000..1c6987a
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/field/AbstractFieldDescriptionTest.java
@@ -0,0 +1,275 @@
+package net.bytebuddy.description.field;
+
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.packaging.VisibilityFieldTestHelper;
+import org.junit.Before;
+import org.junit.Test;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public abstract class AbstractFieldDescriptionTest {
+
+ private Field first, second, genericField;
+
+ protected abstract FieldDescription.InDefinedShape describe(Field field);
+
+ @Before
+ public void setUp() throws Exception {
+ first = FirstSample.class.getDeclaredField("first");
+ second = SecondSample.class.getDeclaredField("second");
+ genericField = GenericField.class.getDeclaredField("foo");
+ }
+
+ @Test
+ public void testPrecondition() throws Exception {
+ assertThat(describe(first), not(describe(second)));
+ assertThat(describe(first), is(describe(first)));
+ assertThat(describe(second), is(describe(second)));
+ assertThat(describe(first), is((FieldDescription) new FieldDescription.ForLoadedField(first)));
+ assertThat(describe(second), is((FieldDescription) new FieldDescription.ForLoadedField(second)));
+ }
+
+ @Test
+ public void testFieldType() throws Exception {
+ assertThat(describe(first).getType(), is((TypeDefinition) new TypeDescription.ForLoadedType(first.getType())));
+ assertThat(describe(second).getType(), is((TypeDefinition) new TypeDescription.ForLoadedType(second.getType())));
+ }
+
+ @Test
+ public void testFieldName() throws Exception {
+ assertThat(describe(first).getName(), is(first.getName()));
+ assertThat(describe(second).getName(), is(second.getName()));
+ assertThat(describe(first).getInternalName(), is(first.getName()));
+ assertThat(describe(second).getInternalName(), is(second.getName()));
+ }
+
+ @Test
+ public void testDescriptor() throws Exception {
+ assertThat(describe(first).getDescriptor(), is(Type.getDescriptor(first.getType())));
+ assertThat(describe(second).getDescriptor(), is(Type.getDescriptor(second.getType())));
+ }
+
+ @Test
+ public void testFieldModifier() throws Exception {
+ assertThat(describe(first).getModifiers(), is(first.getModifiers()));
+ assertThat(describe(second).getModifiers(), is(second.getModifiers()));
+ }
+
+ @Test
+ public void testFieldDeclaringType() throws Exception {
+ assertThat(describe(first).getDeclaringType(), is((TypeDescription) new TypeDescription.ForLoadedType(first.getDeclaringClass())));
+ assertThat(describe(second).getDeclaringType(), is((TypeDescription) new TypeDescription.ForLoadedType(second.getDeclaringClass())));
+ }
+
+ @Test
+ public void testHashCode() throws Exception {
+ assertThat(describe(first).hashCode(), is(new TypeDescription.ForLoadedType(FirstSample.class).hashCode() + 31 * first.getName().hashCode()));
+ assertThat(describe(second).hashCode(), is(new TypeDescription.ForLoadedType(SecondSample.class).hashCode() + 31 * second.getName().hashCode()));
+ assertThat(describe(first).hashCode(), is(describe(first).hashCode()));
+ assertThat(describe(second).hashCode(), is(describe(second).hashCode()));
+ assertThat(describe(first).hashCode(), not(describe(second).hashCode()));
+ }
+
+ @Test
+ public void testEquals() throws Exception {
+ FieldDescription identical = describe(first);
+ assertThat(identical, is(identical));
+ FieldDescription equalFirst = mock(FieldDescription.class);
+ when(equalFirst.getName()).thenReturn(first.getName());
+ when(equalFirst.getDeclaringType()).thenReturn(new TypeDescription.ForLoadedType(FirstSample.class));
+ assertThat(describe(first), is(equalFirst));
+ FieldDescription equalSecond = mock(FieldDescription.class);
+ when(equalSecond.getName()).thenReturn(second.getName());
+ when(equalSecond.getDeclaringType()).thenReturn(new TypeDescription.ForLoadedType(SecondSample.class));
+ assertThat(describe(second), is(equalSecond));
+ FieldDescription equalFirstTypeOnly = mock(FieldDescription.class);
+ when(equalFirstTypeOnly.getName()).thenReturn(second.getName());
+ when(equalFirstTypeOnly.getDeclaringType()).thenReturn(new TypeDescription.ForLoadedType(FirstSample.class));
+ assertThat(describe(first), not(equalFirstTypeOnly));
+ FieldDescription equalFirstNameOnly = mock(FieldDescription.class);
+ when(equalFirstNameOnly.getName()).thenReturn(first.getName());
+ when(equalFirstNameOnly.getDeclaringType()).thenReturn(new TypeDescription.ForLoadedType(SecondSample.class));
+ assertThat(describe(first), not(equalFirstNameOnly));
+ assertThat(describe(first), not(equalSecond));
+ assertThat(describe(first), not(new Object()));
+ assertThat(describe(first), not(equalTo(null)));
+ }
+
+ @Test
+ public void testToString() throws Exception {
+ assertThat(describe(first).toString(), is(first.toString()));
+ assertThat(describe(second).toString(), is(second.toString()));
+ }
+
+ @Test
+ public void testSynthetic() throws Exception {
+ assertThat(describe(first).isSynthetic(), is(first.isSynthetic()));
+ assertThat(describe(second).isSynthetic(), is(second.isSynthetic()));
+ }
+
+ @Test
+ public void testTransient() throws Exception {
+ assertThat(describe(first).isTransient(), is(Modifier.isTransient(first.getModifiers())));
+ assertThat(describe(TransientSample.class.getDeclaredField("foo")).isTransient(),
+ is(Modifier.isTransient(TransientSample.class.getDeclaredField("foo").getModifiers())));
+ }
+
+ @Test
+ public void testIsVisibleTo() throws Exception {
+ assertThat(describe(PublicType.class.getDeclaredField("publicField"))
+ .isVisibleTo(new TypeDescription.ForLoadedType(PublicType.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredField("protectedField"))
+ .isVisibleTo(new TypeDescription.ForLoadedType(PublicType.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredField("packagePrivateField"))
+ .isVisibleTo(new TypeDescription.ForLoadedType(PublicType.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredField("privateField"))
+ .isVisibleTo(new TypeDescription.ForLoadedType(PublicType.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredField("publicField"))
+ .isVisibleTo(new TypeDescription.ForLoadedType(FirstSample.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredField("protectedField"))
+ .isVisibleTo(new TypeDescription.ForLoadedType(FirstSample.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredField("packagePrivateField"))
+ .isVisibleTo(new TypeDescription.ForLoadedType(FirstSample.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredField("privateField"))
+ .isVisibleTo(new TypeDescription.ForLoadedType(FirstSample.class)), is(false));
+ assertThat(describe(PublicType.class.getDeclaredField("publicField"))
+ .isVisibleTo(TypeDescription.OBJECT), is(true));
+ assertThat(describe(PublicType.class.getDeclaredField("protectedField"))
+ .isVisibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PublicType.class.getDeclaredField("packagePrivateField"))
+ .isVisibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PublicType.class.getDeclaredField("privateField"))
+ .isVisibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PublicType.class.getDeclaredField("publicField"))
+ .isVisibleTo(new TypeDescription.ForLoadedType(VisibilityFieldTestHelper.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredField("protectedField"))
+ .isVisibleTo(new TypeDescription.ForLoadedType(VisibilityFieldTestHelper.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredField("packagePrivateField"))
+ .isVisibleTo(new TypeDescription.ForLoadedType(VisibilityFieldTestHelper.class)), is(false));
+ assertThat(describe(PublicType.class.getDeclaredField("privateField"))
+ .isVisibleTo(new TypeDescription.ForLoadedType(VisibilityFieldTestHelper.class)), is(false));
+ assertThat(describe(PackagePrivateType.class.getDeclaredField("publicField"))
+ .isVisibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PackagePrivateType.class.getDeclaredField("protectedField"))
+ .isVisibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PackagePrivateType.class.getDeclaredField("packagePrivateField"))
+ .isVisibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PackagePrivateType.class.getDeclaredField("privateField"))
+ .isVisibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PackagePrivateFieldType.class.getDeclaredField("packagePrivateType"))
+ .isVisibleTo(new TypeDescription.ForLoadedType(PackagePrivateFieldType.class)), is(true));
+ assertThat(describe(PackagePrivateFieldType.class.getDeclaredField("packagePrivateType"))
+ .isVisibleTo(TypeDescription.OBJECT), is(true));
+ }
+
+ @Test
+ public void testAnnotations() throws Exception {
+ assertThat(describe(first).getDeclaredAnnotations(), is((AnnotationList) new AnnotationList.Empty()));
+ assertThat(describe(second).getDeclaredAnnotations(),
+ is((AnnotationList) new AnnotationList.ForLoadedAnnotations(second.getDeclaredAnnotations())));
+ }
+
+ @Test
+ public void testGenericTypes() throws Exception {
+ assertThat(describe(genericField).getType(), is(TypeDefinition.Sort.describe(genericField.getGenericType())));
+ assertThat(describe(genericField).getType().asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(genericField.getType())));
+ }
+
+ @Test
+ public void testToGenericString() throws Exception {
+ assertThat(describe(genericField).toGenericString(), is(genericField.toGenericString()));
+ }
+
+ @Test
+ public void testGetActualModifiers() throws Exception {
+ assertThat(describe(first).getActualModifiers(), is(first.getModifiers()));
+ assertThat(describe(DeprecationSample.class.getDeclaredField("foo")).getActualModifiers(), is(Opcodes.ACC_DEPRECATED | Opcodes.ACC_PRIVATE));
+ }
+
+ @Test
+ public void testSyntheticField() throws Exception {
+ assertThat(describe(SyntheticField.class.getDeclaredFields()[0]).getModifiers(), is(SyntheticField.class.getDeclaredFields()[0].getModifiers()));
+ assertThat(describe(SyntheticField.class.getDeclaredFields()[0]).isSynthetic(), is(SyntheticField.class.getDeclaredFields()[0].isSynthetic()));
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ private @interface SampleAnnotation {
+
+ }
+
+ @SuppressWarnings("unused")
+ protected static class FirstSample {
+
+ private Void first;
+ }
+
+ @SuppressWarnings("unused")
+ protected static class SecondSample {
+
+ @SampleAnnotation
+ int second;
+ }
+
+ @SuppressWarnings("unused")
+ public static class PublicType {
+
+ public Void publicField;
+
+ protected Void protectedField;
+
+ Void packagePrivateField;
+
+ private Void privateField;
+ }
+
+ @SuppressWarnings("unused")
+ static class PackagePrivateType {
+
+ public Void publicField;
+
+ protected Void protectedField;
+
+ Void packagePrivateField;
+
+ private Void privateField;
+ }
+
+ @SuppressWarnings("unused")
+ static class GenericField {
+
+ List<String> foo;
+ }
+
+ public static class PackagePrivateFieldType {
+
+ public PackagePrivateType packagePrivateType;
+ }
+
+ private static class DeprecationSample {
+
+ @Deprecated
+ private Void foo;
+ }
+
+ private class SyntheticField {
+ /* empty */
+ }
+
+ private static class TransientSample {
+
+ public transient Void foo;
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/field/AbstractFieldListTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/field/AbstractFieldListTest.java
new file mode 100644
index 0000000..5ff78d5
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/field/AbstractFieldListTest.java
@@ -0,0 +1,33 @@
+package net.bytebuddy.description.field;
+
+import net.bytebuddy.description.ByteCodeElement;
+import net.bytebuddy.matcher.AbstractFilterableListTest;
+import org.junit.Test;
+
+import java.util.Collections;
+
+import static net.bytebuddy.matcher.ElementMatchers.none;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public abstract class AbstractFieldListTest<U, V extends FieldDescription> extends AbstractFilterableListTest<V, FieldList<V>, U> {
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testTokenWithMatcher() throws Exception {
+ assertThat(asList(getFirst()).asTokenList(none()),
+ is(new ByteCodeElement.Token.TokenList<FieldDescription.Token>(asElement(getFirst()).asToken(none()))));
+ }
+
+ @Test
+ public void testAsDefined() throws Exception {
+ assertThat(asList(getFirst()).asDefined(), is(Collections.singletonList(asElement(getFirst()).asDefined())));
+ }
+
+ protected static class Foo {
+
+ Void foo;
+
+ Void bar;
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/field/FieldDescriptionForLoadedFieldsTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/field/FieldDescriptionForLoadedFieldsTest.java
new file mode 100644
index 0000000..1818856
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/field/FieldDescriptionForLoadedFieldsTest.java
@@ -0,0 +1,11 @@
+package net.bytebuddy.description.field;
+
+import java.lang.reflect.Field;
+
+public class FieldDescriptionForLoadedFieldsTest extends AbstractFieldDescriptionTest {
+
+ @Override
+ protected FieldDescription.InDefinedShape describe(Field field) {
+ return new FieldDescription.ForLoadedField(field);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/field/FieldDescriptionLatentTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/field/FieldDescriptionLatentTest.java
new file mode 100644
index 0000000..02168c0
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/field/FieldDescriptionLatentTest.java
@@ -0,0 +1,19 @@
+package net.bytebuddy.description.field;
+
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+
+import java.lang.reflect.Field;
+
+public class FieldDescriptionLatentTest extends AbstractFieldDescriptionTest {
+
+ @Override
+ protected FieldDescription.InDefinedShape describe(Field field) {
+ return new FieldDescription.Latent(new TypeDescription.ForLoadedType(field.getDeclaringClass()),
+ field.getName(),
+ field.getModifiers(),
+ TypeDefinition.Sort.describe(field.getGenericType()),
+ new AnnotationList.ForLoadedAnnotations(field.getDeclaredAnnotations()));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/field/FieldDescriptionSignatureTokenTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/field/FieldDescriptionSignatureTokenTest.java
new file mode 100644
index 0000000..ae544d6
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/field/FieldDescriptionSignatureTokenTest.java
@@ -0,0 +1,35 @@
+package net.bytebuddy.description.field;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class FieldDescriptionSignatureTokenTest {
+
+ private static final String FOO = "foo";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription type;
+
+ @Test
+ public void testProperties() throws Exception {
+ FieldDescription.SignatureToken token = new FieldDescription.SignatureToken(FOO, type);
+ assertThat(token.getName(), is(FOO));
+ assertThat(token.getType(), is(type));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(FieldDescription.SignatureToken.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/field/FieldDescriptionTokenTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/field/FieldDescriptionTokenTest.java
new file mode 100644
index 0000000..41cb38c
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/field/FieldDescriptionTokenTest.java
@@ -0,0 +1,74 @@
+package net.bytebuddy.description.field;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class FieldDescriptionTokenTest {
+
+ private static final String FOO = "foo";
+
+ private static final int MODIFIERS = 42;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription.Generic type, visitedType;
+
+ @Mock
+ private TypeDescription typeDescription, rawType;
+
+ @Mock
+ private AnnotationDescription annotation;
+
+ @Mock
+ private TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ when(type.asGenericType()).thenReturn(type);
+ when(visitedType.asGenericType()).thenReturn(visitedType);
+ when(type.accept((TypeDescription.Generic.Visitor) visitor)).thenReturn(visitedType);
+ }
+
+ @Test
+ public void testProperties() throws Exception {
+ FieldDescription.Token token = new FieldDescription.Token(FOO, MODIFIERS, type, Collections.singletonList(annotation));
+ assertThat(token.getName(), is(FOO));
+ assertThat(token.getModifiers(), is(MODIFIERS));
+ assertThat(token.getType(), is(type));
+ assertThat(token.getAnnotations(), is(Collections.singletonList(annotation)));
+ }
+
+ @Test
+ public void testVisitor() throws Exception {
+ assertThat(new FieldDescription.Token(FOO, MODIFIERS, type, Collections.singletonList(annotation)).accept(visitor),
+ is(new FieldDescription.Token(FOO, MODIFIERS, visitedType, Collections.singletonList(annotation))));
+ }
+
+ @Test
+ public void testSignatureTokenTransformation() throws Exception {
+ when(type.accept(new TypeDescription.Generic.Visitor.Reducing(typeDescription))).thenReturn(rawType);
+ assertThat(new FieldDescription.Token(FOO, MODIFIERS, type, Collections.singletonList(annotation)).asSignatureToken(typeDescription),
+ is(new FieldDescription.SignatureToken(FOO, rawType)));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(FieldDescription.Token.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/field/FieldListEmptyTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/field/FieldListEmptyTest.java
new file mode 100644
index 0000000..6b76bfa
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/field/FieldListEmptyTest.java
@@ -0,0 +1,20 @@
+package net.bytebuddy.description.field;
+
+import org.junit.Test;
+
+import static net.bytebuddy.matcher.ElementMatchers.none;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class FieldListEmptyTest {
+
+ @Test
+ public void testTokenListWithFilter() throws Exception {
+ assertThat(new FieldList.Empty<FieldDescription>().asTokenList(none()).size(), is(0));
+ }
+
+ @Test
+ public void testDeclaredList() throws Exception {
+ assertThat(new FieldList.Empty<FieldDescription>().asDefined().size(), is(0));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/field/FieldListExplicitTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/field/FieldListExplicitTest.java
new file mode 100644
index 0000000..0cd0ce5
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/field/FieldListExplicitTest.java
@@ -0,0 +1,26 @@
+package net.bytebuddy.description.field;
+
+import java.util.List;
+
+public class FieldListExplicitTest extends AbstractFieldListTest<FieldDescription, FieldDescription> {
+
+ @Override
+ protected FieldDescription getFirst() throws Exception {
+ return new FieldDescription.ForLoadedField(Foo.class.getDeclaredField("foo"));
+ }
+
+ @Override
+ protected FieldDescription getSecond() throws Exception {
+ return new FieldDescription.ForLoadedField(Foo.class.getDeclaredField("bar"));
+ }
+
+ @Override
+ protected FieldList<FieldDescription> asList(List<FieldDescription> elements) {
+ return new FieldList.Explicit<FieldDescription>(elements);
+ }
+
+ @Override
+ protected FieldDescription asElement(FieldDescription element) {
+ return element;
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/field/FieldListForLoadedFieldsTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/field/FieldListForLoadedFieldsTest.java
new file mode 100644
index 0000000..047723d
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/field/FieldListForLoadedFieldsTest.java
@@ -0,0 +1,27 @@
+package net.bytebuddy.description.field;
+
+import java.lang.reflect.Field;
+import java.util.List;
+
+public class FieldListForLoadedFieldsTest extends AbstractFieldListTest<Field, FieldDescription.InDefinedShape> {
+
+ @Override
+ protected Field getFirst() throws Exception {
+ return Foo.class.getDeclaredField("foo");
+ }
+
+ @Override
+ protected Field getSecond() throws Exception {
+ return Foo.class.getDeclaredField("bar");
+ }
+
+ @Override
+ protected FieldList<FieldDescription.InDefinedShape> asList(List<Field> elements) {
+ return new FieldList.ForLoadedFields(elements);
+ }
+
+ @Override
+ protected FieldDescription.InDefinedShape asElement(Field element) {
+ return new FieldDescription.ForLoadedField(element);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/AbstractMethodDescriptionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/AbstractMethodDescriptionTest.java
new file mode 100644
index 0000000..2ad7e2b
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/AbstractMethodDescriptionTest.java
@@ -0,0 +1,995 @@
+package net.bytebuddy.description.method;
+
+import net.bytebuddy.description.TypeVariableSource;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.annotation.AnnotationValue;
+import net.bytebuddy.description.enumeration.EnumerationDescription;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.test.packaging.VisibilityMethodTestHelper;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public abstract class AbstractMethodDescriptionTest {
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ protected Method firstMethod, secondMethod, thirdMethod, genericMethod, genericMethodWithRawException, genericMethodWithTypeVariable;
+
+ protected Constructor<?> firstConstructor, secondConstructor;
+
+ private static int hashCode(Method method) {
+ int hashCode = new TypeDescription.ForLoadedType(method.getDeclaringClass()).hashCode();
+ hashCode = 31 * hashCode + method.getName().hashCode();
+ hashCode = 31 * hashCode + new TypeDescription.ForLoadedType(method.getReturnType()).hashCode();
+ return 31 * hashCode + new TypeList.ForLoadedTypes(method.getParameterTypes()).hashCode();
+ }
+
+ private static int hashCode(Constructor<?> constructor) {
+ int hashCode = new TypeDescription.ForLoadedType(constructor.getDeclaringClass()).hashCode();
+ hashCode = 31 * hashCode + MethodDescription.CONSTRUCTOR_INTERNAL_NAME.hashCode();
+ hashCode = 31 * hashCode + TypeDescription.VOID.hashCode();
+ return 31 * hashCode + new TypeList.ForLoadedTypes(constructor.getParameterTypes()).hashCode();
+ }
+
+ protected abstract MethodDescription.InDefinedShape describe(Method method);
+
+ protected abstract MethodDescription.InDefinedShape describe(Constructor<?> constructor);
+
+ @Before
+ public void setUp() throws Exception {
+ firstMethod = Sample.class.getDeclaredMethod("first");
+ secondMethod = Sample.class.getDeclaredMethod("second", String.class, long.class);
+ thirdMethod = Sample.class.getDeclaredMethod("third", Object[].class, int[].class);
+ firstConstructor = Sample.class.getDeclaredConstructor(Void.class);
+ secondConstructor = Sample.class.getDeclaredConstructor(int[].class, long.class);
+ genericMethod = GenericMethod.class.getDeclaredMethod("foo", Exception.class);
+ genericMethodWithRawException = GenericMethod.class.getDeclaredMethod("bar", Exception.class);
+ genericMethodWithTypeVariable = GenericMethod.class.getDeclaredMethod("qux");
+ }
+
+ @Test
+ public void testPrecondition() throws Exception {
+ assertThat(describe(firstMethod), not(describe(secondMethod)));
+ assertThat(describe(firstMethod), not(describe(thirdMethod)));
+ assertThat(describe(firstMethod), is(describe(firstMethod)));
+ assertThat(describe(secondMethod), is(describe(secondMethod)));
+ assertThat(describe(thirdMethod), is(describe(thirdMethod)));
+ assertThat(describe(firstMethod), is((MethodDescription) new MethodDescription.ForLoadedMethod(firstMethod)));
+ assertThat(describe(secondMethod), is((MethodDescription) new MethodDescription.ForLoadedMethod(secondMethod)));
+ assertThat(describe(thirdMethod), is((MethodDescription) new MethodDescription.ForLoadedMethod(thirdMethod)));
+ assertThat(describe(firstConstructor), not(describe(secondConstructor)));
+ assertThat(describe(firstConstructor), is(describe(firstConstructor)));
+ assertThat(describe(secondConstructor), is(describe(secondConstructor)));
+ assertThat(describe(firstConstructor), is((MethodDescription) new MethodDescription.ForLoadedConstructor(firstConstructor)));
+ assertThat(describe(secondConstructor), is((MethodDescription) new MethodDescription.ForLoadedConstructor(secondConstructor)));
+ }
+
+ @Test
+ public void testReturnType() throws Exception {
+ assertThat(describe(firstMethod).getReturnType(), is((TypeDefinition) new TypeDescription.ForLoadedType(firstMethod.getReturnType())));
+ assertThat(describe(secondMethod).getReturnType(), is((TypeDefinition) new TypeDescription.ForLoadedType(secondMethod.getReturnType())));
+ assertThat(describe(thirdMethod).getReturnType(), is((TypeDefinition) new TypeDescription.ForLoadedType(thirdMethod.getReturnType())));
+ assertThat(describe(firstConstructor).getReturnType(), is(TypeDescription.Generic.VOID));
+ assertThat(describe(secondConstructor).getReturnType(), is(TypeDescription.Generic.VOID));
+ }
+
+ @Test
+ public void testParameterTypes() throws Exception {
+ assertThat(describe(firstMethod).getParameters().asTypeList(), is((TypeList.Generic) new TypeList.Generic.ForLoadedTypes(firstMethod.getParameterTypes())));
+ assertThat(describe(secondMethod).getParameters().asTypeList(), is((TypeList.Generic) new TypeList.Generic.ForLoadedTypes(secondMethod.getParameterTypes())));
+ assertThat(describe(thirdMethod).getParameters().asTypeList(), is((TypeList.Generic) new TypeList.Generic.ForLoadedTypes(thirdMethod.getParameterTypes())));
+ assertThat(describe(firstConstructor).getParameters().asTypeList(), is((TypeList.Generic) new TypeList.Generic.ForLoadedTypes(firstConstructor.getParameterTypes())));
+ assertThat(describe(secondConstructor).getParameters().asTypeList(), is((TypeList.Generic) new TypeList.Generic.ForLoadedTypes(secondConstructor.getParameterTypes())));
+ }
+
+ @Test
+ public void testMethodName() throws Exception {
+ assertThat(describe(firstMethod).getName(), is(firstMethod.getName()));
+ assertThat(describe(secondMethod).getName(), is(secondMethod.getName()));
+ assertThat(describe(thirdMethod).getName(), is(thirdMethod.getName()));
+ assertThat(describe(firstConstructor).getName(), is(firstConstructor.getDeclaringClass().getName()));
+ assertThat(describe(secondConstructor).getName(), is(secondConstructor.getDeclaringClass().getName()));
+ assertThat(describe(firstMethod).getInternalName(), is(firstMethod.getName()));
+ assertThat(describe(secondMethod).getInternalName(), is(secondMethod.getName()));
+ assertThat(describe(thirdMethod).getInternalName(), is(thirdMethod.getName()));
+ assertThat(describe(firstConstructor).getInternalName(), is(MethodDescription.CONSTRUCTOR_INTERNAL_NAME));
+ assertThat(describe(secondConstructor).getInternalName(), is(MethodDescription.CONSTRUCTOR_INTERNAL_NAME));
+ }
+
+ @Test
+ public void testDescriptor() throws Exception {
+ assertThat(describe(firstMethod).getDescriptor(), is(Type.getMethodDescriptor(firstMethod)));
+ assertThat(describe(secondMethod).getDescriptor(), is(Type.getMethodDescriptor(secondMethod)));
+ assertThat(describe(thirdMethod).getDescriptor(), is(Type.getMethodDescriptor(thirdMethod)));
+ assertThat(describe(firstConstructor).getDescriptor(), is(Type.getConstructorDescriptor(firstConstructor)));
+ assertThat(describe(secondConstructor).getDescriptor(), is(Type.getConstructorDescriptor(secondConstructor)));
+ }
+
+ @Test
+ public void testMethodModifiers() throws Exception {
+ assertThat(describe(firstMethod).getModifiers(), is(firstMethod.getModifiers()));
+ assertThat(describe(secondMethod).getModifiers(), is(secondMethod.getModifiers()));
+ assertThat(describe(thirdMethod).getModifiers(), is(thirdMethod.getModifiers()));
+ assertThat(describe(firstConstructor).getModifiers(), is(firstConstructor.getModifiers()));
+ assertThat(describe(secondConstructor).getModifiers(), is(secondConstructor.getModifiers()));
+ }
+
+ @Test
+ public void testMethodDeclaringType() throws Exception {
+ assertThat(describe(firstMethod).getDeclaringType(), is((TypeDescription) new TypeDescription.ForLoadedType(firstMethod.getDeclaringClass())));
+ assertThat(describe(secondMethod).getDeclaringType(), is((TypeDescription) new TypeDescription.ForLoadedType(secondMethod.getDeclaringClass())));
+ assertThat(describe(thirdMethod).getDeclaringType(), is((TypeDescription) new TypeDescription.ForLoadedType(thirdMethod.getDeclaringClass())));
+ assertThat(describe(firstConstructor).getDeclaringType(), is((TypeDescription) new TypeDescription.ForLoadedType(firstConstructor.getDeclaringClass())));
+ assertThat(describe(secondConstructor).getDeclaringType(), is((TypeDescription) new TypeDescription.ForLoadedType(secondConstructor.getDeclaringClass())));
+ }
+
+ @Test
+ public void testHashCode() throws Exception {
+ assertThat(describe(firstMethod).hashCode(), is(hashCode(firstMethod)));
+ assertThat(describe(secondMethod).hashCode(), is(hashCode(secondMethod)));
+ assertThat(describe(thirdMethod).hashCode(), is(hashCode(thirdMethod)));
+ assertThat(describe(firstMethod).hashCode(), not(hashCode(secondMethod)));
+ assertThat(describe(firstMethod).hashCode(), not(hashCode(thirdMethod)));
+ assertThat(describe(firstMethod).hashCode(), not(hashCode(firstConstructor)));
+ assertThat(describe(firstMethod).hashCode(), not(hashCode(secondConstructor)));
+ assertThat(describe(firstConstructor).hashCode(), is(hashCode(firstConstructor)));
+ assertThat(describe(secondConstructor).hashCode(), is(hashCode(secondConstructor)));
+ assertThat(describe(firstConstructor).hashCode(), not(hashCode(firstMethod)));
+ assertThat(describe(firstConstructor).hashCode(), not(hashCode(secondMethod)));
+ assertThat(describe(firstConstructor).hashCode(), not(hashCode(thirdMethod)));
+ assertThat(describe(firstConstructor).hashCode(), not(hashCode(secondConstructor)));
+ }
+
+ @Test
+ public void testEqualsMethod() throws Exception {
+ MethodDescription identical = describe(firstMethod);
+ assertThat(identical, is(identical));
+ assertThat(describe(firstMethod), is(describe(firstMethod)));
+ assertThat(describe(firstMethod), not(describe(secondMethod)));
+ assertThat(describe(firstMethod), not(describe(thirdMethod)));
+ assertThat(describe(firstMethod), not(describe(firstConstructor)));
+ assertThat(describe(firstMethod), not(describe(secondConstructor)));
+ assertThat(describe(firstMethod), is((MethodDescription) new MethodDescription.ForLoadedMethod(firstMethod)));
+ assertThat(describe(firstMethod), not((MethodDescription) new MethodDescription.ForLoadedMethod(secondMethod)));
+ assertThat(describe(firstMethod), not((MethodDescription) new MethodDescription.ForLoadedMethod(thirdMethod)));
+ assertThat(describe(firstMethod), not((MethodDescription) new MethodDescription.ForLoadedConstructor(firstConstructor)));
+ assertThat(describe(firstMethod), not((MethodDescription) new MethodDescription.ForLoadedConstructor(secondConstructor)));
+ MethodDescription.InDefinedShape equalMethod = mock(MethodDescription.InDefinedShape.class);
+ when(equalMethod.getInternalName()).thenReturn(firstMethod.getName());
+ when(equalMethod.getDeclaringType()).thenReturn(new TypeDescription.ForLoadedType(firstMethod.getDeclaringClass()));
+ when(equalMethod.getReturnType()).thenReturn(new TypeDescription.Generic.OfNonGenericType.ForLoadedType(firstMethod.getReturnType()));
+ when(equalMethod.getParameters()).thenReturn(new ParameterList.Explicit.ForTypes(equalMethod,
+ new TypeList.Generic.ForLoadedTypes(firstMethod.getParameterTypes())));
+ assertThat(describe(firstMethod), is(equalMethod));
+ MethodDescription.InDefinedShape equalMethodButName = mock(MethodDescription.InDefinedShape.class);
+ when(equalMethodButName.getInternalName()).thenReturn(secondMethod.getName());
+ when(equalMethodButName.getDeclaringType()).thenReturn(new TypeDescription.ForLoadedType(firstMethod.getDeclaringClass()));
+ when(equalMethodButName.getReturnType()).thenReturn(new TypeDescription.Generic.OfNonGenericType.ForLoadedType(firstMethod.getReturnType()));
+ when(equalMethodButName.getParameters()).thenReturn(new ParameterList.Explicit.ForTypes(equalMethodButName,
+ new TypeList.Generic.ForLoadedTypes(firstMethod.getParameterTypes())));
+ assertThat(describe(firstMethod), not(equalMethodButName));
+ MethodDescription.InDefinedShape equalMethodButReturnType = mock(MethodDescription.InDefinedShape.class);
+ when(equalMethodButReturnType.getInternalName()).thenReturn(firstMethod.getName());
+ when(equalMethodButReturnType.getDeclaringType()).thenReturn(TypeDescription.OBJECT);
+ when(equalMethodButReturnType.getReturnType()).thenReturn(new TypeDescription.Generic.OfNonGenericType.ForLoadedType(firstMethod.getReturnType()));
+ when(equalMethodButReturnType.getParameters()).thenReturn(new ParameterList.Explicit.ForTypes(equalMethodButReturnType,
+ new TypeList.Generic.ForLoadedTypes(firstMethod.getParameterTypes())));
+ assertThat(describe(firstMethod), not(equalMethodButReturnType));
+ MethodDescription.InDefinedShape equalMethodButDeclaringType = mock(MethodDescription.InDefinedShape.class);
+ when(equalMethodButDeclaringType.getInternalName()).thenReturn(firstMethod.getName());
+ when(equalMethodButDeclaringType.getDeclaringType()).thenReturn(new TypeDescription.ForLoadedType(firstMethod.getDeclaringClass()));
+ when(equalMethodButDeclaringType.getReturnType()).thenReturn(new TypeDescription.Generic.OfNonGenericType.ForLoadedType(secondMethod.getReturnType()));
+ when(equalMethodButDeclaringType.getParameters()).thenReturn(new ParameterList.Explicit.ForTypes(equalMethodButDeclaringType,
+ new TypeList.Generic.ForLoadedTypes(firstMethod.getParameterTypes())));
+ assertThat(describe(firstMethod), not(equalMethodButDeclaringType));
+ MethodDescription.InDefinedShape equalMethodButParameterTypes = mock(MethodDescription.InDefinedShape.class);
+ when(equalMethodButParameterTypes.getInternalName()).thenReturn(firstMethod.getName());
+ when(equalMethodButParameterTypes.getDeclaringType()).thenReturn(new TypeDescription.ForLoadedType(firstMethod.getDeclaringClass()));
+ when(equalMethodButParameterTypes.getReturnType()).thenReturn(new TypeDescription.Generic.OfNonGenericType.ForLoadedType(firstMethod.getReturnType()));
+ when(equalMethodButParameterTypes.getParameters()).thenReturn(new ParameterList.Explicit.ForTypes(equalMethodButParameterTypes,
+ new TypeList.Generic.ForLoadedTypes(secondMethod.getParameterTypes())));
+ assertThat(describe(firstMethod), not(equalMethodButParameterTypes));
+ assertThat(describe(firstMethod), not(new Object()));
+ assertThat(describe(firstMethod), not(equalTo(null)));
+ }
+
+ @Test
+ public void testEqualsConstructor() throws Exception {
+ MethodDescription identical = describe(firstConstructor);
+ assertThat(identical, is(identical));
+ assertThat(describe(firstConstructor), is(describe(firstConstructor)));
+ assertThat(describe(firstConstructor), not(describe(secondConstructor)));
+ assertThat(describe(firstConstructor), not(describe(firstMethod)));
+ assertThat(describe(firstConstructor), not(describe(secondMethod)));
+ assertThat(describe(firstConstructor), not(describe(thirdMethod)));
+ assertThat(describe(firstConstructor), is((MethodDescription) new MethodDescription.ForLoadedConstructor(firstConstructor)));
+ assertThat(describe(firstConstructor), not((MethodDescription) new MethodDescription.ForLoadedConstructor(secondConstructor)));
+ assertThat(describe(firstConstructor), not((MethodDescription) new MethodDescription.ForLoadedMethod(firstMethod)));
+ assertThat(describe(firstConstructor), not((MethodDescription) new MethodDescription.ForLoadedMethod(secondMethod)));
+ assertThat(describe(firstConstructor), not((MethodDescription) new MethodDescription.ForLoadedMethod(thirdMethod)));
+ MethodDescription.InDefinedShape equalMethod = mock(MethodDescription.InDefinedShape.class);
+ when(equalMethod.getInternalName()).thenReturn(MethodDescription.CONSTRUCTOR_INTERNAL_NAME);
+ when(equalMethod.getDeclaringType()).thenReturn(new TypeDescription.ForLoadedType(firstConstructor.getDeclaringClass()));
+ when(equalMethod.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(equalMethod.getParameters()).thenReturn(new ParameterList.Explicit.ForTypes(equalMethod,
+ new TypeList.ForLoadedTypes(firstConstructor.getParameterTypes())));
+ assertThat(describe(firstConstructor), is(equalMethod));
+ MethodDescription.InDefinedShape equalMethodButName = mock(MethodDescription.InDefinedShape.class);
+ when(equalMethodButName.getInternalName()).thenReturn(firstMethod.getName());
+ when(equalMethodButName.getDeclaringType()).thenReturn(new TypeDescription.ForLoadedType(firstConstructor.getDeclaringClass()));
+ when(equalMethodButName.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(equalMethodButName.getParameters()).thenReturn(new ParameterList.Explicit.ForTypes(equalMethodButName,
+ new TypeList.ForLoadedTypes(firstConstructor.getParameterTypes())));
+ assertThat(describe(firstConstructor), not(equalMethodButName));
+ MethodDescription.InDefinedShape equalMethodButReturnType = mock(MethodDescription.InDefinedShape.class);
+ when(equalMethodButReturnType.getInternalName()).thenReturn(MethodDescription.CONSTRUCTOR_INTERNAL_NAME);
+ when(equalMethodButReturnType.getDeclaringType()).thenReturn(TypeDescription.OBJECT);
+ when(equalMethodButReturnType.getReturnType()).thenReturn(TypeDescription.Generic.OBJECT);
+ when(equalMethodButReturnType.getParameters()).thenReturn(new ParameterList.Explicit.ForTypes(equalMethodButReturnType,
+ new TypeList.ForLoadedTypes(firstConstructor.getParameterTypes())));
+ assertThat(describe(firstConstructor), not(equalMethodButReturnType));
+ MethodDescription.InDefinedShape equalMethodButDeclaringType = mock(MethodDescription.InDefinedShape.class);
+ when(equalMethodButDeclaringType.getInternalName()).thenReturn(MethodDescription.CONSTRUCTOR_INTERNAL_NAME);
+ when(equalMethodButDeclaringType.getDeclaringType()).thenReturn(TypeDescription.OBJECT);
+ when(equalMethodButDeclaringType.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(equalMethodButDeclaringType.getParameters()).thenReturn(new ParameterList.Explicit.ForTypes(equalMethodButDeclaringType,
+ new TypeList.ForLoadedTypes(firstConstructor.getParameterTypes())));
+ assertThat(describe(firstConstructor), not(equalMethodButDeclaringType));
+ MethodDescription.InDefinedShape equalMethodButParameterTypes = mock(MethodDescription.InDefinedShape.class);
+ when(equalMethodButParameterTypes.getInternalName()).thenReturn(MethodDescription.CONSTRUCTOR_INTERNAL_NAME);
+ when(equalMethodButParameterTypes.getDeclaringType()).thenReturn(new TypeDescription.ForLoadedType(firstConstructor.getDeclaringClass()));
+ when(equalMethodButParameterTypes.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(equalMethodButParameterTypes.getParameters()).thenReturn(new ParameterList.Explicit.ForTypes(equalMethodButParameterTypes,
+ new TypeList.ForLoadedTypes(secondConstructor.getParameterTypes())));
+ assertThat(describe(firstConstructor), not(equalMethodButParameterTypes));
+ assertThat(describe(firstConstructor), not(new Object()));
+ assertThat(describe(firstConstructor), not(equalTo(null)));
+ }
+
+ @Test
+ public void testToString() throws Exception {
+ assertThat(describe(firstMethod).toString(), is(firstMethod.toString()));
+ assertThat(describe(secondMethod).toString(), is(secondMethod.toString()));
+ assertThat(describe(thirdMethod).toString(), is(thirdMethod.toString()));
+ assertThat(describe(firstConstructor).toString(), is(firstConstructor.toString()));
+ assertThat(describe(secondConstructor).toString(), is(secondConstructor.toString()));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testEqualsParameter() throws Exception {
+ ParameterDescription identical = describe(secondMethod).getParameters().get(0);
+ assertThat(identical, is(identical));
+ assertThat(identical, not(new Object()));
+ assertThat(identical, not(equalTo(null)));
+ assertThat(describe(secondMethod).getParameters().get(0), is(describe(secondMethod).getParameters().get(0)));
+ ParameterDescription equal = mock(ParameterDescription.class);
+ when(equal.getDeclaringMethod()).thenReturn(describe(secondMethod));
+ when(equal.getIndex()).thenReturn(0);
+ assertThat(describe(secondMethod).getParameters().get(0), is(equal));
+ ParameterDescription notEqualMethod = mock(ParameterDescription.class);
+ when(equal.getDeclaringMethod()).thenReturn(mock(MethodDescription.class));
+ when(equal.getIndex()).thenReturn(0);
+ assertThat(describe(secondMethod).getParameters().get(0), not(notEqualMethod));
+ ParameterDescription notEqualMethodIndex = mock(ParameterDescription.class);
+ when(equal.getDeclaringMethod()).thenReturn(describe(secondMethod));
+ when(equal.getIndex()).thenReturn(1);
+ assertThat(describe(secondMethod).getParameters().get(0), not(notEqualMethodIndex));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testHashCodeParameter() throws Exception {
+ assertThat(describe(secondMethod).getParameters().get(0).hashCode(), is(hashCode(secondMethod, 0)));
+ assertThat(describe(secondMethod).getParameters().get(1).hashCode(), is(hashCode(secondMethod, 1)));
+ assertThat(describe(firstConstructor).getParameters().get(0).hashCode(), is(hashCode(firstConstructor, 0)));
+ }
+
+ private int hashCode(Method method, int index) {
+ return hashCode(method) ^ index;
+ }
+
+ private int hashCode(Constructor<?> constructor, int index) {
+ return hashCode(constructor) ^ index;
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testToStringParameter() throws Exception {
+ Class<?> executable = Class.forName("java.lang.reflect.Executable");
+ Method method = executable.getDeclaredMethod("getParameters");
+ assertThat(describe(secondMethod).getParameters().get(0).toString(), is(((Object[]) method.invoke(secondMethod))[0].toString()));
+ assertThat(describe(secondMethod).getParameters().get(1).toString(), is(((Object[]) method.invoke(secondMethod))[1].toString()));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testParameterNameAndModifiers() throws Exception {
+ Class<?> type = Class.forName("net.bytebuddy.test.precompiled.ParameterNames");
+ assertThat(describe(type.getDeclaredMethod("foo", String.class, long.class, int.class)).getParameters().get(0).isNamed(), is(true));
+ assertThat(describe(type.getDeclaredMethod("foo", String.class, long.class, int.class)).getParameters().get(1).isNamed(), is(true));
+ assertThat(describe(type.getDeclaredMethod("foo", String.class, long.class, int.class)).getParameters().get(2).isNamed(), is(true));
+ assertThat(describe(type.getDeclaredMethod("foo", String.class, long.class, int.class)).getParameters().get(0).getName(), is("first"));
+ assertThat(describe(type.getDeclaredMethod("foo", String.class, long.class, int.class)).getParameters().get(1).getName(), is("second"));
+ assertThat(describe(type.getDeclaredMethod("foo", String.class, long.class, int.class)).getParameters().get(2).getName(), is("third"));
+ assertThat(describe(type.getDeclaredMethod("foo", String.class, long.class, int.class)).getParameters().get(0).getModifiers(), is(Opcodes.ACC_FINAL));
+ assertThat(describe(type.getDeclaredMethod("foo", String.class, long.class, int.class)).getParameters().get(1).getModifiers(), is(0));
+ assertThat(describe(type.getDeclaredMethod("foo", String.class, long.class, int.class)).getParameters().get(2).getModifiers(), is(0));
+ assertThat(describe(type.getDeclaredMethod("bar", String.class, long.class, int.class)).getParameters().get(0).isNamed(), is(true));
+ assertThat(describe(type.getDeclaredMethod("bar", String.class, long.class, int.class)).getParameters().get(1).isNamed(), is(true));
+ assertThat(describe(type.getDeclaredMethod("bar", String.class, long.class, int.class)).getParameters().get(2).isNamed(), is(true));
+ assertThat(describe(type.getDeclaredMethod("bar", String.class, long.class, int.class)).getParameters().get(0).getName(), is("first"));
+ assertThat(describe(type.getDeclaredMethod("bar", String.class, long.class, int.class)).getParameters().get(1).getName(), is("second"));
+ assertThat(describe(type.getDeclaredMethod("bar", String.class, long.class, int.class)).getParameters().get(2).getName(), is("third"));
+ assertThat(describe(type.getDeclaredMethod("bar", String.class, long.class, int.class)).getParameters().get(0).getModifiers(), is(0));
+ assertThat(describe(type.getDeclaredMethod("bar", String.class, long.class, int.class)).getParameters().get(1).getModifiers(), is(Opcodes.ACC_FINAL));
+ assertThat(describe(type.getDeclaredMethod("bar", String.class, long.class, int.class)).getParameters().get(2).getModifiers(), is(0));
+ assertThat(describe(type.getDeclaredConstructor(String.class, int.class)).getParameters().get(0).isNamed(), is(true));
+ assertThat(describe(type.getDeclaredConstructor(String.class, int.class)).getParameters().get(1).isNamed(), is(true));
+ assertThat(describe(type.getDeclaredConstructor(String.class, int.class)).getParameters().get(0).getName(), is("first"));
+ assertThat(describe(type.getDeclaredConstructor(String.class, int.class)).getParameters().get(1).getName(), is("second"));
+ assertThat(describe(type.getDeclaredConstructor(String.class, int.class)).getParameters().get(0).getModifiers(), is(0));
+ assertThat(describe(type.getDeclaredConstructor(String.class, int.class)).getParameters().get(1).getModifiers(), is(Opcodes.ACC_FINAL));
+ }
+
+ @Test
+ public void testNoParameterNameAndModifiers() throws Exception {
+ assertThat(describe(secondMethod).getParameters().get(0).isNamed(), is(false));
+ assertThat(describe(secondMethod).getParameters().get(1).isNamed(), is(false));
+ assertThat(describe(secondMethod).getParameters().get(0).getName(), is("arg0"));
+ assertThat(describe(secondMethod).getParameters().get(1).getName(), is("arg1"));
+ assertThat(describe(secondMethod).getParameters().get(0).getModifiers(), is(0));
+ assertThat(describe(secondMethod).getParameters().get(1).getModifiers(), is(0));
+ assertThat(describe(firstConstructor).getParameters().get(0).isNamed(), is(canReadDebugInformation()));
+ assertThat(describe(firstConstructor).getParameters().get(0).getName(), is(canReadDebugInformation() ? "argument" : "arg0"));
+ assertThat(describe(firstConstructor).getParameters().get(0).getModifiers(), is(0));
+ }
+
+ protected abstract boolean canReadDebugInformation();
+
+ @Test
+ public void testSynthetic() throws Exception {
+ assertThat(describe(firstMethod).isSynthetic(), is(firstMethod.isSynthetic()));
+ assertThat(describe(secondMethod).isSynthetic(), is(secondMethod.isSynthetic()));
+ assertThat(describe(thirdMethod).isSynthetic(), is(thirdMethod.isSynthetic()));
+ assertThat(describe(firstConstructor).isSynthetic(), is(firstConstructor.isSynthetic()));
+ assertThat(describe(secondConstructor).isSynthetic(), is(secondConstructor.isSynthetic()));
+ }
+
+ @Test
+ public void testType() throws Exception {
+ assertThat(describe(firstMethod).isMethod(), is(true));
+ assertThat(describe(firstMethod).isConstructor(), is(false));
+ assertThat(describe(firstMethod).isTypeInitializer(), is(false));
+ assertThat(describe(firstConstructor).isMethod(), is(false));
+ assertThat(describe(firstConstructor).isConstructor(), is(true));
+ assertThat(describe(firstConstructor).isTypeInitializer(), is(false));
+ }
+
+ @Test
+ public void testMethodIsVisibleTo() throws Exception {
+ assertThat(describe(PublicType.class.getDeclaredMethod("publicMethod"))
+ .isVisibleTo(new TypeDescription.ForLoadedType(PublicType.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredMethod("protectedMethod"))
+ .isVisibleTo(new TypeDescription.ForLoadedType(PublicType.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredMethod("packagePrivateMethod"))
+ .isVisibleTo(new TypeDescription.ForLoadedType(PublicType.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredMethod("privateMethod"))
+ .isVisibleTo(new TypeDescription.ForLoadedType(PublicType.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredMethod("publicMethod"))
+ .isVisibleTo(new TypeDescription.ForLoadedType(Sample.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredMethod("protectedMethod"))
+ .isVisibleTo(new TypeDescription.ForLoadedType(Sample.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredMethod("packagePrivateMethod"))
+ .isVisibleTo(new TypeDescription.ForLoadedType(Sample.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredMethod("privateMethod"))
+ .isVisibleTo(new TypeDescription.ForLoadedType(Sample.class)), is(false));
+ assertThat(describe(PublicType.class.getDeclaredMethod("publicMethod"))
+ .isVisibleTo(TypeDescription.OBJECT), is(true));
+ assertThat(describe(PublicType.class.getDeclaredMethod("protectedMethod"))
+ .isVisibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PublicType.class.getDeclaredMethod("packagePrivateMethod"))
+ .isVisibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PublicType.class.getDeclaredMethod("privateMethod"))
+ .isVisibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PublicType.class.getDeclaredMethod("publicMethod"))
+ .isVisibleTo(new TypeDescription.ForLoadedType(VisibilityMethodTestHelper.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredMethod("protectedMethod"))
+ .isVisibleTo(new TypeDescription.ForLoadedType(VisibilityMethodTestHelper.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredMethod("packagePrivateMethod"))
+ .isVisibleTo(new TypeDescription.ForLoadedType(VisibilityMethodTestHelper.class)), is(false));
+ assertThat(describe(PublicType.class.getDeclaredMethod("privateMethod"))
+ .isVisibleTo(new TypeDescription.ForLoadedType(VisibilityMethodTestHelper.class)), is(false));
+ assertThat(describe(PackagePrivateType.class.getDeclaredMethod("publicMethod"))
+ .isVisibleTo(TypeDescription.OBJECT), is(true));
+ assertThat(describe(PackagePrivateType.class.getDeclaredMethod("protectedMethod"))
+ .isVisibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PackagePrivateType.class.getDeclaredMethod("packagePrivateMethod"))
+ .isVisibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PackagePrivateType.class.getDeclaredMethod("privateMethod"))
+ .isVisibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PackagePrivateType.class.getDeclaredMethod("staticPublicMethod"))
+ .isVisibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PackagePrivateType.class.getDeclaredMethod("staticProtectedMethod"))
+ .isVisibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PackagePrivateType.class.getDeclaredMethod("staticPackagePrivateMethod"))
+ .isVisibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PackagePrivateType.class.getDeclaredMethod("staticPrivateMethod"))
+ .isVisibleTo(TypeDescription.OBJECT), is(false));
+ }
+
+ @Test
+ public void testConstructorIsVisibleTo() throws Exception {
+ assertThat(describe(PublicType.class.getDeclaredConstructor())
+ .isVisibleTo(new TypeDescription.ForLoadedType(PublicType.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredConstructor(Void.class))
+ .isVisibleTo(new TypeDescription.ForLoadedType(PublicType.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredConstructor(Object.class))
+ .isVisibleTo(new TypeDescription.ForLoadedType(PublicType.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredConstructor(String.class))
+ .isVisibleTo(new TypeDescription.ForLoadedType(PublicType.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredConstructor())
+ .isVisibleTo(new TypeDescription.ForLoadedType(Sample.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredConstructor(Void.class))
+ .isVisibleTo(new TypeDescription.ForLoadedType(Sample.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredConstructor(Object.class))
+ .isVisibleTo(new TypeDescription.ForLoadedType(Sample.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredConstructor(String.class))
+ .isVisibleTo(new TypeDescription.ForLoadedType(Sample.class)), is(false));
+ assertThat(describe(PublicType.class.getDeclaredConstructor())
+ .isVisibleTo(TypeDescription.OBJECT), is(true));
+ assertThat(describe(PublicType.class.getDeclaredConstructor(Void.class))
+ .isVisibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PublicType.class.getDeclaredConstructor(Object.class))
+ .isVisibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PublicType.class.getDeclaredConstructor(String.class))
+ .isVisibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PublicType.class.getDeclaredConstructor())
+ .isVisibleTo(new TypeDescription.ForLoadedType(VisibilityMethodTestHelper.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredConstructor(Void.class))
+ .isVisibleTo(new TypeDescription.ForLoadedType(VisibilityMethodTestHelper.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredConstructor(Object.class))
+ .isVisibleTo(new TypeDescription.ForLoadedType(VisibilityMethodTestHelper.class)), is(false));
+ assertThat(describe(PublicType.class.getDeclaredConstructor(String.class))
+ .isVisibleTo(new TypeDescription.ForLoadedType(VisibilityMethodTestHelper.class)), is(false));
+ assertThat(describe(PackagePrivateType.class.getDeclaredConstructor())
+ .isVisibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PackagePrivateType.class.getDeclaredConstructor(Void.class))
+ .isVisibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PackagePrivateType.class.getDeclaredConstructor(Object.class))
+ .isVisibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PackagePrivateType.class.getDeclaredConstructor(String.class))
+ .isVisibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(MethodVisibilityType.class.getDeclaredMethod("packagePrivateArgument", PackagePrivateType.class))
+ .isVisibleTo(new TypeDescription.ForLoadedType(MethodVisibilityType.class)), is(true));
+ assertThat(describe(MethodVisibilityType.class.getDeclaredMethod("packagePrivateReturnType"))
+ .isVisibleTo(new TypeDescription.ForLoadedType(MethodVisibilityType.class)), is(true));
+ assertThat(describe(MethodVisibilityType.class.getDeclaredMethod("packagePrivateArgument", PackagePrivateType.class))
+ .isVisibleTo(TypeDescription.OBJECT), is(true));
+ assertThat(describe(MethodVisibilityType.class.getDeclaredMethod("packagePrivateReturnType"))
+ .isVisibleTo(TypeDescription.OBJECT), is(true));
+ }
+
+ @Test
+ public void testMethodIsAccessibleTo() throws Exception {
+ assertThat(describe(PublicType.class.getDeclaredMethod("publicMethod"))
+ .isAccessibleTo(new TypeDescription.ForLoadedType(PublicType.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredMethod("protectedMethod"))
+ .isAccessibleTo(new TypeDescription.ForLoadedType(PublicType.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredMethod("packagePrivateMethod"))
+ .isAccessibleTo(new TypeDescription.ForLoadedType(PublicType.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredMethod("privateMethod"))
+ .isAccessibleTo(new TypeDescription.ForLoadedType(PublicType.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredMethod("publicMethod"))
+ .isAccessibleTo(new TypeDescription.ForLoadedType(Sample.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredMethod("protectedMethod"))
+ .isAccessibleTo(new TypeDescription.ForLoadedType(Sample.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredMethod("packagePrivateMethod"))
+ .isAccessibleTo(new TypeDescription.ForLoadedType(Sample.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredMethod("privateMethod"))
+ .isAccessibleTo(new TypeDescription.ForLoadedType(Sample.class)), is(false));
+ assertThat(describe(PublicType.class.getDeclaredMethod("publicMethod"))
+ .isAccessibleTo(TypeDescription.OBJECT), is(true));
+ assertThat(describe(PublicType.class.getDeclaredMethod("protectedMethod"))
+ .isAccessibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PublicType.class.getDeclaredMethod("packagePrivateMethod"))
+ .isAccessibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PublicType.class.getDeclaredMethod("privateMethod"))
+ .isAccessibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PublicType.class.getDeclaredMethod("publicMethod"))
+ .isAccessibleTo(new TypeDescription.ForLoadedType(VisibilityMethodTestHelper.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredMethod("protectedMethod"))
+ .isAccessibleTo(new TypeDescription.ForLoadedType(VisibilityMethodTestHelper.class)), is(false));
+ assertThat(describe(PublicType.class.getDeclaredMethod("packagePrivateMethod"))
+ .isAccessibleTo(new TypeDescription.ForLoadedType(VisibilityMethodTestHelper.class)), is(false));
+ assertThat(describe(PublicType.class.getDeclaredMethod("privateMethod"))
+ .isAccessibleTo(new TypeDescription.ForLoadedType(VisibilityMethodTestHelper.class)), is(false));
+ assertThat(describe(PackagePrivateType.class.getDeclaredMethod("publicMethod"))
+ .isAccessibleTo(TypeDescription.OBJECT), is(true));
+ assertThat(describe(PackagePrivateType.class.getDeclaredMethod("protectedMethod"))
+ .isAccessibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PackagePrivateType.class.getDeclaredMethod("packagePrivateMethod"))
+ .isAccessibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PackagePrivateType.class.getDeclaredMethod("privateMethod"))
+ .isAccessibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PackagePrivateType.class.getDeclaredMethod("staticPublicMethod"))
+ .isAccessibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PackagePrivateType.class.getDeclaredMethod("staticProtectedMethod"))
+ .isAccessibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PackagePrivateType.class.getDeclaredMethod("staticPackagePrivateMethod"))
+ .isAccessibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PackagePrivateType.class.getDeclaredMethod("staticPrivateMethod"))
+ .isAccessibleTo(TypeDescription.OBJECT), is(false));
+ }
+
+ @Test
+ public void testConstructorIsAccessibleTo() throws Exception {
+ assertThat(describe(PublicType.class.getDeclaredConstructor())
+ .isAccessibleTo(new TypeDescription.ForLoadedType(PublicType.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredConstructor(Void.class))
+ .isAccessibleTo(new TypeDescription.ForLoadedType(PublicType.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredConstructor(Object.class))
+ .isAccessibleTo(new TypeDescription.ForLoadedType(PublicType.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredConstructor(String.class))
+ .isAccessibleTo(new TypeDescription.ForLoadedType(PublicType.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredConstructor())
+ .isAccessibleTo(new TypeDescription.ForLoadedType(Sample.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredConstructor(Void.class))
+ .isAccessibleTo(new TypeDescription.ForLoadedType(Sample.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredConstructor(Object.class))
+ .isAccessibleTo(new TypeDescription.ForLoadedType(Sample.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredConstructor(String.class))
+ .isAccessibleTo(new TypeDescription.ForLoadedType(Sample.class)), is(false));
+ assertThat(describe(PublicType.class.getDeclaredConstructor())
+ .isAccessibleTo(TypeDescription.OBJECT), is(true));
+ assertThat(describe(PublicType.class.getDeclaredConstructor(Void.class))
+ .isAccessibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PublicType.class.getDeclaredConstructor(Object.class))
+ .isAccessibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PublicType.class.getDeclaredConstructor(String.class))
+ .isAccessibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PublicType.class.getDeclaredConstructor())
+ .isAccessibleTo(new TypeDescription.ForLoadedType(VisibilityMethodTestHelper.class)), is(true));
+ assertThat(describe(PublicType.class.getDeclaredConstructor(Void.class))
+ .isAccessibleTo(new TypeDescription.ForLoadedType(VisibilityMethodTestHelper.class)), is(false));
+ assertThat(describe(PublicType.class.getDeclaredConstructor(Object.class))
+ .isAccessibleTo(new TypeDescription.ForLoadedType(VisibilityMethodTestHelper.class)), is(false));
+ assertThat(describe(PublicType.class.getDeclaredConstructor(String.class))
+ .isAccessibleTo(new TypeDescription.ForLoadedType(VisibilityMethodTestHelper.class)), is(false));
+ assertThat(describe(PackagePrivateType.class.getDeclaredConstructor())
+ .isAccessibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PackagePrivateType.class.getDeclaredConstructor(Void.class))
+ .isAccessibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PackagePrivateType.class.getDeclaredConstructor(Object.class))
+ .isAccessibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(PackagePrivateType.class.getDeclaredConstructor(String.class))
+ .isAccessibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(MethodVisibilityType.class.getDeclaredMethod("packagePrivateArgument", PackagePrivateType.class))
+ .isAccessibleTo(new TypeDescription.ForLoadedType(MethodVisibilityType.class)), is(true));
+ assertThat(describe(MethodVisibilityType.class.getDeclaredMethod("packagePrivateReturnType"))
+ .isAccessibleTo(new TypeDescription.ForLoadedType(MethodVisibilityType.class)), is(true));
+ assertThat(describe(MethodVisibilityType.class.getDeclaredMethod("packagePrivateArgument", PackagePrivateType.class))
+ .isAccessibleTo(TypeDescription.OBJECT), is(true));
+ assertThat(describe(MethodVisibilityType.class.getDeclaredMethod("packagePrivateReturnType"))
+ .isAccessibleTo(TypeDescription.OBJECT), is(true));
+ }
+
+ @Test
+ public void testExceptions() throws Exception {
+ assertThat(describe(firstMethod).getExceptionTypes(),
+ is((TypeList.Generic) new TypeList.Generic.ForLoadedTypes(firstMethod.getExceptionTypes())));
+ assertThat(describe(secondMethod).getExceptionTypes(),
+ is((TypeList.Generic) new TypeList.Generic.ForLoadedTypes(secondMethod.getExceptionTypes())));
+ assertThat(describe(thirdMethod).getExceptionTypes(),
+ is((TypeList.Generic) new TypeList.Generic.ForLoadedTypes(thirdMethod.getExceptionTypes())));
+ assertThat(describe(firstConstructor).getExceptionTypes(),
+ is((TypeList.Generic) new TypeList.Generic.ForLoadedTypes(firstConstructor.getExceptionTypes())));
+ assertThat(describe(secondConstructor).getExceptionTypes(),
+ is((TypeList.Generic) new TypeList.Generic.ForLoadedTypes(secondConstructor.getExceptionTypes())));
+ }
+
+ @Test
+ public void testAnnotations() throws Exception {
+ assertThat(describe(firstMethod).getDeclaredAnnotations(),
+ is((AnnotationList) new AnnotationList.Empty()));
+ assertThat(describe(secondMethod).getDeclaredAnnotations(),
+ is((AnnotationList) new AnnotationList.Empty()));
+ assertThat(describe(thirdMethod).getDeclaredAnnotations(),
+ is((AnnotationList) new AnnotationList.ForLoadedAnnotations(thirdMethod.getDeclaredAnnotations())));
+ assertThat(describe(firstConstructor).getDeclaredAnnotations(), is((AnnotationList) new AnnotationList.Empty()));
+ assertThat(describe(secondConstructor).getDeclaredAnnotations(),
+ is((AnnotationList) new AnnotationList.ForLoadedAnnotations(secondConstructor.getDeclaredAnnotations())));
+ }
+
+ @Test
+ public void testParameterAnnotations() throws Exception {
+ assertThat(describe(secondMethod).getParameters().get(0).getDeclaredAnnotations(),
+ is((AnnotationList) new AnnotationList.Empty()));
+ assertThat(describe(secondMethod).getParameters().get(1).getDeclaredAnnotations(),
+ is((AnnotationList) new AnnotationList.Empty()));
+ assertThat(describe(thirdMethod).getParameters().get(0).getDeclaredAnnotations(),
+ is((AnnotationList) new AnnotationList.ForLoadedAnnotations(thirdMethod.getParameterAnnotations()[0])));
+ assertThat(describe(thirdMethod).getParameters().get(1).getDeclaredAnnotations(),
+ is((AnnotationList) new AnnotationList.ForLoadedAnnotations(thirdMethod.getParameterAnnotations()[1])));
+ assertThat(describe(firstConstructor).getParameters().get(0).getDeclaredAnnotations(),
+ is((AnnotationList) new AnnotationList.Empty()));
+ assertThat(describe(secondConstructor).getParameters().get(0).getDeclaredAnnotations(),
+ is((AnnotationList) new AnnotationList.ForLoadedAnnotations(secondConstructor.getParameterAnnotations()[0])));
+ assertThat(describe(secondConstructor).getParameters().get(1).getDeclaredAnnotations(),
+ is((AnnotationList) new AnnotationList.ForLoadedAnnotations(secondConstructor.getParameterAnnotations()[1])));
+ }
+
+ @Test
+ public void testRepresents() throws Exception {
+ assertThat(describe(firstMethod).represents(firstMethod), is(true));
+ assertThat(describe(firstMethod).represents(secondMethod), is(false));
+ assertThat(describe(firstMethod).represents(thirdMethod), is(false));
+ assertThat(describe(firstMethod).represents(firstConstructor), is(false));
+ assertThat(describe(firstMethod).represents(secondConstructor), is(false));
+ assertThat(describe(firstConstructor).represents(firstConstructor), is(true));
+ assertThat(describe(firstConstructor).represents(secondConstructor), is(false));
+ assertThat(describe(firstConstructor).represents(firstMethod), is(false));
+ assertThat(describe(firstConstructor).represents(secondMethod), is(false));
+ assertThat(describe(firstConstructor).represents(thirdMethod), is(false));
+ }
+
+ @Test
+ public void testSpecializable() throws Exception {
+ assertThat(describe(firstMethod).isSpecializableFor(new TypeDescription.ForLoadedType(Sample.class)), is(false));
+ assertThat(describe(secondMethod).isSpecializableFor(new TypeDescription.ForLoadedType(Sample.class)), is(false));
+ assertThat(describe(thirdMethod).isSpecializableFor(new TypeDescription.ForLoadedType(Sample.class)), is(true));
+ assertThat(describe(thirdMethod).isSpecializableFor(new TypeDescription.ForLoadedType(SampleSub.class)), is(true));
+ assertThat(describe(thirdMethod).isSpecializableFor(TypeDescription.OBJECT), is(false));
+ assertThat(describe(firstConstructor).isSpecializableFor(new TypeDescription.ForLoadedType(Sample.class)), is(true));
+ assertThat(describe(firstConstructor).isSpecializableFor(new TypeDescription.ForLoadedType(SampleSub.class)), is(false));
+ }
+
+ @Test
+ public void testInvokable() throws Exception {
+ assertThat(describe(firstMethod).isInvokableOn(new TypeDescription.ForLoadedType(Sample.class)), is(false));
+ assertThat(describe(secondMethod).isInvokableOn(new TypeDescription.ForLoadedType(Sample.class)), is(true));
+ assertThat(describe(secondMethod).isInvokableOn(new TypeDescription.ForLoadedType(SampleSub.class)), is(true));
+ assertThat(describe(secondMethod).isInvokableOn(TypeDescription.OBJECT), is(false));
+ }
+
+ @Test
+ public void testGenericTypes() throws Exception {
+ assertThat(describe(genericMethod).getReturnType(), is(TypeDefinition.Sort.describe(genericMethod.getGenericReturnType())));
+ assertThat(describe(genericMethod).getParameters().asTypeList(),
+ is((TypeList.Generic) new TypeList.Generic.ForLoadedTypes(genericMethod.getGenericParameterTypes())));
+ assertThat(describe(genericMethod).getExceptionTypes(),
+ is((TypeList.Generic) new TypeList.Generic.ForLoadedTypes(genericMethod.getGenericExceptionTypes())));
+ }
+
+ @Test
+ public void testGenericTypesOfMethodWithoutException() throws Exception {
+ assertThat(describe(genericMethodWithRawException).getReturnType(),
+ is(TypeDefinition.Sort.describe(genericMethodWithRawException.getGenericReturnType())));
+ assertThat(describe(genericMethodWithRawException).getParameters().asTypeList(),
+ is((TypeList.Generic) new TypeList.Generic.ForLoadedTypes(genericMethodWithRawException.getGenericParameterTypes())));
+ assertThat(describe(genericMethodWithRawException).getExceptionTypes(),
+ is((TypeList.Generic) new TypeList.Generic.ForLoadedTypes(genericMethodWithRawException.getGenericExceptionTypes())));
+ }
+
+ @Test
+ public void testToGenericString() throws Exception {
+ assertThat(describe(genericMethod).toGenericString(), is(genericMethod.toGenericString()));
+ }
+
+ @Test
+ public void testEnclosingSource() throws Exception {
+ assertThat(describe(firstMethod).getEnclosingSource(), nullValue(TypeVariableSource.class));
+ assertThat(describe(secondMethod).getEnclosingSource(), is((TypeVariableSource) new TypeDescription.ForLoadedType(Sample.class)));
+ }
+
+ @Test
+ public void testIsGenerified() throws Exception {
+ assertThat(describe(genericMethodWithTypeVariable).isGenerified(), is(true));
+ assertThat(describe(firstMethod).isGenerified(), is(false));
+ }
+
+ @Test
+ public void testImplicitReceiverTypes() throws Exception {
+ assertThat(describe(firstMethod).getReceiverType(), nullValue(TypeDescription.Generic.class));
+ assertThat(describe(secondMethod).getReceiverType(), is(TypeDefinition.Sort.describe(Sample.class)));
+ assertThat(describe(firstConstructor).getReceiverType(), is(TypeDefinition.Sort.describe(AbstractMethodDescriptionTest.class)));
+ assertThat(describe(AbstractMethodDescriptionTest.class.getDeclaredConstructor()).getReceiverType(),
+ is(TypeDefinition.Sort.describe(AbstractMethodDescriptionTest.class)));
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ public void testGetActualModifiers() throws Exception {
+ assertThat(describe(firstMethod).getActualModifiers(), is(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC));
+ assertThat(describe(firstMethod).getActualModifiers(true), is(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC));
+ assertThat(describe(firstMethod).getActualModifiers(false), is(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_ABSTRACT));
+ assertThat(describe(DeprecationSample.class.getDeclaredMethod("foo")).getActualModifiers(), is(Opcodes.ACC_PRIVATE | Opcodes.ACC_DEPRECATED));
+ assertThat(describe(firstMethod).getActualModifiers(true, Visibility.PUBLIC), is(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC));
+ assertThat(describe(secondMethod).getActualModifiers(false, Visibility.PRIVATE), is(Opcodes.ACC_PROTECTED | Opcodes.ACC_ABSTRACT));
+ }
+
+ @Test
+ public void testBridgeCompatible() throws Exception {
+ assertThat(describe(firstMethod).isBridgeCompatible(new MethodDescription.TypeToken(TypeDescription.VOID, Collections.<TypeDescription>emptyList())), is(true));
+ assertThat(describe(firstMethod).isBridgeCompatible(new MethodDescription.TypeToken(TypeDescription.VOID, Collections.singletonList(TypeDescription.OBJECT))), is(false));
+ assertThat(describe(firstMethod).isBridgeCompatible(new MethodDescription.TypeToken(TypeDescription.OBJECT, Collections.<TypeDescription>emptyList())), is(false));
+ assertThat(describe(firstMethod).isBridgeCompatible(new MethodDescription.TypeToken(new TypeDescription.ForLoadedType(int.class), Collections.<TypeDescription>emptyList())), is(false));
+ assertThat(describe(secondMethod).isBridgeCompatible(new MethodDescription.TypeToken(TypeDescription.OBJECT,
+ Arrays.asList(new TypeDescription.ForLoadedType(String.class), new TypeDescription.ForLoadedType(long.class)))), is(true));
+ assertThat(describe(secondMethod).isBridgeCompatible(new MethodDescription.TypeToken(TypeDescription.OBJECT,
+ Arrays.asList(new TypeDescription.ForLoadedType(Object.class), new TypeDescription.ForLoadedType(long.class)))), is(true));
+ assertThat(describe(secondMethod).isBridgeCompatible(new MethodDescription.TypeToken(new TypeDescription.ForLoadedType(String.class),
+ Arrays.asList(new TypeDescription.ForLoadedType(Object.class), new TypeDescription.ForLoadedType(long.class)))), is(true));
+ assertThat(describe(secondMethod).isBridgeCompatible(new MethodDescription.TypeToken(TypeDescription.VOID,
+ Arrays.asList(new TypeDescription.ForLoadedType(String.class), new TypeDescription.ForLoadedType(long.class)))), is(false));
+ assertThat(describe(secondMethod).isBridgeCompatible(new MethodDescription.TypeToken(TypeDescription.OBJECT,
+ Arrays.asList(new TypeDescription.ForLoadedType(int.class), new TypeDescription.ForLoadedType(long.class)))), is(false));
+ assertThat(describe(secondMethod).isBridgeCompatible(new MethodDescription.TypeToken(TypeDescription.OBJECT,
+ Arrays.asList(new TypeDescription.ForLoadedType(String.class), new TypeDescription.ForLoadedType(Object.class)))), is(false));
+ assertThat(describe(secondMethod).isBridgeCompatible(new MethodDescription.TypeToken(TypeDescription.OBJECT,
+ Arrays.asList(new TypeDescription.ForLoadedType(String.class), new TypeDescription.ForLoadedType(int.class)))), is(false));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testIsDefault() throws Exception {
+ Map<String, AnnotationValue<?, ?>> properties = new LinkedHashMap<String, AnnotationValue<?, ?>>();
+ properties.put("boolean_property", AnnotationValue.ForConstant.of(true));
+ properties.put("boolean_property_array", AnnotationValue.ForConstant.of(new boolean[]{true}));
+ properties.put("byte_property", AnnotationValue.ForConstant.of((byte) 0));
+ properties.put("byte_property_array", AnnotationValue.ForConstant.of(new byte[]{0}));
+ properties.put("short_property", AnnotationValue.ForConstant.of((short) 0));
+ properties.put("short_property_array", AnnotationValue.ForConstant.of(new short[]{0}));
+ properties.put("int_property", AnnotationValue.ForConstant.of(0));
+ properties.put("int_property_array", AnnotationValue.ForConstant.of(new int[]{0}));
+ properties.put("long_property", AnnotationValue.ForConstant.of(0L));
+ properties.put("long_property_array", AnnotationValue.ForConstant.of(new long[]{0}));
+ properties.put("float_property", AnnotationValue.ForConstant.of(0f));
+ properties.put("float_property_array", AnnotationValue.ForConstant.of(new float[]{0}));
+ properties.put("double_property", AnnotationValue.ForConstant.of(0d));
+ properties.put("double_property_array", AnnotationValue.ForConstant.of(new double[]{0d}));
+ properties.put("string_property", AnnotationValue.ForConstant.of("foo"));
+ properties.put("string_property_array", AnnotationValue.ForConstant.of(new String[]{"foo"}));
+ AnnotationDescription annotationDescription = mock(AnnotationDescription.class);
+ when(annotationDescription.getAnnotationType()).thenReturn(new TypeDescription.ForLoadedType(SampleAnnotation.class));
+ properties.put("annotation_property", new AnnotationValue.ForAnnotationDescription(annotationDescription));
+ properties.put("annotation_property_array", AnnotationValue.ForDescriptionArray.of(new TypeDescription.ForLoadedType(SampleAnnotation.class), new AnnotationDescription[]{annotationDescription}));
+ EnumerationDescription enumerationDescription = mock(EnumerationDescription.class);
+ when(enumerationDescription.getEnumerationType()).thenReturn(new TypeDescription.ForLoadedType(SampleEnumeration.class));
+ properties.put("enum_property", AnnotationValue.ForEnumerationDescription.<Enum>of(enumerationDescription));
+ properties.put("enum_property_array", AnnotationValue.ForDescriptionArray.<Enum>of(new TypeDescription.ForLoadedType(SampleEnumeration.class), new EnumerationDescription[]{enumerationDescription}));
+ MethodList<?> methods = new TypeDescription.ForLoadedType(AnnotationValues.class).getDeclaredMethods();
+ for (Map.Entry<String, AnnotationValue<?, ?>> entry : properties.entrySet()) {
+ assertThat(methods.filter(named(entry.getKey())).getOnly().isDefaultValue(entry.getValue()), is(true));
+ assertThat(methods.filter(named(entry.getKey())).getOnly().isDefaultValue(mock(AnnotationValue.class)), is(false));
+ }
+ when(annotationDescription.getAnnotationType()).thenReturn(TypeDescription.OBJECT);
+ assertThat(methods.filter(named("annotation_property")).getOnly().isDefaultValue(new AnnotationValue.ForAnnotationDescription(annotationDescription)), is(false));
+ assertThat(methods.filter(named("annotation_property_array")).getOnly().isDefaultValue(AnnotationValue.ForDescriptionArray.of(new TypeDescription.ForLoadedType(Object.class), new AnnotationDescription[]{annotationDescription})), is(false));
+ when(enumerationDescription.getEnumerationType()).thenReturn(TypeDescription.OBJECT);
+ assertThat(methods.filter(named("enum_property")).getOnly().isDefaultValue(AnnotationValue.ForEnumerationDescription.<Enum>of(enumerationDescription)), is(false));
+ assertThat(methods.filter(named("enum_property_array")).getOnly().isDefaultValue(AnnotationValue.ForDescriptionArray.<Enum>of(new TypeDescription.ForLoadedType(Object.class), new EnumerationDescription[]{enumerationDescription})), is(false));
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ private @interface SampleAnnotation {
+ /* empty */
+ }
+
+ private enum SampleEnumeration {
+ INSTANCE
+ }
+
+ @SuppressWarnings("unused")
+ private abstract static class Sample {
+
+ Sample(final Void argument) {
+
+ }
+
+ @SampleAnnotation
+ private Sample(int[] first, @SampleAnnotation long second) throws IOException {
+
+ }
+
+ private static void first() {
+ /* do nothing */
+ }
+
+ protected abstract Object second(String first, long second) throws RuntimeException, IOException;
+
+ @SampleAnnotation
+ public boolean[] third(@SampleAnnotation final Object[] first, int[] second) throws Throwable {
+ return null;
+ }
+ }
+
+ private abstract static class SampleSub extends Sample {
+
+ protected SampleSub(Void argument) {
+ super(argument);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public abstract static class PublicType {
+
+ public PublicType() {
+ /* do nothing*/
+ }
+
+ protected PublicType(Void protectedConstructor) {
+ /* do nothing*/
+ }
+
+ PublicType(Object packagePrivateConstructor) {
+ /* do nothing*/
+ }
+
+ private PublicType(String privateConstructor) {
+ /* do nothing*/
+ }
+
+ public abstract void publicMethod();
+
+ protected abstract void protectedMethod();
+
+ abstract void packagePrivateMethod();
+
+ private void privateMethod() {
+ /* do nothing*/
+ }
+ }
+
+ @SuppressWarnings("unused")
+ abstract static class PackagePrivateType {
+
+ public PackagePrivateType() {
+ /* do nothing*/
+ }
+
+ protected PackagePrivateType(Void protectedConstructor) {
+ /* do nothing*/
+ }
+
+ PackagePrivateType(Object packagePrivateConstructor) {
+ /* do nothing*/
+ }
+
+ private PackagePrivateType(String privateConstructor) {
+ /* do nothing*/
+ }
+
+ public abstract void publicMethod();
+
+ protected abstract void protectedMethod();
+
+ abstract void packagePrivateMethod();
+
+ public static void staticPublicMethod() {
+ /* empty */
+ }
+
+ protected static void staticProtectedMethod() {
+ /* empty */
+ }
+
+ static void staticPackagePrivateMethod() {
+ /* empty */
+ }
+
+ private static void staticPrivateMethod() {
+ /* empty */
+ }
+
+ private void privateMethod() {
+ /* do nothing*/
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class MethodVisibilityType {
+
+ public void packagePrivateArgument(PackagePrivateType arg) {
+ /* do nothing */
+ }
+
+ public PackagePrivateType packagePrivateReturnType() {
+ return null;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ static class GenericMethod<T extends Exception> {
+
+ T foo(T t) throws T {
+ return null;
+ }
+
+ T bar(T t) throws Exception {
+ return null;
+ }
+
+ <Q> void qux() {
+ /* empty */
+ }
+ }
+
+ private static class DeprecationSample {
+
+ @Deprecated
+ private void foo() {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private @interface AnnotationValues {
+
+ boolean boolean_property();
+
+ boolean[] boolean_property_array();
+
+ byte byte_property();
+
+ byte[] byte_property_array();
+
+ short short_property();
+
+ short[] short_property_array();
+
+ int int_property();
+
+ int[] int_property_array();
+
+ long long_property();
+
+ long[] long_property_array();
+
+ float float_property();
+
+ float[] float_property_array();
+
+ double double_property();
+
+ double[] double_property_array();
+
+ String string_property();
+
+ String[] string_property_array();
+
+ SampleAnnotation annotation_property();
+
+ SampleAnnotation[] annotation_property_array();
+
+ SampleEnumeration enum_property();
+
+ SampleEnumeration[] enum_property_array();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/AbstractMethodListTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/AbstractMethodListTest.java
new file mode 100644
index 0000000..1220fd7
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/AbstractMethodListTest.java
@@ -0,0 +1,33 @@
+package net.bytebuddy.description.method;
+
+import net.bytebuddy.description.ByteCodeElement;
+import net.bytebuddy.matcher.AbstractFilterableListTest;
+import org.junit.Test;
+
+import java.util.Collections;
+
+import static net.bytebuddy.matcher.ElementMatchers.none;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public abstract class AbstractMethodListTest<U, V extends MethodDescription> extends AbstractFilterableListTest<V, MethodList<V>, U> {
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testTokenWithMatcher() throws Exception {
+ assertThat(asList(getFirst()).asTokenList(none()),
+ is(new ByteCodeElement.Token.TokenList<MethodDescription.Token>(asElement(getFirst()).asToken(none()))));
+ }
+
+ @Test
+ public void testAsDefined() throws Exception {
+ assertThat(asList(getFirst()).asDefined(), is(Collections.singletonList(asElement(getFirst()).asDefined())));
+ }
+
+ public abstract static class Foo {
+
+ abstract void foo();
+
+ abstract void bar();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/AbstractParameterListTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/AbstractParameterListTest.java
new file mode 100644
index 0000000..1991298
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/AbstractParameterListTest.java
@@ -0,0 +1,43 @@
+package net.bytebuddy.description.method;
+
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.matcher.AbstractFilterableListTest;
+import org.junit.Test;
+
+import java.util.Collections;
+
+import static net.bytebuddy.matcher.ElementMatchers.none;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public abstract class AbstractParameterListTest<U extends ParameterDescription, V> extends AbstractFilterableListTest<U, ParameterList<U>, V> {
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testTokenWithMatcher() throws Exception {
+ assertThat(asList(getFirst()).asTokenList(none()).size(), is(1));
+ assertThat(asList(getFirst()).asTokenList(none()).getOnly().getType(), is(TypeDefinition.Sort.describe(Void.class)));
+ }
+
+ @Test
+ public void testTypeList() throws Exception {
+ assertThat(asList(getFirst()).asTypeList(), is(Collections.singletonList(asElement(getFirst()).getType())));
+ }
+
+ @Test
+ public void testDeclared() throws Exception {
+ assertThat(asList(getFirst()).asDefined(), is(Collections.singletonList(asElement(getFirst()).asDefined())));
+ }
+
+ protected static class Foo {
+
+ public void foo(Void v) {
+ /* empty */
+ }
+
+ public void bar(Void v) {
+ /* empty */
+ }
+ }
+
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/MethodDescriptionForLoadedTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/MethodDescriptionForLoadedTest.java
new file mode 100644
index 0000000..990724d
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/MethodDescriptionForLoadedTest.java
@@ -0,0 +1,33 @@
+package net.bytebuddy.description.method;
+
+import org.junit.Test;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodDescriptionForLoadedTest extends AbstractMethodDescriptionTest {
+
+ @Override
+ protected MethodDescription.InDefinedShape describe(Method method) {
+ return new MethodDescription.ForLoadedMethod(method);
+ }
+
+ @Override
+ protected MethodDescription.InDefinedShape describe(Constructor<?> constructor) {
+ return new MethodDescription.ForLoadedConstructor(constructor);
+ }
+
+ @Test
+ public void testGetLoadedMethod() throws Exception {
+ Method method = Object.class.getDeclaredMethod("toString");
+ assertThat(new MethodDescription.ForLoadedMethod(method).getLoadedMethod(), sameInstance(method));
+ }
+
+ @Override
+ protected boolean canReadDebugInformation() {
+ return false;
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/MethodDescriptionLatentTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/MethodDescriptionLatentTest.java
new file mode 100644
index 0000000..31c4c71
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/MethodDescriptionLatentTest.java
@@ -0,0 +1,46 @@
+package net.bytebuddy.description.method;
+
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.matcher.ElementMatchers;
+import org.junit.Test;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class MethodDescriptionLatentTest extends AbstractMethodDescriptionTest {
+
+ @Override
+ protected MethodDescription.InDefinedShape describe(Method method) {
+ return new MethodDescription.Latent(new TypeDescription.ForLoadedType(method.getDeclaringClass()),
+ new MethodDescription.ForLoadedMethod(method).asToken(ElementMatchers.is(method.getDeclaringClass())));
+ }
+
+ @Override
+ protected MethodDescription.InDefinedShape describe(Constructor<?> constructor) {
+ return new MethodDescription.Latent(new TypeDescription.ForLoadedType(constructor.getDeclaringClass()),
+ new MethodDescription.ForLoadedConstructor(constructor).asToken(ElementMatchers.is(constructor.getDeclaringClass())));
+ }
+
+ @Test
+ public void testTypeInitializer() throws Exception {
+ TypeDescription typeDescription = mock(TypeDescription.class);
+ MethodDescription.InDefinedShape typeInitializer = new MethodDescription.Latent.TypeInitializer(typeDescription);
+ assertThat(typeInitializer.getDeclaringType(), is(typeDescription));
+ assertThat(typeInitializer.getReturnType(), is(TypeDescription.Generic.VOID));
+ assertThat(typeInitializer.getParameters(), is((ParameterList<ParameterDescription.InDefinedShape>) new ParameterList.Empty<ParameterDescription.InDefinedShape>()));
+ assertThat(typeInitializer.getExceptionTypes(), is((TypeList.Generic) new TypeList.Generic.Empty()));
+ assertThat(typeInitializer.getDeclaredAnnotations(), is((AnnotationList) new AnnotationList.Empty()));
+ assertThat(typeInitializer.getModifiers(), is(MethodDescription.TYPE_INITIALIZER_MODIFIER));
+ }
+
+ @Override
+ protected boolean canReadDebugInformation() {
+ return false;
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/MethodDescriptionLatentTypeInitializerTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/MethodDescriptionLatentTypeInitializerTest.java
new file mode 100644
index 0000000..83fdf6f
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/MethodDescriptionLatentTypeInitializerTest.java
@@ -0,0 +1,63 @@
+package net.bytebuddy.description.method;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodDescriptionLatentTypeInitializerTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ private MethodDescription.InDefinedShape typeInitializer;
+
+ @Before
+ public void setUp() throws Exception {
+ typeInitializer = new MethodDescription.Latent.TypeInitializer(typeDescription);
+ }
+
+ @Test
+ public void testDeclaringType() throws Exception {
+ assertThat(typeInitializer.getDeclaringType(), is(typeDescription));
+ }
+
+ @Test
+ public void testName() throws Exception {
+ assertThat(typeInitializer.getInternalName(), is(MethodDescription.TYPE_INITIALIZER_INTERNAL_NAME));
+ }
+
+ @Test
+ public void testModifiers() throws Exception {
+ assertThat(typeInitializer.getModifiers(), is(MethodDescription.TYPE_INITIALIZER_MODIFIER));
+ }
+
+ @Test
+ public void testAnnotations() throws Exception {
+ assertThat(typeInitializer.getDeclaredAnnotations().size(), is(0));
+ }
+
+ @Test
+ public void testExceptions() throws Exception {
+ assertThat(typeInitializer.getExceptionTypes().size(), is(0));
+ }
+
+ @Test
+ public void testParameters() throws Exception {
+ assertThat(typeInitializer.getParameters().size(), is(0));
+ }
+
+ @Test
+ public void testReturnType() throws Exception {
+ assertThat(typeInitializer.getReturnType(), is(TypeDescription.Generic.VOID));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/MethodDescriptionSignatureTokenTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/MethodDescriptionSignatureTokenTest.java
new file mode 100644
index 0000000..2cb3797
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/MethodDescriptionSignatureTokenTest.java
@@ -0,0 +1,44 @@
+package net.bytebuddy.description.method;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodDescriptionSignatureTokenTest {
+
+ private static final String FOO = "foo";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription returnType, parameterType;
+
+ @Test
+ public void testProperties() throws Exception {
+ MethodDescription.SignatureToken token = new MethodDescription.SignatureToken(FOO, returnType, Collections.singletonList(parameterType));
+ assertThat(token.getName(), is(FOO));
+ assertThat(token.getReturnType(), is(returnType));
+ assertThat(token.getParameterTypes(), is(Collections.singletonList(parameterType)));
+ }
+
+ @Test
+ public void testTypeToken() throws Exception {
+ assertThat(new MethodDescription.SignatureToken(FOO, returnType, Collections.singletonList(parameterType)).asTypeToken(),
+ is(new MethodDescription.TypeToken(returnType, Collections.singletonList(parameterType))));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodDescription.SignatureToken.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/MethodDescriptionTokenTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/MethodDescriptionTokenTest.java
new file mode 100644
index 0000000..2552ba2
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/MethodDescriptionTokenTest.java
@@ -0,0 +1,144 @@
+package net.bytebuddy.description.method;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationValue;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeVariableToken;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.Collections;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class MethodDescriptionTokenTest {
+
+ private static final String FOO = "foo";
+
+ private static final int MODIFIERS = 42;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription.Generic returnType, visitedReturnType, exceptionType, visitedExceptionType, parameterType, receiverType, visitedReceiverType;
+
+ @Mock
+ private ParameterDescription.Token parameterToken, visitedParameterToken;
+
+ @Mock
+ private TypeVariableToken typeVariableToken, visitedTypeVariableToken;
+
+ @Mock
+ private TypeDescription typeDescription, rawReturnType, rawParameterType;
+
+ @Mock
+ private AnnotationDescription annotation;
+
+ @Mock
+ private AnnotationValue<?, ?> defaultValue;
+
+ @Mock
+ private TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ when(typeVariableToken.accept(visitor)).thenReturn(visitedTypeVariableToken);
+ when(returnType.asGenericType()).thenReturn(returnType);
+ when(visitedReturnType.asGenericType()).thenReturn(visitedReturnType);
+ when(returnType.accept((TypeDescription.Generic.Visitor) visitor)).thenReturn(visitedReturnType);
+ when(exceptionType.asGenericType()).thenReturn(exceptionType);
+ when(visitedExceptionType.asGenericType()).thenReturn(visitedExceptionType);
+ when(exceptionType.accept((TypeDescription.Generic.Visitor) visitor)).thenReturn(visitedExceptionType);
+ when(parameterToken.accept(visitor)).thenReturn(visitedParameterToken);
+ when(receiverType.accept((TypeDescription.Generic.Visitor) visitor)).thenReturn(visitedReceiverType);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testProperties() throws Exception {
+ MethodDescription.Token token = new MethodDescription.Token(FOO,
+ MODIFIERS,
+ Collections.singletonList(typeVariableToken),
+ returnType,
+ Collections.singletonList(parameterToken),
+ Collections.singletonList(exceptionType),
+ Collections.singletonList(annotation),
+ defaultValue,
+ receiverType);
+ assertThat(token.getName(), is(FOO));
+ assertThat(token.getModifiers(), is(MODIFIERS));
+ assertThat(token.getTypeVariableTokens(), is(Collections.singletonList(typeVariableToken)));
+ assertThat(token.getReturnType(), is(returnType));
+ assertThat(token.getParameterTokens(), is(Collections.singletonList(parameterToken)));
+ assertThat(token.getExceptionTypes(), is(Collections.singletonList(exceptionType)));
+ assertThat(token.getAnnotations(), is(Collections.singletonList(annotation)));
+ assertThat(token.getDefaultValue(), is((AnnotationValue) defaultValue));
+ assertThat(token.getReceiverType(), is(receiverType));
+ }
+
+ @Test
+ public void testVisitor() throws Exception {
+ assertThat(new MethodDescription.Token(FOO,
+ MODIFIERS,
+ Collections.singletonList(typeVariableToken),
+ returnType,
+ Collections.singletonList(parameterToken),
+ Collections.singletonList(exceptionType),
+ Collections.singletonList(annotation),
+ defaultValue,
+ receiverType).accept(visitor),
+ is(new MethodDescription.Token(FOO,
+ MODIFIERS,
+ Collections.singletonList(visitedTypeVariableToken),
+ visitedReturnType,
+ Collections.singletonList(visitedParameterToken),
+ Collections.singletonList(visitedExceptionType),
+ Collections.singletonList(annotation),
+ defaultValue,
+ visitedReceiverType)));
+ }
+
+ @Test
+ public void testSignatureTokenTransformation() throws Exception {
+ when(returnType.accept(new TypeDescription.Generic.Visitor.Reducing(typeDescription, Collections.singletonList(typeVariableToken))))
+ .thenReturn(rawReturnType);
+ when(parameterToken.getType()).thenReturn(parameterType);
+ when(parameterType.accept(new TypeDescription.Generic.Visitor.Reducing(typeDescription, Collections.singletonList(typeVariableToken))))
+ .thenReturn(rawParameterType);
+ assertThat(new MethodDescription.Token(FOO,
+ MODIFIERS,
+ Collections.singletonList(typeVariableToken),
+ returnType,
+ Collections.singletonList(parameterToken),
+ Collections.singletonList(exceptionType),
+ Collections.singletonList(annotation),
+ defaultValue,
+ receiverType).asSignatureToken(typeDescription),
+ is(new MethodDescription.SignatureToken(FOO, rawReturnType, Collections.singletonList(rawParameterType))));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodDescription.Token.class).create(new ObjectPropertyAssertion.Creator<List<?>>() {
+ @Override
+ public List<?> create() {
+ TypeDescription.Generic typeDescription = mock(TypeDescription.Generic.class);
+ when(typeDescription.asGenericType()).thenReturn(typeDescription);
+ return Collections.singletonList(typeDescription);
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(MethodDescription.SignatureToken.class).apply();
+ ObjectPropertyAssertion.of(MethodDescription.TypeToken.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/MethodDescriptionTypeTokenTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/MethodDescriptionTypeTokenTest.java
new file mode 100644
index 0000000..010eadb
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/MethodDescriptionTypeTokenTest.java
@@ -0,0 +1,37 @@
+package net.bytebuddy.description.method;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodDescriptionTypeTokenTest {
+
+ private static final String FOO = "foo";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription returnType, parameterType;
+
+ @Test
+ public void testProperties() throws Exception {
+ MethodDescription.TypeToken token = new MethodDescription.TypeToken(returnType, Collections.singletonList(parameterType));
+ assertThat(token.getReturnType(), is(returnType));
+ assertThat(token.getParameterTypes(), is(Collections.singletonList(parameterType)));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodDescription.TypeToken.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/MethodListEmptyTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/MethodListEmptyTest.java
new file mode 100644
index 0000000..bba063b
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/MethodListEmptyTest.java
@@ -0,0 +1,20 @@
+package net.bytebuddy.description.method;
+
+import org.junit.Test;
+
+import static net.bytebuddy.matcher.ElementMatchers.none;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodListEmptyTest {
+
+ @Test
+ public void testTokenListWithFilter() throws Exception {
+ assertThat(new MethodList.Empty<MethodDescription>().asTokenList(none()).size(), is(0));
+ }
+
+ @Test
+ public void testDeclaredList() throws Exception {
+ assertThat(new MethodList.Empty<MethodDescription>().asDefined().size(), is(0));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/MethodListExplicitTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/MethodListExplicitTest.java
new file mode 100644
index 0000000..7cb26eb
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/MethodListExplicitTest.java
@@ -0,0 +1,26 @@
+package net.bytebuddy.description.method;
+
+import java.util.List;
+
+public class MethodListExplicitTest extends AbstractMethodListTest<MethodDescription, MethodDescription> {
+
+ @Override
+ protected MethodDescription getFirst() throws Exception {
+ return new MethodDescription.ForLoadedMethod(Foo.class.getDeclaredMethod("foo"));
+ }
+
+ @Override
+ protected MethodDescription getSecond() throws Exception {
+ return new MethodDescription.ForLoadedMethod(Foo.class.getDeclaredMethod("bar"));
+ }
+
+ @Override
+ protected MethodList<MethodDescription> asList(List<MethodDescription> elements) {
+ return new MethodList.Explicit<MethodDescription>(elements);
+ }
+
+ @Override
+ protected MethodDescription asElement(MethodDescription element) {
+ return element;
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/MethodListForLoadedTypesTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/MethodListForLoadedTypesTest.java
new file mode 100644
index 0000000..8d2fb53
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/MethodListForLoadedTypesTest.java
@@ -0,0 +1,28 @@
+package net.bytebuddy.description.method;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.List;
+
+public class MethodListForLoadedTypesTest extends AbstractMethodListTest<Method, MethodDescription.InDefinedShape> {
+
+ @Override
+ protected Method getFirst() throws Exception {
+ return Foo.class.getDeclaredMethod("foo");
+ }
+
+ @Override
+ protected Method getSecond() throws Exception {
+ return Foo.class.getDeclaredMethod("bar");
+ }
+
+ @Override
+ protected MethodList<MethodDescription.InDefinedShape> asList(List<Method> elements) {
+ return new MethodList.ForLoadedMethods(new Constructor<?>[0], elements.toArray(new Method[elements.size()]));
+ }
+
+ @Override
+ protected MethodDescription.InDefinedShape asElement(Method element) {
+ return new MethodDescription.ForLoadedMethod(element);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/ParameterDescriptionForLoadedParameterDispatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/ParameterDescriptionForLoadedParameterDispatcherTest.java
new file mode 100644
index 0000000..02ef8c0
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/ParameterDescriptionForLoadedParameterDispatcherTest.java
@@ -0,0 +1,54 @@
+package net.bytebuddy.description.method;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Iterator;
+
+public class ParameterDescriptionForLoadedParameterDispatcherTest {
+
+ private static final int FOO = 42;
+
+ private AccessibleObject accessibleObject;
+
+ @Before
+ public void setUp() throws Exception {
+ accessibleObject = Foo.class.getDeclaredConstructor();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testLegacyVmGetName() throws Exception {
+ ParameterDescription.ForLoadedParameter.Dispatcher.ForLegacyVm.INSTANCE.getName(accessibleObject, FOO);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testLegacyVmGetModifiers() throws Exception {
+ ParameterDescription.ForLoadedParameter.Dispatcher.ForLegacyVm.INSTANCE.getModifiers(accessibleObject, FOO);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testLegacyVmIsNamePresent() throws Exception {
+ ParameterDescription.ForLoadedParameter.Dispatcher.ForLegacyVm.INSTANCE.isNamePresent(accessibleObject, FOO);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ final Iterator<Method> methods = Arrays.asList(Object.class.getDeclaredMethods()).iterator();
+ ObjectPropertyAssertion.of(ParameterDescription.ForLoadedParameter.Dispatcher.ForJava8CapableVm.class).create(new ObjectPropertyAssertion.Creator<Method>() {
+ @Override
+ public Method create() {
+ return methods.next();
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(ParameterDescription.ForLoadedParameter.Dispatcher.ForLegacyVm.class).apply();
+ ObjectPropertyAssertion.of(ParameterDescription.ForLoadedParameter.Dispatcher.CreationAction.class).apply();
+ }
+
+ private static class Foo {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/ParameterDescriptionTokenTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/ParameterDescriptionTokenTest.java
new file mode 100644
index 0000000..719bf1f
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/ParameterDescriptionTokenTest.java
@@ -0,0 +1,70 @@
+package net.bytebuddy.description.method;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.Collections;
+import java.util.Random;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class ParameterDescriptionTokenTest {
+
+ private static final String FOO = "foo";
+
+ private static final int MODIFIERS = 42;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription.Generic type, visitedType;
+
+ @Mock
+ private AnnotationDescription annotation;
+
+ @Mock
+ private TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ when(type.asGenericType()).thenReturn(type);
+ when(visitedType.asGenericType()).thenReturn(visitedType);
+ when(type.accept((TypeDescription.Generic.Visitor) visitor)).thenReturn(visitedType);
+ }
+
+ @Test
+ public void testProperties() throws Exception {
+ ParameterDescription.Token token = new ParameterDescription.Token(type, Collections.singletonList(annotation), FOO, MODIFIERS);
+ assertThat(token.getType(), is(type));
+ assertThat(token.getAnnotations(), is(Collections.singletonList(annotation)));
+ assertThat(token.getName(), is(FOO));
+ assertThat(token.getModifiers(), is(MODIFIERS));
+ }
+
+ @Test
+ public void testVisitor() throws Exception {
+ assertThat(new ParameterDescription.Token(type, Collections.singletonList(annotation), FOO, MODIFIERS).accept(visitor),
+ is(new ParameterDescription.Token(visitedType, Collections.singletonList(annotation), FOO, MODIFIERS)));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ParameterDescription.Token.class).create(new ObjectPropertyAssertion.Creator<Integer>() {
+ @Override
+ public Integer create() {
+ return new Random().nextInt();
+ }
+ }).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/ParameterListEmptyTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/ParameterListEmptyTest.java
new file mode 100644
index 0000000..95e9828
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/ParameterListEmptyTest.java
@@ -0,0 +1,30 @@
+package net.bytebuddy.description.method;
+
+import org.junit.Test;
+
+import static net.bytebuddy.matcher.ElementMatchers.none;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class ParameterListEmptyTest {
+
+ @Test
+ public void testTokenListWithFilter() throws Exception {
+ assertThat(new ParameterList.Empty<ParameterDescription>().asTokenList(none()).size(), is(0));
+ }
+
+ @Test
+ public void testTokenListMetaData() throws Exception {
+ assertThat(new ParameterList.Empty<ParameterDescription>().hasExplicitMetaData(), is(true));
+ }
+
+ @Test
+ public void testTypeList() throws Exception {
+ assertThat(new ParameterList.Empty<ParameterDescription>().asTypeList().size(), is(0));
+ }
+
+ @Test
+ public void testDeclaredList() throws Exception {
+ assertThat(new ParameterList.Empty<ParameterDescription>().asDefined().size(), is(0));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/ParameterListExplicitTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/ParameterListExplicitTest.java
new file mode 100644
index 0000000..4314019
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/ParameterListExplicitTest.java
@@ -0,0 +1,26 @@
+package net.bytebuddy.description.method;
+
+import java.util.List;
+
+public class ParameterListExplicitTest extends AbstractParameterListTest<ParameterDescription, ParameterDescription> {
+
+ @Override
+ protected ParameterDescription getFirst() throws Exception {
+ return new MethodDescription.ForLoadedMethod(Foo.class.getDeclaredMethod("foo", Void.class)).getParameters().getOnly();
+ }
+
+ @Override
+ protected ParameterDescription getSecond() throws Exception {
+ return new MethodDescription.ForLoadedMethod(Foo.class.getDeclaredMethod("bar", Void.class)).getParameters().getOnly();
+ }
+
+ @Override
+ protected ParameterList<ParameterDescription> asList(List<ParameterDescription> elements) {
+ return new ParameterList.Explicit<ParameterDescription>(elements);
+ }
+
+ @Override
+ protected ParameterDescription asElement(ParameterDescription element) {
+ return element;
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/ParameterListForLoadedExectutableDispatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/ParameterListForLoadedExectutableDispatcherTest.java
new file mode 100644
index 0000000..df06982
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/ParameterListForLoadedExectutableDispatcherTest.java
@@ -0,0 +1,47 @@
+package net.bytebuddy.description.method;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Iterator;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class ParameterListForLoadedExectutableDispatcherTest {
+
+ @Test
+ public void testLegacyMethod() throws Exception {
+ assertThat(ParameterList.ForLoadedExecutable.Dispatcher.ForLegacyVm.INSTANCE.describe(Object.class.getDeclaredMethod("toString")),
+ CoreMatchers.<ParameterList<ParameterDescription.InDefinedShape>>is(new ParameterList.ForLoadedExecutable.OfLegacyVmMethod(Object.class
+ .getDeclaredMethod("toString"))));
+ }
+
+ @Test
+ public void testLegacyConstructor() throws Exception {
+ assertThat(ParameterList.ForLoadedExecutable.Dispatcher.ForLegacyVm.INSTANCE.describe(Object.class.getDeclaredConstructor()),
+ CoreMatchers.<ParameterList<ParameterDescription.InDefinedShape>>is(new ParameterList.ForLoadedExecutable.OfLegacyVmConstructor(Object.class
+ .getDeclaredConstructor())));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testLegacyGetParameterCount() throws Exception {
+ ParameterList.ForLoadedExecutable.Dispatcher.ForLegacyVm.INSTANCE.getParameterCount(mock(Object.class));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ final Iterator<Method> methods = Arrays.asList(Object.class.getDeclaredMethods()).iterator();
+ ObjectPropertyAssertion.of(ParameterList.ForLoadedExecutable.Dispatcher.ForJava8CapableVm.class).create(new ObjectPropertyAssertion.Creator<Method>() {
+ @Override
+ public Method create() {
+ return methods.next();
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(ParameterList.ForLoadedExecutable.Dispatcher.ForLegacyVm.class).apply();
+ ObjectPropertyAssertion.of(ParameterList.ForLoadedExecutable.Dispatcher.CreationAction.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/ParameterListForLoadedParameterTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/ParameterListForLoadedParameterTest.java
new file mode 100644
index 0000000..84173ce
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/method/ParameterListForLoadedParameterTest.java
@@ -0,0 +1,32 @@
+package net.bytebuddy.description.method;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ParameterListForLoadedParameterTest extends AbstractParameterListTest<ParameterDescription.InDefinedShape, Method> {
+
+ @Override
+ protected Method getFirst() throws Exception {
+ return Foo.class.getDeclaredMethod("foo", Void.class);
+ }
+
+ @Override
+ protected Method getSecond() throws Exception {
+ return Foo.class.getDeclaredMethod("bar", Void.class);
+ }
+
+ @Override
+ protected ParameterList<ParameterDescription.InDefinedShape> asList(List<Method> elements) {
+ List<ParameterDescription.InDefinedShape> parameters = new ArrayList<ParameterDescription.InDefinedShape>(elements.size());
+ for (Method method : elements) {
+ parameters.add(new MethodDescription.ForLoadedMethod(method).getParameters().getOnly());
+ }
+ return new ParameterList.Explicit<ParameterDescription.InDefinedShape>(parameters);
+ }
+
+ @Override
+ protected ParameterDescription.InDefinedShape asElement(Method element) {
+ return new MethodDescription.ForLoadedMethod(element).getParameters().getOnly();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/AbstractModifierContributorTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/AbstractModifierContributorTest.java
new file mode 100644
index 0000000..390d5a0
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/AbstractModifierContributorTest.java
@@ -0,0 +1,31 @@
+package net.bytebuddy.description.modifier;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public abstract class AbstractModifierContributorTest {
+
+ protected final ModifierContributor modifierContributor;
+
+ protected final int expectedModifier;
+
+ protected final boolean defaultModifier;
+
+ public AbstractModifierContributorTest(ModifierContributor modifierContributor, int expectedModifier, boolean defaultModifier) {
+ this.modifierContributor = modifierContributor;
+ this.expectedModifier = expectedModifier;
+ this.defaultModifier = defaultModifier;
+ }
+
+ @Test
+ public void testModifierContributor() throws Exception {
+ assertThat(modifierContributor.getMask(), is(expectedModifier));
+ }
+
+ @Test
+ public void testDefaultModifier() throws Exception {
+ assertThat(modifierContributor.isDefault(), is(defaultModifier));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/EnumerationStateTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/EnumerationStateTest.java
new file mode 100644
index 0000000..49309c6
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/EnumerationStateTest.java
@@ -0,0 +1,33 @@
+package net.bytebuddy.description.modifier;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class EnumerationStateTest extends AbstractModifierContributorTest {
+
+ public EnumerationStateTest(ModifierContributor modifierContributor, int expectedModifier, boolean defaultModifier) {
+ super(modifierContributor, expectedModifier, defaultModifier);
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {EnumerationState.PLAIN, 0, true},
+ {EnumerationState.ENUMERATION, Opcodes.ACC_ENUM, false}
+ });
+ }
+
+ @Test
+ public void testProperties() throws Exception {
+ assertThat(((EnumerationState) modifierContributor).isEnumeration(), is((expectedModifier & Opcodes.ACC_ENUM) != 0));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/FieldManifestationTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/FieldManifestationTest.java
new file mode 100644
index 0000000..068825e
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/FieldManifestationTest.java
@@ -0,0 +1,46 @@
+package net.bytebuddy.description.modifier;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class FieldManifestationTest extends AbstractModifierContributorTest {
+
+ private final boolean isFinal, isVolatile, isPlain;
+
+ public FieldManifestationTest(ModifierContributor modifierContributor,
+ int expectedModifier,
+ boolean defaultModifier,
+ boolean isFinal,
+ boolean isVolatile,
+ boolean isPlain) {
+ super(modifierContributor, expectedModifier, defaultModifier);
+ this.isFinal = isFinal;
+ this.isVolatile = isVolatile;
+ this.isPlain = isPlain;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {FieldManifestation.PLAIN, 0, true, false, false, true},
+ {FieldManifestation.FINAL, Opcodes.ACC_FINAL, false, true, false, false},
+ {FieldManifestation.VOLATILE, Opcodes.ACC_VOLATILE, false, false, true, false}
+ });
+ }
+
+ @Test
+ public void testProperties() throws Exception {
+ assertThat(((FieldManifestation) modifierContributor).isFinal(), is(isFinal));
+ assertThat(((FieldManifestation) modifierContributor).isVolatile(), is(isVolatile));
+ assertThat(((FieldManifestation) modifierContributor).isPlain(), is(isPlain));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/FieldPersistenceTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/FieldPersistenceTest.java
new file mode 100644
index 0000000..263d5e0
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/FieldPersistenceTest.java
@@ -0,0 +1,39 @@
+package net.bytebuddy.description.modifier;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class FieldPersistenceTest extends AbstractModifierContributorTest {
+
+ private final boolean isTransient;
+
+ public FieldPersistenceTest(ModifierContributor modifierContributor,
+ int expectedModifier,
+ boolean defaultModifier,
+ boolean isTransient) {
+ super(modifierContributor, expectedModifier, defaultModifier);
+ this.isTransient = isTransient;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {FieldPersistence.PLAIN, 0, true, false},
+ {FieldPersistence.TRANSIENT, Opcodes.ACC_TRANSIENT, false, true}
+ });
+ }
+
+ @Test
+ public void testProperties() throws Exception {
+ assertThat(((FieldPersistence) modifierContributor).isTransient(), is(isTransient));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/MethodArgumentsTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/MethodArgumentsTest.java
new file mode 100644
index 0000000..3dbd702
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/MethodArgumentsTest.java
@@ -0,0 +1,33 @@
+package net.bytebuddy.description.modifier;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class MethodArgumentsTest extends AbstractModifierContributorTest {
+
+ public MethodArgumentsTest(ModifierContributor modifierContributor, int expectedModifier, boolean defaultModifier) {
+ super(modifierContributor, expectedModifier, defaultModifier);
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {MethodArguments.PLAIN, 0, true},
+ {MethodArguments.VARARGS, Opcodes.ACC_VARARGS, false}
+ });
+ }
+
+ @Test
+ public void testState() throws Exception {
+ assertThat(((MethodArguments) modifierContributor).isVarArgs(), is(expectedModifier != 0));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/MethodManifestationTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/MethodManifestationTest.java
new file mode 100644
index 0000000..b5cb7e4
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/MethodManifestationTest.java
@@ -0,0 +1,40 @@
+package net.bytebuddy.description.modifier;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class MethodManifestationTest extends AbstractModifierContributorTest {
+
+ public MethodManifestationTest(ModifierContributor modifierContributor, int expectedModifier, boolean defaultModifier) {
+ super(modifierContributor, expectedModifier, defaultModifier);
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {MethodManifestation.PLAIN, 0, true},
+ {MethodManifestation.NATIVE, Opcodes.ACC_NATIVE, false},
+ {MethodManifestation.ABSTRACT, Opcodes.ACC_ABSTRACT, false},
+ {MethodManifestation.FINAL, Opcodes.ACC_FINAL, false},
+ {MethodManifestation.FINAL_NATIVE, Opcodes.ACC_FINAL | Opcodes.ACC_NATIVE, false},
+ {MethodManifestation.BRIDGE, Opcodes.ACC_BRIDGE, false}
+ });
+ }
+
+ @Test
+ public void testProperties() throws Exception {
+ assertThat(((MethodManifestation) modifierContributor).isAbstract(), is((expectedModifier & Opcodes.ACC_ABSTRACT) != 0));
+ assertThat(((MethodManifestation) modifierContributor).isBridge(), is((expectedModifier & Opcodes.ACC_BRIDGE) != 0));
+ assertThat(((MethodManifestation) modifierContributor).isFinal(), is((expectedModifier & Opcodes.ACC_FINAL) != 0));
+ assertThat(((MethodManifestation) modifierContributor).isNative(), is((expectedModifier & Opcodes.ACC_NATIVE) != 0));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/MethodStrictnessTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/MethodStrictnessTest.java
new file mode 100644
index 0000000..d463bd2
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/MethodStrictnessTest.java
@@ -0,0 +1,33 @@
+package net.bytebuddy.description.modifier;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class MethodStrictnessTest extends AbstractModifierContributorTest {
+
+ public MethodStrictnessTest(ModifierContributor modifierContributor, int expectedModifier, boolean defaultModifier) {
+ super(modifierContributor, expectedModifier, defaultModifier);
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {MethodStrictness.PLAIN, 0, true},
+ {MethodStrictness.STRICT, Opcodes.ACC_STRICT, false}
+ });
+ }
+
+ @Test
+ public void testState() throws Exception {
+ assertThat(((MethodStrictness) modifierContributor).isStrict(), is(expectedModifier != 0));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/ModifierConstributorObjectPropertiesTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/ModifierConstributorObjectPropertiesTest.java
new file mode 100644
index 0000000..0795da8
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/ModifierConstributorObjectPropertiesTest.java
@@ -0,0 +1,23 @@
+package net.bytebuddy.description.modifier;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+public class ModifierConstributorObjectPropertiesTest {
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(EnumerationState.class).apply();
+ ObjectPropertyAssertion.of(FieldManifestation.class).apply();
+ ObjectPropertyAssertion.of(MethodArguments.class).apply();
+ ObjectPropertyAssertion.of(MethodManifestation.class).apply();
+ ObjectPropertyAssertion.of(MethodStrictness.class).apply();
+ ObjectPropertyAssertion.of(Ownership.class).apply();
+ ObjectPropertyAssertion.of(ParameterManifestation.class).apply();
+ ObjectPropertyAssertion.of(ProvisioningState.class).apply();
+ ObjectPropertyAssertion.of(SynchronizationState.class).apply();
+ ObjectPropertyAssertion.of(SyntheticState.class).apply();
+ ObjectPropertyAssertion.of(TypeManifestation.class).apply();
+ ObjectPropertyAssertion.of(Visibility.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/ModifierContributorResolverTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/ModifierContributorResolverTest.java
new file mode 100644
index 0000000..ada7778
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/ModifierContributorResolverTest.java
@@ -0,0 +1,56 @@
+package net.bytebuddy.description.modifier;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+import org.objectweb.asm.Opcodes;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class ModifierContributorResolverTest {
+
+ @Test
+ public void testForType() throws Exception {
+ assertThat(ModifierContributor.Resolver.of(Visibility.PUBLIC, TypeManifestation.FINAL).resolve(),
+ is(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL));
+ assertThat(ModifierContributor.Resolver.of(Visibility.PUBLIC, TypeManifestation.INTERFACE, TypeManifestation.FINAL).resolve(),
+ is(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL));
+ assertThat(ModifierContributor.Resolver.of(Visibility.PUBLIC, TypeManifestation.FINAL).resolve(1),
+ is(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | 1));
+ }
+
+ @Test
+ public void testForField() throws Exception {
+ assertThat(ModifierContributor.Resolver.of(Visibility.PUBLIC, FieldManifestation.FINAL).resolve(),
+ is(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL));
+ assertThat(ModifierContributor.Resolver.of(Visibility.PUBLIC, FieldManifestation.VOLATILE, FieldManifestation.FINAL).resolve(),
+ is(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL));
+ assertThat(ModifierContributor.Resolver.of(Visibility.PUBLIC, FieldManifestation.FINAL).resolve(1),
+ is(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | 1));
+ }
+
+ @Test
+ public void testForMethod() throws Exception {
+ assertThat(ModifierContributor.Resolver.of(Visibility.PUBLIC, MethodManifestation.FINAL).resolve(),
+ is(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL));
+ assertThat(ModifierContributor.Resolver.of(Visibility.PUBLIC, MethodManifestation.ABSTRACT, MethodManifestation.FINAL).resolve(),
+ is(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL));
+ assertThat(ModifierContributor.Resolver.of(Visibility.PUBLIC, MethodManifestation.FINAL).resolve(1),
+ is(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | 1));
+ }
+
+ @Test
+ public void testForParameter() throws Exception {
+ assertThat(ModifierContributor.Resolver.of(ProvisioningState.MANDATED, ParameterManifestation.FINAL).resolve(),
+ is(Opcodes.ACC_MANDATED | Opcodes.ACC_FINAL));
+ assertThat(ModifierContributor.Resolver.of(ProvisioningState.MANDATED, ParameterManifestation.PLAIN, ParameterManifestation.FINAL).resolve(),
+ is(Opcodes.ACC_MANDATED | Opcodes.ACC_FINAL));
+ assertThat(ModifierContributor.Resolver.of(ProvisioningState.MANDATED, ParameterManifestation.FINAL).resolve(1),
+ is(Opcodes.ACC_MANDATED | Opcodes.ACC_FINAL | 1));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ModifierContributor.Resolver.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/ModifierContributorTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/ModifierContributorTest.java
new file mode 100644
index 0000000..c292245
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/ModifierContributorTest.java
@@ -0,0 +1,57 @@
+package net.bytebuddy.description.modifier;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class ModifierContributorTest {
+
+ private final Class<? extends ModifierContributor> type;
+
+ public ModifierContributorTest(Class<? extends ModifierContributor> type) {
+ this.type = type;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {EnumerationState.class},
+ {FieldManifestation.class},
+ {MethodArguments.class},
+ {MethodManifestation.class},
+ {MethodStrictness.class},
+ {Ownership.class},
+ {ParameterManifestation.class},
+ {ProvisioningState.class},
+ {SynchronizationState.class},
+ {SyntheticState.class},
+ {TypeManifestation.class},
+ {Visibility.class}
+ });
+ }
+
+ @Test
+ public void testRange() throws Exception {
+ ModifierContributor[] modifierContributor = (ModifierContributor[]) type.getDeclaredMethod("values").invoke(null);
+ int mask = 0;
+ for (ModifierContributor contributor : modifierContributor) {
+ mask |= contributor.getMask();
+ }
+ for (ModifierContributor contributor : modifierContributor) {
+ assertThat(mask, is(contributor.getRange()));
+ }
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(type).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/OwnershipTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/OwnershipTest.java
new file mode 100644
index 0000000..c9cf987
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/OwnershipTest.java
@@ -0,0 +1,33 @@
+package net.bytebuddy.description.modifier;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class OwnershipTest extends AbstractModifierContributorTest {
+
+ public OwnershipTest(ModifierContributor modifierContributor, int expectedModifier, boolean defaultModifier) {
+ super(modifierContributor, expectedModifier, defaultModifier);
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {Ownership.MEMBER, 0, true},
+ {Ownership.STATIC, Opcodes.ACC_STATIC, false}
+ });
+ }
+
+ @Test
+ public void testState() throws Exception {
+ assertThat(((Ownership) modifierContributor).isStatic(), is(expectedModifier != 0));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/ParameterManifestationTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/ParameterManifestationTest.java
new file mode 100644
index 0000000..f358850
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/ParameterManifestationTest.java
@@ -0,0 +1,33 @@
+package net.bytebuddy.description.modifier;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class ParameterManifestationTest extends AbstractModifierContributorTest {
+
+ public ParameterManifestationTest(ModifierContributor modifierContributor, int expectedModifier, boolean defaultModifier) {
+ super(modifierContributor, expectedModifier, defaultModifier);
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {ParameterManifestation.PLAIN, 0, true},
+ {ParameterManifestation.FINAL, Opcodes.ACC_FINAL, false}
+ });
+ }
+
+ @Test
+ public void testState() throws Exception {
+ assertThat(((ParameterManifestation) modifierContributor).isFinal(), is(expectedModifier != 0));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/ProvisioningStateTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/ProvisioningStateTest.java
new file mode 100644
index 0000000..f753a56
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/ProvisioningStateTest.java
@@ -0,0 +1,33 @@
+package net.bytebuddy.description.modifier;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class ProvisioningStateTest extends AbstractModifierContributorTest {
+
+ public ProvisioningStateTest(ModifierContributor modifierContributor, int expectedModifier, boolean defaultModifier) {
+ super(modifierContributor, expectedModifier, defaultModifier);
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {ProvisioningState.PLAIN, 0, true},
+ {ProvisioningState.MANDATED, Opcodes.ACC_MANDATED, false}
+ });
+ }
+
+ @Test
+ public void testState() throws Exception {
+ assertThat(((ProvisioningState) modifierContributor).isMandated(), is(expectedModifier != 0));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/SynchronizationStateTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/SynchronizationStateTest.java
new file mode 100644
index 0000000..da7a358
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/SynchronizationStateTest.java
@@ -0,0 +1,33 @@
+package net.bytebuddy.description.modifier;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class SynchronizationStateTest extends AbstractModifierContributorTest {
+
+ public SynchronizationStateTest(ModifierContributor modifierContributor, int expectedModifier, boolean defaultModifier) {
+ super(modifierContributor, expectedModifier, defaultModifier);
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {SynchronizationState.PLAIN, 0, true},
+ {SynchronizationState.SYNCHRONIZED, Opcodes.ACC_SYNCHRONIZED, false}
+ });
+ }
+
+ @Test
+ public void testState() throws Exception {
+ assertThat(((SynchronizationState) modifierContributor).isSynchronized(), is(expectedModifier != 0));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/SyntheticStateTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/SyntheticStateTest.java
new file mode 100644
index 0000000..e796fc1
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/SyntheticStateTest.java
@@ -0,0 +1,33 @@
+package net.bytebuddy.description.modifier;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class SyntheticStateTest extends AbstractModifierContributorTest {
+
+ public SyntheticStateTest(ModifierContributor modifierContributor, int expectedModifier, boolean defaultModifier) {
+ super(modifierContributor, expectedModifier, defaultModifier);
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {SyntheticState.PLAIN, 0, true},
+ {SyntheticState.SYNTHETIC, Opcodes.ACC_SYNTHETIC, false}
+ });
+ }
+
+ @Test
+ public void testState() throws Exception {
+ assertThat(((SyntheticState) modifierContributor).isSynthetic(), is(expectedModifier != 0));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/TypeManifestationTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/TypeManifestationTest.java
new file mode 100644
index 0000000..9e1930e
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/TypeManifestationTest.java
@@ -0,0 +1,40 @@
+package net.bytebuddy.description.modifier;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class TypeManifestationTest extends AbstractModifierContributorTest {
+
+ public TypeManifestationTest(ModifierContributor modifierContributor, int expectedModifier, boolean defaultModifier) {
+ super(modifierContributor, expectedModifier, defaultModifier);
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {TypeManifestation.PLAIN, 0, true},
+ {TypeManifestation.ABSTRACT, Opcodes.ACC_ABSTRACT, false},
+ {TypeManifestation.FINAL, Opcodes.ACC_FINAL, false},
+ {TypeManifestation.INTERFACE, Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT, false},
+ {TypeManifestation.ANNOTATION, Opcodes.ACC_ANNOTATION | Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT, false}
+ });
+ }
+
+ @Test
+ public void testProperties() throws Exception {
+ assertThat(((TypeManifestation) modifierContributor).isAbstract(),
+ is((expectedModifier & Opcodes.ACC_ABSTRACT) != 0 && (expectedModifier & Opcodes.ACC_INTERFACE) == 0));
+ assertThat(((TypeManifestation) modifierContributor).isFinal(), is((expectedModifier & Opcodes.ACC_FINAL) != 0));
+ assertThat(((TypeManifestation) modifierContributor).isInterface(), is((expectedModifier & Opcodes.ACC_INTERFACE) != 0));
+ assertThat(((TypeManifestation) modifierContributor).isAnnotation(), is((expectedModifier & Opcodes.ACC_ANNOTATION) != 0));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/VisibilityExpansionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/VisibilityExpansionTest.java
new file mode 100644
index 0000000..e067906
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/VisibilityExpansionTest.java
@@ -0,0 +1,41 @@
+package net.bytebuddy.description.modifier;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class VisibilityExpansionTest {
+
+ @Test
+ public void testPublic() throws Exception {
+ assertThat(Visibility.PUBLIC.expandTo(Visibility.PRIVATE), is(Visibility.PUBLIC));
+ assertThat(Visibility.PUBLIC.expandTo(Visibility.PACKAGE_PRIVATE), is(Visibility.PUBLIC));
+ assertThat(Visibility.PUBLIC.expandTo(Visibility.PROTECTED), is(Visibility.PUBLIC));
+ assertThat(Visibility.PUBLIC.expandTo(Visibility.PUBLIC), is(Visibility.PUBLIC));
+ }
+
+ @Test
+ public void testProtected() throws Exception {
+ assertThat(Visibility.PROTECTED.expandTo(Visibility.PRIVATE), is(Visibility.PROTECTED));
+ assertThat(Visibility.PROTECTED.expandTo(Visibility.PACKAGE_PRIVATE), is(Visibility.PROTECTED));
+ assertThat(Visibility.PROTECTED.expandTo(Visibility.PROTECTED), is(Visibility.PROTECTED));
+ assertThat(Visibility.PROTECTED.expandTo(Visibility.PUBLIC), is(Visibility.PUBLIC));
+ }
+
+ @Test
+ public void testPackagePrivate() throws Exception {
+ assertThat(Visibility.PACKAGE_PRIVATE.expandTo(Visibility.PRIVATE), is(Visibility.PACKAGE_PRIVATE));
+ assertThat(Visibility.PACKAGE_PRIVATE.expandTo(Visibility.PACKAGE_PRIVATE), is(Visibility.PACKAGE_PRIVATE));
+ assertThat(Visibility.PACKAGE_PRIVATE.expandTo(Visibility.PROTECTED), is(Visibility.PROTECTED));
+ assertThat(Visibility.PACKAGE_PRIVATE.expandTo(Visibility.PUBLIC), is(Visibility.PUBLIC));
+ }
+
+ @Test
+ public void testPrivate() throws Exception {
+ assertThat(Visibility.PRIVATE.expandTo(Visibility.PRIVATE), is(Visibility.PRIVATE));
+ assertThat(Visibility.PRIVATE.expandTo(Visibility.PACKAGE_PRIVATE), is(Visibility.PACKAGE_PRIVATE));
+ assertThat(Visibility.PRIVATE.expandTo(Visibility.PROTECTED), is(Visibility.PROTECTED));
+ assertThat(Visibility.PRIVATE.expandTo(Visibility.PUBLIC), is(Visibility.PUBLIC));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/VisibilityTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/VisibilityTest.java
new file mode 100644
index 0000000..bc9f3d5
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/modifier/VisibilityTest.java
@@ -0,0 +1,50 @@
+package net.bytebuddy.description.modifier;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class VisibilityTest extends AbstractModifierContributorTest {
+
+ private final boolean isPublic, isProtected, isPackagePrivate, isPrivate;
+
+ public VisibilityTest(ModifierContributor modifierContributor,
+ int expectedModifier,
+ boolean defaultModifier,
+ boolean isPublic,
+ boolean isProtected,
+ boolean isPackagePrivate,
+ boolean isPrivate) {
+ super(modifierContributor, expectedModifier, defaultModifier);
+ this.isPublic = isPublic;
+ this.isProtected = isProtected;
+ this.isPackagePrivate = isPackagePrivate;
+ this.isPrivate = isPrivate;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {Visibility.PUBLIC, Opcodes.ACC_PUBLIC, false, true, false, false, false},
+ {Visibility.PROTECTED, Opcodes.ACC_PROTECTED, false, false, true, false, false},
+ {Visibility.PACKAGE_PRIVATE, 0, true, false, false, true, false},
+ {Visibility.PRIVATE, Opcodes.ACC_PRIVATE, false, false, false, false, true}
+ });
+ }
+
+ @Test
+ public void testProperties() throws Exception {
+ assertThat(((Visibility) modifierContributor).isPublic(), is(isPublic));
+ assertThat(((Visibility) modifierContributor).isPackagePrivate(), is(isPackagePrivate));
+ assertThat(((Visibility) modifierContributor).isProtected(), is(isProtected));
+ assertThat(((Visibility) modifierContributor).isPrivate(), is(isPrivate));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/AbstractPackageDescriptionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/AbstractPackageDescriptionTest.java
new file mode 100644
index 0000000..1065d1c
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/AbstractPackageDescriptionTest.java
@@ -0,0 +1,63 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.test.visibility.Sample;
+import net.bytebuddy.test.visibility.child.Child;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public abstract class AbstractPackageDescriptionTest {
+
+ protected abstract PackageDescription describe(Class<?> type);
+
+ @Test
+ public void testTrivialPackage() throws Exception {
+ assertThat(describe(Child.class).getName(), is(Child.class.getPackage().getName()));
+ assertThat(describe(Child.class).getDeclaredAnnotations(), is((AnnotationList) new AnnotationList.Empty()));
+ }
+
+ @Test
+ public void testNonTrivialPackage() throws Exception {
+ assertThat(describe(Sample.class).getName(), is(Sample.class.getPackage().getName()));
+ assertThat(describe(Sample.class).getDeclaredAnnotations(),
+ is((AnnotationList) new AnnotationList.ForLoadedAnnotations(Sample.class.getPackage().getDeclaredAnnotations())));
+ }
+
+ @Test
+ public void testPackageContains() throws Exception {
+ assertThat(describe(Child.class).contains(new TypeDescription.ForLoadedType(Child.class)), is(true));
+ assertThat(describe(Object.class).contains(new TypeDescription.ForLoadedType(Child.class)), is(false));
+ }
+
+ @Test
+ public void testHashCode() throws Exception {
+ assertThat(describe(Child.class).hashCode(), is(Child.class.getPackage().hashCode()));
+ assertThat(describe(Child.class).hashCode(), is(describe(Child.class).hashCode()));
+ assertThat(describe(Child.class).hashCode(), not(describe(Sample.class).hashCode()));
+ assertThat(describe(Sample.class).hashCode(), is(Sample.class.getPackage().hashCode()));
+ assertThat(describe(Sample.class).hashCode(), is(describe(Sample.class).hashCode()));
+ assertThat(describe(Sample.class).hashCode(), not(describe(Child.class).hashCode()));
+ }
+
+ @Test
+ public void testEquals() throws Exception {
+ assertThat(describe(Child.class).toString(), not(equalTo(null)));
+ assertThat(describe(Child.class).toString(), not(new Object()));
+ assertThat(describe(Child.class).toString(), is(describe(Child.class).toString()));
+ assertThat(describe(Child.class).toString(), not(describe(Sample.class).toString()));
+ assertThat(describe(Sample.class).toString(), is(describe(Sample.class).toString()));
+ assertThat(describe(Sample.class).toString(), not(describe(Child.class).toString()));
+ }
+
+ @Test
+ public void testToString() throws Exception {
+ assertThat(describe(Child.class).toString(), is(Child.class.getPackage().toString()));
+ assertThat(describe(Child.class).toString(), is(describe(Child.class).toString()));
+ assertThat(describe(Child.class).toString(), not(describe(Sample.class).toString()));
+ assertThat(describe(Sample.class).toString(), is(Sample.class.getPackage().toString()));
+ assertThat(describe(Sample.class).toString(), is(describe(Sample.class).toString()));
+ assertThat(describe(Sample.class).toString(), not(describe(Child.class).toString()));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/AbstractTypeDescriptionGenericTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/AbstractTypeDescriptionGenericTest.java
new file mode 100644
index 0000000..ed4b29b
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/AbstractTypeDescriptionGenericTest.java
@@ -0,0 +1,2006 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.description.TypeVariableSource;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.dynamic.loading.ByteArrayClassLoader;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.MatcherAssert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+import org.objectweb.asm.*;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import static net.bytebuddy.matcher.ElementMatchers.*;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+
+public abstract class AbstractTypeDescriptionGenericTest {
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux", BAZ = "baz";
+
+ private static final String T = "T", S = "S", U = "U", V = "V";
+
+ private static final String TYPE_ANNOTATION = "net.bytebuddy.test.precompiled.TypeAnnotation";
+
+ private static final String OTHER_TYPE_ANNOTATION = "net.bytebuddy.test.precompiled.OtherTypeAnnotation";
+
+ private static final String TYPE_ANNOTATION_SAMPLES = "net.bytebuddy.test.precompiled.TypeAnnotationSamples";
+
+ private static final String TYPE_ANNOTATION_OTHER_SAMPLES = "net.bytebuddy.test.precompiled.TypeAnnotationOtherSamples";
+
+ protected abstract TypeDescription.Generic describeType(Field field);
+
+ protected abstract TypeDescription.Generic describeReturnType(Method method);
+
+ protected abstract TypeDescription.Generic describeParameterType(Method method, int index);
+
+ protected abstract TypeDescription.Generic describeExceptionType(Method method, int index);
+
+ protected abstract TypeDescription.Generic describeSuperClass(Class<?> type);
+
+ protected abstract TypeDescription.Generic describeInterfaceType(Class<?> type, int index);
+
+ @Test
+ public void testNonGenericTypeOwnerType() throws Exception {
+ assertThat(describeType(NonGeneric.class.getDeclaredField(FOO)).getOwnerType(), nullValue(TypeDescription.Generic.class));
+ assertThat(describeType(NonGeneric.class.getDeclaredField(BAR)).getOwnerType(), is(TypeDefinition.Sort.describe(NonGeneric.class)));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonGenericTypeNoTypeArguments() throws Exception {
+ describeType(NonGeneric.class.getDeclaredField(FOO)).getTypeArguments();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonGenericTypeNoBindLocation() throws Exception {
+ describeType(NonGeneric.class.getDeclaredField(FOO)).findBindingOf(mock(TypeDescription.Generic.class));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonGenericTypeNoUpperBounds() throws Exception {
+ describeType(NonGeneric.class.getDeclaredField(FOO)).getUpperBounds();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonGenericTypeNoLowerBounds() throws Exception {
+ describeType(NonGeneric.class.getDeclaredField(FOO)).getLowerBounds();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonGenericTypeNoSymbol() throws Exception {
+ describeType(NonGeneric.class.getDeclaredField(FOO)).getSymbol();
+ }
+
+ @Test
+ public void testSimpleParameterizedType() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(SimpleParameterizedType.class.getDeclaredField(FOO));
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getActualName(), is(SimpleParameterizedType.class.getDeclaredField(FOO).getGenericType().toString()));
+ assertThat(typeDescription.getTypeName(), is(SimpleParameterizedType.class.getDeclaredField(FOO).getGenericType().toString()));
+ assertThat(typeDescription.toString(), is(SimpleParameterizedType.class.getDeclaredField(FOO).getGenericType().toString()));
+ assertThat(typeDescription.hashCode(),
+ is(TypeDefinition.Sort.describe(SimpleParameterizedType.class.getDeclaredField(FOO).getGenericType()).hashCode()));
+ assertThat(typeDescription, is(TypeDefinition.Sort.describe(SimpleParameterizedType.class.getDeclaredField(FOO).getGenericType())));
+ assertThat(typeDescription, CoreMatchers.not(TypeDefinition.Sort.describe(SimpleGenericArrayType.class.getDeclaredField(FOO).getType())));
+ assertThat(typeDescription, CoreMatchers.not(new Object()));
+ assertThat(typeDescription.equals(null), is(false));
+ assertThat(typeDescription.getTypeArguments().size(), is(1));
+ assertThat(typeDescription.getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getTypeArguments().getOnly().asErasure().represents(String.class), is(true));
+ assertThat(typeDescription.getTypeName(), is(SimpleParameterizedType.class.getDeclaredField(FOO).getGenericType().toString()));
+ assertThat(typeDescription.getOwnerType(), nullValue(TypeDescription.Generic.class));
+ }
+
+ @Test
+ public void testParameterizedTypeIterator() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(SimpleParameterizedType.class.getDeclaredField(FOO));
+ Iterator<TypeDefinition> iterator = typeDescription.iterator();
+ assertThat(iterator.hasNext(), is(true));
+ assertThat(iterator.next(), is((TypeDefinition) typeDescription));
+ assertThat(iterator.hasNext(), is(false));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testParameterizedTypeNoComponentType() throws Exception {
+ describeType(SimpleParameterizedType.class.getDeclaredField(FOO)).getComponentType();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testParameterizedTypeNoVariableSource() throws Exception {
+ describeType(SimpleParameterizedType.class.getDeclaredField(FOO)).getTypeVariableSource();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testParameterizedTypeNoSymbol() throws Exception {
+ describeType(SimpleParameterizedType.class.getDeclaredField(FOO)).getSymbol();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testParameterizedTypeNoUpperBounds() throws Exception {
+ describeType(SimpleParameterizedType.class.getDeclaredField(FOO)).getUpperBounds();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testParameterizedTypeNoLowerBounds() throws Exception {
+ describeType(SimpleParameterizedType.class.getDeclaredField(FOO)).getLowerBounds();
+ }
+
+ @Test
+ public void testUpperBoundWildcardParameterizedType() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(UpperBoundWildcardParameterizedType.class.getDeclaredField(FOO));
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getActualName(), is(UpperBoundWildcardParameterizedType.class.getDeclaredField(FOO).getGenericType().toString()));
+ assertThat(typeDescription.getTypeName(), is(UpperBoundWildcardParameterizedType.class.getDeclaredField(FOO).getGenericType().toString()));
+ assertThat(typeDescription.toString(), is(UpperBoundWildcardParameterizedType.class.getDeclaredField(FOO).getGenericType().toString()));
+ assertThat(typeDescription.hashCode(),
+ is(TypeDefinition.Sort.describe(UpperBoundWildcardParameterizedType.class.getDeclaredField(FOO).getGenericType()).hashCode()));
+ assertThat(typeDescription,
+ is(TypeDefinition.Sort.describe(UpperBoundWildcardParameterizedType.class.getDeclaredField(FOO).getGenericType())));
+ assertThat(typeDescription,
+ CoreMatchers.not(TypeDefinition.Sort.describe(UpperBoundWildcardParameterizedType.class.getDeclaredField(FOO).getType())));
+ assertThat(typeDescription, CoreMatchers.not(new Object()));
+ assertThat(typeDescription.equals(null), is(false));
+ assertThat(typeDescription.getTypeArguments().size(), is(1));
+ assertThat(typeDescription.getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.WILDCARD));
+ assertThat(typeDescription.getTypeArguments().getOnly().getUpperBounds().size(), is(1));
+ assertThat(typeDescription.getTypeArguments().getOnly().getUpperBounds().getOnly().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getTypeArguments().getOnly().getUpperBounds().getOnly().asErasure().represents(String.class), is(true));
+ assertThat(typeDescription.getTypeArguments().getOnly().getLowerBounds().size(), is(0));
+ assertThat(typeDescription.getTypeName(), is(UpperBoundWildcardParameterizedType.class.getDeclaredField(FOO).getGenericType().toString()));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUpperBoundWildcardParameterizedTypeNoComponentType() throws Exception {
+ describeType(UpperBoundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getComponentType();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUpperBoundWildcardParameterizedTypeNoOwnerType() throws Exception {
+ describeType(UpperBoundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getOwnerType();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUpperBoundWildcardParameterizedTypeNoVariableSource() throws Exception {
+ describeType(UpperBoundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getTypeVariableSource();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUpperBoundWildcardParameterizedTypeNoSymbol() throws Exception {
+ describeType(UpperBoundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getSymbol();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUpperBoundWildcardParameterizedTypeNoErasure() throws Exception {
+ describeType(UpperBoundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().asErasure();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUpperBoundWildcardParameterizedTypeNoStackSize() throws Exception {
+ describeType(UpperBoundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getStackSize();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUpperBoundWildcardParameterizedTypeNoSuperClass() throws Exception {
+ describeType(UpperBoundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getSuperClass();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUpperBoundWildcardParameterizedTypeNoInterfaces() throws Exception {
+ describeType(UpperBoundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getInterfaces();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUpperBoundWildcardParameterizedTypeNoFields() throws Exception {
+ describeType(UpperBoundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getDeclaredFields();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUpperBoundWildcardParameterizedTypeNoMethods() throws Exception {
+ describeType(UpperBoundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getDeclaredMethods();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUpperBoundWildcardParameterizedTypeNoIterator() throws Exception {
+ describeType(UpperBoundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().iterator();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUpperBoundWildcardTypeNoTypeArguments() throws Exception {
+ describeType(UpperBoundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getTypeArguments();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUpperBoundWildcardTypeNoBindLocation() throws Exception {
+ describeType(UpperBoundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().findBindingOf(mock(TypeDescription.Generic.class));
+ }
+
+ @Test
+ public void testLowerBoundWildcardParameterizedType() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(LowerBoundWildcardParameterizedType.class.getDeclaredField(FOO));
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getActualName(), is(LowerBoundWildcardParameterizedType.class.getDeclaredField(FOO).getGenericType().toString()));
+ assertThat(typeDescription.getTypeName(), is(LowerBoundWildcardParameterizedType.class.getDeclaredField(FOO).getGenericType().toString()));
+ assertThat(typeDescription.toString(), is(LowerBoundWildcardParameterizedType.class.getDeclaredField(FOO).getGenericType().toString()));
+ assertThat(typeDescription.hashCode(),
+ is(TypeDefinition.Sort.describe(LowerBoundWildcardParameterizedType.class.getDeclaredField(FOO).getGenericType()).hashCode()));
+ assertThat(typeDescription,
+ is(TypeDefinition.Sort.describe(LowerBoundWildcardParameterizedType.class.getDeclaredField(FOO).getGenericType())));
+ assertThat(typeDescription,
+ CoreMatchers.not(TypeDefinition.Sort.describe(LowerBoundWildcardParameterizedType.class.getDeclaredField(FOO).getType())));
+ assertThat(typeDescription, CoreMatchers.not(new Object()));
+ assertThat(typeDescription.equals(null), is(false));
+ assertThat(typeDescription.getTypeArguments().size(), is(1));
+ assertThat(typeDescription.getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.WILDCARD));
+ assertThat(typeDescription.getTypeArguments().getOnly().getUpperBounds().size(), is(1));
+ assertThat(typeDescription.getTypeArguments().getOnly().getUpperBounds().getOnly().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getTypeArguments().getOnly().getLowerBounds().size(), is(1));
+ assertThat(typeDescription.getTypeArguments().getOnly().getLowerBounds().getOnly().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getTypeArguments().getOnly().getLowerBounds().getOnly().asErasure().represents(String.class), is(true));
+ assertThat(typeDescription.getTypeName(), is(LowerBoundWildcardParameterizedType.class.getDeclaredField(FOO).getGenericType().toString()));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testLowerBoundWildcardParameterizedTypeNoComponentType() throws Exception {
+ describeType(LowerBoundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getComponentType();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testLowerBoundWildcardParameterizedTypeNoOwnerType() throws Exception {
+ describeType(LowerBoundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getOwnerType();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testLowerBoundWildcardParameterizedTypeNoVariableSource() throws Exception {
+ describeType(LowerBoundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getTypeVariableSource();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testLowerBoundWildcardParameterizedTypeNoSymbol() throws Exception {
+ describeType(LowerBoundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getSymbol();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testLowerBoundWildcardParameterizedTypeNoErasure() throws Exception {
+ describeType(LowerBoundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().asErasure();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testLowerBoundWildcardParameterizedTypeNoStackSize() throws Exception {
+ describeType(LowerBoundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getStackSize();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testLowerBoundWildcardParameterizedTypeNoSuperClass() throws Exception {
+ describeType(LowerBoundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getSuperClass();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testLowerBoundWildcardParameterizedTypeNoInterfaces() throws Exception {
+ describeType(LowerBoundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getInterfaces();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testLowerBoundWildcardParameterizedTypeNoFields() throws Exception {
+ describeType(LowerBoundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getDeclaredFields();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testLowerBoundWildcardParameterizedTypeNoMethods() throws Exception {
+ describeType(LowerBoundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getDeclaredMethods();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testLowerBoundWildcardParameterizedTypeNoIterator() throws Exception {
+ describeType(LowerBoundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().iterator();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testLowerBoundWildcardTypeNoTypeArguments() throws Exception {
+ describeType(LowerBoundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getTypeArguments();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testLowerBoundWildcardTypeNoBindLocation() throws Exception {
+ describeType(LowerBoundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().findBindingOf(mock(TypeDescription.Generic.class));
+ }
+
+ @Test
+ public void testUnboundWildcardParameterizedType() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(UnboundWildcardParameterizedType.class.getDeclaredField(FOO));
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getActualName(), is(UnboundWildcardParameterizedType.class.getDeclaredField(FOO).getGenericType().toString()));
+ assertThat(typeDescription.getTypeName(), is(UnboundWildcardParameterizedType.class.getDeclaredField(FOO).getGenericType().toString()));
+ assertThat(typeDescription.toString(), is(UnboundWildcardParameterizedType.class.getDeclaredField(FOO).getGenericType().toString()));
+ assertThat(typeDescription.hashCode(),
+ is(TypeDefinition.Sort.describe(UnboundWildcardParameterizedType.class.getDeclaredField(FOO).getGenericType()).hashCode()));
+ assertThat(typeDescription,
+ is(TypeDefinition.Sort.describe(UnboundWildcardParameterizedType.class.getDeclaredField(FOO).getGenericType())));
+ assertThat(typeDescription,
+ CoreMatchers.not(TypeDefinition.Sort.describe(UnboundWildcardParameterizedType.class.getDeclaredField(FOO).getType())));
+ assertThat(typeDescription, CoreMatchers.not(new Object()));
+ assertThat(typeDescription.equals(null), is(false));
+ assertThat(typeDescription.getTypeArguments().size(), is(1));
+ assertThat(typeDescription.getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.WILDCARD));
+ assertThat(typeDescription.getTypeArguments().getOnly().getUpperBounds().size(), is(1));
+ assertThat(typeDescription.getTypeArguments().getOnly().getUpperBounds().getOnly().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getTypeArguments().getOnly().getUpperBounds().getOnly().asErasure().represents(Object.class), is(true));
+ assertThat(typeDescription.getTypeArguments().getOnly().getLowerBounds().size(), is(0));
+ assertThat(typeDescription.getTypeName(), is(UnboundWildcardParameterizedType.class.getDeclaredField(FOO).getGenericType().toString()));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUnboundWildcardParameterizedTypeNoComponentType() throws Exception {
+ describeType(UnboundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getComponentType();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUnboundWildcardParameterizedTypeNoOwnerType() throws Exception {
+ describeType(UnboundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getOwnerType();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUnboundWildcardParameterizedTypeNoVariableSource() throws Exception {
+ describeType(UnboundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getTypeVariableSource();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUnboundWildcardParameterizedTypeNoSymbol() throws Exception {
+ describeType(UnboundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getSymbol();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUnboundWildcardParameterizedTypeNoErasure() throws Exception {
+ describeType(UnboundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().asErasure();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUnboundWildcardParameterizedTypeNoStackSize() throws Exception {
+ describeType(UnboundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getStackSize();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUnboundWildcardParameterizedTypeNoSuperClass() throws Exception {
+ describeType(UnboundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getSuperClass();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUnboundWildcardParameterizedTypeNoInterfaces() throws Exception {
+ describeType(UnboundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getInterfaces();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUnboundBoundWildcardParameterizedTypeNoFields() throws Exception {
+ describeType(UnboundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getDeclaredFields();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUnboundBoundWildcardParameterizedTypeNoMethods() throws Exception {
+ describeType(UnboundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getDeclaredMethods();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUnboundBoundWildcardParameterizedTypeNoIterator() throws Exception {
+ describeType(UnboundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().iterator();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUnboundWildcardTypeNoTypeArguments() throws Exception {
+ describeType(UnboundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getTypeArguments();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUnboundWildcardTypeNoBindLocation() throws Exception {
+ describeType(UnboundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().findBindingOf(mock(TypeDescription.Generic.class));
+ }
+
+ @Test
+ public void testExplicitlyUnboundWildcardParameterizedType() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(ExplicitlyUnboundWildcardParameterizedType.class.getDeclaredField(FOO));
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getActualName(),
+ is(ExplicitlyUnboundWildcardParameterizedType.class.getDeclaredField(FOO).getGenericType().toString()));
+ assertThat(typeDescription.getTypeName(),
+ is(ExplicitlyUnboundWildcardParameterizedType.class.getDeclaredField(FOO).getGenericType().toString()));
+ assertThat(typeDescription.toString(),
+ is(ExplicitlyUnboundWildcardParameterizedType.class.getDeclaredField(FOO).getGenericType().toString()));
+ assertThat(typeDescription.hashCode(),
+ is(TypeDefinition.Sort.describe(ExplicitlyUnboundWildcardParameterizedType.class.getDeclaredField(FOO).getGenericType()).hashCode()));
+ assertThat(typeDescription,
+ is(TypeDefinition.Sort.describe(ExplicitlyUnboundWildcardParameterizedType.class.getDeclaredField(FOO).getGenericType())));
+ assertThat(typeDescription,
+ CoreMatchers.not(TypeDefinition.Sort.describe(ExplicitlyUnboundWildcardParameterizedType.class.getDeclaredField(FOO).getType())));
+ assertThat(typeDescription, CoreMatchers.not(new Object()));
+ assertThat(typeDescription.equals(null), is(false));
+ assertThat(typeDescription.getTypeArguments().size(), is(1));
+ assertThat(typeDescription.getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.WILDCARD));
+ assertThat(typeDescription.getTypeArguments().getOnly().getUpperBounds().size(), is(1));
+ assertThat(typeDescription.getTypeArguments().getOnly().getUpperBounds().getOnly().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getTypeArguments().getOnly().getUpperBounds().getOnly().asErasure().represents(Object.class), is(true));
+ assertThat(typeDescription.getTypeArguments().getOnly().getLowerBounds().size(), is(0));
+ assertThat(typeDescription.getTypeName(), is(ExplicitlyUnboundWildcardParameterizedType.class.getDeclaredField(FOO).getGenericType().toString()));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testExplicitlyUnboundWildcardParameterizedTypeNoComponentType() throws Exception {
+ describeType(ExplicitlyUnboundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getComponentType();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testExplicitlyUnboundWildcardParameterizedTypeNoOwnerType() throws Exception {
+ describeType(ExplicitlyUnboundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getOwnerType();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testExplicitlyUnboundWildcardParameterizedTypeNoVariableSource() throws Exception {
+ describeType(ExplicitlyUnboundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getTypeVariableSource();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testExplicitlyUnboundWildcardParameterizedTypeNoSymbol() throws Exception {
+ describeType(ExplicitlyUnboundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getSymbol();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testExplicitlyUnboundWildcardParameterizedTypeNoErasure() throws Exception {
+ describeType(ExplicitlyUnboundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().asErasure();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testExplicitlyUnboundWildcardParameterizedTypeNoStackSize() throws Exception {
+ describeType(ExplicitlyUnboundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getStackSize();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testExplicitlyUnboundWildcardParameterizedTypeNoSuperClass() throws Exception {
+ describeType(ExplicitlyUnboundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getSuperClass();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testExplicitlyUnboundWildcardParameterizedTypeNoInterfaces() throws Exception {
+ describeType(ExplicitlyUnboundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getInterfaces();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testExplicitlyUnboundBoundWildcardParameterizedTypeNoFields() throws Exception {
+ describeType(ExplicitlyUnboundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getDeclaredFields();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testExplicitlyUnboundBoundWildcardParameterizedTypeNoMethods() throws Exception {
+ describeType(ExplicitlyUnboundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getDeclaredMethods();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testExplicitlyUnboundBoundWildcardParameterizedTypeNoIterator() throws Exception {
+ describeType(ExplicitlyUnboundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().iterator();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testExplicitlyUnboundWildcardTypeNoTypeArguments() throws Exception {
+ describeType(ExplicitlyUnboundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().getTypeArguments();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testExplicitlyUnboundWildcardTypeNoBindLocation() throws Exception {
+ describeType(ExplicitlyUnboundWildcardParameterizedType.class.getDeclaredField(FOO)).getTypeArguments().getOnly().findBindingOf(mock(TypeDescription.Generic.class));
+ }
+
+ @Test
+ public void testNestedParameterizedType() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(NestedParameterizedType.class.getDeclaredField(FOO));
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getTypeArguments().size(), is(1));
+ assertThat(typeDescription.getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getTypeArguments().getOnly().getTypeArguments().size(), is(1));
+ assertThat(typeDescription.getTypeArguments().getOnly().getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getTypeArguments().getOnly().getTypeArguments().getOnly().asErasure().represents(Foo.class), is(true));
+ }
+
+ @Test
+ public void testGenericArrayType() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(SimpleGenericArrayType.class.getDeclaredField(FOO));
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.GENERIC_ARRAY));
+ assertThat(typeDescription.getStackSize(), is(StackSize.SINGLE));
+ assertThat(typeDescription.getDeclaredFields().size(), is(0));
+ assertThat(typeDescription.getDeclaredMethods().size(), is(0));
+ assertThat(typeDescription.getSuperClass(), is(TypeDescription.Generic.OBJECT));
+ assertThat(typeDescription.getInterfaces(), is(TypeDescription.ARRAY_INTERFACES));
+ assertThat(typeDescription.getActualName(), is(SimpleGenericArrayType.class.getDeclaredField(FOO).getGenericType().toString()));
+ assertThat(typeDescription.getTypeName(), is(SimpleGenericArrayType.class.getDeclaredField(FOO).getGenericType().toString()));
+ assertThat(typeDescription.toString(), is(SimpleGenericArrayType.class.getDeclaredField(FOO).getGenericType().toString()));
+ assertThat(typeDescription.hashCode(),
+ is(TypeDefinition.Sort.describe(SimpleGenericArrayType.class.getDeclaredField(FOO).getGenericType()).hashCode()));
+ assertThat(typeDescription, is(TypeDefinition.Sort.describe(SimpleGenericArrayType.class.getDeclaredField(FOO).getGenericType())));
+ assertThat(typeDescription, CoreMatchers.not(TypeDefinition.Sort.describe(SimpleGenericArrayType.class.getDeclaredField(FOO).getType())));
+ assertThat(typeDescription, CoreMatchers.not(new Object()));
+ assertThat(typeDescription.equals(null), is(false));
+ assertThat(typeDescription.getComponentType().getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getComponentType().getTypeArguments().size(), is(1));
+ assertThat(typeDescription.getComponentType().getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getComponentType().getTypeArguments().getOnly().asErasure().represents(String.class), is(true));
+ assertThat(typeDescription.getTypeName(), is(SimpleGenericArrayType.class.getDeclaredField(FOO).getGenericType().toString()));
+ }
+
+ @Test
+ public void testGenericArrayTypeIterator() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(SimpleGenericArrayType.class.getDeclaredField(FOO));
+ Iterator<TypeDefinition> iterator = typeDescription.iterator();
+ assertThat(iterator.hasNext(), is(true));
+ assertThat(iterator.next(), is((TypeDefinition) typeDescription));
+ assertThat(iterator.hasNext(), is(true));
+ assertThat(iterator.next(), is((TypeDefinition) TypeDescription.OBJECT));
+ assertThat(iterator.hasNext(), is(false));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testGenericArrayTypeNoVariableSource() throws Exception {
+ describeType(SimpleGenericArrayType.class.getDeclaredField(FOO)).getTypeVariableSource();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testGenericArrayTypeNoSymbol() throws Exception {
+ describeType(SimpleGenericArrayType.class.getDeclaredField(FOO)).getSymbol();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testGenericArrayTypeNoUpperBounds() throws Exception {
+ describeType(SimpleGenericArrayType.class.getDeclaredField(FOO)).getUpperBounds();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testGenericArrayTypeNoLowerBounds() throws Exception {
+ describeType(SimpleGenericArrayType.class.getDeclaredField(FOO)).getLowerBounds();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testGenericArrayTypeNoTypeArguments() throws Exception {
+ describeType(SimpleGenericArrayType.class.getDeclaredField(FOO)).getTypeArguments();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testGenericArrayTypeNoBindLocation() throws Exception {
+ describeType(SimpleGenericArrayType.class.getDeclaredField(FOO)).findBindingOf(mock(TypeDescription.Generic.class));
+ }
+
+ @Test
+ public void testGenericArrayOfGenericComponentType() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(GenericArrayOfGenericComponentType.class.getDeclaredField(FOO));
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.GENERIC_ARRAY));
+ assertThat(typeDescription.getStackSize(), is(StackSize.SINGLE));
+ assertThat(typeDescription.getDeclaredFields().size(), is(0));
+ assertThat(typeDescription.getDeclaredMethods().size(), is(0));
+ assertThat(typeDescription.getOwnerType(), nullValue(TypeDescription.Generic.class));
+ assertThat(typeDescription.getSuperClass(), is(TypeDescription.Generic.OBJECT));
+ assertThat(typeDescription.getInterfaces(), is(TypeDescription.ARRAY_INTERFACES));
+ assertThat(typeDescription.getActualName(), is(GenericArrayOfGenericComponentType.class.getDeclaredField(FOO).getGenericType().toString()));
+ assertThat(typeDescription.getTypeName(), is(GenericArrayOfGenericComponentType.class.getDeclaredField(FOO).getGenericType().toString()));
+ assertThat(typeDescription.toString(), is(GenericArrayOfGenericComponentType.class.getDeclaredField(FOO).getGenericType().toString()));
+ assertThat(typeDescription.hashCode(),
+ is(TypeDefinition.Sort.describe(GenericArrayOfGenericComponentType.class.getDeclaredField(FOO).getGenericType()).hashCode()));
+ assertThat(typeDescription, is(TypeDefinition.Sort.describe(GenericArrayOfGenericComponentType.class.getDeclaredField(FOO).getGenericType())));
+ assertThat(typeDescription, CoreMatchers.not(TypeDefinition.Sort.describe(GenericArrayOfGenericComponentType.class.getDeclaredField(FOO).getType())));
+ assertThat(typeDescription, CoreMatchers.not(new Object()));
+ assertThat(typeDescription.equals(null), is(false));
+ assertThat(typeDescription.getComponentType().getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getComponentType().getTypeArguments().size(), is(1));
+ assertThat(typeDescription.getComponentType().getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(typeDescription.getComponentType().getTypeArguments().getOnly().asErasure().represents(String.class), is(true));
+ assertThat(typeDescription.getTypeName(), is(GenericArrayOfGenericComponentType.class.getDeclaredField(FOO).getGenericType().toString()));
+ }
+
+ @Test
+ public void testGenericArrayOfGenericComponentTypeIterator() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(GenericArrayOfGenericComponentType.class.getDeclaredField(FOO));
+ Iterator<TypeDefinition> iterator = typeDescription.iterator();
+ assertThat(iterator.hasNext(), is(true));
+ assertThat(iterator.next(), is((TypeDefinition) typeDescription));
+ assertThat(iterator.hasNext(), is(true));
+ assertThat(iterator.next(), is((TypeDefinition) TypeDescription.OBJECT));
+ assertThat(iterator.hasNext(), is(false));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testGenericArrayOfGenericComponentTypeNoVariableSource() throws Exception {
+ describeType(GenericArrayOfGenericComponentType.class.getDeclaredField(FOO)).getTypeVariableSource();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testGenericArrayOfGenericComponentTypeNoSymbol() throws Exception {
+ describeType(GenericArrayOfGenericComponentType.class.getDeclaredField(FOO)).getSymbol();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testGenericArrayOfGenericComponentTypeNoUpperBounds() throws Exception {
+ describeType(GenericArrayOfGenericComponentType.class.getDeclaredField(FOO)).getUpperBounds();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testGenericArrayOfGenericComponentTypeNoLowerBounds() throws Exception {
+ describeType(GenericArrayOfGenericComponentType.class.getDeclaredField(FOO)).getLowerBounds();
+ }
+
+ @Test
+ public void testTypeVariableType() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(SimpleTypeVariableType.class.getDeclaredField(FOO));
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(typeDescription.getActualName(), is(SimpleTypeVariableType.class.getDeclaredField(FOO).getGenericType().toString()));
+ assertThat(typeDescription.getTypeName(), is(SimpleTypeVariableType.class.getDeclaredField(FOO).getGenericType().toString()));
+ assertThat(typeDescription.toString(), is(SimpleTypeVariableType.class.getDeclaredField(FOO).getGenericType().toString()));
+ assertThat(typeDescription.hashCode(),
+ is(TypeDefinition.Sort.describe(SimpleTypeVariableType.class.getDeclaredField(FOO).getGenericType()).hashCode()));
+ assertThat(typeDescription, is(TypeDefinition.Sort.describe(SimpleTypeVariableType.class.getDeclaredField(FOO).getGenericType())));
+ assertThat(typeDescription,
+ CoreMatchers.not(TypeDefinition.Sort.describe(SimpleTypeVariableType.class.getDeclaredField(FOO).getType())));
+ assertThat(typeDescription, CoreMatchers.not(new Object()));
+ assertThat(typeDescription.equals(null), is(false));
+ assertThat(typeDescription.getSymbol(), is(T));
+ assertThat(typeDescription.getUpperBounds().size(), is(1));
+ assertThat(typeDescription.getUpperBounds().getOnly(), is(TypeDescription.Generic.OBJECT));
+ assertThat(typeDescription.getUpperBounds().getOnly().getStackSize(), is(StackSize.SINGLE));
+ assertThat(typeDescription.getTypeName(), is(SimpleTypeVariableType.class.getDeclaredField(FOO).getGenericType().toString()));
+ MatcherAssert.assertThat(typeDescription.getTypeVariableSource(), is((TypeVariableSource) new TypeDescription.ForLoadedType(SimpleTypeVariableType.class)));
+ assertThat(typeDescription.getTypeVariableSource().getTypeVariables().size(), is(1));
+ assertThat(typeDescription.getTypeVariableSource().getTypeVariables().getOnly(), is(typeDescription));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testTypeVariableNoLowerBounds() throws Exception {
+ describeType(SimpleTypeVariableType.class.getDeclaredField(FOO)).getLowerBounds();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testTypeVariableNoComponentType() throws Exception {
+ describeType(SimpleTypeVariableType.class.getDeclaredField(FOO)).getComponentType();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testTypeVariableNoOwnerType() throws Exception {
+ describeType(SimpleTypeVariableType.class.getDeclaredField(FOO)).getOwnerType();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testTypeVariableTypeNoSuperClass() throws Exception {
+ describeType(SimpleTypeVariableType.class.getDeclaredField(FOO)).getSuperClass();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testTypeVariableTypeNoInterfaceTypes() throws Exception {
+ describeType(SimpleTypeVariableType.class.getDeclaredField(FOO)).getInterfaces();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testTypeVariableTypeNoFields() throws Exception {
+ describeType(SimpleTypeVariableType.class.getDeclaredField(FOO)).getDeclaredFields();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testTypeVariableTypeNoMethods() throws Exception {
+ describeType(SimpleTypeVariableType.class.getDeclaredField(FOO)).getDeclaredMethods();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testTypeVariableTypeNoIterator() throws Exception {
+ describeType(SimpleTypeVariableType.class.getDeclaredField(FOO)).iterator();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testTypeVariableNoTypeArguments() throws Exception {
+ describeType(SimpleTypeVariableType.class.getDeclaredField(FOO)).getTypeArguments();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testTypeVariableNoBindLocation() throws Exception {
+ describeType(SimpleTypeVariableType.class.getDeclaredField(FOO)).findBindingOf(mock(TypeDescription.Generic.class));
+ }
+
+ @Test
+ public void testSingleUpperBoundTypeVariableType() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(SingleUpperBoundTypeVariableType.class.getDeclaredField(FOO));
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(typeDescription.getSymbol(), is(T));
+ assertThat(typeDescription.getUpperBounds().size(), is(1));
+ assertThat(typeDescription.getUpperBounds().getOnly(), is((TypeDefinition) new TypeDescription.ForLoadedType(String.class)));
+ assertThat(typeDescription.getUpperBounds().getOnly().getStackSize(), is(StackSize.SINGLE));
+ assertThat(typeDescription.getTypeName(), is(SingleUpperBoundTypeVariableType.class.getDeclaredField(FOO).getGenericType().toString()));
+ assertThat(typeDescription.getTypeVariableSource(), is((TypeVariableSource) new TypeDescription.ForLoadedType(SingleUpperBoundTypeVariableType.class)));
+ assertThat(typeDescription.getTypeVariableSource().getTypeVariables().size(), is(1));
+ assertThat(typeDescription.getTypeVariableSource().getTypeVariables().getOnly(), is(typeDescription));
+ }
+
+ @Test
+ public void testMultipleUpperBoundTypeVariableType() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(MultipleUpperBoundTypeVariableType.class.getDeclaredField(FOO));
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(typeDescription.getSymbol(), is(T));
+ assertThat(typeDescription.getStackSize(), is(StackSize.SINGLE));
+ assertThat(typeDescription.getUpperBounds().size(), is(3));
+ assertThat(typeDescription.getUpperBounds().get(0), is((TypeDefinition) new TypeDescription.ForLoadedType(String.class)));
+ assertThat(typeDescription.getUpperBounds().get(1), is((TypeDefinition) new TypeDescription.ForLoadedType(Foo.class)));
+ assertThat(typeDescription.getUpperBounds().get(2), is((TypeDefinition) new TypeDescription.ForLoadedType(Bar.class)));
+ assertThat(typeDescription.getTypeName(), is(MultipleUpperBoundTypeVariableType.class.getDeclaredField(FOO).getGenericType().toString()));
+ assertThat(typeDescription.getTypeVariableSource(), is((TypeVariableSource) new TypeDescription.ForLoadedType(MultipleUpperBoundTypeVariableType.class)));
+ assertThat(typeDescription.getTypeVariableSource().getTypeVariables().size(), is(1));
+ assertThat(typeDescription.getTypeVariableSource().getTypeVariables().getOnly(), is(typeDescription));
+ }
+
+ @Test
+ public void testInterfaceOnlyMultipleUpperBoundTypeVariableType() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(InterfaceOnlyMultipleUpperBoundTypeVariableType.class.getDeclaredField(FOO));
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(typeDescription.getSymbol(), is(T));
+ assertThat(typeDescription.getStackSize(), is(StackSize.SINGLE));
+ assertThat(typeDescription.getUpperBounds().size(), is(2));
+ assertThat(typeDescription.getUpperBounds().get(0), is((TypeDefinition) new TypeDescription.ForLoadedType(Foo.class)));
+ assertThat(typeDescription.getUpperBounds().get(1), is((TypeDefinition) new TypeDescription.ForLoadedType(Bar.class)));
+ assertThat(typeDescription.getTypeName(), is(InterfaceOnlyMultipleUpperBoundTypeVariableType.class.getDeclaredField(FOO).getGenericType().toString()));
+ assertThat(typeDescription.getTypeVariableSource(), is((TypeVariableSource) new TypeDescription.ForLoadedType(InterfaceOnlyMultipleUpperBoundTypeVariableType.class)));
+ assertThat(typeDescription.getTypeVariableSource().getTypeVariables().size(), is(1));
+ assertThat(typeDescription.getTypeVariableSource().getTypeVariables().getOnly(), is(typeDescription));
+ }
+
+ @Test
+ public void testShadowedTypeVariableType() throws Exception {
+ TypeDescription.Generic typeDescription = describeReturnType(ShadowingTypeVariableType.class.getDeclaredMethod(FOO));
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(typeDescription.getSymbol(), is(T));
+ assertThat(typeDescription.getStackSize(), is(StackSize.SINGLE));
+ assertThat(typeDescription.getUpperBounds().size(), is(1));
+ assertThat(typeDescription.getUpperBounds().getOnly(), is(TypeDescription.Generic.OBJECT));
+ assertThat(typeDescription.getTypeName(), is(ShadowingTypeVariableType.class.getDeclaredMethod(FOO).getGenericReturnType().toString()));
+ assertThat(typeDescription.getTypeVariableSource(), is((TypeVariableSource) new MethodDescription.ForLoadedMethod(ShadowingTypeVariableType.class.getDeclaredMethod(FOO))));
+ assertThat(typeDescription.getTypeVariableSource().getTypeVariables().size(), is(1));
+ assertThat(typeDescription.getTypeVariableSource().getTypeVariables().getOnly(), is(typeDescription));
+ }
+
+ @Test
+ public void testNestedTypeVariableType() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(NestedTypeVariableType.class.getDeclaredField(FOO));
+ assertThat(typeDescription.getTypeName(), is(NestedTypeVariableType.class.getDeclaredField(FOO).getGenericType().toString()));
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getStackSize(), is(StackSize.SINGLE));
+ assertThat(typeDescription.getTypeArguments().size(), is(0));
+ Type ownerType = ((ParameterizedType) NestedTypeVariableType.class.getDeclaredField(FOO).getGenericType()).getOwnerType();
+ assertThat(typeDescription.getOwnerType(), is(TypeDefinition.Sort.describe(ownerType)));
+ assertThat(typeDescription.getOwnerType().getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getOwnerType().getTypeArguments().size(), is(1));
+ assertThat(typeDescription.getOwnerType().getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(typeDescription.getOwnerType().getTypeArguments().getOnly().getSymbol(), is(T));
+ }
+
+ @Test
+ public void testNestedSpecifiedTypeVariableType() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(NestedSpecifiedTypeVariableType.class.getDeclaredField(FOO));
+ assertThat(typeDescription.getTypeName(), is(NestedSpecifiedTypeVariableType.class.getDeclaredField(FOO).getGenericType().toString()));
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getStackSize(), is(StackSize.SINGLE));
+ assertThat(typeDescription.getTypeArguments().size(), is(0));
+ Type ownerType = ((ParameterizedType) NestedSpecifiedTypeVariableType.class.getDeclaredField(FOO).getGenericType()).getOwnerType();
+ assertThat(typeDescription.getOwnerType(), is(TypeDefinition.Sort.describe(ownerType)));
+ assertThat(typeDescription.getOwnerType().getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getOwnerType().getTypeArguments().size(), is(1));
+ assertThat(typeDescription.getOwnerType().getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getOwnerType().getTypeArguments().getOnly(), is((TypeDefinition) new TypeDescription.ForLoadedType(String.class)));
+ }
+
+ @Test
+ public void testNestedStaticTypeVariableType() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(NestedStaticTypeVariableType.class.getDeclaredField(FOO));
+ assertThat(typeDescription.getTypeName(), is(NestedStaticTypeVariableType.class.getDeclaredField(FOO).getGenericType().toString()));
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getStackSize(), is(StackSize.SINGLE));
+ assertThat(typeDescription.getTypeArguments().size(), is(1));
+ assertThat(typeDescription.getTypeArguments().getOnly(), is((TypeDefinition) new TypeDescription.ForLoadedType(String.class)));
+ Type ownerType = ((ParameterizedType) NestedStaticTypeVariableType.class.getDeclaredField(FOO).getGenericType()).getOwnerType();
+ assertThat(typeDescription.getOwnerType(), is(TypeDefinition.Sort.describe(ownerType)));
+ assertThat(typeDescription.getOwnerType().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ }
+
+ @Test
+ public void testNestedInnerType() throws Exception {
+ TypeDescription.Generic foo = describeReturnType(NestedInnerType.InnerType.class.getDeclaredMethod(FOO));
+ assertThat(foo.getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(foo.getSymbol(), is(T));
+ assertThat(foo.getUpperBounds().size(), is(1));
+ assertThat(foo.getUpperBounds().getOnly(), is(TypeDescription.Generic.OBJECT));
+ assertThat(foo.getTypeVariableSource(), is((TypeVariableSource) new TypeDescription.ForLoadedType(NestedInnerType.class)));
+ TypeDescription.Generic bar = describeReturnType(NestedInnerType.InnerType.class.getDeclaredMethod(BAR));
+ assertThat(bar.getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(bar.getSymbol(), is(S));
+ assertThat(bar.getUpperBounds().size(), is(1));
+ assertThat(bar.getUpperBounds().getOnly(), is(foo));
+ assertThat(bar.getTypeVariableSource(), is((TypeVariableSource) new TypeDescription.ForLoadedType(NestedInnerType.InnerType.class)));
+ TypeDescription.Generic qux = describeReturnType(NestedInnerType.InnerType.class.getDeclaredMethod(QUX));
+ assertThat(qux.getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(qux.getSymbol(), is(U));
+ assertThat(qux.getUpperBounds().size(), is(1));
+ assertThat(qux.getUpperBounds().getOnly(), is(bar));
+ MethodDescription quxMethod = new MethodDescription.ForLoadedMethod(NestedInnerType.InnerType.class.getDeclaredMethod(QUX));
+ assertThat(qux.getTypeVariableSource(), is((TypeVariableSource) quxMethod));
+ }
+
+ @Test
+ public void testNestedInnerMethod() throws Exception {
+ Class<?> innerType = new NestedInnerMethod().foo();
+ TypeDescription.Generic foo = describeReturnType(innerType.getDeclaredMethod(FOO));
+ assertThat(foo.getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(foo.getSymbol(), is(T));
+ assertThat(foo.getUpperBounds().size(), is(1));
+ assertThat(foo.getUpperBounds().getOnly(), is(TypeDescription.Generic.OBJECT));
+ assertThat(foo.getTypeVariableSource(), is((TypeVariableSource) new TypeDescription.ForLoadedType(NestedInnerMethod.class)));
+ TypeDescription.Generic bar = describeReturnType(innerType.getDeclaredMethod(BAR));
+ assertThat(bar.getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(bar.getSymbol(), is(S));
+ assertThat(bar.getUpperBounds().size(), is(1));
+ assertThat(bar.getUpperBounds().getOnly(), is(foo));
+ assertThat(bar.getTypeVariableSource(), is((TypeVariableSource) new MethodDescription.ForLoadedMethod(NestedInnerMethod.class.getDeclaredMethod(FOO))));
+ TypeDescription.Generic qux = describeReturnType(innerType.getDeclaredMethod(QUX));
+ assertThat(qux.getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(qux.getSymbol(), is(U));
+ assertThat(qux.getUpperBounds().size(), is(1));
+ assertThat(qux.getUpperBounds().getOnly(), is(bar));
+ assertThat(qux.getTypeVariableSource(), is((TypeVariableSource) new TypeDescription.ForLoadedType(innerType)));
+ TypeDescription.Generic baz = describeReturnType(innerType.getDeclaredMethod(BAZ));
+ assertThat(baz.getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(baz.getSymbol(), is(V));
+ assertThat(baz.getUpperBounds().size(), is(1));
+ assertThat(baz.getUpperBounds().getOnly(), is(qux));
+ assertThat(baz.getTypeVariableSource(), is((TypeVariableSource) new MethodDescription.ForLoadedMethod(innerType.getDeclaredMethod(BAZ))));
+
+ }
+
+ @Test
+ public void testRecursiveTypeVariable() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(RecursiveTypeVariable.class.getDeclaredField(FOO));
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(typeDescription.getSymbol(), is(T));
+ assertThat(typeDescription.getUpperBounds().size(), is(1));
+ TypeDescription.Generic upperBound = typeDescription.getUpperBounds().getOnly();
+ assertThat(upperBound.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(upperBound.asErasure(), is(typeDescription.asErasure()));
+ assertThat(upperBound.getTypeArguments().size(), is(1));
+ assertThat(upperBound.getTypeArguments().getOnly(), is(typeDescription));
+ }
+
+ @Test
+ public void testBackwardsReferenceTypeVariable() throws Exception {
+ TypeDescription.Generic foo = describeType(BackwardsReferenceTypeVariable.class.getDeclaredField(FOO));
+ assertThat(foo.getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(foo.getSymbol(), is(S));
+ assertThat(foo.getUpperBounds().size(), is(1));
+ TypeDescription backwardsReference = new TypeDescription.ForLoadedType(BackwardsReferenceTypeVariable.class);
+ assertThat(foo.getUpperBounds().getOnly(), is(backwardsReference.getTypeVariables().filter(named(T)).getOnly()));
+ TypeDescription.Generic bar = describeType(BackwardsReferenceTypeVariable.class.getDeclaredField(BAR));
+ assertThat(bar.getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(bar.getSymbol(), is(T));
+ assertThat(bar.getUpperBounds().size(), is(1));
+ assertThat(bar.getUpperBounds().getOnly(), is(TypeDescription.Generic.OBJECT));
+ }
+
+ @Test
+ public void testParameterizedTypeSuperClassResolution() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(TypeResolution.class.getDeclaredField(FOO));
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getTypeArguments().size(), is(1));
+ TypeDescription.Generic superClass = typeDescription.getSuperClass();
+ assertThat(superClass.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(superClass.asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(TypeResolution.Base.class)));
+ assertThat(superClass.getTypeArguments().size(), is(2));
+ assertThat(superClass.getTypeArguments().get(0), is((TypeDefinition) new TypeDescription.ForLoadedType(Foo.class)));
+ assertThat(superClass.getTypeArguments().get(1), is((TypeDefinition) new TypeDescription.ForLoadedType(Bar.class)));
+ assertThat(superClass.getDeclaredFields().size(), is(1));
+ assertThat(superClass.getDeclaredFields().getOnly().getDeclaringType(), is(superClass));
+ TypeDescription.Generic fieldType = superClass.getDeclaredFields().getOnly().getType();
+ assertThat(fieldType.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(fieldType.asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(Qux.class)));
+ assertThat(fieldType.getTypeArguments().size(), is(2));
+ assertThat(fieldType.getTypeArguments().get(0), is((TypeDefinition) new TypeDescription.ForLoadedType(Foo.class)));
+ assertThat(fieldType.getTypeArguments().get(1), is((TypeDefinition) new TypeDescription.ForLoadedType(Bar.class)));
+ assertThat(superClass.getDeclaredMethods().filter(isConstructor()).size(), is(1));
+ assertThat(superClass.getDeclaredMethods().filter(isMethod()).size(), is(1));
+ assertThat(superClass.getDeclaredMethods().filter(isMethod()).getOnly().getDeclaringType(), is((superClass)));
+ assertThat(superClass.getDeclaredMethods().filter(isConstructor()).getOnly().getDeclaringType(), is((superClass)));
+ TypeDescription.Generic methodReturnType = superClass.getDeclaredMethods().filter(isMethod()).getOnly().getReturnType();
+ assertThat(methodReturnType.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(methodReturnType.asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(Qux.class)));
+ assertThat(methodReturnType.getTypeArguments().size(), is(2));
+ assertThat(methodReturnType.getTypeArguments().get(0), is((TypeDefinition) new TypeDescription.ForLoadedType(Foo.class)));
+ assertThat(methodReturnType.getTypeArguments().get(1), is((TypeDefinition) new TypeDescription.ForLoadedType(Bar.class)));
+ TypeDescription.Generic methodParameterType = superClass.getDeclaredMethods().filter(isMethod()).getOnly().getParameters().asTypeList().getOnly();
+ assertThat(methodParameterType.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(methodParameterType.asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(Qux.class)));
+ assertThat(methodParameterType.getTypeArguments().size(), is(2));
+ assertThat(methodParameterType.getTypeArguments().get(0), is((TypeDefinition) new TypeDescription.ForLoadedType(Foo.class)));
+ assertThat(methodParameterType.getTypeArguments().get(1), is((TypeDefinition) new TypeDescription.ForLoadedType(Bar.class)));
+ }
+
+ @Test
+ public void testParameterizedTypeFindBoundValue() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(TypeResolution.class.getDeclaredField(FOO));
+ assertThat(typeDescription.findBindingOf(typeDescription.asErasure().getTypeVariables().getOnly()),
+ is(typeDescription.getTypeArguments().getOnly()));
+ assertThat(typeDescription.findBindingOf(typeDescription.getOwnerType().asErasure().getTypeVariables().getOnly()),
+ is(typeDescription.getOwnerType().getTypeArguments().getOnly()));
+ assertThat(typeDescription.findBindingOf(mock(TypeDescription.Generic.class)),
+ nullValue(TypeDescription.Generic.class));
+ }
+
+ @Test
+ public void testParameterizedTypeInterfaceResolution() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(TypeResolution.class.getDeclaredField(FOO));
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getTypeArguments().size(), is(1));
+ assertThat(typeDescription.getInterfaces().size(), is(1));
+ TypeDescription.Generic interfaceType = typeDescription.getInterfaces().getOnly();
+ assertThat(interfaceType.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(interfaceType.asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(TypeResolution.BaseInterface.class)));
+ assertThat(interfaceType.getTypeArguments().size(), is(2));
+ assertThat(interfaceType.getTypeArguments().get(0), is((TypeDefinition) new TypeDescription.ForLoadedType(Foo.class)));
+ assertThat(interfaceType.getTypeArguments().get(1), is((TypeDefinition) new TypeDescription.ForLoadedType(Bar.class)));
+ assertThat(interfaceType.getDeclaredFields().size(), is(0));
+ assertThat(interfaceType.getDeclaredMethods().filter(isConstructor()).size(), is(0));
+ assertThat(interfaceType.getDeclaredMethods().filter(isMethod()).size(), is(1));
+ assertThat(interfaceType.getDeclaredMethods().filter(isMethod()).getOnly().getDeclaringType(), is((interfaceType)));
+ TypeDescription.Generic methodReturnType = interfaceType.getDeclaredMethods().filter(isMethod()).getOnly().getReturnType();
+ assertThat(methodReturnType.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(methodReturnType.asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(Qux.class)));
+ assertThat(methodReturnType.getTypeArguments().size(), is(2));
+ assertThat(methodReturnType.getTypeArguments().get(0), is((TypeDefinition) new TypeDescription.ForLoadedType(Foo.class)));
+ assertThat(methodReturnType.getTypeArguments().get(1), is((TypeDefinition) new TypeDescription.ForLoadedType(Bar.class)));
+ TypeDescription.Generic methodParameterType = interfaceType.getDeclaredMethods().filter(isMethod()).getOnly().getParameters().asTypeList().getOnly();
+ assertThat(methodParameterType.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(methodParameterType.asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(Qux.class)));
+ assertThat(methodParameterType.getTypeArguments().size(), is(2));
+ assertThat(methodParameterType.getTypeArguments().get(0), is((TypeDefinition) new TypeDescription.ForLoadedType(Foo.class)));
+ assertThat(methodParameterType.getTypeArguments().get(1), is((TypeDefinition) new TypeDescription.ForLoadedType(Bar.class)));
+ }
+
+ @Test
+ public void testParameterizedTypeRawSuperClassResolution() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(TypeResolution.class.getDeclaredField(BAR));
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getTypeArguments().size(), is(1));
+ TypeDescription.Generic superClass = typeDescription.getSuperClass();
+ assertThat(superClass.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(superClass.asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(TypeResolution.Base.class)));
+ assertThat(superClass.getDeclaredFields().size(), is(1));
+ assertThat(superClass.getDeclaredFields().getOnly().getDeclaringType().getDeclaredFields().getOnly().getType(),
+ is(superClass.getDeclaredFields().getOnly().getType()));
+ TypeDescription.Generic fieldType = superClass.getDeclaredFields().getOnly().getType();
+ assertThat(fieldType.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(fieldType.asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(Qux.class)));
+ TypeDescription.Generic methodReturnType = superClass.getDeclaredMethods().filter(isMethod()).getOnly().getReturnType();
+ assertThat(methodReturnType.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(methodReturnType.asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(Qux.class)));
+ TypeDescription.Generic methodParameterType = superClass.getDeclaredMethods().filter(isMethod()).getOnly().getParameters().asTypeList().getOnly();
+ assertThat(methodParameterType.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(methodParameterType.asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(Qux.class)));
+ assertThat(superClass.getDeclaredMethods().filter(isMethod()).getOnly().getDeclaringType().getDeclaredMethods().filter(isMethod()).getOnly().getReturnType(),
+ is(superClass.getDeclaredMethods().filter(isMethod()).getOnly().getReturnType()));
+ assertThat(superClass.getDeclaredMethods().filter(isMethod()).getOnly().getDeclaringType().getDeclaredMethods().filter(isMethod()).getOnly().getParameters().getOnly().getType(),
+ is(superClass.getDeclaredMethods().filter(isMethod()).getOnly().getParameters().getOnly().getType()));
+ }
+
+ @Test
+ public void testParameterizedTypeRawInterfaceTypeResolution() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(TypeResolution.class.getDeclaredField(BAR));
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getTypeArguments().size(), is(1));
+ TypeDescription.Generic interfaceType = typeDescription.getInterfaces().getOnly();
+ assertThat(interfaceType.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(interfaceType.asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(TypeResolution.BaseInterface.class)));
+ assertThat(interfaceType.getDeclaredFields().size(), is(0));
+ TypeDescription.Generic methodReturnType = interfaceType.getDeclaredMethods().filter(isMethod()).getOnly().getReturnType();
+ assertThat(methodReturnType.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(methodReturnType.asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(Qux.class)));
+ TypeDescription.Generic methodParameterType = interfaceType.getDeclaredMethods().filter(isMethod()).getOnly().getParameters().asTypeList().getOnly();
+ assertThat(methodParameterType.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(methodParameterType.asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(Qux.class)));
+ assertThat(interfaceType.getDeclaredMethods().getOnly().getDeclaringType().getDeclaredMethods().getOnly().getReturnType(),
+ is(interfaceType.getDeclaredMethods().getOnly().getReturnType()));
+ assertThat(interfaceType.getDeclaredMethods().getOnly().getDeclaringType().getDeclaredMethods().getOnly().getParameters().getOnly().getType(),
+ is(interfaceType.getDeclaredMethods().getOnly().getParameters().getOnly().getType()));
+ }
+
+ @Test
+ public void testParameterizedTypePartiallyRawSuperClassResolution() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(TypeResolution.class.getDeclaredField(QUX));
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getTypeArguments().size(), is(1));
+ TypeDescription.Generic superClass = typeDescription.getSuperClass();
+ assertThat(superClass.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(superClass.asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(TypeResolution.Intermediate.class)));
+ TypeDescription.Generic superSuperClass = superClass.getSuperClass();
+ assertThat(superSuperClass.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(superSuperClass.asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(TypeResolution.Base.class)));
+ }
+
+ @Test
+ public void testParameterizedTypePartiallyRawInterfaceTypeResolution() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(TypeResolution.class.getDeclaredField(QUX));
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getTypeArguments().size(), is(1));
+ TypeDescription.Generic superClass = typeDescription.getSuperClass();
+ assertThat(superClass.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(superClass.asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(TypeResolution.Intermediate.class)));
+ TypeDescription.Generic superInterfaceType = superClass.getInterfaces().getOnly();
+ assertThat(superInterfaceType.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(superInterfaceType.asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(TypeResolution.BaseInterface.class)));
+ }
+
+ @Test
+ public void testParameterizedTypeNestedPartiallyRawSuperClassResolution() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(TypeResolution.class.getDeclaredField(BAZ));
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getTypeArguments().size(), is(1));
+ TypeDescription.Generic superClass = typeDescription.getSuperClass();
+ assertThat(superClass.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(superClass.asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(TypeResolution.NestedIntermediate.class)));
+ TypeDescription.Generic superSuperClass = superClass.getSuperClass();
+ assertThat(superSuperClass.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(superSuperClass.asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(TypeResolution.Base.class)));
+ }
+
+ @Test
+ public void testParameterizedTypeNestedPartiallyRawInterfaceTypeResolution() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(TypeResolution.class.getDeclaredField(BAZ));
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getTypeArguments().size(), is(1));
+ TypeDescription.Generic superClass = typeDescription.getSuperClass();
+ assertThat(superClass.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(superClass.asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(TypeResolution.NestedIntermediate.class)));
+ TypeDescription.Generic superInterfaceType = superClass.getInterfaces().getOnly();
+ assertThat(superInterfaceType.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(superInterfaceType.asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(TypeResolution.BaseInterface.class)));
+ }
+
+ @Test
+ public void testShadowedTypeSuperClassResolution() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(TypeResolution.class.getDeclaredField(FOO + BAR));
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getTypeArguments().size(), is(2));
+ TypeDescription.Generic superClass = typeDescription.getSuperClass();
+ assertThat(superClass.getTypeArguments().size(), is(2));
+ assertThat(superClass.getTypeArguments().get(0).getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(superClass.getTypeArguments().get(0), is((TypeDefinition) new TypeDescription.ForLoadedType(Bar.class)));
+ assertThat(superClass.getTypeArguments().get(1).getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(superClass.getTypeArguments().get(1), is((TypeDefinition) new TypeDescription.ForLoadedType(Foo.class)));
+ }
+
+ @Test
+ public void testShadowedTypeInterfaceTypeResolution() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(TypeResolution.class.getDeclaredField(FOO + BAR));
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getTypeArguments().size(), is(2));
+ TypeDescription.Generic interfaceType = typeDescription.getInterfaces().getOnly();
+ assertThat(interfaceType.getTypeArguments().size(), is(2));
+ assertThat(interfaceType.getTypeArguments().get(0).getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(interfaceType.getTypeArguments().get(0), is((TypeDefinition) new TypeDescription.ForLoadedType(Bar.class)));
+ assertThat(interfaceType.getTypeArguments().get(1).getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(interfaceType.getTypeArguments().get(1), is((TypeDefinition) new TypeDescription.ForLoadedType(Foo.class)));
+ }
+
+ @Test
+ public void testMethodTypeVariableIsRetained() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(MemberVariable.class.getDeclaredField(FOO));
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getTypeArguments().size(), is(2));
+ assertThat(typeDescription.getTypeArguments().get(0).getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getTypeArguments().get(0).asErasure().represents(Number.class), is(true));
+ assertThat(typeDescription.getTypeArguments().get(1).getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getTypeArguments().get(1).asErasure().represents(Integer.class), is(true));
+ MethodDescription methodDescription = typeDescription.getDeclaredMethods().filter(named(FOO)).getOnly();
+ assertThat(methodDescription.getReturnType().getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(methodDescription.getReturnType().getSymbol(), is("S"));
+ assertThat(methodDescription.getReturnType().getTypeVariableSource(), is((TypeVariableSource) methodDescription.asDefined()));
+ }
+
+ @Test
+ public void testShadowedMethodTypeVariableIsRetained() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(MemberVariable.class.getDeclaredField(FOO));
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getTypeArguments().size(), is(2));
+ assertThat(typeDescription.getTypeArguments().get(0).getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getTypeArguments().get(0).asErasure().represents(Number.class), is(true));
+ assertThat(typeDescription.getTypeArguments().get(1).getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getTypeArguments().get(1).asErasure().represents(Integer.class), is(true));
+ MethodDescription methodDescription = typeDescription.getDeclaredMethods().filter(named(BAR)).getOnly();
+ assertThat(methodDescription.getReturnType().getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(methodDescription.getReturnType().getSymbol(), is("T"));
+ assertThat(methodDescription.getReturnType().getTypeVariableSource(), is((TypeVariableSource) methodDescription.asDefined()));
+ }
+
+ @Test
+ public void testMethodTypeVariableWithExtensionIsRetained() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(MemberVariable.class.getDeclaredField(FOO));
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getTypeArguments().size(), is(2));
+ assertThat(typeDescription.getTypeArguments().get(0).getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getTypeArguments().get(0).asErasure().represents(Number.class), is(true));
+ assertThat(typeDescription.getTypeArguments().get(1).getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getTypeArguments().get(1).asErasure().represents(Integer.class), is(true));
+ MethodDescription methodDescription = typeDescription.getDeclaredMethods().filter(named(QUX)).getOnly();
+ assertThat(methodDescription.getReturnType().getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(methodDescription.getReturnType().getSymbol(), is("S"));
+ assertThat(methodDescription.getReturnType().getTypeVariableSource(), is((TypeVariableSource) methodDescription.asDefined()));
+ assertThat(methodDescription.getReturnType().getUpperBounds().size(), is(1));
+ assertThat(methodDescription.getReturnType().getUpperBounds().getOnly().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(methodDescription.getReturnType().getUpperBounds().getOnly().asErasure().represents(Number.class), is(true));
+ }
+
+ @Test
+ public void testMethodTypeVariableErasedBound() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(MemberVariable.class.getDeclaredField(BAR)).getSuperClass();
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ MethodDescription methodDescription = typeDescription.getDeclaredMethods().filter(named(FOO)).getOnly();
+ assertThat(methodDescription.getReturnType().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(methodDescription.getReturnType().asErasure(), is(TypeDescription.OBJECT));
+ }
+
+ @Test
+ public void testMethodTypeVariableWithExtensionErasedBound() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(MemberVariable.class.getDeclaredField(BAR)).getSuperClass();
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ MethodDescription methodDescription = typeDescription.getDeclaredMethods().filter(named(QUX)).getOnly();
+ assertThat(methodDescription.getReturnType().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(methodDescription.getReturnType().asErasure(), is(TypeDescription.OBJECT));
+ }
+
+ @Test
+ public void testGenericFieldHashCode() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(MemberVariable.class.getDeclaredField(FOO));
+ assertThat(typeDescription.getDeclaredFields().filter(named(FOO)).getOnly().hashCode(),
+ CoreMatchers.not(new FieldDescription.ForLoadedField(MemberVariable.class.getDeclaredField(FOO)).hashCode()));
+ assertThat(typeDescription.getDeclaredFields().filter(named(FOO)).getOnly().asDefined().hashCode(),
+ is(new FieldDescription.ForLoadedField(MemberVariable.class.getDeclaredField(FOO)).hashCode()));
+ }
+
+ @Test
+ public void testGenericFieldEquality() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(MemberVariable.class.getDeclaredField(FOO));
+ assertThat(typeDescription.getDeclaredFields().filter(named(FOO)).getOnly(),
+ CoreMatchers.not((FieldDescription) new FieldDescription.ForLoadedField(MemberVariable.class.getDeclaredField(FOO))));
+ assertThat(typeDescription.getDeclaredFields().filter(named(FOO)).getOnly().asDefined(),
+ is((FieldDescription) new FieldDescription.ForLoadedField(MemberVariable.class.getDeclaredField(FOO))));
+ }
+
+ @Test
+ public void testGenericMethodHashCode() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(MemberVariable.class.getDeclaredField(FOO));
+ assertThat(typeDescription.getDeclaredMethods().filter(named(FOO)).getOnly().hashCode(),
+ CoreMatchers.not(new MethodDescription.ForLoadedMethod(MemberVariable.class.getDeclaredMethod(FOO)).hashCode()));
+ assertThat(typeDescription.getDeclaredMethods().filter(named(FOO)).getOnly().asDefined().hashCode(),
+ is(new MethodDescription.ForLoadedMethod(MemberVariable.class.getDeclaredMethod(FOO)).hashCode()));
+ }
+
+ @Test
+ public void testGenericMethodEquality() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(MemberVariable.class.getDeclaredField(FOO));
+ assertThat(typeDescription.getDeclaredMethods().filter(named(FOO)).getOnly(),
+ CoreMatchers.not((MethodDescription) new MethodDescription.ForLoadedMethod(MemberVariable.class.getDeclaredMethod(FOO))));
+ assertThat(typeDescription.getDeclaredMethods().filter(named(FOO)).getOnly().asDefined(),
+ is((MethodDescription) new MethodDescription.ForLoadedMethod(MemberVariable.class.getDeclaredMethod(FOO))));
+ }
+
+ @Test
+ public void testGenericParameterHashCode() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(MemberVariable.class.getDeclaredField(FOO));
+ assertThat(typeDescription.getDeclaredMethods().filter(named(BAZ)).getOnly().getParameters().getOnly().hashCode(), CoreMatchers.not(
+ new MethodDescription.ForLoadedMethod(MemberVariable.class.getDeclaredMethod(BAZ, Object.class)).getParameters().getOnly().hashCode()));
+ assertThat(typeDescription.getDeclaredMethods().filter(named(BAZ)).getOnly().getParameters().getOnly().asDefined().hashCode(), is(
+ new MethodDescription.ForLoadedMethod(MemberVariable.class.getDeclaredMethod(BAZ, Object.class)).getParameters().getOnly().hashCode()));
+ }
+
+ @Test
+ public void testGenericParameterEquality() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(MemberVariable.class.getDeclaredField(FOO));
+ assertThat(typeDescription.getDeclaredMethods().filter(named(BAZ)).getOnly().getParameters().getOnly(), CoreMatchers.not((ParameterDescription)
+ new MethodDescription.ForLoadedMethod(MemberVariable.class.getDeclaredMethod(BAZ, Object.class)).getParameters().getOnly()));
+ assertThat(typeDescription.getDeclaredMethods().filter(named(BAZ)).getOnly().getParameters().getOnly().asDefined(), is((ParameterDescription)
+ new MethodDescription.ForLoadedMethod(MemberVariable.class.getDeclaredMethod(BAZ, Object.class)).getParameters().getOnly()));
+ }
+
+ @Test
+ public void testGenericTypeInconsistency() throws Exception {
+ TypeDescription.Generic typeDescription = describeType(GenericDisintegrator.make());
+ assertThat(typeDescription.getInterfaces().size(), is(2));
+ assertThat(typeDescription.getInterfaces().get(0).getSort(), is(TypeDescription.Generic.Sort.NON_GENERIC));
+ assertThat(typeDescription.getInterfaces().get(0).asErasure().represents(Callable.class), is(true));
+ assertThat(typeDescription.getInterfaces().get(1).getSort(), is(TypeDescription.Generic.Sort.NON_GENERIC));
+ assertThat(typeDescription.getInterfaces().get(1).represents(Serializable.class), is(true));
+ assertThat(typeDescription.getDeclaredMethods().filter(named(FOO)).getOnly().getParameters().size(), is(2));
+ assertThat(typeDescription.getDeclaredMethods().filter(named(FOO)).getOnly().getParameters().get(0).getType().getSort(),
+ is(TypeDescription.Generic.Sort.NON_GENERIC));
+ assertThat(typeDescription.getDeclaredMethods().filter(named(FOO)).getOnly().getParameters().get(0).getType().asErasure().represents(Exception.class),
+ is(true));
+ assertThat(typeDescription.getDeclaredMethods().filter(named(FOO)).getOnly().getParameters().get(1).getType().getSort(),
+ is(TypeDescription.Generic.Sort.NON_GENERIC));
+ assertThat(typeDescription.getDeclaredMethods().filter(named(FOO)).getOnly().getParameters().get(1).getType().represents(Void.class),
+ is(true));
+ assertThat(typeDescription.getDeclaredMethods().filter(named(FOO)).getOnly().getExceptionTypes().size(), is(2));
+ assertThat(typeDescription.getDeclaredMethods().filter(named(FOO)).getOnly().getExceptionTypes().get(0).getSort(),
+ is(TypeDescription.Generic.Sort.NON_GENERIC));
+ assertThat(typeDescription.getDeclaredMethods().filter(named(FOO)).getOnly().getExceptionTypes().get(0).asErasure().represents(Exception.class),
+ is(true));
+ assertThat(typeDescription.getDeclaredMethods().filter(named(FOO)).getOnly().getExceptionTypes().get(1).getSort(),
+ is(TypeDescription.Generic.Sort.NON_GENERIC));
+ assertThat(typeDescription.getDeclaredMethods().filter(named(FOO)).getOnly().getExceptionTypes().get(1).represents(RuntimeException.class),
+ is(true));
+ assertThat(typeDescription.getDeclaredMethods().filter(isConstructor()).getOnly().getParameters().size(), is(2));
+ assertThat(typeDescription.getDeclaredMethods().filter(isConstructor()).getOnly().getParameters().get(0).getType().getSort(),
+ is(TypeDescription.Generic.Sort.NON_GENERIC));
+ assertThat(typeDescription.getDeclaredMethods().filter(isConstructor()).getOnly().getParameters().get(0).getType().asErasure().represents(Exception.class),
+ is(true));
+ assertThat(typeDescription.getDeclaredMethods().filter(isConstructor()).getOnly().getParameters().get(1).getType().getSort(),
+ is(TypeDescription.Generic.Sort.NON_GENERIC));
+ assertThat(typeDescription.getDeclaredMethods().filter(isConstructor()).getOnly().getParameters().get(1).getType().represents(Void.class),
+ is(true));
+ assertThat(typeDescription.getDeclaredMethods().filter(isConstructor()).getOnly().getExceptionTypes().size(), is(2));
+ assertThat(typeDescription.getDeclaredMethods().filter(isConstructor()).getOnly().getExceptionTypes().get(0).getSort(),
+ is(TypeDescription.Generic.Sort.NON_GENERIC));
+ assertThat(typeDescription.getDeclaredMethods().filter(isConstructor()).getOnly().getExceptionTypes().get(0).asErasure().represents(Exception.class),
+ is(true));
+ assertThat(typeDescription.getDeclaredMethods().filter(isConstructor()).getOnly().getExceptionTypes().get(1).getSort(),
+ is(TypeDescription.Generic.Sort.NON_GENERIC));
+ assertThat(typeDescription.getDeclaredMethods().filter(isConstructor()).getOnly().getExceptionTypes().get(1).represents(RuntimeException.class),
+ is(true));
+ }
+
+ @Test
+ public void testRepresents() throws Exception {
+ assertThat(describeType(SimpleParameterizedType.class.getDeclaredField(FOO))
+ .represents(SimpleParameterizedType.class.getDeclaredField(FOO).getGenericType()), is(true));
+ assertThat(describeType(SimpleParameterizedType.class.getDeclaredField(FOO))
+ .represents(List.class), is(false));
+ }
+
+ @Test
+ public void testRawType() throws Exception {
+ TypeDescription.Generic type = describeType(RawType.class.getDeclaredField(FOO)).getSuperClass().getSuperClass();
+ FieldDescription fieldDescription = type.getDeclaredFields().filter(named(BAR)).getOnly();
+ assertThat(fieldDescription.getType().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(fieldDescription.getType().asErasure(), is(TypeDescription.OBJECT));
+ }
+
+ @Test
+ public void testIntermediateRawType() throws Exception {
+ TypeDescription.Generic type = describeType(IntermediateRaw.class.getDeclaredField(FOO)).getSuperClass().getSuperClass().getSuperClass();
+ FieldDescription fieldDescription = type.getDeclaredFields().filter(named(BAR)).getOnly();
+ assertThat(fieldDescription.getType().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(fieldDescription.getType().asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(Integer.class)));
+ }
+
+ @Test
+ public void testMixedTypeVariables() throws Exception {
+ MethodDescription methodDescription = describeInterfaceType(MixedTypeVariables.Inner.class, 0).getDeclaredMethods().getOnly();
+ assertThat(methodDescription.getParameters().getOnly().getType().getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(methodDescription.getParameters().getOnly().getType().asRawType().represents(MixedTypeVariables.SampleType.class), is(true));
+ assertThat(methodDescription.getParameters().getOnly().getType().getTypeArguments().get(0), is(methodDescription.getTypeVariables().getOnly()));
+ assertThat(methodDescription.getParameters().getOnly().getType().getTypeArguments().get(1).represents(Void.class), is(true));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testTypeAnnotationsFieldType() throws Exception {
+ Class<? extends Annotation> typeAnnotation = (Class<? extends Annotation>) Class.forName(TYPE_ANNOTATION);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotation).getDeclaredMethods().getOnly();
+ Class<?> samples = Class.forName(TYPE_ANNOTATION_SAMPLES);
+ TypeDescription.Generic fieldType = describeType(samples.getDeclaredField(FOO));
+ assertThat(fieldType.getSort(), is(TypeDefinition.Sort.GENERIC_ARRAY));
+ assertThat(fieldType.getDeclaredAnnotations().size(), is(1));
+ assertThat(fieldType.getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(fieldType.getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(24));
+ assertThat(fieldType.getComponentType().getSort(), is(TypeDefinition.Sort.GENERIC_ARRAY));
+ assertThat(fieldType.getComponentType().getDeclaredAnnotations().size(), is(1));
+ assertThat(fieldType.getComponentType().getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(fieldType.getComponentType().getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(25));
+ assertThat(fieldType.getComponentType().getComponentType().getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(fieldType.getComponentType().getComponentType().getDeclaredAnnotations().size(), is(1));
+ assertThat(fieldType.getComponentType().getComponentType().getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(fieldType.getComponentType().getComponentType().getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(22));
+ assertThat(fieldType.getComponentType().getComponentType().getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.WILDCARD));
+ assertThat(fieldType.getComponentType().getComponentType().getTypeArguments().getOnly().getDeclaredAnnotations().size(), is(1));
+ assertThat(fieldType.getComponentType().getComponentType().getTypeArguments().getOnly().getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(fieldType.getComponentType().getComponentType().getTypeArguments().getOnly().getDeclaredAnnotations()
+ .ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(23));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testTypeAnnotationsMethodReturnType() throws Exception {
+ Class<? extends Annotation> typeAnnotation = (Class<? extends Annotation>) Class.forName(TYPE_ANNOTATION);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotation).getDeclaredMethods().getOnly();
+ Class<?> samples = Class.forName(TYPE_ANNOTATION_SAMPLES);
+ TypeDescription.Generic returnType = describeReturnType(samples.getDeclaredMethod(FOO, Exception[][].class));
+ assertThat(returnType.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(returnType.getDeclaredAnnotations().size(), is(1));
+ assertThat(returnType.getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(returnType.getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(28));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testTypeAnnotationsMethodParameterType() throws Exception {
+ Class<? extends Annotation> typeAnnotation = (Class<? extends Annotation>) Class.forName(TYPE_ANNOTATION);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotation).getDeclaredMethods().getOnly();
+ Class<?> samples = Class.forName(TYPE_ANNOTATION_SAMPLES);
+ TypeDescription.Generic parameterType = describeParameterType(samples.getDeclaredMethod(FOO, Exception[][].class), 0);
+ assertThat(parameterType.getSort(), is(TypeDefinition.Sort.GENERIC_ARRAY));
+ assertThat(parameterType.getDeclaredAnnotations().size(), is(1));
+ assertThat(parameterType.getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(parameterType.getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(30));
+ assertThat(parameterType.getComponentType().getSort(), is(TypeDefinition.Sort.GENERIC_ARRAY));
+ assertThat(parameterType.getComponentType().getDeclaredAnnotations().size(), is(1));
+ assertThat(parameterType.getComponentType().getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(parameterType.getComponentType().getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(31));
+ assertThat(parameterType.getComponentType().getComponentType().getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(parameterType.getComponentType().getComponentType().getDeclaredAnnotations().size(), is(1));
+ assertThat(parameterType.getComponentType().getComponentType().getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(parameterType.getComponentType().getComponentType().getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(29));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testTypeAnnotationsSuperType() throws Exception {
+ Class<? extends Annotation> typeAnnotation = (Class<? extends Annotation>) Class.forName(TYPE_ANNOTATION);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotation).getDeclaredMethods().getOnly();
+ Class<?> samples = Class.forName(TYPE_ANNOTATION_SAMPLES);
+ TypeDescription.Generic superClass = describeSuperClass(samples);
+ assertThat(superClass.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(superClass.getDeclaredAnnotations().size(), is(1));
+ assertThat(superClass.getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(superClass.getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(18));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testTypeAnnotationsInterfaceType() throws Exception {
+ Class<? extends Annotation> typeAnnotation = (Class<? extends Annotation>) Class.forName(TYPE_ANNOTATION);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotation).getDeclaredMethods().getOnly();
+ Class<?> samples = Class.forName(TYPE_ANNOTATION_SAMPLES);
+ TypeDescription.Generic firstInterfaceType = describeInterfaceType(samples, 0);
+ assertThat(firstInterfaceType.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(firstInterfaceType.getDeclaredAnnotations().size(), is(1));
+ assertThat(firstInterfaceType.getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(firstInterfaceType.getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(19));
+ assertThat(firstInterfaceType.getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(firstInterfaceType.getTypeArguments().getOnly().getDeclaredAnnotations().size(), is(1));
+ assertThat(firstInterfaceType.getTypeArguments().getOnly().getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(firstInterfaceType.getTypeArguments().getOnly().getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(20));
+ TypeDescription.Generic secondInterfaceType = describeInterfaceType(samples, 1);
+ assertThat(secondInterfaceType.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(secondInterfaceType.getDeclaredAnnotations().size(), is(0));
+ assertThat(secondInterfaceType.getTypeArguments().get(0).getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(secondInterfaceType.getTypeArguments().get(0).getDeclaredAnnotations().size(), is(1));
+ assertThat(secondInterfaceType.getTypeArguments().get(0).getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(secondInterfaceType.getTypeArguments().get(0).getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(21));
+ assertThat(secondInterfaceType.getTypeArguments().get(1).getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(secondInterfaceType.getTypeArguments().get(1).getDeclaredAnnotations().size(), is(0));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testTypeAnnotationExceptionType() throws Exception {
+ Class<? extends Annotation> typeAnnotation = (Class<? extends Annotation>) Class.forName(TYPE_ANNOTATION);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotation).getDeclaredMethods().getOnly();
+ Class<?> samples = Class.forName(TYPE_ANNOTATION_SAMPLES);
+ TypeDescription.Generic firstExceptionType = describeExceptionType(samples.getDeclaredMethod(FOO, Exception[][].class), 0);
+ assertThat(firstExceptionType.getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(firstExceptionType.getDeclaredAnnotations().size(), is(1));
+ assertThat(firstExceptionType.getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(firstExceptionType.getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(32));
+ TypeDescription.Generic secondExceptionType = describeExceptionType(samples.getDeclaredMethod(FOO, Exception[][].class), 1);
+ assertThat(secondExceptionType.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(secondExceptionType.getDeclaredAnnotations().size(), is(1));
+ assertThat(secondExceptionType.getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(secondExceptionType.getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(33));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testTypeAnnotationOnNonGenericField() throws Exception {
+ Class<? extends Annotation> typeAnnotation = (Class<? extends Annotation>) Class.forName(TYPE_ANNOTATION);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotation).getDeclaredMethods().getOnly();
+ Class<?> samples = Class.forName(TYPE_ANNOTATION_OTHER_SAMPLES);
+ TypeDescription.Generic fieldType = describeType(samples.getDeclaredField(FOO));
+ assertThat(fieldType.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(fieldType.getDeclaredAnnotations().size(), is(1));
+ assertThat(fieldType.getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(fieldType.getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(0));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testTypeAnnotationOnNonGenericReturnType() throws Exception {
+ Class<? extends Annotation> typeAnnotation = (Class<? extends Annotation>) Class.forName(TYPE_ANNOTATION);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotation).getDeclaredMethods().getOnly();
+ Class<?> samples = Class.forName(TYPE_ANNOTATION_OTHER_SAMPLES);
+ TypeDescription.Generic returnType = describeReturnType(samples.getDeclaredMethod(FOO, Void.class));
+ assertThat(returnType.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(returnType.getDeclaredAnnotations().size(), is(1));
+ assertThat(returnType.getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(returnType.getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(9));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testTypeAnnotationOnNonGenericParameterType() throws Exception {
+ Class<? extends Annotation> typeAnnotation = (Class<? extends Annotation>) Class.forName(TYPE_ANNOTATION);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotation).getDeclaredMethods().getOnly();
+ Class<?> samples = Class.forName(TYPE_ANNOTATION_OTHER_SAMPLES);
+ TypeDescription.Generic parameterType = describeParameterType(samples.getDeclaredMethod(FOO, Void.class), 0);
+ assertThat(parameterType.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(parameterType.getDeclaredAnnotations().size(), is(1));
+ assertThat(parameterType.getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(parameterType.getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(10));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testTypeAnnotationOnNonGenericExceptionType() throws Exception {
+ Class<? extends Annotation> typeAnnotation = (Class<? extends Annotation>) Class.forName(TYPE_ANNOTATION);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotation).getDeclaredMethods().getOnly();
+ Class<?> samples = Class.forName(TYPE_ANNOTATION_OTHER_SAMPLES);
+ TypeDescription.Generic exceptionType = describeExceptionType(samples.getDeclaredMethod(FOO, Void.class), 0);
+ assertThat(exceptionType.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(exceptionType.getDeclaredAnnotations().size(), is(1));
+ assertThat(exceptionType.getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(exceptionType.getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(11));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testTypeAnnotationOnNonGenericArrayType() throws Exception {
+ Class<? extends Annotation> typeAnnotation = (Class<? extends Annotation>) Class.forName(TYPE_ANNOTATION);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotation).getDeclaredMethods().getOnly();
+ Class<?> samples = Class.forName(TYPE_ANNOTATION_SAMPLES);
+ TypeDescription.Generic returnType = describeReturnType(samples.getDeclaredMethod(BAR, Void[][].class));
+ assertThat(returnType.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(returnType.getDeclaredAnnotations().size(), is(1));
+ assertThat(returnType.getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(returnType.getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(35));
+ assertThat(returnType.getComponentType().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(returnType.getComponentType().getDeclaredAnnotations().size(), is(1));
+ assertThat(returnType.getComponentType().getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(returnType.getComponentType().getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(36));
+ assertThat(returnType.getComponentType().getComponentType().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(returnType.getComponentType().getComponentType().getDeclaredAnnotations().size(), is(1));
+ assertThat(returnType.getComponentType().getComponentType().getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(returnType.getComponentType().getComponentType().getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(34));
+ TypeDescription.Generic parameterType = describeParameterType(samples.getDeclaredMethod(BAR, Void[][].class), 0);
+ assertThat(parameterType.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(parameterType.getDeclaredAnnotations().size(), is(1));
+ assertThat(parameterType.getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(parameterType.getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(38));
+ assertThat(parameterType.getComponentType().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(parameterType.getComponentType().getDeclaredAnnotations().size(), is(1));
+ assertThat(parameterType.getComponentType().getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(parameterType.getComponentType().getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(39));
+ assertThat(parameterType.getComponentType().getComponentType().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(parameterType.getComponentType().getComponentType().getDeclaredAnnotations().size(), is(1));
+ assertThat(parameterType.getComponentType().getComponentType().getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(parameterType.getComponentType().getComponentType().getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(37));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testTypeAnnotationOnNonGenericArrayTypeWithGenericSignature() throws Exception {
+ Class<? extends Annotation> typeAnnotation = (Class<? extends Annotation>) Class.forName(TYPE_ANNOTATION);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotation).getDeclaredMethods().getOnly();
+ Class<?> samples = Class.forName(TYPE_ANNOTATION_SAMPLES);
+ TypeDescription.Generic returnType = describeReturnType(samples.getDeclaredMethod(QUX, Void[][].class));
+ assertThat(returnType.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(returnType.getDeclaredAnnotations().size(), is(1));
+ assertThat(returnType.getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(returnType.getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(41));
+ assertThat(returnType.getComponentType().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(returnType.getComponentType().getDeclaredAnnotations().size(), is(1));
+ assertThat(returnType.getComponentType().getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(returnType.getComponentType().getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(42));
+ assertThat(returnType.getComponentType().getComponentType().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(returnType.getComponentType().getComponentType().getDeclaredAnnotations().size(), is(1));
+ assertThat(returnType.getComponentType().getComponentType().getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(returnType.getComponentType().getComponentType().getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(40));
+ TypeDescription.Generic parameterType = describeParameterType(samples.getDeclaredMethod(QUX, Void[][].class), 0);
+ assertThat(parameterType.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(parameterType.getDeclaredAnnotations().size(), is(1));
+ assertThat(parameterType.getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(parameterType.getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(44));
+ assertThat(parameterType.getComponentType().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(parameterType.getComponentType().getDeclaredAnnotations().size(), is(1));
+ assertThat(parameterType.getComponentType().getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(parameterType.getComponentType().getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(45));
+ assertThat(parameterType.getComponentType().getComponentType().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(parameterType.getComponentType().getComponentType().getDeclaredAnnotations().size(), is(1));
+ assertThat(parameterType.getComponentType().getComponentType().getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(parameterType.getComponentType().getComponentType().getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(43));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testTypeAnnotationOwnerType() throws Exception {
+ Class<? extends Annotation> typeAnnotation = (Class<? extends Annotation>) Class.forName(TYPE_ANNOTATION);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotation).getDeclaredMethods().getOnly();
+ Class<?> samples = Class.forName(TYPE_ANNOTATION_OTHER_SAMPLES);
+ TypeDescription.Generic fieldType = describeType(samples.getDeclaredField(BAR));
+ assertThat(fieldType.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(fieldType.getDeclaredAnnotations().size(), is(1));
+ assertThat(fieldType.getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(fieldType.getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(3));
+ assertThat(fieldType.getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(fieldType.getTypeArguments().getOnly().getDeclaredAnnotations().size(), is(1));
+ assertThat(fieldType.getTypeArguments().getOnly().getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(fieldType.getTypeArguments().getOnly().getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(4));
+ assertThat(fieldType.getOwnerType().getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(fieldType.getOwnerType().getDeclaredAnnotations().size(), is(1));
+ assertThat(fieldType.getOwnerType().getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(fieldType.getOwnerType().getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(1));
+ assertThat(fieldType.getOwnerType().getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(fieldType.getOwnerType().getTypeArguments().getOnly().getDeclaredAnnotations().size(), is(1));
+ assertThat(fieldType.getOwnerType().getTypeArguments().getOnly().getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(fieldType.getOwnerType().getTypeArguments().getOnly().getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(2));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testTypeAnnotationTwoAnnotations() throws Exception {
+ Class<? extends Annotation> typeAnnotation = (Class<? extends Annotation>) Class.forName(TYPE_ANNOTATION);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotation).getDeclaredMethods().getOnly();
+ Class<? extends Annotation> otherTypeAnnotation = (Class<? extends Annotation>) Class.forName(OTHER_TYPE_ANNOTATION);
+ MethodDescription.InDefinedShape otherValue = new TypeDescription.ForLoadedType(otherTypeAnnotation).getDeclaredMethods().getOnly();
+ Class<?> samples = Class.forName(TYPE_ANNOTATION_OTHER_SAMPLES);
+ TypeDescription.Generic fieldType = describeType(samples.getDeclaredField(QUX));
+ assertThat(fieldType.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(fieldType.getDeclaredAnnotations().size(), is(2));
+ assertThat(fieldType.getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(fieldType.getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(5));
+ assertThat(fieldType.getDeclaredAnnotations().isAnnotationPresent(otherTypeAnnotation), is(true));
+ assertThat(fieldType.getDeclaredAnnotations().ofType(otherTypeAnnotation).getValue(otherValue).resolve(Integer.class), is(6));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testTypeAnnotationNonGenericInnerType() throws Exception {
+ Class<? extends Annotation> typeAnnotation = (Class<? extends Annotation>) Class.forName(TYPE_ANNOTATION);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotation).getDeclaredMethods().getOnly();
+ Class<?> samples = Class.forName(TYPE_ANNOTATION_OTHER_SAMPLES);
+ TypeDescription.Generic fieldType = describeType(samples.getDeclaredField(BAZ));
+ assertThat(fieldType.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(fieldType.getDeclaredAnnotations().size(), is(1));
+ assertThat(fieldType.getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(fieldType.getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(8));
+ assertThat(fieldType.getOwnerType().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(fieldType.getOwnerType().getDeclaredAnnotations().size(), is(1));
+ assertThat(fieldType.getOwnerType().getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(fieldType.getOwnerType().getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(7));
+ }
+
+ @SuppressWarnings("unused")
+ public interface Foo {
+ /* empty */
+ }
+
+ @SuppressWarnings("unused")
+ public interface Bar {
+ /* empty */
+ }
+
+ @SuppressWarnings("unused")
+ public interface Qux<T, U> {
+ /* empty */
+ }
+
+ @SuppressWarnings("unused")
+ public static class NonGeneric {
+
+ Object foo;
+
+ Inner bar;
+
+ class Inner {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class SimpleParameterizedType {
+
+ List<String> foo;
+ }
+
+ @SuppressWarnings("unused")
+ public static class UpperBoundWildcardParameterizedType {
+
+ List<? extends String> foo;
+ }
+
+ @SuppressWarnings("unused")
+ public static class LowerBoundWildcardParameterizedType {
+
+ List<? super String> foo;
+ }
+
+ @SuppressWarnings("unused")
+ public static class UnboundWildcardParameterizedType {
+
+ List<?> foo;
+ }
+
+ @SuppressWarnings("all")
+ public static class ExplicitlyUnboundWildcardParameterizedType {
+
+ List<? extends Object> foo;
+ }
+
+ @SuppressWarnings("unused")
+ public static class NestedParameterizedType {
+
+ List<List<Foo>> foo;
+ }
+
+ @SuppressWarnings("unused")
+ public static class SimpleGenericArrayType {
+
+ List<String>[] foo;
+ }
+
+ @SuppressWarnings("unused")
+ public static class GenericArrayOfGenericComponentType<T extends String> {
+
+ List<T>[] foo;
+ }
+
+ @SuppressWarnings("unused")
+ public static class SimpleTypeVariableType<T> {
+
+ T foo;
+ }
+
+ @SuppressWarnings("unused")
+ public static class SingleUpperBoundTypeVariableType<T extends String> {
+
+ T foo;
+ }
+
+ @SuppressWarnings("unused")
+ public static class MultipleUpperBoundTypeVariableType<T extends String & Foo & Bar> {
+
+ T foo;
+ }
+
+ @SuppressWarnings("unused")
+ public static class InterfaceOnlyMultipleUpperBoundTypeVariableType<T extends Foo & Bar> {
+
+ T foo;
+ }
+
+ @SuppressWarnings("unused")
+ public static class ShadowingTypeVariableType<T> {
+
+ @SuppressWarnings("all")
+ <T> T foo() {
+ return null;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class NestedTypeVariableType<T> {
+
+ Placeholder foo;
+
+ class Placeholder {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class NestedSpecifiedTypeVariableType<T> {
+
+ NestedSpecifiedTypeVariableType<String>.Placeholder foo;
+
+ class Placeholder {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class NestedStaticTypeVariableType<T> {
+
+ NestedStaticTypeVariableType.Placeholder<String> foo;
+
+ static class Placeholder<S> {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class NestedInnerType<T> {
+
+ class InnerType<S extends T> {
+
+ <U extends S> T foo() {
+ return null;
+ }
+
+ <U extends S> S bar() {
+ return null;
+ }
+
+ <U extends S> U qux() {
+ return null;
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class NestedInnerMethod<T> {
+
+ <S extends T> Class<?> foo() {
+ class InnerType<U extends S> {
+
+ <V extends U> T foo() {
+ return null;
+ }
+
+ <V extends U> S bar() {
+ return null;
+ }
+
+ <V extends U> U qux() {
+ return null;
+ }
+
+ <V extends U> V baz() {
+ return null;
+ }
+ }
+ return InnerType.class;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class RecursiveTypeVariable<T extends RecursiveTypeVariable<T>> {
+
+ T foo;
+ }
+
+ @SuppressWarnings("unused")
+ public static class BackwardsReferenceTypeVariable<T, S extends T> {
+
+ S foo;
+
+ T bar;
+ }
+
+ @SuppressWarnings("unused")
+ public static class TypeResolution<T> {
+
+ private TypeResolution<Foo>.Inner<Bar> foo;
+
+ private TypeResolution<Foo>.Raw<Bar> bar;
+
+ private TypeResolution<Foo>.PartiallyRaw<Bar> qux;
+
+ private TypeResolution<Foo>.NestedPartiallyRaw<Bar> baz;
+
+ private TypeResolution<Foo>.Shadowed<Bar, Foo> foobar;
+
+ public interface BaseInterface<V, W> {
+
+ Qux<V, W> qux(Qux<V, W> qux);
+ }
+
+ public static class Intermediate<V, W> extends Base<List<V>, List<? extends W>> implements BaseInterface<List<V>, List<? extends W>> {
+ /* empty */
+ }
+
+ public static class NestedIntermediate<V, W> extends Base<List<List<V>>, List<String>> implements BaseInterface<List<List<V>>, List<String>> {
+ /* empty */
+ }
+
+ public static class Base<V, W> {
+
+ Qux<V, W> qux;
+
+ public Qux<V, W> qux(Qux<V, W> qux) {
+ return null;
+ }
+ }
+
+ public class Inner<S> extends Base<T, S> implements BaseInterface<T, S> {
+ /* empty */
+ }
+
+ @SuppressWarnings("unchecked")
+ public class Raw<S> extends Base implements BaseInterface {
+ /* empty */
+ }
+
+ @SuppressWarnings("unchecked")
+ public class PartiallyRaw<S> extends Intermediate {
+ /* empty */
+ }
+
+ @SuppressWarnings("unchecked")
+ public class NestedPartiallyRaw<S> extends NestedIntermediate {
+ /* empty */
+ }
+
+ @SuppressWarnings("all")
+ public class Shadowed<T, S> extends Base<T, S> implements BaseInterface<T, S> {
+ /* empty */
+ }
+ }
+
+ public static class RawType<T> {
+
+ Extension foo;
+
+ T bar;
+
+ public static class Intermediate<T extends Number> extends RawType<T> {
+ /* empty */
+ }
+
+ public static class Extension extends Intermediate {
+ /* empty */
+ }
+ }
+
+ public static class IntermediateRaw<T> {
+
+ Extension foo;
+
+ T bar;
+
+ public static class NonGenericIntermediate extends IntermediateRaw<Integer> {
+ /* empty */
+ }
+
+ public static class GenericIntermediate<T> extends NonGenericIntermediate {
+ /* empty */
+ }
+
+ public static class Extension extends GenericIntermediate {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class MemberVariable<U, T extends U> {
+
+ public MemberVariable<Number, Integer> foo;
+
+ public Raw bar;
+
+ public <S> S foo() {
+ return null;
+ }
+
+ @SuppressWarnings("all")
+ public <T> T bar() {
+ return null;
+ }
+
+ @SuppressWarnings("all")
+ public <S extends U> S qux() {
+ return null;
+ }
+
+ public U baz(U u) {
+ return u;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static class Raw extends MemberVariable {
+ /* empty */
+ }
+ }
+
+ interface MixedTypeVariables<T> {
+
+ interface Inner extends MixedTypeVariables<Void> {
+ /* empty */
+ }
+
+ <S> void qux(SampleType<S, T> arg);
+
+ interface SampleType<U, V> {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public abstract static class InconsistentGenerics<T extends Exception> implements Callable<T> {
+
+ InconsistentGenerics(T t) throws T {
+ /* empty */
+ }
+
+ abstract void foo(T t) throws T;
+
+ private InconsistentGenerics<T> foo;
+ }
+
+ public static class GenericDisintegrator extends ClassVisitor {
+
+ public static Field make() throws IOException, ClassNotFoundException, NoSuchFieldException {
+ ClassReader classReader = new ClassReader(InconsistentGenerics.class.getName());
+ ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS);
+ classReader.accept(new GenericDisintegrator(classWriter), 0);
+ return new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER,
+ Collections.singletonMap(InconsistentGenerics.class.getName(), classWriter.toByteArray()),
+ ByteArrayClassLoader.PersistenceHandler.MANIFEST)
+ .loadClass(InconsistentGenerics.class.getName()).getDeclaredField(FOO);
+ }
+
+ public GenericDisintegrator(ClassVisitor classVisitor) {
+ super(Opcodes.ASM5, classVisitor);
+ }
+
+ @Override
+ public void visit(int version, int modifiers, String name, String signature, String superName, String[] interfaces) {
+ super.visit(version,
+ modifiers,
+ name,
+ signature,
+ superName,
+ new String[]{Callable.class.getName().replace('.', '/'), Serializable.class.getName().replace('.', '/')});
+ }
+
+ @Override
+ public void visitOuterClass(String owner, String name, String desc) {
+ /* do nothing */
+ }
+
+ @Override
+ public void visitInnerClass(String name, String outerName, String innerName, int access) {
+ /* do nothing */
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
+ return super.visitMethod(access,
+ name,
+ "(L" + Exception.class.getName().replace('.', '/') + ";L" + Void.class.getName().replace('.', '/') + ";)V",
+ signature,
+ new String[]{Exception.class.getName().replace('.', '/'), RuntimeException.class.getName().replace('.', '/')});
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/AbstractTypeDescriptionGenericVariableDefiningTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/AbstractTypeDescriptionGenericVariableDefiningTest.java
new file mode 100644
index 0000000..b121e6f
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/AbstractTypeDescriptionGenericVariableDefiningTest.java
@@ -0,0 +1,455 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import org.junit.Test;
+
+import java.lang.annotation.Annotation;
+
+import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public abstract class AbstractTypeDescriptionGenericVariableDefiningTest extends AbstractTypeDescriptionGenericTest {
+
+ private static final String FOO = "foo";
+
+ private static final String T = "T", S = "S", U = "U", V = "V", W = "W", X = "X";
+
+ private static final String TYPE_ANNOTATION = "net.bytebuddy.test.precompiled.TypeAnnotation";
+
+ private static final String TYPE_ANNOTATION_SAMPLES = "net.bytebuddy.test.precompiled.TypeAnnotationSamples";
+
+ private static final String RECEIVER_TYPE_SAMPLE = "net.bytebuddy.test.precompiled.ReceiverTypeSample", INNER = "Inner", NESTED = "Nested", GENERIC = "Generic";
+
+ protected abstract TypeDescription describe(Class<?> type);
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testTypeVariableT() throws Exception {
+ Class<? extends Annotation> typeAnnotation = (Class<? extends Annotation>) Class.forName(TYPE_ANNOTATION);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotation).getDeclaredMethods().getOnly();
+ TypeDescription typeDescription = describe(Class.forName(TYPE_ANNOTATION_SAMPLES));
+ TypeDescription.Generic t = typeDescription.getTypeVariables().filter(named(T)).getOnly();
+ assertThat(t.getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(t.getDeclaredAnnotations().size(), is(1));
+ assertThat(t.getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(t.getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(0));
+ assertThat(t.getUpperBounds().size(), is(1));
+ assertThat(t.getUpperBounds().contains(TypeDescription.Generic.OBJECT), is(true));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testTypeVariableS() throws Exception {
+ TypeDescription typeDescription = describe(Class.forName(TYPE_ANNOTATION_SAMPLES));
+ TypeDescription.Generic t = typeDescription.getTypeVariables().filter(named(S)).getOnly();
+ assertThat(t.getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(t.getDeclaredAnnotations().size(), is(0));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testTypeVariableU() throws Exception {
+ Class<? extends Annotation> typeAnnotation = (Class<? extends Annotation>) Class.forName(TYPE_ANNOTATION);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotation).getDeclaredMethods().getOnly();
+ TypeDescription typeDescription = describe(Class.forName(TYPE_ANNOTATION_SAMPLES));
+ TypeDescription.Generic u = typeDescription.getTypeVariables().filter(named(U)).getOnly();
+ assertThat(u.getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(u.getDeclaredAnnotations().size(), is(1));
+ assertThat(u.getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(u.getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(2));
+ assertThat(u.getUpperBounds().get(0).getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(u.getUpperBounds().get(0).getDeclaredAnnotations().size(), is(0));
+ assertThat(u.getUpperBounds().get(1).getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(u.getUpperBounds().get(1).getDeclaredAnnotations().size(), is(1));
+ assertThat(u.getUpperBounds().get(1).getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(u.getUpperBounds().get(1).getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(3));
+ assertThat(u.getUpperBounds().get(1).getTypeArguments().get(0).getSort(), is(TypeDefinition.Sort.WILDCARD));
+ assertThat(u.getUpperBounds().get(1).getTypeArguments().get(0).getDeclaredAnnotations().size(), is(1));
+ assertThat(u.getUpperBounds().get(1).getTypeArguments().get(0).getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(u.getUpperBounds().get(1).getTypeArguments().get(0).getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(4));
+ assertThat(u.getUpperBounds().get(2).getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(u.getUpperBounds().get(2).getDeclaredAnnotations().size(), is(1));
+ assertThat(u.getUpperBounds().get(2).getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(u.getUpperBounds().get(2).getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(5));
+ assertThat(u.getUpperBounds().get(2).getTypeArguments().get(0).getSort(), is(TypeDefinition.Sort.WILDCARD));
+ assertThat(u.getUpperBounds().get(2).getTypeArguments().get(0).getDeclaredAnnotations().size(), is(1));
+ assertThat(u.getUpperBounds().get(2).getTypeArguments().get(0).getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(u.getUpperBounds().get(2).getTypeArguments().get(0).getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(6));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testTypeVariableV() throws Exception {
+ Class<? extends Annotation> typeAnnotation = (Class<? extends Annotation>) Class.forName(TYPE_ANNOTATION);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotation).getDeclaredMethods().getOnly();
+ TypeDescription typeDescription = describe(Class.forName(TYPE_ANNOTATION_SAMPLES));
+ TypeDescription.Generic v = typeDescription.getTypeVariables().filter(named(V)).getOnly();
+ assertThat(v.getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(v.getDeclaredAnnotations().size(), is(1));
+ assertThat(v.getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(v.getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(7));
+ assertThat(v.getUpperBounds().get(0).getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(v.getUpperBounds().get(0).getDeclaredAnnotations().size(), is(0));
+ assertThat(v.getUpperBounds().get(0).getTypeArguments().get(0).getSort(), is(TypeDefinition.Sort.WILDCARD));
+ assertThat(v.getUpperBounds().get(0).getTypeArguments().get(0).getDeclaredAnnotations().size(), is(1));
+ assertThat(v.getUpperBounds().get(0).getTypeArguments().get(0).getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(v.getUpperBounds().get(0).getTypeArguments().get(0).getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(8));
+ assertThat(v.getUpperBounds().get(0).getTypeArguments().get(0).getUpperBounds().getOnly().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(v.getUpperBounds().get(0).getTypeArguments().get(0).getUpperBounds().getOnly().getDeclaredAnnotations().size(), is(1));
+ assertThat(v.getUpperBounds().get(0).getTypeArguments().get(0).getUpperBounds().getOnly().getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(v.getUpperBounds().get(0).getTypeArguments().get(0).getUpperBounds().getOnly().getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(9));
+ assertThat(v.getUpperBounds().get(0).getTypeArguments().get(1).getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(v.getUpperBounds().get(0).getTypeArguments().get(1).getDeclaredAnnotations().size(), is(1));
+ assertThat(v.getUpperBounds().get(0).getTypeArguments().get(1).getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(v.getUpperBounds().get(0).getTypeArguments().get(1).getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(10));
+ assertThat(v.getUpperBounds().get(0).getTypeArguments().get(1).getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.WILDCARD));
+ assertThat(v.getUpperBounds().get(0).getTypeArguments().get(1).getTypeArguments().getOnly().getDeclaredAnnotations().size(), is(1));
+ assertThat(v.getUpperBounds().get(0).getTypeArguments().get(1).getTypeArguments().getOnly().getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(v.getUpperBounds().get(0).getTypeArguments().get(1).getTypeArguments().getOnly().getDeclaredAnnotations().ofType(typeAnnotation)
+ .getValue(value).resolve(Integer.class), is(11));
+ assertThat(v.getUpperBounds().get(0).getTypeArguments().get(1).getTypeArguments().getOnly().getLowerBounds().getOnly().getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(v.getUpperBounds().get(0).getTypeArguments().get(1).getTypeArguments().getOnly().getLowerBounds().getOnly().getDeclaredAnnotations().size(), is(1));
+ assertThat(v.getUpperBounds().get(0).getTypeArguments().get(1).getTypeArguments().getOnly().getLowerBounds().getOnly().getDeclaredAnnotations()
+ .isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(v.getUpperBounds().get(0).getTypeArguments().get(1).getTypeArguments().getOnly().getLowerBounds().getOnly().getDeclaredAnnotations()
+ .ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(12));
+ assertThat(v.getUpperBounds().get(0).getTypeArguments().get(1).getTypeArguments().getOnly().getLowerBounds().getOnly().getUpperBounds().get(0)
+ .getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(v.getUpperBounds().get(0).getTypeArguments().get(1).getTypeArguments().getOnly().getLowerBounds().getOnly().getUpperBounds().get(0)
+ .getDeclaredAnnotations().size(), is(0));
+ assertThat(v.getUpperBounds().get(0).getTypeArguments().get(1).getTypeArguments().getOnly().getLowerBounds().getOnly().getUpperBounds().get(1)
+ .getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(v.getUpperBounds().get(0).getTypeArguments().get(1).getTypeArguments().getOnly().getLowerBounds().getOnly().getUpperBounds().get(1)
+ .getDeclaredAnnotations().size(), is(1));
+ assertThat(v.getUpperBounds().get(0).getTypeArguments().get(1).getTypeArguments().getOnly().getLowerBounds().getOnly().getUpperBounds().get(1)
+ .getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(v.getUpperBounds().get(0).getTypeArguments().get(1).getTypeArguments().getOnly().getLowerBounds().getOnly().getUpperBounds().get(1)
+ .getDeclaredAnnotations().getOnly().prepare(typeAnnotation).getValue(value).resolve(Integer.class), is(3));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testTypeVariableW() throws Exception {
+ Class<? extends Annotation> typeAnnotation = (Class<? extends Annotation>) Class.forName(TYPE_ANNOTATION);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotation).getDeclaredMethods().getOnly();
+ TypeDescription typeDescription = describe(Class.forName(TYPE_ANNOTATION_SAMPLES));
+ TypeDescription.Generic t = typeDescription.getTypeVariables().filter(named(W)).getOnly();
+ assertThat(t.getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(t.getDeclaredAnnotations().size(), is(1));
+ assertThat(t.getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(t.getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(13));
+ assertThat(t.getUpperBounds().size(), is(1));
+ assertThat(t.getUpperBounds().getOnly().getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(t.getUpperBounds().getOnly().getDeclaredAnnotations().size(), is(1));
+ assertThat(t.getUpperBounds().getOnly().getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(t.getUpperBounds().getOnly().getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(14));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testTypeVariableX() throws Exception {
+ Class<? extends Annotation> typeAnnotation = (Class<? extends Annotation>) Class.forName(TYPE_ANNOTATION);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotation).getDeclaredMethods().getOnly();
+ TypeDescription typeDescription = describe(Class.forName(TYPE_ANNOTATION_SAMPLES));
+ TypeDescription.Generic t = typeDescription.getTypeVariables().filter(named(X)).getOnly();
+ assertThat(t.getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(t.getDeclaredAnnotations().size(), is(1));
+ assertThat(t.getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(t.getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(15));
+ assertThat(t.getUpperBounds().size(), is(1));
+ assertThat(t.getUpperBounds().getOnly().getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(t.getUpperBounds().getOnly().getDeclaredAnnotations().size(), is(1));
+ assertThat(t.getUpperBounds().getOnly().getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(t.getUpperBounds().getOnly().getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(16));
+ assertThat(t.getUpperBounds().getOnly().getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.WILDCARD));
+ assertThat(t.getUpperBounds().getOnly().getTypeArguments().getOnly().getDeclaredAnnotations().size(), is(1));
+ assertThat(t.getUpperBounds().getOnly().getTypeArguments().getOnly().getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(t.getUpperBounds().getOnly().getTypeArguments().getOnly().getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(17));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ @JavaVersionRule.Enforce(8)
+ public void testMethodVariableT() throws Exception {
+ Class<? extends Annotation> typeAnnotation = (Class<? extends Annotation>) Class.forName(TYPE_ANNOTATION);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotation).getDeclaredMethods().getOnly();
+ MethodDescription methodDescription = describe(Class.forName(TYPE_ANNOTATION_SAMPLES)).getDeclaredMethods().filter(named(FOO)).getOnly();
+ TypeDescription.Generic t = methodDescription.getTypeVariables().getOnly();
+ assertThat(t.getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(t.getDeclaredAnnotations().size(), is(1));
+ assertThat(t.getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(t.getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(26));
+ assertThat(t.getUpperBounds().getOnly().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(t.getUpperBounds().getOnly().getDeclaredAnnotations().size(), is(1));
+ assertThat(t.getUpperBounds().getOnly().getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(t.getUpperBounds().getOnly().getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(27));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ @JavaVersionRule.Enforce(8)
+ public void testNonGenericTypeAnnotationReceiverTypeOnMethod() throws Exception {
+ Class<? extends Annotation> typeAnnotation = (Class<? extends Annotation>) Class.forName(TYPE_ANNOTATION);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotation).getDeclaredMethods().getOnly();
+ TypeDescription.Generic receiverType = describe(Class.forName(RECEIVER_TYPE_SAMPLE))
+ .getDeclaredMethods()
+ .filter(named(FOO))
+ .getOnly()
+ .getReceiverType();
+ assertThat(receiverType, notNullValue(TypeDescription.Generic.class));
+ assertThat(receiverType.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(receiverType.represents(Class.forName(RECEIVER_TYPE_SAMPLE)), is(true));
+ assertThat(receiverType.getDeclaredAnnotations().size(), is(1));
+ assertThat(receiverType.getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(receiverType.getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(0));
+ assertThat(receiverType.getOwnerType(), nullValue(TypeDescription.Generic.class));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ @JavaVersionRule.Enforce(8)
+ public void testNonGenericTypeAnnotationReceiverTypeOnConstructor() throws Exception {
+ TypeDescription.Generic receiverType = describe(Class.forName(RECEIVER_TYPE_SAMPLE))
+ .getDeclaredMethods()
+ .filter(isConstructor())
+ .getOnly()
+ .getReceiverType();
+ assertThat(receiverType, notNullValue(TypeDescription.Generic.class));
+ assertThat(receiverType.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(receiverType.represents(Class.forName(RECEIVER_TYPE_SAMPLE)), is(true));
+ assertThat(receiverType.getDeclaredAnnotations().size(), is(0));
+ assertThat(receiverType.getOwnerType(), nullValue(TypeDescription.Generic.class));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ @JavaVersionRule.Enforce(8)
+ public void testNonGenericInnerTypeAnnotationReceiverTypeOnMethod() throws Exception {
+ Class<? extends Annotation> typeAnnotation = (Class<? extends Annotation>) Class.forName(TYPE_ANNOTATION);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotation).getDeclaredMethods().getOnly();
+ TypeDescription.Generic receiverType = describe(Class.forName(RECEIVER_TYPE_SAMPLE + "$" + INNER))
+ .getDeclaredMethods()
+ .filter(named(FOO))
+ .getOnly()
+ .getReceiverType();
+ assertThat(receiverType, notNullValue(TypeDescription.Generic.class));
+ assertThat(receiverType.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(receiverType.represents(Class.forName(RECEIVER_TYPE_SAMPLE + "$" + INNER)), is(true));
+ assertThat(receiverType.getDeclaredAnnotations().size(), is(1));
+ assertThat(receiverType.getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(receiverType.getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(1));
+ assertThat(receiverType.getOwnerType().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(receiverType.getOwnerType().represents(Class.forName(RECEIVER_TYPE_SAMPLE)), is(true));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ @JavaVersionRule.Enforce(8)
+ public void testNonGenericInnerTypeAnnotationReceiverTypeOnConstructor() throws Exception {
+ Class<? extends Annotation> typeAnnotation = (Class<? extends Annotation>) Class.forName(TYPE_ANNOTATION);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotation).getDeclaredMethods().getOnly();
+ TypeDescription.Generic receiverType = describe(Class.forName(RECEIVER_TYPE_SAMPLE + "$" + INNER))
+ .getDeclaredMethods()
+ .filter(isConstructor())
+ .getOnly()
+ .getReceiverType();
+ assertThat(receiverType, notNullValue(TypeDescription.Generic.class));
+ assertThat(receiverType.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(receiverType.represents(Class.forName(RECEIVER_TYPE_SAMPLE)), is(true));
+ assertThat(receiverType.getDeclaredAnnotations().size(), is(1));
+ assertThat(receiverType.getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(receiverType.getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(2));
+ assertThat(receiverType.getOwnerType(), nullValue(TypeDescription.Generic.class));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ @JavaVersionRule.Enforce(8)
+ public void testNonGenericNestedTypeAnnotationReceiverTypeOnMethod() throws Exception {
+ Class<? extends Annotation> typeAnnotation = (Class<? extends Annotation>) Class.forName(TYPE_ANNOTATION);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotation).getDeclaredMethods().getOnly();
+ TypeDescription.Generic receiverType = describe(Class.forName(RECEIVER_TYPE_SAMPLE + "$" + NESTED))
+ .getDeclaredMethods()
+ .filter(named(FOO))
+ .getOnly()
+ .getReceiverType();
+ assertThat(receiverType, notNullValue(TypeDescription.Generic.class));
+ assertThat(receiverType.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(receiverType.represents(Class.forName(RECEIVER_TYPE_SAMPLE + "$" + NESTED)), is(true));
+ assertThat(receiverType.getDeclaredAnnotations().size(), is(1));
+ assertThat(receiverType.getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(receiverType.getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(3));
+ assertThat(receiverType.getOwnerType().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(receiverType.getOwnerType().represents(Class.forName(RECEIVER_TYPE_SAMPLE)), is(true));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ @JavaVersionRule.Enforce(8)
+ public void testNonGenericNestedTypeAnnotationReceiverTypeOnConstructor() throws Exception {
+ TypeDescription.Generic receiverType = describe(Class.forName(RECEIVER_TYPE_SAMPLE + "$" + NESTED))
+ .getDeclaredMethods()
+ .filter(isConstructor())
+ .getOnly()
+ .getReceiverType();
+ assertThat(receiverType, notNullValue(TypeDescription.Generic.class));
+ assertThat(receiverType.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(receiverType.represents(Class.forName(RECEIVER_TYPE_SAMPLE)), is(true));
+ assertThat(receiverType.getDeclaredAnnotations().size(), is(0));
+ assertThat(receiverType.getOwnerType(), nullValue(TypeDescription.Generic.class));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ @JavaVersionRule.Enforce(8)
+ public void testGenericTypeAnnotationReceiverTypeOnMethod() throws Exception {
+ Class<? extends Annotation> typeAnnotation = (Class<? extends Annotation>) Class.forName(TYPE_ANNOTATION);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotation).getDeclaredMethods().getOnly();
+ TypeDescription.Generic receiverType = describe(Class.forName(RECEIVER_TYPE_SAMPLE + "$" + GENERIC))
+ .getDeclaredMethods()
+ .filter(named(FOO))
+ .getOnly()
+ .getReceiverType();
+ assertThat(receiverType, notNullValue(TypeDescription.Generic.class));
+ assertThat(receiverType.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(receiverType.asErasure().represents(Class.forName(RECEIVER_TYPE_SAMPLE + "$" + GENERIC)), is(true));
+ assertThat(receiverType.getDeclaredAnnotations().size(), is(1));
+ assertThat(receiverType.getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(receiverType.getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(4));
+ assertThat(receiverType.getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(receiverType.getTypeArguments().getOnly().getSymbol(), is(T));
+ assertThat(receiverType.getTypeArguments().getOnly().getDeclaredAnnotations().size(), is(1));
+ assertThat(receiverType.getTypeArguments().getOnly().getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(receiverType.getTypeArguments().getOnly().getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(5));
+ assertThat(receiverType.getOwnerType().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(receiverType.getOwnerType().represents(Class.forName(RECEIVER_TYPE_SAMPLE)), is(true));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ @JavaVersionRule.Enforce(8)
+ public void testGenericTypeAnnotationReceiverTypeOnConstructor() throws Exception {
+ TypeDescription.Generic receiverType = describe(Class.forName(RECEIVER_TYPE_SAMPLE + "$" + GENERIC))
+ .getDeclaredMethods()
+ .filter(isConstructor())
+ .getOnly()
+ .getReceiverType();
+ assertThat(receiverType, notNullValue(TypeDescription.Generic.class));
+ assertThat(receiverType.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(receiverType.asErasure().represents(Class.forName(RECEIVER_TYPE_SAMPLE)), is(true));
+ assertThat(receiverType.getDeclaredAnnotations().size(), is(0));
+ assertThat(receiverType.getOwnerType(), nullValue(TypeDescription.Generic.class));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ @JavaVersionRule.Enforce(8)
+ public void testGenericInnerTypeAnnotationReceiverTypeOnMethod() throws Exception {
+ Class<? extends Annotation> typeAnnotation = (Class<? extends Annotation>) Class.forName(TYPE_ANNOTATION);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotation).getDeclaredMethods().getOnly();
+ TypeDescription.Generic receiverType = describe(Class.forName(RECEIVER_TYPE_SAMPLE + "$" + GENERIC + "$" + INNER))
+ .getDeclaredMethods()
+ .filter(named(FOO))
+ .getOnly()
+ .getReceiverType();
+ assertThat(receiverType, notNullValue(TypeDescription.Generic.class));
+ assertThat(receiverType.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(receiverType.asErasure().represents(Class.forName(RECEIVER_TYPE_SAMPLE + "$" + GENERIC + "$" + INNER)), is(true));
+ assertThat(receiverType.getDeclaredAnnotations().size(), is(1));
+ assertThat(receiverType.getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(receiverType.getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(8));
+ assertThat(receiverType.getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(receiverType.getTypeArguments().getOnly().getSymbol(), is(S));
+ assertThat(receiverType.getTypeArguments().getOnly().getDeclaredAnnotations().size(), is(1));
+ assertThat(receiverType.getTypeArguments().getOnly().getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(receiverType.getTypeArguments().getOnly().getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(9));
+ assertThat(receiverType.getOwnerType().getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(receiverType.getOwnerType().asErasure().represents(Class.forName(RECEIVER_TYPE_SAMPLE + "$" + GENERIC)), is(true));
+ assertThat(receiverType.getOwnerType().getDeclaredAnnotations().size(), is(1));
+ assertThat(receiverType.getOwnerType().getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(receiverType.getOwnerType().getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(6));
+ assertThat(receiverType.getOwnerType().getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(receiverType.getOwnerType().getTypeArguments().getOnly().getSymbol(), is(T));
+ assertThat(receiverType.getOwnerType().getTypeArguments().getOnly().getDeclaredAnnotations().size(), is(1));
+ assertThat(receiverType.getOwnerType().getTypeArguments().getOnly().getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(receiverType.getOwnerType().getTypeArguments().getOnly().getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(7));
+ assertThat(receiverType.getOwnerType().getOwnerType().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(receiverType.getOwnerType().getOwnerType().represents(Class.forName(RECEIVER_TYPE_SAMPLE)), is(true));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ @JavaVersionRule.Enforce(8)
+ public void testGenericInnerTypeAnnotationReceiverTypeOnConstructor() throws Exception {
+ Class<? extends Annotation> typeAnnotation = (Class<? extends Annotation>) Class.forName(TYPE_ANNOTATION);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotation).getDeclaredMethods().getOnly();
+ TypeDescription.Generic receiverType = describe(Class.forName(RECEIVER_TYPE_SAMPLE + "$" + GENERIC + "$" + INNER))
+ .getDeclaredMethods()
+ .filter(isConstructor())
+ .getOnly()
+ .getReceiverType();
+ assertThat(receiverType, notNullValue(TypeDescription.Generic.class));
+ assertThat(receiverType.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(receiverType.asErasure().represents(Class.forName(RECEIVER_TYPE_SAMPLE + "$" + GENERIC)), is(true));
+ assertThat(receiverType.getDeclaredAnnotations().size(), is(1));
+ assertThat(receiverType.getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(receiverType.getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(10));
+ assertThat(receiverType.getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(receiverType.getTypeArguments().getOnly().getSymbol(), is(T));
+ assertThat(receiverType.getTypeArguments().getOnly().getDeclaredAnnotations().size(), is(1));
+ assertThat(receiverType.getTypeArguments().getOnly().getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(receiverType.getTypeArguments().getOnly().getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(11));
+ assertThat(receiverType.getOwnerType().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(receiverType.getOwnerType().represents(Class.forName(RECEIVER_TYPE_SAMPLE)), is(true));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ @JavaVersionRule.Enforce(8)
+ public void testGenericNestedTypeAnnotationReceiverTypeOnMethod() throws Exception {
+ Class<? extends Annotation> typeAnnotation = (Class<? extends Annotation>) Class.forName(TYPE_ANNOTATION);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotation).getDeclaredMethods().getOnly();
+ TypeDescription.Generic receiverType = describe(Class.forName(RECEIVER_TYPE_SAMPLE + "$" + GENERIC + "$" + NESTED))
+ .getDeclaredMethods()
+ .filter(named(FOO))
+ .getOnly()
+ .getReceiverType();
+ assertThat(receiverType, notNullValue(TypeDescription.Generic.class));
+ assertThat(receiverType.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(receiverType.asErasure().represents(Class.forName(RECEIVER_TYPE_SAMPLE + "$" + GENERIC + "$" + NESTED)), is(true));
+ assertThat(receiverType.getDeclaredAnnotations().size(), is(1));
+ assertThat(receiverType.getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(receiverType.getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(12));
+ assertThat(receiverType.getTypeArguments().getOnly().getDeclaredAnnotations().size(), is(1));
+ assertThat(receiverType.getTypeArguments().getOnly().getDeclaredAnnotations().isAnnotationPresent(typeAnnotation), is(true));
+ assertThat(receiverType.getTypeArguments().getOnly().getDeclaredAnnotations().ofType(typeAnnotation).getValue(value).resolve(Integer.class), is(13));
+ assertThat(receiverType.getOwnerType().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(receiverType.getOwnerType().represents(Class.forName(RECEIVER_TYPE_SAMPLE + "$" + GENERIC)), is(true));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ @JavaVersionRule.Enforce(8)
+ public void testGenericNestedTypeAnnotationReceiverTypeOnConstructor() throws Exception {
+ TypeDescription.Generic receiverType = describe(Class.forName(RECEIVER_TYPE_SAMPLE + "$" + GENERIC + "$" + NESTED))
+ .getDeclaredMethods()
+ .filter(isConstructor())
+ .getOnly()
+ .getReceiverType();
+ assertThat(receiverType, notNullValue(TypeDescription.Generic.class));
+ assertThat(receiverType.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(receiverType.asErasure().represents(Class.forName(RECEIVER_TYPE_SAMPLE + "$" + GENERIC)), is(true));
+ assertThat(receiverType.getDeclaredAnnotations().size(), is(0));
+ assertThat(receiverType.getOwnerType().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(receiverType.getOwnerType().represents(Class.forName(RECEIVER_TYPE_SAMPLE)), is(true));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/AbstractTypeDescriptionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/AbstractTypeDescriptionTest.java
new file mode 100644
index 0000000..ea1ede3
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/AbstractTypeDescriptionTest.java
@@ -0,0 +1,791 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.description.TypeVariableSource;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.dynamic.loading.ByteArrayClassLoader;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.test.packaging.SimpleType;
+import net.bytebuddy.test.scope.EnclosingType;
+import net.bytebuddy.test.utility.ClassFileExtraction;
+import net.bytebuddy.test.visibility.Sample;
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.Matcher;
+import org.junit.Test;
+import org.objectweb.asm.*;
+
+import java.io.Serializable;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Array;
+import java.lang.reflect.GenericSignatureFormatError;
+import java.util.*;
+import java.util.concurrent.Callable;
+
+import static net.bytebuddy.matcher.ElementMatchers.isMethod;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public abstract class AbstractTypeDescriptionTest extends AbstractTypeDescriptionGenericVariableDefiningTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ private final List<Class<?>> standardTypes;
+
+ @SuppressWarnings({"unchecked", "deprecation"})
+ protected AbstractTypeDescriptionTest() {
+ standardTypes = Arrays.asList(
+ Object.class,
+ Object[].class,
+ SampleClass.class,
+ SampleClass[].class,
+ SampleInterface.class,
+ SampleInterface[].class,
+ SampleAnnotation.class,
+ SampleAnnotation[].class,
+ void.class,
+ byte.class,
+ byte[].class,
+ short.class,
+ short[].class,
+ char.class,
+ char[].class,
+ int.class,
+ int[].class,
+ long.class,
+ long[].class,
+ float.class,
+ float[].class,
+ double.class,
+ double[].class,
+ new EnclosingType().localMethod,
+ Array.newInstance(new EnclosingType().localConstructor, 1).getClass(),
+ new EnclosingType().anonymousMethod,
+ Array.newInstance(new EnclosingType().anonymousMethod, 1).getClass(),
+ new EnclosingType().localConstructor,
+ Array.newInstance(new EnclosingType().localConstructor, 1).getClass(),
+ new EnclosingType().anonymousConstructor,
+ Array.newInstance(new EnclosingType().anonymousConstructor, 1).getClass(),
+ EnclosingType.LOCAL_INITIALIZER,
+ Array.newInstance(EnclosingType.LOCAL_INITIALIZER.getClass(), 1).getClass(),
+ EnclosingType.ANONYMOUS_INITIALIZER,
+ Array.newInstance(EnclosingType.ANONYMOUS_INITIALIZER, 1).getClass(),
+ EnclosingType.LOCAL_METHOD,
+ Array.newInstance(EnclosingType.LOCAL_METHOD.getClass(), 1).getClass(),
+ EnclosingType.ANONYMOUS_METHOD,
+ Array.newInstance(EnclosingType.ANONYMOUS_METHOD, 1).getClass(),
+ EnclosingType.INNER,
+ Array.newInstance(EnclosingType.INNER, 1).getClass(),
+ EnclosingType.NESTED,
+ Array.newInstance(EnclosingType.NESTED, 1).getClass(),
+ EnclosingType.PRIVATE_INNER,
+ Array.newInstance(EnclosingType.PRIVATE_INNER, 1).getClass(),
+ EnclosingType.PRIVATE_NESTED,
+ Array.newInstance(EnclosingType.PRIVATE_NESTED, 1).getClass(),
+ EnclosingType.PROTECTED_INNER,
+ Array.newInstance(EnclosingType.PROTECTED_INNER, 1).getClass(),
+ EnclosingType.PROTECTED_NESTED,
+ Array.newInstance(EnclosingType.PROTECTED_NESTED, 1).getClass(),
+ EnclosingType.PACKAGE_INNER,
+ Array.newInstance(EnclosingType.PACKAGE_INNER, 1).getClass(),
+ EnclosingType.PACKAGE_NESTED,
+ Array.newInstance(EnclosingType.PACKAGE_NESTED, 1).getClass(),
+ EnclosingType.FINAL_NESTED,
+ Array.newInstance(EnclosingType.FINAL_NESTED, 1).getClass(),
+ EnclosingType.FINAL_INNER,
+ Array.newInstance(EnclosingType.FINAL_INNER, 1).getClass(),
+ EnclosingType.DEPRECATED,
+ Array.newInstance(EnclosingType.DEPRECATED, 1).getClass());
+ }
+
+ @Test
+ public void testPrecondition() throws Exception {
+ assertThat(describe(SampleClass.class), not(describe(SampleInterface.class)));
+ assertThat(describe(SampleClass.class), not(describe(SampleAnnotation.class)));
+ assertThat(describe(SampleClass.class), is(describe(SampleClass.class)));
+ assertThat(describe(SampleInterface.class), is(describe(SampleInterface.class)));
+ assertThat(describe(SampleAnnotation.class), is(describe(SampleAnnotation.class)));
+ assertThat(describe(SampleClass.class), is((TypeDescription) new TypeDescription.ForLoadedType(SampleClass.class)));
+ assertThat(describe(SampleInterface.class), is((TypeDescription) new TypeDescription.ForLoadedType(SampleInterface.class)));
+ assertThat(describe(SampleAnnotation.class), is((TypeDescription) new TypeDescription.ForLoadedType(SampleAnnotation.class)));
+ }
+
+ @Test
+ public void testStackSize() throws Exception {
+ assertThat(describe(void.class).getStackSize(), is(StackSize.ZERO));
+ assertThat(describe(boolean.class).getStackSize(), is(StackSize.SINGLE));
+ assertThat(describe(byte.class).getStackSize(), is(StackSize.SINGLE));
+ assertThat(describe(short.class).getStackSize(), is(StackSize.SINGLE));
+ assertThat(describe(char.class).getStackSize(), is(StackSize.SINGLE));
+ assertThat(describe(int.class).getStackSize(), is(StackSize.SINGLE));
+ assertThat(describe(long.class).getStackSize(), is(StackSize.DOUBLE));
+ assertThat(describe(float.class).getStackSize(), is(StackSize.SINGLE));
+ assertThat(describe(double.class).getStackSize(), is(StackSize.DOUBLE));
+ assertThat(describe(Object.class).getStackSize(), is(StackSize.SINGLE));
+ assertThat(describe(SampleClass.class).getStackSize(), is(StackSize.SINGLE));
+ assertThat(describe(Object[].class).getStackSize(), is(StackSize.SINGLE));
+ assertThat(describe(long[].class).getStackSize(), is(StackSize.SINGLE));
+ }
+
+ @Test
+ public void testName() throws Exception {
+ for (Class<?> type : standardTypes) {
+ assertThat(describe(type).getName(), is(type.getName()));
+ }
+ }
+
+ @Test
+ public void testSourceName() throws Exception {
+ for (Class<?> type : standardTypes) {
+ if (type.isArray()) {
+ assertThat(describe(type).getActualName(), is(type.getComponentType().getName() + "[]"));
+ } else {
+ assertThat(describe(type).getActualName(), is(type.getName()));
+ }
+ }
+ }
+
+ @Test
+ public void testInternalName() throws Exception {
+ for (Class<?> type : standardTypes) {
+ assertThat(describe(type).getInternalName(), is(Type.getInternalName(type)));
+ }
+ }
+
+ @Test
+ public void testCanonicalName() throws Exception {
+ for (Class<?> type : standardTypes) {
+ assertThat(describe(type).getCanonicalName(), is(type.getCanonicalName()));
+ }
+ }
+
+ @Test
+ public void testSimpleName() throws Exception {
+ for (Class<?> type : standardTypes) {
+ assertThat(describe(type).getSimpleName(), is(type.getSimpleName()));
+ }
+ }
+
+ @Test
+ public void testIsMemberClass() throws Exception {
+ for (Class<?> type : standardTypes) {
+ assertThat(describe(type).isMemberClass(), is(type.isMemberClass()));
+ }
+ }
+
+ @Test
+ public void testIsAnonymousClass() throws Exception {
+ for (Class<?> type : standardTypes) {
+ assertThat(describe(type).isAnonymousClass(), is(type.isAnonymousClass()));
+ }
+ }
+
+ @Test
+ public void testIsLocalClass() throws Exception {
+ for (Class<?> type : standardTypes) {
+ assertThat(describe(type).isLocalClass(), is(type.isLocalClass()));
+ }
+ }
+
+ @Test
+ public void testJavaName() throws Exception {
+ assertThat(describe(Object.class).getActualName(), is(Object.class.getName()));
+ assertThat(describe(SampleClass.class).getActualName(), is(SampleClass.class.getName()));
+ assertThat(describe(void.class).getActualName(), is(void.class.getName()));
+ assertThat(describe(boolean.class).getActualName(), is(boolean.class.getName()));
+ assertThat(describe(byte.class).getActualName(), is(byte.class.getName()));
+ assertThat(describe(short.class).getActualName(), is(short.class.getName()));
+ assertThat(describe(char.class).getActualName(), is(char.class.getName()));
+ assertThat(describe(int.class).getActualName(), is(int.class.getName()));
+ assertThat(describe(long.class).getActualName(), is(long.class.getName()));
+ assertThat(describe(float.class).getActualName(), is(float.class.getName()));
+ assertThat(describe(double.class).getActualName(), is(double.class.getName()));
+ assertThat(describe(Object[].class).getActualName(), is(Object.class.getName() + "[]"));
+ assertThat(describe(SampleClass[].class).getActualName(), is(SampleClass.class.getName() + "[]"));
+ assertThat(describe(Object[][].class).getActualName(), is(Object.class.getName() + "[][]"));
+ assertThat(describe(boolean[].class).getActualName(), is(boolean.class.getName() + "[]"));
+ assertThat(describe(byte[].class).getActualName(), is(byte.class.getName() + "[]"));
+ assertThat(describe(short[].class).getActualName(), is(short.class.getName() + "[]"));
+ assertThat(describe(char[].class).getActualName(), is(char.class.getName() + "[]"));
+ assertThat(describe(int[].class).getActualName(), is(int.class.getName() + "[]"));
+ assertThat(describe(long[].class).getActualName(), is(long.class.getName() + "[]"));
+ assertThat(describe(float[].class).getActualName(), is(float.class.getName() + "[]"));
+ assertThat(describe(double[].class).getActualName(), is(double.class.getName() + "[]"));
+ }
+
+ @Test
+ public void testDescriptor() throws Exception {
+ for (Class<?> type : standardTypes) {
+ assertThat(describe(type).getDescriptor(), is(Type.getDescriptor(type)));
+ }
+ }
+
+ @Test
+ public void testModifiers() throws Exception {
+ for (Class<?> type : standardTypes) {
+ assertThat(describe(type).getModifiers(), is(type.getModifiers()));
+ }
+ }
+
+ @Test
+ public void testDeclaringType() throws Exception {
+ for (Class<?> type : standardTypes) {
+ assertThat(describe(type).getDeclaringType(), type.getDeclaringClass() == null
+ ? nullValue(TypeDescription.class)
+ : is((TypeDescription) new TypeDescription.ForLoadedType(type.getDeclaringClass())));
+ }
+ }
+
+ @Test
+ public void testEnclosingMethod() throws Exception {
+ for (Class<?> type : standardTypes) {
+ Matcher<MethodDescription> matcher;
+ if (type.getEnclosingMethod() != null) {
+ matcher = CoreMatchers.<MethodDescription>is(new MethodDescription.ForLoadedMethod(type.getEnclosingMethod()));
+ } else if (type.getEnclosingConstructor() != null) {
+ matcher = CoreMatchers.<MethodDescription>is(new MethodDescription.ForLoadedConstructor(type.getEnclosingConstructor()));
+ } else {
+ matcher = nullValue(MethodDescription.class);
+ }
+ assertThat(describe(type).getEnclosingMethod(), matcher);
+ }
+ }
+
+ @Test
+ public void testEnclosingType() throws Exception {
+ for (Class<?> type : standardTypes) {
+ assertThat(describe(type).getEnclosingType(), type.getEnclosingClass() == null
+ ? nullValue(TypeDescription.class)
+ : is((TypeDescription) new TypeDescription.ForLoadedType(type.getEnclosingClass())));
+ }
+ }
+
+ @Test
+ public void testHashCode() throws Exception {
+ assertThat(describe(SampleClass.class).hashCode(), is(SampleClass.class.getName().hashCode()));
+ assertThat(describe(SampleInterface.class).hashCode(), is(SampleInterface.class.getName().hashCode()));
+ assertThat(describe(SampleAnnotation.class).hashCode(), is(SampleAnnotation.class.getName().hashCode()));
+ assertThat(describe(SampleClass.class).hashCode(), is(describe(SampleClass.class).hashCode()));
+ assertThat(describe(SampleClass.class).hashCode(), not(describe(SampleInterface.class).hashCode()));
+ assertThat(describe(SampleClass.class).hashCode(), not(describe(SampleAnnotation.class).hashCode()));
+ assertThat(describe(Object[].class).hashCode(), is(describe(Object[].class).hashCode()));
+ assertThat(describe(Object[].class).hashCode(), not(describe(Object.class).hashCode()));
+ assertThat(describe(void.class).hashCode(), is(void.class.getName().hashCode()));
+ }
+
+ @Test
+ public void testEquals() throws Exception {
+ TypeDescription identical = describe(SampleClass.class);
+ assertThat(identical, is(identical));
+ TypeDescription equalFirst = mock(TypeDescription.class);
+ when(equalFirst.getSort()).thenReturn(TypeDefinition.Sort.NON_GENERIC);
+ when(equalFirst.asErasure()).thenReturn(equalFirst);
+ when(equalFirst.getName()).thenReturn(SampleClass.class.getName());
+ assertThat(describe(SampleClass.class), is(equalFirst));
+ assertThat(describe(SampleClass.class), not(describe(SampleInterface.class)));
+ assertThat(describe(SampleClass.class), not((TypeDescription) new TypeDescription.ForLoadedType(SampleInterface.class)));
+ TypeDefinition nonRawType = mock(TypeDescription.Generic.class);
+ when(nonRawType.getSort()).thenReturn(TypeDefinition.Sort.VARIABLE);
+ assertThat(describe(SampleClass.class), not(nonRawType));
+ assertThat(describe(SampleClass.class), not(new Object()));
+ assertThat(describe(SampleClass.class), not(equalTo(null)));
+ assertThat(describe(Object[].class), is((TypeDescription) new TypeDescription.ForLoadedType(Object[].class)));
+ assertThat(describe(Object[].class), not(TypeDescription.OBJECT));
+ }
+
+ @Test
+ public void testIsInstance() throws Exception {
+ assertThat(describe(SampleClass.class).isInstance(new SampleClass()), is(true));
+ assertThat(describe(SampleClass.class).isInstance(new Object()), is(false));
+ assertThat(describe(SampleInterface.class).isInstance(new SampleInterfaceImplementation()), is(true));
+ assertThat(describe(Object[].class).isInstance(new Object[0]), is(true));
+ assertThat(describe(Object[].class).isInstance(new Object()), is(false));
+ }
+
+ @Test
+ public void testPackage() throws Exception {
+ assertThat(describe(SampleClass.class).getPackage(),
+ is((PackageDescription) new PackageDescription.ForLoadedPackage(SampleClass.class.getPackage())));
+ assertThat(describe(Object.class).getPackage(),
+ is((PackageDescription) new PackageDescription.ForLoadedPackage(Object.class.getPackage())));
+ assertThat(describe(Object[].class).getPackage(), nullValue(PackageDescription.class));
+ }
+
+ @Test
+ public void testActualModifiers() throws Exception {
+ assertThat(describe(SampleClass.class).getActualModifiers(true), is(SampleClass.class.getModifiers() | Opcodes.ACC_SUPER));
+ assertThat(describe(SampleClass.class).getActualModifiers(false), is(SampleClass.class.getModifiers()));
+ assertThat(describe(SampleInterface.class).getActualModifiers(true),
+ is((SampleInterface.class.getModifiers() & ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_STATIC) | Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER)));
+ assertThat(describe(SampleInterface.class).getActualModifiers(false),
+ is((SampleInterface.class.getModifiers() & ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_STATIC) | Opcodes.ACC_PUBLIC)));
+ assertThat(describe(SampleAnnotation.class).getActualModifiers(true),
+ is((SampleAnnotation.class.getModifiers() & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC)) | Opcodes.ACC_SUPER));
+ assertThat(describe(SampleAnnotation.class).getActualModifiers(false),
+ is((SampleAnnotation.class.getModifiers() & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC))));
+ assertThat(describe(SamplePackagePrivate.class).getActualModifiers(true),
+ is((SamplePackagePrivate.class.getModifiers() & ~(Opcodes.ACC_STATIC)) | Opcodes.ACC_SUPER));
+ assertThat(describe(SamplePackagePrivate.class).getActualModifiers(false),
+ is((SamplePackagePrivate.class.getModifiers() & ~(Opcodes.ACC_STATIC))));
+ }
+
+ @Test
+ public void testActualModifiersDeprecation() throws Exception {
+ assertThat(describe(EnclosingType.DEPRECATED).getActualModifiers(false), is(Opcodes.ACC_DEPRECATED));
+ assertThat(describe(EnclosingType.DEPRECATED).getActualModifiers(true), is(Opcodes.ACC_DEPRECATED | Opcodes.ACC_SUPER));
+ }
+
+ @Test
+ public void testSuperClass() throws Exception {
+ assertThat(describe(Object.class).getSuperClass(), nullValue(TypeDescription.Generic.class));
+ assertThat(describe(SampleInterface.class).getSuperClass(), nullValue(TypeDescription.Generic.class));
+ assertThat(describe(SampleAnnotation.class).getSuperClass(), nullValue(TypeDescription.Generic.class));
+ assertThat(describe(void.class).getSuperClass(), nullValue(TypeDescription.Generic.class));
+ assertThat(describe(SampleClass.class).getSuperClass(), is(TypeDescription.Generic.OBJECT));
+ assertThat(describe(SampleIndirectInterfaceImplementation.class).getSuperClass(),
+ is((TypeDefinition) new TypeDescription.ForLoadedType(SampleInterfaceImplementation.class)));
+ assertThat(describe(Object[].class).getSuperClass(), is(TypeDescription.Generic.OBJECT));
+ }
+
+ @Test
+ public void testInterfaces() throws Exception {
+ assertThat(describe(Object.class).getInterfaces(), is((TypeList.Generic) new TypeList.Generic.Empty()));
+ assertThat(describe(SampleInterface.class).getInterfaces(), is((TypeList.Generic) new TypeList.Generic.Empty()));
+ assertThat(describe(SampleAnnotation.class).getInterfaces(), is((TypeList.Generic) new TypeList.Generic.ForLoadedTypes(Annotation.class)));
+ assertThat(describe(SampleInterfaceImplementation.class).getInterfaces(),
+ is((TypeList.Generic) new TypeList.Generic.ForLoadedTypes(SampleInterface.class)));
+ assertThat(describe(SampleIndirectInterfaceImplementation.class).getInterfaces(), is((TypeList.Generic) new TypeList.Generic.Empty()));
+ assertThat(describe(SampleTransitiveInterfaceImplementation.class).getInterfaces(),
+ is((TypeList.Generic) new TypeList.Generic.ForLoadedTypes(SampleTransitiveInterface.class)));
+ assertThat(describe(Object[].class).getInterfaces(), is((TypeList.Generic) new TypeList.Generic.ForLoadedTypes(Cloneable.class, Serializable.class)));
+ }
+
+ @Test
+ public void testToString() throws Exception {
+ for (Class<?> type : standardTypes) {
+ assertThat(describe(type).toString(), is(type.toString()));
+ }
+ }
+
+ @Test
+ public void testIsAssignable() throws Exception {
+ assertThat(describe(Object.class).isAssignableTo(Object.class), is(true));
+ assertThat(describe(Object.class).isAssignableFrom(Object.class), is(true));
+ assertThat(describe(Object[].class).isAssignableTo(Object.class), is(true));
+ assertThat(describe(Object.class).isAssignableFrom(Object[].class), is(true));
+ assertThat(describe(Object[].class).isAssignableTo(Serializable.class), is(true));
+ assertThat(describe(Serializable.class).isAssignableFrom(Object[].class), is(true));
+ assertThat(describe(Object[].class).isAssignableTo(Cloneable.class), is(true));
+ assertThat(describe(Cloneable.class).isAssignableFrom(Object[].class), is(true));
+ assertThat(describe(String[].class).isAssignableTo(Object[].class), is(true));
+ assertThat(describe(Object[].class).isAssignableFrom(String[].class), is(true));
+ assertThat(describe(Object[].class).isAssignableFrom(String[][].class), is(true));
+ assertThat(describe(String[][].class).isAssignableTo(Object[].class), is(true));
+ assertThat(describe(String[].class).isAssignableFrom(String[][].class), is(false));
+ assertThat(describe(String[][].class).isAssignableTo(String[].class), is(false));
+ assertThat(describe(Cloneable[].class).isAssignableFrom(String[].class), is(false));
+ assertThat(describe(String[].class).isAssignableTo(Cloneable[].class), is(false));
+ assertThat(describe(Foo[].class).isAssignableFrom(String[].class), is(false));
+ assertThat(describe(String[].class).isAssignableTo(Foo[].class), is(false));
+ assertThat(describe(int.class).isAssignableTo(int.class), is(true));
+ assertThat(describe(int.class).isAssignableFrom(int.class), is(true));
+ assertThat(describe(void.class).isAssignableTo(void.class), is(true));
+ assertThat(describe(void.class).isAssignableFrom(void.class), is(true));
+ assertThat(describe(SampleInterfaceImplementation.class).isAssignableTo(SampleInterface.class), is(true));
+ assertThat(describe(SampleInterface.class).isAssignableFrom(SampleInterfaceImplementation.class), is(true));
+ assertThat(describe(SampleTransitiveInterfaceImplementation.class).isAssignableTo(SampleInterface.class), is(true));
+ assertThat(describe(SampleInterface.class).isAssignableFrom(SampleTransitiveInterfaceImplementation.class), is(true));
+ assertThat(describe(SampleInterface.class).isAssignableTo(Object.class), is(true));
+ assertThat(describe(Object.class).isAssignableFrom(SampleInterface.class), is(true));
+ assertThat(describe(SampleInterfaceImplementation.class).isAssignableTo(SampleClass.class), is(false));
+ assertThat(describe(SampleClass.class).isAssignableFrom(SampleInterfaceImplementation.class), is(false));
+ assertThat(describe(SampleInterfaceImplementation.class).isAssignableTo(boolean.class), is(false));
+ assertThat(describe(boolean.class).isAssignableFrom(SampleInterfaceImplementation.class), is(false));
+ assertThat(describe(boolean.class).isAssignableTo(Object.class), is(false));
+ assertThat(describe(Object.class).isAssignableFrom(boolean.class), is(false));
+ assertThat(describe(boolean[].class).isAssignableTo(Object.class), is(true));
+ assertThat(describe(Object.class).isAssignableFrom(boolean[].class), is(true));
+ assertThat(describe(boolean[].class).isAssignableTo(Object[].class), is(false));
+ assertThat(describe(Object[].class).isAssignableFrom(boolean[].class), is(false));
+ }
+
+ @Test
+ public void testIsAssignableClassLoader() throws Exception {
+ ClassLoader classLoader = new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER,
+ ClassFileExtraction.of(SimpleType.class),
+ ByteArrayClassLoader.PersistenceHandler.MANIFEST);
+ Class<?> otherSimpleType = classLoader.loadClass(SimpleType.class.getName());
+ assertThat(describe(SimpleType.class).isAssignableFrom(describe(otherSimpleType)), is(true));
+ assertThat(describe(SimpleType.class).isAssignableTo(describe(otherSimpleType)), is(true));
+ assertThat(describe(Object.class).isAssignableFrom(describe(otherSimpleType)), is(true));
+ assertThat(describe(otherSimpleType).isAssignableTo(describe(Object.class)), is(true));
+ }
+
+ @Test
+ public void testIsVisible() throws Exception {
+ assertThat(describe(SampleClass.class).isVisibleTo(new TypeDescription.ForLoadedType(SampleInterface.class)), is(true));
+ assertThat(describe(SamplePackagePrivate.class).isVisibleTo(new TypeDescription.ForLoadedType(SampleClass.class)), is(true));
+ assertThat(describe(SampleInterface.class).isVisibleTo(new TypeDescription.ForLoadedType(SampleClass.class)), is(true));
+ assertThat(describe(SampleAnnotation.class).isVisibleTo(new TypeDescription.ForLoadedType(SampleClass.class)), is(true));
+ assertThat(describe(SamplePackagePrivate.class).isVisibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(SampleInterface.class).isVisibleTo(TypeDescription.OBJECT), is(true));
+ assertThat(describe(SampleAnnotation.class).isVisibleTo(TypeDescription.OBJECT), is(false));
+ assertThat(describe(int.class).isVisibleTo(TypeDescription.OBJECT), is(true));
+ assertThat(describe(SampleInterface[].class).isVisibleTo(TypeDescription.OBJECT), is(true));
+ assertThat(describe(SamplePackagePrivate[].class).isVisibleTo(TypeDescription.OBJECT), is(false));
+ }
+
+ @Test
+ public void testAnnotations() throws Exception {
+ assertAnnotations(SampleClass.class);
+ assertAnnotations(SampleInterface.class);
+ assertAnnotations(SampleClassInherited.class);
+ assertAnnotations(SampleClassInheritedOverride.class);
+ }
+
+ private void assertAnnotations(Class<?> type) {
+ assertThat(describe(type).getDeclaredAnnotations(),
+ hasItems(new AnnotationList.ForLoadedAnnotations(type.getDeclaredAnnotations())
+ .toArray(new AnnotationDescription[type.getDeclaredAnnotations().length])));
+ assertThat(describe(type).getDeclaredAnnotations().size(), is(type.getDeclaredAnnotations().length));
+ assertThat(describe(type).getInheritedAnnotations(),
+ hasItems(new AnnotationList.ForLoadedAnnotations(type.getAnnotations())
+ .toArray(new AnnotationDescription[type.getAnnotations().length])));
+ assertThat(describe(type).getInheritedAnnotations().size(), is(type.getAnnotations().length));
+ }
+
+ @Test
+ public void testDeclaredTypes() throws Exception {
+ assertThat(describe(SampleClass.class).getDeclaredTypes().size(), is(0));
+ assertThat(describe(AbstractTypeDescriptionTest.class).getDeclaredTypes(),
+ is((TypeList) new TypeList.ForLoadedTypes(AbstractTypeDescriptionTest.class.getDeclaredClasses())));
+ }
+
+ @Test
+ public void testComponentType() throws Exception {
+ assertThat(describe(Object.class).getComponentType(), nullValue(Object.class));
+ assertThat(describe(Object[].class).getComponentType(), is(describe(Object.class)));
+ }
+
+ @Test
+ public void testWrapperType() throws Exception {
+ assertThat(describe(Object.class).isPrimitiveWrapper(), is(false));
+ assertThat(describe(Boolean.class).isPrimitiveWrapper(), is(true));
+ assertThat(describe(Byte.class).isPrimitiveWrapper(), is(true));
+ assertThat(describe(Short.class).isPrimitiveWrapper(), is(true));
+ assertThat(describe(Character.class).isPrimitiveWrapper(), is(true));
+ assertThat(describe(Integer.class).isPrimitiveWrapper(), is(true));
+ assertThat(describe(Long.class).isPrimitiveWrapper(), is(true));
+ assertThat(describe(Float.class).isPrimitiveWrapper(), is(true));
+ assertThat(describe(Double.class).isPrimitiveWrapper(), is(true));
+ assertThat(describe(Void.class).isPrimitiveWrapper(), is(false));
+ }
+
+ @Test
+ public void testConstantPool() throws Exception {
+ assertThat(describe(Object.class).isConstantPool(), is(false));
+ assertThat(describe(boolean.class).isConstantPool(), is(false));
+ assertThat(describe(int.class).isConstantPool(), is(true));
+ assertThat(describe(Integer.class).isConstantPool(), is(false));
+ assertThat(describe(String.class).isConstantPool(), is(true));
+ assertThat(describe(Class.class).isConstantPool(), is(true));
+ }
+
+ @Test
+ public void testGenericType() throws Exception {
+ assertThat(describe(SampleGenericType.class).getTypeVariables(), is(new TypeDescription.ForLoadedType(SampleGenericType.class).getTypeVariables()));
+ assertThat(describe(SampleGenericType.class).getSuperClass(), is(new TypeDescription.ForLoadedType(SampleGenericType.class).getSuperClass()));
+ assertThat(describe(SampleGenericType.class).getInterfaces(), is(new TypeDescription.ForLoadedType(SampleGenericType.class).getInterfaces()));
+ }
+
+ @Test
+ public void testHierarchyIteration() throws Exception {
+ Iterator<TypeDefinition> iterator = describe(Traversal.class).iterator();
+ assertThat(iterator.hasNext(), is(true));
+ assertThat(iterator.next(), is((TypeDefinition) new TypeDescription.ForLoadedType(Traversal.class)));
+ assertThat(iterator.hasNext(), is(true));
+ assertThat(iterator.next(), is((TypeDefinition) TypeDescription.OBJECT));
+ assertThat(iterator.hasNext(), is(false));
+ }
+
+ @Test(expected = NoSuchElementException.class)
+ public void testHierarchyEnds() throws Exception {
+ Iterator<TypeDefinition> iterator = describe(Object.class).iterator();
+ assertThat(iterator.hasNext(), is(true));
+ assertThat(iterator.next(), is((TypeDefinition) TypeDescription.OBJECT));
+ assertThat(iterator.hasNext(), is(false));
+ iterator.next();
+ }
+
+ @Test(expected = GenericSignatureFormatError.class)
+ public void testMalformedTypeSignature() throws Exception {
+ TypeDescription typeDescription = describe(SignatureMalformer.malform(MalformedBase.class));
+ assertThat(typeDescription.getInterfaces().size(), is(1));
+ typeDescription.getInterfaces().getOnly().getSort();
+ }
+
+ @Test(expected = GenericSignatureFormatError.class)
+ public void testMalformedFieldSignature() throws Exception {
+ TypeDescription typeDescription = describe(SignatureMalformer.malform(MalformedBase.class));
+ assertThat(typeDescription.getDeclaredFields().size(), is(1));
+ typeDescription.getDeclaredFields().getOnly().getType().getSort();
+ }
+
+ @Test(expected = GenericSignatureFormatError.class)
+ public void testMalformedMethodSignature() throws Exception {
+ TypeDescription typeDescription = describe(SignatureMalformer.malform(MalformedBase.class));
+ assertThat(typeDescription.getDeclaredMethods().filter(isMethod()).size(), is(1));
+ typeDescription.getDeclaredMethods().filter(isMethod()).getOnly().getReturnType().getSort();
+ }
+
+ @Test
+ public void testRepresents() throws Exception {
+ assertThat(describe(Object.class).represents(Object.class), is(true));
+ assertThat(describe(Object.class).represents(Serializable.class), is(false));
+ assertThat(describe(List.class).represents(SimpleParameterizedType.class.getDeclaredField(FOO).getGenericType()), is(false));
+ }
+
+ @Test
+ public void testNonAvailableAnnotations() throws Exception {
+ TypeDescription typeDescription = describe(new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER,
+ ClassFileExtraction.of(MissingAnnotations.class),
+ ByteArrayClassLoader.PersistenceHandler.MANIFEST).loadClass(MissingAnnotations.class.getName()));
+ assertThat(typeDescription.getDeclaredAnnotations().isAnnotationPresent(SampleAnnotation.class), is(false));
+ assertThat(typeDescription.getDeclaredFields().getOnly().getDeclaredAnnotations().isAnnotationPresent(SampleAnnotation.class), is(false));
+ assertThat(typeDescription.getDeclaredMethods().filter(isMethod()).getOnly().getDeclaredAnnotations().isAnnotationPresent(SampleAnnotation.class), is(false));
+ }
+
+ @Test
+ public void testIsPackageDescription() throws Exception {
+ assertThat(describe(Class.forName(Sample.class.getPackage().getName() + "." + PackageDescription.PACKAGE_CLASS_NAME)).isPackageType(), is(true));
+ assertThat(describe(Object.class).isPackageType(), is(false));
+ }
+
+ @Test
+ public void testEnclosingSource() throws Exception {
+ assertThat(describe(SampleClass.class).getEnclosingSource(), is((TypeVariableSource) describe(AbstractTypeDescriptionTest.class)));
+ assertThat(describe(Traversal.class).getEnclosingSource(), nullValue(TypeVariableSource.class));
+ }
+
+ @Test
+ public void testInMethodType() throws Exception {
+ assertThat(describe(inMethodClass()).getEnclosingMethod(),
+ is((TypeVariableSource) new MethodDescription.ForLoadedMethod(AbstractTypeDescriptionTest.class.getDeclaredMethod("inMethodClass"))));
+ assertThat(describe(inMethodClass()).getEnclosingSource(),
+ is((TypeVariableSource) new MethodDescription.ForLoadedMethod(AbstractTypeDescriptionTest.class.getDeclaredMethod("inMethodClass"))));
+ }
+
+ @Test
+ public void testEnclosingAndDeclaringType() throws Exception {
+ assertThat(describe(SampleClass.class).getEnclosingType(), is(describe(AbstractTypeDescriptionTest.class)));
+ assertThat(describe(SampleClass.class).getDeclaringType(), is(describe(AbstractTypeDescriptionTest.class)));
+ Class<?> anonymousType = new Object() {
+ /* empty */
+ }.getClass();
+ assertThat(describe(anonymousType).getEnclosingType(), is(describe(AbstractTypeDescriptionTest.class)));
+ assertThat(describe(anonymousType).getDeclaringType(), nullValue(TypeDescription.class));
+ }
+
+ @Test
+ public void testIsGenerified() throws Exception {
+ assertThat(describe(GenericSample.class).isGenerified(), is(true));
+ assertThat(describe(GenericSample.Inner.class).isGenerified(), is(true));
+ assertThat(describe(GenericSample.Nested.class).isGenerified(), is(false));
+ assertThat(describe(GenericSample.NestedInterface.class).isGenerified(), is(false));
+ assertThat(describe(Object.class).isGenerified(), is(false));
+ }
+
+ @Test
+ public void testGetSegmentCount() throws Exception {
+ assertThat(describe(GenericSample.class).getSegmentCount(), is(0));
+ assertThat(describe(GenericSample.Inner.class).getSegmentCount(), is(1));
+ assertThat(describe(GenericSample.Nested.class).getSegmentCount(), is(0));
+ assertThat(describe(GenericSample.NestedInterface.class).getSegmentCount(), is(0));
+ assertThat(describe(Object.class).getSegmentCount(), is(0));
+ }
+
+ @Test
+ public void testBoxed() throws Exception {
+ assertThat(describe(boolean.class).asBoxed(), is(describe(Boolean.class)));
+ assertThat(describe(byte.class).asBoxed(), is(describe(Byte.class)));
+ assertThat(describe(short.class).asBoxed(), is(describe(Short.class)));
+ assertThat(describe(char.class).asBoxed(), is(describe(Character.class)));
+ assertThat(describe(int.class).asBoxed(), is(describe(Integer.class)));
+ assertThat(describe(long.class).asBoxed(), is(describe(Long.class)));
+ assertThat(describe(float.class).asBoxed(), is(describe(Float.class)));
+ assertThat(describe(double.class).asBoxed(), is(describe(Double.class)));
+ assertThat(describe(void.class).asBoxed(), is(describe(void.class)));
+ assertThat(describe(Object.class).asBoxed(), is(describe(Object.class)));
+ }
+
+ @Test
+ public void testUnboxed() throws Exception {
+ assertThat(describe(Boolean.class).asUnboxed(), is(describe(boolean.class)));
+ assertThat(describe(Byte.class).asUnboxed(), is(describe(byte.class)));
+ assertThat(describe(Short.class).asUnboxed(), is(describe(short.class)));
+ assertThat(describe(Character.class).asUnboxed(), is(describe(char.class)));
+ assertThat(describe(Integer.class).asUnboxed(), is(describe(int.class)));
+ assertThat(describe(Long.class).asUnboxed(), is(describe(long.class)));
+ assertThat(describe(Float.class).asUnboxed(), is(describe(float.class)));
+ assertThat(describe(Double.class).asUnboxed(), is(describe(double.class)));
+ assertThat(describe(Void.class).asUnboxed(), is(describe(Void.class)));
+ assertThat(describe(Object.class).asUnboxed(), is(describe(Object.class)));
+ }
+
+ private Class<?> inMethodClass() {
+ class InMethod {
+ /* empty */
+ }
+ return InMethod.class;
+ }
+
+ protected interface SampleInterface {
+ /* empty */
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ private @interface SampleAnnotation {
+ /* empty */
+ }
+
+ @Inherited
+ @Retention(RetentionPolicy.RUNTIME)
+ private @interface OtherAnnotation {
+
+ String value();
+ }
+
+ public interface SampleTransitiveInterface extends SampleInterface {
+ /* empty */
+ }
+
+ private static class SignatureMalformer extends ClassVisitor {
+
+ private static final String FOO = "foo";
+
+ public SignatureMalformer(ClassVisitor classVisitor) {
+ super(Opcodes.ASM5, classVisitor);
+ }
+
+ public static Class<?> malform(Class<?> type) throws Exception {
+ ClassReader classReader = new ClassReader(type.getName());
+ ClassWriter classWriter = new ClassWriter(classReader, 0);
+ classReader.accept(new SignatureMalformer(classWriter), 0);
+ ClassLoader classLoader = new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER,
+ Collections.singletonMap(type.getName(), classWriter.toByteArray()),
+ ByteArrayClassLoader.PersistenceHandler.MANIFEST);
+ return classLoader.loadClass(type.getName());
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+ super.visit(version, access, name, FOO, superName, interfaces);
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
+ return super.visitField(access, name, desc, FOO, value);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+ return super.visitMethod(access, name, desc, FOO, exceptions);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public abstract static class MalformedBase<T> implements Callable<T> {
+
+ Callable<T> foo;
+
+ abstract Callable<T> foo();
+ }
+
+ static class SamplePackagePrivate {
+ /* empty */
+ }
+
+ public static class SampleInterfaceImplementation implements SampleInterface {
+ /* empty */
+ }
+
+ public static class SampleIndirectInterfaceImplementation extends SampleInterfaceImplementation {
+ /* empty */
+ }
+
+ public static class SampleTransitiveInterfaceImplementation implements SampleTransitiveInterface {
+ /* empty */
+ }
+
+ public static class SampleGenericType<T extends ArrayList<T> & Callable<T>,
+ S extends Callable<?>,
+ U extends Callable<? extends Callable<U>>,
+ V extends ArrayList<? super ArrayList<V>>,
+ W extends Callable<W[]>> extends ArrayList<T> implements Callable<T> {
+
+ @Override
+ public T call() throws Exception {
+ return null;
+ }
+ }
+
+ public static class Traversal {
+ /* empty */
+ }
+
+ @SampleAnnotation
+ @OtherAnnotation(FOO)
+ public class SampleClass {
+ /* empty */
+ }
+
+ public class SampleClassInherited extends SampleClass {
+ /* empty */
+ }
+
+ @OtherAnnotation(BAR)
+ public class SampleClassInheritedOverride extends SampleClass {
+ /* empty */
+ }
+
+ @SuppressWarnings("unused")
+ public static class InnerInnerClass {
+
+ public static class Foo {
+ /* empty */
+ }
+ }
+
+ @SampleAnnotation
+ public static class MissingAnnotations {
+
+ @SampleAnnotation
+ Void foo;
+
+ @SampleAnnotation
+ void foo(@SampleAnnotation Void foo) {
+ /* empty */
+ }
+ }
+
+ private static class GenericSample<T> {
+
+ static class Nested {
+ /* empty */
+ }
+
+ class Inner {
+ /* empty */
+ }
+
+ interface NestedInterface {
+ /* empty */
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/AbstractTypeListGenericTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/AbstractTypeListGenericTest.java
new file mode 100644
index 0000000..bc7b212
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/AbstractTypeListGenericTest.java
@@ -0,0 +1,54 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.matcher.AbstractFilterableListTest;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public abstract class AbstractTypeListGenericTest<U> extends AbstractFilterableListTest<TypeDescription.Generic, TypeList.Generic, U> {
+
+ @Test
+ public void testErasures() throws Exception {
+ assertThat(asList(getFirst()).asErasures().size(), is(1));
+ assertThat(asList(getFirst()).asErasures().getOnly(), is(asElement(getFirst()).asErasure()));
+ }
+
+ @Test
+ public void testRawTypes() throws Exception {
+ assertThat(asList(getFirst()).asRawTypes().size(), is(1));
+ assertThat(asList(getFirst()).asRawTypes().getOnly(), is(asElement(getFirst()).asRawType()));
+ }
+
+ @Test
+ public void testVisitor() throws Exception {
+ assertThat(asList(getFirst()).accept(TypeDescription.Generic.Visitor.NoOp.INSTANCE), is(asList(getFirst())));
+ }
+
+ @Test
+ public void testStackSizeEmpty() throws Exception {
+ assertThat(emptyList().getStackSize(), is(0));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testStackSizeNonEmpty() throws Exception {
+ assertThat(asList(getFirst(), getSecond()).getStackSize(), is(2));
+ }
+
+ protected interface Foo<T> {
+ /* empty */
+ }
+
+ protected interface Bar<S> {
+ /* empty */
+ }
+
+ public static class Holder implements Foo<String>, Bar<Integer> {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/AbstractTypeListTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/AbstractTypeListTest.java
new file mode 100644
index 0000000..0f5a885
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/AbstractTypeListTest.java
@@ -0,0 +1,47 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.matcher.AbstractFilterableListTest;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public abstract class AbstractTypeListTest<U> extends AbstractFilterableListTest<TypeDescription, TypeList, U> {
+
+ @Test
+ public void testEmptyToInternalNames() throws Exception {
+ assertThat(emptyList().toInternalNames(), nullValue(String[].class));
+ }
+
+ @Test
+ public void testNonEmptyToInternalNames() throws Exception {
+ assertThat(asList(getFirst()).toInternalNames(), is(new String[]{asElement(getFirst()).getInternalName()}));
+ }
+
+ @Test
+ public void testEmptyStackSize() throws Exception {
+ assertThat(emptyList().getStackSize(), is(0));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testNonEmptyStackSize() throws Exception {
+ assertThat(asList(getFirst(), getSecond()).getStackSize(), is(2));
+ }
+
+ protected interface Foo {
+ /* empty */
+ }
+
+ protected interface Bar {
+ /* empty */
+ }
+
+ public static class Holder implements Foo, Bar {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/GenericSignatureResolutionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/GenericSignatureResolutionTest.java
new file mode 100644
index 0000000..12ddb77
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/GenericSignatureResolutionTest.java
@@ -0,0 +1,260 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.FixedValue;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.concurrent.Callable;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class GenericSignatureResolutionTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ @Test
+ public void testGenericType() throws Exception {
+ DynamicType.Unloaded<?> unloaded = new ByteBuddy()
+ .redefine(GenericType.class)
+ .make();
+ Class<?> type = unloaded.load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER).getLoaded();
+ TypeDescription createdType = new TypeDescription.ForLoadedType(type);
+ TypeDescription originalType = new TypeDescription.ForLoadedType(GenericType.class);
+ assertThat(createdType.getTypeVariables(), is(originalType.getTypeVariables()));
+ assertThat(createdType.getSuperClass(), is(originalType.getSuperClass()));
+ assertThat(createdType.getInterfaces(), is(originalType.getInterfaces()));
+ }
+
+ @Test
+ public void testGenericField() throws Exception {
+ DynamicType.Unloaded<?> unloaded = new ByteBuddy()
+ .redefine(GenericField.class)
+ .make();
+ Class<?> type = unloaded.load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER).getLoaded();
+ FieldDescription createdField = new FieldDescription.ForLoadedField(type.getDeclaredField(FOO));
+ FieldDescription originalField = new FieldDescription.ForLoadedField(GenericField.class.getDeclaredField(FOO));
+ assertThat(createdField.getType(), is(originalField.getType()));
+ }
+
+ @Test
+ public void testGenericMethod() throws Exception {
+ DynamicType.Unloaded<?> unloaded = new ByteBuddy()
+ .redefine(GenericMethod.class)
+ .method(named(FOO))
+ .intercept(FixedValue.nullValue())
+ .make();
+ Class<?> type = unloaded.load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER).getLoaded();
+ MethodDescription createdMethod = new MethodDescription.ForLoadedMethod(type.getDeclaredMethod(FOO, Exception.class));
+ MethodDescription originalMethod = new MethodDescription.ForLoadedMethod(GenericMethod.class.getDeclaredMethod(FOO, Exception.class));
+ assertThat(createdMethod.getTypeVariables(), is(originalMethod.getTypeVariables()));
+ assertThat(createdMethod.getReturnType(), is(originalMethod.getReturnType()));
+ assertThat(createdMethod.getParameters().getOnly().getType(), is(originalMethod.getParameters().getOnly().getType()));
+ assertThat(createdMethod.getExceptionTypes().getOnly(), is(originalMethod.getExceptionTypes().getOnly()));
+ }
+
+ @Test
+ public void testGenericMethodWithoutGenericExceptionTypes() throws Exception {
+ DynamicType.Unloaded<?> unloaded = new ByteBuddy()
+ .redefine(GenericMethod.class)
+ .method(named(BAR))
+ .intercept(FixedValue.nullValue())
+ .make();
+ Class<?> type = unloaded.load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER).getLoaded();
+ MethodDescription createdMethod = new MethodDescription.ForLoadedMethod(type.getDeclaredMethod(BAR, Object.class));
+ MethodDescription originalMethod = new MethodDescription.ForLoadedMethod(GenericMethod.class.getDeclaredMethod(BAR, Object.class));
+ assertThat(createdMethod.getTypeVariables(), is(originalMethod.getTypeVariables()));
+ assertThat(createdMethod.getReturnType(), is(originalMethod.getReturnType()));
+ assertThat(createdMethod.getParameters().getOnly().getType(), is(originalMethod.getParameters().getOnly().getType()));
+ assertThat(createdMethod.getExceptionTypes().getOnly(), is(originalMethod.getExceptionTypes().getOnly()));
+ }
+
+ @Test
+ public void testNoSuperClass() throws Exception {
+ assertThat(new ByteBuddy().redefine(Object.class).make(), notNullValue(DynamicType.class));
+ }
+
+ @Test
+ public void testTypeVariableClassBound() throws Exception {
+ DynamicType.Unloaded<?> unloaded = new ByteBuddy()
+ .redefine(TypeVariableClassBound.class)
+ .make();
+ Class<?> type = unloaded.load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER).getLoaded();
+ TypeDescription createdType = new TypeDescription.ForLoadedType(type);
+ TypeDescription originalType = new TypeDescription.ForLoadedType(TypeVariableClassBound.class);
+ assertThat(createdType.getTypeVariables(), is(originalType.getTypeVariables()));
+ assertThat(createdType.getSuperClass(), is(originalType.getSuperClass()));
+ assertThat(createdType.getInterfaces(), is(originalType.getInterfaces()));
+ }
+
+ @Test
+ public void testTypeVariableInterfaceBound() throws Exception {
+ DynamicType.Unloaded<?> unloaded = new ByteBuddy()
+ .redefine(TypeVariableInterfaceBound.class)
+ .make();
+ Class<?> type = unloaded.load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER).getLoaded();
+ TypeDescription createdType = new TypeDescription.ForLoadedType(type);
+ TypeDescription originalType = new TypeDescription.ForLoadedType(TypeVariableInterfaceBound.class);
+ assertThat(createdType.getTypeVariables(), is(originalType.getTypeVariables()));
+ assertThat(createdType.getSuperClass(), is(originalType.getSuperClass()));
+ assertThat(createdType.getInterfaces(), is(originalType.getInterfaces()));
+ }
+
+ @Test
+ public void testTypeVariableClassAndInterfaceBound() throws Exception {
+ DynamicType.Unloaded<?> unloaded = new ByteBuddy()
+ .redefine(TypeVariableClassAndInterfaceBound.class)
+ .make();
+ Class<?> type = unloaded.load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER).getLoaded();
+ TypeDescription createdType = new TypeDescription.ForLoadedType(type);
+ TypeDescription originalType = new TypeDescription.ForLoadedType(TypeVariableClassAndInterfaceBound.class);
+ assertThat(createdType.getTypeVariables(), is(originalType.getTypeVariables()));
+ assertThat(createdType.getSuperClass(), is(originalType.getSuperClass()));
+ assertThat(createdType.getInterfaces(), is(originalType.getInterfaces()));
+ }
+
+ @Test
+ public void testTypeVariableWildcardNoBound() throws Exception {
+ DynamicType.Unloaded<?> unloaded = new ByteBuddy()
+ .redefine(TypeVariableWildcardNoBound.class)
+ .make();
+ Class<?> type = unloaded.load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER).getLoaded();
+ TypeDescription createdType = new TypeDescription.ForLoadedType(type);
+ TypeDescription originalType = new TypeDescription.ForLoadedType(TypeVariableWildcardNoBound.class);
+ assertThat(createdType.getTypeVariables(), is(originalType.getTypeVariables()));
+ assertThat(createdType.getSuperClass(), is(originalType.getSuperClass()));
+ assertThat(createdType.getInterfaces(), is(originalType.getInterfaces()));
+ }
+
+ @Test
+ public void testTypeVariableWildcardUpperClassBound() throws Exception {
+ DynamicType.Unloaded<?> unloaded = new ByteBuddy()
+ .redefine(TypeVariableWildcardUpperClassBound.class)
+ .make();
+ Class<?> type = unloaded.load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER).getLoaded();
+ TypeDescription createdType = new TypeDescription.ForLoadedType(type);
+ TypeDescription originalType = new TypeDescription.ForLoadedType(TypeVariableWildcardUpperClassBound.class);
+ assertThat(createdType.getTypeVariables(), is(originalType.getTypeVariables()));
+ assertThat(createdType.getSuperClass(), is(originalType.getSuperClass()));
+ assertThat(createdType.getInterfaces(), is(originalType.getInterfaces()));
+ }
+
+ @Test
+ public void testTypeVariableWildcardUpperInterfaceBound() throws Exception {
+ DynamicType.Unloaded<?> unloaded = new ByteBuddy()
+ .redefine(TypeVariableWildcardUpperInterfaceBound.class)
+ .make();
+ Class<?> type = unloaded.load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER).getLoaded();
+ TypeDescription createdType = new TypeDescription.ForLoadedType(type);
+ TypeDescription originalType = new TypeDescription.ForLoadedType(TypeVariableWildcardUpperInterfaceBound.class);
+ assertThat(createdType.getTypeVariables(), is(originalType.getTypeVariables()));
+ assertThat(createdType.getSuperClass(), is(originalType.getSuperClass()));
+ assertThat(createdType.getInterfaces(), is(originalType.getInterfaces()));
+ }
+
+ @Test
+ public void testTypeVariableWildcardLowerClassBound() throws Exception {
+ DynamicType.Unloaded<?> unloaded = new ByteBuddy()
+ .redefine(TypeVariableWildcardLowerClassBound.class)
+ .make();
+ Class<?> type = unloaded.load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER).getLoaded();
+ TypeDescription createdType = new TypeDescription.ForLoadedType(type);
+ TypeDescription originalType = new TypeDescription.ForLoadedType(TypeVariableWildcardLowerClassBound.class);
+ assertThat(createdType.getTypeVariables(), is(originalType.getTypeVariables()));
+ assertThat(createdType.getSuperClass(), is(originalType.getSuperClass()));
+ assertThat(createdType.getInterfaces(), is(originalType.getInterfaces()));
+ }
+
+ @Test
+ public void testTypeVariableWildcardLowerInterfaceBound() throws Exception {
+ DynamicType.Unloaded<?> unloaded = new ByteBuddy()
+ .redefine(TypeVariableWildcardLowerInterfaceBound.class)
+ .make();
+ Class<?> type = unloaded.load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER).getLoaded();
+ TypeDescription createdType = new TypeDescription.ForLoadedType(type);
+ TypeDescription originalType = new TypeDescription.ForLoadedType(TypeVariableWildcardLowerInterfaceBound.class);
+ assertThat(createdType.getTypeVariables(), is(originalType.getTypeVariables()));
+ assertThat(createdType.getSuperClass(), is(originalType.getSuperClass()));
+ assertThat(createdType.getInterfaces(), is(originalType.getInterfaces()));
+ }
+
+ @Test
+ public void testInterfaceType() throws Exception {
+ DynamicType.Unloaded<?> unloaded = new ByteBuddy()
+ .redefine(InterfaceType.class)
+ .make();
+ Class<?> type = unloaded.load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER).getLoaded();
+ TypeDescription createdType = new TypeDescription.ForLoadedType(type);
+ TypeDescription originalType = new TypeDescription.ForLoadedType(InterfaceType.class);
+ assertThat(createdType.getTypeVariables(), is(originalType.getTypeVariables()));
+ assertThat(createdType.getSuperClass(), nullValue(TypeDescription.Generic.class));
+ assertThat(createdType.getInterfaces(), is(originalType.getInterfaces()));
+ }
+
+ public abstract static class GenericType<T extends ArrayList<T> & Callable<T>,
+ S extends Callable<?>,
+ U extends Callable<? extends Callable<U>>,
+ V extends ArrayList<? super ArrayList<V>>,
+ W extends Callable<W[]>> extends ArrayList<T> implements Callable<T> {
+
+ }
+
+ @SuppressWarnings("unused")
+ public static class GenericMethod {
+
+ <T extends Exception & Callable<T>> T foo(T arg) throws T {
+ return null;
+ }
+
+ <T> T bar(T arg) throws Exception {
+ return null;
+ }
+ }
+
+ public static class GenericField<T> {
+
+ T foo;
+ }
+
+ public static class TypeVariableClassBound<T extends ArrayList<T>> {
+ /* empty */
+ }
+
+ public abstract static class TypeVariableInterfaceBound<T extends Callable<T>> {
+ /* empty */
+ }
+
+ public abstract static class TypeVariableClassAndInterfaceBound<T extends ArrayList<T> & Callable<T>> {
+ /* empty */
+ }
+
+ public static class TypeVariableWildcardNoBound<T extends ArrayList<?>> {
+ /* empty */
+ }
+
+ public static class TypeVariableWildcardUpperClassBound<T extends ArrayList<? extends ArrayList<T>>> {
+ /* empty */
+ }
+
+ public static class TypeVariableWildcardUpperInterfaceBound<T extends ArrayList<? extends Callable<T>>> {
+ /* empty */
+ }
+
+ public static class TypeVariableWildcardLowerClassBound<T extends ArrayList<? super ArrayList<T>>> {
+ /* empty */
+ }
+
+ public static class TypeVariableWildcardLowerInterfaceBound<T extends ArrayList<? super Callable<T>>> {
+ /* empty */
+ }
+
+ public interface InterfaceType<T> extends Callable<T> {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/PackageDescriptionForLoadedPackageTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/PackageDescriptionForLoadedPackageTest.java
new file mode 100644
index 0000000..ab507a9
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/PackageDescriptionForLoadedPackageTest.java
@@ -0,0 +1,9 @@
+package net.bytebuddy.description.type;
+
+public class PackageDescriptionForLoadedPackageTest extends AbstractPackageDescriptionTest {
+
+ @Override
+ protected PackageDescription describe(Class<?> type) {
+ return new PackageDescription.ForLoadedPackage(type.getPackage());
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/PackageDescriptionSimpleTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/PackageDescriptionSimpleTest.java
new file mode 100644
index 0000000..2f56b9e
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/PackageDescriptionSimpleTest.java
@@ -0,0 +1,22 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.description.annotation.AnnotationList;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class PackageDescriptionSimpleTest {
+
+ private static final String FOO = "foo";
+
+ @Test
+ public void testPackageName() throws Exception {
+ assertThat(new PackageDescription.Simple(FOO).getName(), is(FOO));
+ }
+
+ @Test
+ public void testPackageAnnotations() throws Exception {
+ assertThat(new PackageDescription.Simple(FOO).getDeclaredAnnotations(), is((AnnotationList) new AnnotationList.Empty()));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDefinitionSortOtherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDefinitionSortOtherTest.java
new file mode 100644
index 0000000..df10d81
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDefinitionSortOtherTest.java
@@ -0,0 +1,21 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.lang.reflect.Type;
+
+import static org.mockito.Mockito.mock;
+
+public class TypeDefinitionSortOtherTest {
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testUnknownType() throws Exception {
+ TypeDefinition.Sort.describe(mock(Type.class));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeDefinition.Sort.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDefinitionSortTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDefinitionSortTest.java
new file mode 100644
index 0000000..40f4340
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDefinitionSortTest.java
@@ -0,0 +1,70 @@
+package net.bytebuddy.description.type;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class TypeDefinitionSortTest {
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {TypeDefinition.Sort.NON_GENERIC, true, false, false, false, false},
+ {TypeDefinition.Sort.PARAMETERIZED, false, true, false, false, false},
+ {TypeDefinition.Sort.VARIABLE, false, false, true, false, false},
+ {TypeDefinition.Sort.VARIABLE_SYMBOLIC, false, false, true, false, false},
+ {TypeDefinition.Sort.GENERIC_ARRAY, false, false, false, true, false},
+ {TypeDefinition.Sort.WILDCARD, false, false, false, false, true}
+ });
+ }
+
+ private final TypeDefinition.Sort sort;
+
+ private final boolean nonGeneric, parameterized, typeVariable, genericArray, wildcard;
+
+ public TypeDefinitionSortTest(TypeDefinition.Sort sort,
+ boolean nonGeneric,
+ boolean parameterized,
+ boolean typeVariable,
+ boolean genericArray,
+ boolean wildcard) {
+ this.sort = sort;
+ this.nonGeneric = nonGeneric;
+ this.parameterized = parameterized;
+ this.typeVariable = typeVariable;
+ this.genericArray = genericArray;
+ this.wildcard = wildcard;
+ }
+
+ @Test
+ public void testNonGeneric() throws Exception {
+ assertThat(sort.isNonGeneric(), is(nonGeneric));
+ }
+
+ @Test
+ public void testParameterized() throws Exception {
+ assertThat(sort.isParameterized(), is(parameterized));
+ }
+
+ @Test
+ public void testTypeVariable() throws Exception {
+ assertThat(sort.isTypeVariable(), is(typeVariable));
+ }
+
+ @Test
+ public void testGenericArray() throws Exception {
+ assertThat(sort.isGenericArray(), is(genericArray));
+ }
+
+ @Test
+ public void testWildcard() throws Exception {
+ assertThat(sort.isWildcard(), is(wildcard));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDefinitionSuperClassIteratorTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDefinitionSuperClassIteratorTest.java
new file mode 100644
index 0000000..e6fed57
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDefinitionSuperClassIteratorTest.java
@@ -0,0 +1,56 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class TypeDefinitionSuperClassIteratorTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Mock
+ private TypeDescription.Generic superClass;
+
+ @Before
+ public void setUp() throws Exception {
+ when(typeDescription.getSuperClass()).thenReturn(superClass);
+ }
+
+ @Test
+ public void testHasNext() throws Exception {
+ Iterator<TypeDefinition> iterator = new TypeDefinition.SuperClassIterator(typeDescription);
+ assertThat(iterator.hasNext(), is(true));
+ assertThat(iterator.next(), is((TypeDefinition) typeDescription));
+ assertThat(iterator.hasNext(), is(true));
+ assertThat(iterator.next(), is((TypeDefinition) superClass));
+ assertThat(iterator.hasNext(), is(false));
+ }
+
+ @Test(expected = NoSuchElementException.class)
+ public void testHasNotNext() throws Exception {
+ Iterator<TypeDefinition> iterator = new TypeDefinition.SuperClassIterator(typeDescription);
+ assertThat(iterator.next(), is((TypeDefinition) typeDescription));
+ assertThat(iterator.next(), is((TypeDefinition) superClass));
+ iterator.next();
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testNoRemoval() throws Exception {
+ new TypeDefinition.SuperClassIterator(typeDescription).remove();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionArrayProjectionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionArrayProjectionTest.java
new file mode 100644
index 0000000..9aaa81a
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionArrayProjectionTest.java
@@ -0,0 +1,131 @@
+package net.bytebuddy.description.type;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+import static org.mockito.Mockito.mock;
+
+public class TypeDescriptionArrayProjectionTest extends AbstractTypeDescriptionTest {
+
+ @Override
+ protected TypeDescription describe(Class<?> type) {
+ return TypeDescription.ArrayProjection.of(new TypeDescription.ForLoadedType(type), 0);
+ }
+
+ @Override
+ protected TypeDescription.Generic describeType(Field field) {
+ return TypeDefinition.Sort.describe(field.getGenericType(), TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveFieldType(field));
+ }
+
+ @Override
+ protected TypeDescription.Generic describeReturnType(Method method) {
+ return TypeDefinition.Sort.describe(method.getGenericReturnType(), TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveReturnType(method));
+ }
+
+ @Override
+ protected TypeDescription.Generic describeParameterType(Method method, int index) {
+ return TypeDefinition.Sort.describe(method.getGenericParameterTypes()[index],
+ TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveParameterType(method, index));
+ }
+
+ @Override
+ protected TypeDescription.Generic describeExceptionType(Method method, int index) {
+ return TypeDefinition.Sort.describe(method.getGenericExceptionTypes()[index],
+ TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveExceptionType(method, index));
+ }
+
+ @Override
+ protected TypeDescription.Generic describeSuperClass(Class<?> type) {
+ return TypeDefinition.Sort.describe(type.getGenericSuperclass(), TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveSuperClassType(type));
+ }
+
+ @Override
+ protected TypeDescription.Generic describeInterfaceType(Class<?> type, int index) {
+ return TypeDefinition.Sort.describe(type.getGenericInterfaces()[index], TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveInterfaceType(type, index));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testIllegalArity() throws Exception {
+ TypeDescription.ArrayProjection.of(mock(TypeDescription.class), -1);
+ }
+
+ @Override
+ @Test
+ @Ignore("The Java reflection API suffers a bug that affects parsing of type variable bounds")
+ public void testTypeVariableU() throws Exception {
+ super.testTypeVariableU();
+ }
+
+ @Override
+ @Test
+ @Ignore("The Java reflection API suffers a bug that affects parsing of type variable bounds")
+ public void testTypeVariableV() throws Exception {
+ super.testTypeVariableV();
+ }
+
+ @Override
+ @Test
+ @Ignore("The Java reflection API suffers a bug that affects parsing of type variable bounds")
+ public void testTypeVariableW() throws Exception {
+ super.testTypeVariableW();
+ }
+
+ @Override
+ @Test
+ @Ignore("The Java reflection API suffers a bug that affects parsing of type variable bounds")
+ public void testTypeVariableX() throws Exception {
+ super.testTypeVariableX();
+ }
+
+ @Override
+ @Test
+ @Ignore("The Java reflection API does not currently support owner types")
+ public void testTypeAnnotationOwnerType() throws Exception {
+ super.testTypeAnnotationOwnerType();
+ }
+
+ @Override
+ @Test
+ @Ignore("The Java reflection API does not currently support generic receiver types")
+ public void testGenericTypeAnnotationReceiverTypeOnMethod() throws Exception {
+ super.testGenericTypeAnnotationReceiverTypeOnMethod();
+ }
+
+ @Override
+ @Test
+ @Ignore("The Java reflection API does not currently support generic receiver types")
+ public void testGenericNestedTypeAnnotationReceiverTypeOnMethod() throws Exception {
+ super.testGenericNestedTypeAnnotationReceiverTypeOnMethod();
+ }
+
+ @Override
+ @Test
+ @Ignore("The Java reflection API does not currently support generic receiver types")
+ public void testGenericNestedTypeAnnotationReceiverTypeOnConstructor() throws Exception {
+ super.testGenericNestedTypeAnnotationReceiverTypeOnConstructor();
+ }
+
+ @Override
+ @Test
+ @Ignore("The Java reflection API does not currently support generic receiver types")
+ public void testGenericInnerTypeAnnotationReceiverTypeOnConstructor() throws Exception {
+ super.testGenericInnerTypeAnnotationReceiverTypeOnConstructor();
+ }
+
+ @Override
+ @Test
+ @Ignore("The Java reflection API does not currently support generic receiver types")
+ public void testGenericInnerTypeAnnotationReceiverTypeOnMethod() throws Exception {
+ super.testGenericInnerTypeAnnotationReceiverTypeOnMethod();
+ }
+
+ @Override
+ @Test
+ @Ignore("The Java reflection API does not currently support nested non-generic types")
+ public void testTypeAnnotationNonGenericInnerType() throws Exception {
+ super.testTypeAnnotationNonGenericInnerType();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionForLoadedTypeTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionForLoadedTypeTest.java
new file mode 100644
index 0000000..89fe1c4
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionForLoadedTypeTest.java
@@ -0,0 +1,152 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.dynamic.loading.ByteArrayClassLoader;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.dynamic.loading.PackageDefinitionStrategy;
+import net.bytebuddy.matcher.ElementMatchers;
+import net.bytebuddy.test.utility.ClassFileExtraction;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class TypeDescriptionForLoadedTypeTest extends AbstractTypeDescriptionTest {
+
+ @Override
+ protected TypeDescription describe(Class<?> type) {
+ return new TypeDescription.ForLoadedType(type);
+ }
+
+ @Override
+ protected TypeDescription.Generic describeType(Field field) {
+ return new TypeDescription.ForLoadedType(field.getDeclaringClass()).getDeclaredFields().filter(ElementMatchers.is(field)).getOnly().getType();
+ }
+
+ @Override
+ protected TypeDescription.Generic describeReturnType(Method method) {
+ return new TypeDescription.ForLoadedType(method.getDeclaringClass()).getDeclaredMethods().filter(ElementMatchers.is(method)).getOnly().getReturnType();
+ }
+
+ @Override
+ protected TypeDescription.Generic describeParameterType(Method method, int index) {
+ return new TypeDescription.ForLoadedType(method.getDeclaringClass()).getDeclaredMethods().filter(ElementMatchers.is(method)).getOnly().getParameters().get(index).getType();
+ }
+
+ @Override
+ protected TypeDescription.Generic describeExceptionType(Method method, int index) {
+ return new TypeDescription.ForLoadedType(method.getDeclaringClass()).getDeclaredMethods().filter(ElementMatchers.is(method)).getOnly().getExceptionTypes().get(index);
+ }
+
+ @Override
+ protected TypeDescription.Generic describeSuperClass(Class<?> type) {
+ return new TypeDescription.ForLoadedType(type).getSuperClass();
+ }
+
+ @Override
+ protected TypeDescription.Generic describeInterfaceType(Class<?> type, int index) {
+ return new TypeDescription.ForLoadedType(type).getInterfaces().get(index);
+ }
+
+ @Override
+ @Test
+ @Ignore("The Java reflection API suffers a bug that affects parsing of type variable bounds")
+ public void testTypeVariableU() throws Exception {
+ super.testTypeVariableU();
+ }
+
+ @Override
+ @Test
+ @Ignore("The Java reflection API suffers a bug that affects parsing of type variable bounds")
+ public void testTypeVariableV() throws Exception {
+ super.testTypeVariableV();
+ }
+
+ @Override
+ @Test
+ @Ignore("The Java reflection API suffers a bug that affects parsing of type variable bounds")
+ public void testTypeVariableW() throws Exception {
+ super.testTypeVariableW();
+ }
+
+ @Override
+ @Test
+ @Ignore("The Java reflection API suffers a bug that affects parsing of type variable bounds")
+ public void testTypeVariableX() throws Exception {
+ super.testTypeVariableX();
+ }
+
+ @Override
+ @Test
+ @Ignore("The Java reflection API does not currently support owner types")
+ public void testTypeAnnotationOwnerType() throws Exception {
+ super.testTypeAnnotationOwnerType();
+ }
+
+ @Override
+ @Test
+ @Ignore("The Java reflection API does not currently support generic receiver types")
+ public void testGenericTypeAnnotationReceiverTypeOnMethod() throws Exception {
+ super.testGenericTypeAnnotationReceiverTypeOnMethod();
+ }
+
+ @Override
+ @Test
+ @Ignore("The Java reflection API does not currently support generic receiver types")
+ public void testGenericNestedTypeAnnotationReceiverTypeOnMethod() throws Exception {
+ super.testGenericNestedTypeAnnotationReceiverTypeOnMethod();
+ }
+
+ @Override
+ @Test
+ @Ignore("The Java reflection API does not currently support generic receiver types")
+ public void testGenericNestedTypeAnnotationReceiverTypeOnConstructor() throws Exception {
+ super.testGenericNestedTypeAnnotationReceiverTypeOnConstructor();
+ }
+
+ @Override
+ @Test
+ @Ignore("The Java reflection API does not currently support generic receiver types")
+ public void testGenericInnerTypeAnnotationReceiverTypeOnConstructor() throws Exception {
+ super.testGenericInnerTypeAnnotationReceiverTypeOnConstructor();
+ }
+
+ @Override
+ @Test
+ @Ignore("The Java reflection API does not currently support generic receiver types")
+ public void testGenericInnerTypeAnnotationReceiverTypeOnMethod() throws Exception {
+ super.testGenericInnerTypeAnnotationReceiverTypeOnMethod();
+ }
+
+ @Override
+ @Test
+ @Ignore("The Java reflection API does not currently support nested non-generic types")
+ public void testTypeAnnotationNonGenericInnerType() throws Exception {
+ super.testTypeAnnotationNonGenericInnerType();
+ }
+
+ @Test
+ public void testNameEqualityNonAnonymous() throws Exception {
+ assertThat(TypeDescription.ForLoadedType.getName(Object.class), is(Object.class.getName()));
+ }
+
+ @Test
+ public void testLazyResolution() throws Exception {
+ ClassLoader classLoader = new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassFileExtraction.of(Foo.class));
+ new TypeDescription.ForLoadedType(classLoader.loadClass(Foo.class.getName()));
+ }
+
+ public static class Foo {
+
+ public Bar bar() {
+ return new Bar();
+ }
+ }
+
+ public static class Bar {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionForPackageDescriptionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionForPackageDescriptionTest.java
new file mode 100644
index 0000000..7a74b36
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionForPackageDescriptionTest.java
@@ -0,0 +1,80 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class TypeDescriptionForPackageDescriptionTest {
+
+ private static final String FOO = "foo";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ private TypeDescription typeDescription;
+
+ @Mock
+ private PackageDescription packageDescription;
+
+ @Before
+ public void setUp() throws Exception {
+ typeDescription = new TypeDescription.ForPackageDescription(packageDescription);
+ }
+
+ @Test
+ public void testName() throws Exception {
+ when(packageDescription.getName()).thenReturn(FOO);
+ assertThat(typeDescription.getName(), is(FOO + "." + PackageDescription.PACKAGE_CLASS_NAME));
+ }
+
+ @Test
+ public void testModifiers() throws Exception {
+ assertThat(typeDescription.getModifiers(), is(PackageDescription.PACKAGE_MODIFIERS));
+ }
+
+ @Test
+ public void testInterfaces() throws Exception {
+ assertThat(typeDescription.getInterfaces().size(), is(0));
+ }
+
+ @Test
+ public void testAnnotations() throws Exception {
+ AnnotationList annotationList = mock(AnnotationList.class);
+ when(packageDescription.getDeclaredAnnotations()).thenReturn(annotationList);
+ assertThat(typeDescription.getDeclaredAnnotations(), is(annotationList));
+ }
+
+ @Test
+ public void testTypeVariables() throws Exception {
+ assertThat(typeDescription.getTypeVariables().size(), is(0));
+ }
+
+ @Test
+ public void testFields() throws Exception {
+ assertThat(typeDescription.getDeclaredFields().size(), is(0));
+ }
+
+ @Test
+ public void testMethods() throws Exception {
+ assertThat(typeDescription.getDeclaredMethods().size(), is(0));
+ }
+
+ @Test
+ public void testPackage() throws Exception {
+ assertThat(typeDescription.getPackage(), is(packageDescription));
+ }
+
+ @Test
+ public void testSuperClass() throws Exception {
+ assertThat(typeDescription.getSuperClass(), is(TypeDescription.Generic.OBJECT));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericAnnotationReaderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericAnnotationReaderTest.java
new file mode 100644
index 0000000..f65d00b
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericAnnotationReaderTest.java
@@ -0,0 +1,149 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.utility.CompoundList;
+import org.junit.Test;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Iterator;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+public class TypeDescriptionGenericAnnotationReaderTest {
+
+ @Test
+ public void testLegacyVmReturnsNoOpReaders() throws Exception {
+ assertThat(TypeDescription.Generic.AnnotationReader.Dispatcher.ForLegacyVm.INSTANCE.resolveFieldType(null),
+ is((TypeDescription.Generic.AnnotationReader) TypeDescription.Generic.AnnotationReader.NoOp.INSTANCE));
+ assertThat(TypeDescription.Generic.AnnotationReader.Dispatcher.ForLegacyVm.INSTANCE.resolveSuperClassType(null),
+ is((TypeDescription.Generic.AnnotationReader) TypeDescription.Generic.AnnotationReader.NoOp.INSTANCE));
+ assertThat(TypeDescription.Generic.AnnotationReader.Dispatcher.ForLegacyVm.INSTANCE.resolveInterfaceType(null, 0),
+ is((TypeDescription.Generic.AnnotationReader) TypeDescription.Generic.AnnotationReader.NoOp.INSTANCE));
+ assertThat(TypeDescription.Generic.AnnotationReader.Dispatcher.ForLegacyVm.INSTANCE.resolveReturnType(null),
+ is((TypeDescription.Generic.AnnotationReader) TypeDescription.Generic.AnnotationReader.NoOp.INSTANCE));
+ assertThat(TypeDescription.Generic.AnnotationReader.Dispatcher.ForLegacyVm.INSTANCE.resolveParameterType(null, 0),
+ is((TypeDescription.Generic.AnnotationReader) TypeDescription.Generic.AnnotationReader.NoOp.INSTANCE));
+ assertThat(TypeDescription.Generic.AnnotationReader.Dispatcher.ForLegacyVm.INSTANCE.resolveExceptionType(null, 0),
+ is((TypeDescription.Generic.AnnotationReader) TypeDescription.Generic.AnnotationReader.NoOp.INSTANCE));
+ assertThat(TypeDescription.Generic.AnnotationReader.Dispatcher.ForLegacyVm.INSTANCE.resolveTypeVariable(null),
+ is((TypeDescription.Generic.AnnotationReader) TypeDescription.Generic.AnnotationReader.NoOp.INSTANCE));
+ assertThat(TypeDescription.Generic.AnnotationReader.Dispatcher.ForLegacyVm.INSTANCE.resolveReceiverType(null),
+ nullValue(TypeDescription.Generic.class));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testCannotResolveAnnotatedType() throws Exception {
+ TypeDescription.Generic.AnnotationReader.Dispatcher.ForLegacyVm.INSTANCE.resolve(null);
+ }
+
+ @Test
+ public void testNoOpReaderReturnsZeroAnnotations() throws Exception {
+ assertThat(TypeDescription.Generic.AnnotationReader.NoOp.INSTANCE.getDeclaredAnnotations().length, is(0));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNoOpReaderNoHierarchyAnnotations() throws Exception {
+ TypeDescription.Generic.AnnotationReader.NoOp.INSTANCE.getAnnotations();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNoOpReaderNoSpecificAnnotations() throws Exception {
+ TypeDescription.Generic.AnnotationReader.NoOp.INSTANCE.getAnnotation(null);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNoOpReaderNoSpecificAnnotationPresent() throws Exception {
+ TypeDescription.Generic.AnnotationReader.NoOp.INSTANCE.isAnnotationPresent(null);
+ }
+
+ @Test
+ public void testAnnotationReaderNoOpTest() throws Exception {
+ assertThat(TypeDescription.Generic.AnnotationReader.NoOp.INSTANCE.ofComponentType(),
+ is((TypeDescription.Generic.AnnotationReader) TypeDescription.Generic.AnnotationReader.NoOp.INSTANCE));
+ assertThat(TypeDescription.Generic.AnnotationReader.NoOp.INSTANCE.ofOuterClass(),
+ is((TypeDescription.Generic.AnnotationReader) TypeDescription.Generic.AnnotationReader.NoOp.INSTANCE));
+ assertThat(TypeDescription.Generic.AnnotationReader.NoOp.INSTANCE.ofOwnerType(),
+ is((TypeDescription.Generic.AnnotationReader) TypeDescription.Generic.AnnotationReader.NoOp.INSTANCE));
+ assertThat(TypeDescription.Generic.AnnotationReader.NoOp.INSTANCE.ofTypeArgument(0),
+ is((TypeDescription.Generic.AnnotationReader) TypeDescription.Generic.AnnotationReader.NoOp.INSTANCE));
+ assertThat(TypeDescription.Generic.AnnotationReader.NoOp.INSTANCE.ofTypeVariableBoundType(0),
+ is((TypeDescription.Generic.AnnotationReader) TypeDescription.Generic.AnnotationReader.NoOp.INSTANCE));
+ assertThat(TypeDescription.Generic.AnnotationReader.NoOp.INSTANCE.ofWildcardLowerBoundType(0),
+ is((TypeDescription.Generic.AnnotationReader) TypeDescription.Generic.AnnotationReader.NoOp.INSTANCE));
+ assertThat(TypeDescription.Generic.AnnotationReader.NoOp.INSTANCE.ofWildcardUpperBoundType(0),
+ is((TypeDescription.Generic.AnnotationReader) TypeDescription.Generic.AnnotationReader.NoOp.INSTANCE));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeDescription.Generic.AnnotationReader.NoOp.class).apply();
+ ObjectPropertyAssertion.of(TypeDescription.Generic.AnnotationReader.ForComponentType.class).apply();
+ ObjectPropertyAssertion.of(TypeDescription.Generic.AnnotationReader.ForOwnerType.class).apply();
+ ObjectPropertyAssertion.of(TypeDescription.Generic.AnnotationReader.ForTypeArgument.class).apply();
+ ObjectPropertyAssertion.of(TypeDescription.Generic.AnnotationReader.ForTypeVariableBoundType.class).apply();
+ ObjectPropertyAssertion.of(TypeDescription.Generic.AnnotationReader.ForWildcardLowerBoundType.class).apply();
+ ObjectPropertyAssertion.of(TypeDescription.Generic.AnnotationReader.ForWildcardUpperBoundType.class).apply();
+ ObjectPropertyAssertion.of(TypeDescription.Generic.AnnotationReader.Dispatcher.ForLegacyVm.class).apply();
+ final Iterator<Method> methods = CompoundList.of(Arrays.asList(Object.class.getDeclaredMethods()), Arrays.asList(String.class.getDeclaredMethods())).iterator();
+ ObjectPropertyAssertion.of(TypeDescription.Generic.AnnotationReader.Dispatcher.ForJava8CapableVm.class)
+ .create(new ObjectPropertyAssertion.Creator<Method>() {
+ @Override
+ public Method create() {
+ return methods.next();
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(TypeDescription.Generic.AnnotationReader.Dispatcher.ForJava8CapableVm.Resolved.class).apply();
+ final Iterator<Field> fields = Arrays.asList(Thread.class.getDeclaredFields()).iterator();
+ ObjectPropertyAssertion.of(TypeDescription.Generic.AnnotationReader.Dispatcher.ForJava8CapableVm.AnnotatedFieldType.class)
+ .create(new ObjectPropertyAssertion.Creator<Field>() {
+ @Override
+ public Field create() {
+ return fields.next();
+ }
+ }).apply();
+ final Iterator<Class<?>> types = Arrays.<Class<?>>asList(Object.class, String.class, Integer.class, Number.class).iterator();
+ ObjectPropertyAssertion.of(TypeDescription.Generic.AnnotationReader.Dispatcher.ForJava8CapableVm.AnnotatedInterfaceType.class)
+ .create(new ObjectPropertyAssertion.Creator<Class<?>>() {
+ @Override
+ public Class<?> create() {
+ return types.next();
+ }
+ }).apply();
+ final Iterator<AccessibleObject> iterator = Arrays.<AccessibleObject>asList(Object.class.getDeclaredMethods()).iterator();
+ ObjectPropertyAssertion.of(TypeDescription.Generic.AnnotationReader.Dispatcher.ForJava8CapableVm.AnnotatedParameterizedType.class).create(new ObjectPropertyAssertion.Creator<AccessibleObject>() {
+ @Override
+ public AccessibleObject create() {
+ return iterator.next();
+ }
+ }).apply();
+ final Iterator<Method> methods2 = Arrays.asList(Object.class.getDeclaredMethods()).iterator();
+ ObjectPropertyAssertion.of(TypeDescription.Generic.AnnotationReader.Dispatcher.ForJava8CapableVm.AnnotatedReturnType.class)
+ .create(new ObjectPropertyAssertion.Creator<Method>() {
+ @Override
+ public Method create() {
+ return methods2.next();
+ }
+ }).apply();
+ final Iterator<Class<?>> types2 = Arrays.<Class<?>>asList(Object.class, String.class, Integer.class, Number.class).iterator();
+ ObjectPropertyAssertion.of(TypeDescription.Generic.AnnotationReader.Dispatcher.ForJava8CapableVm.AnnotatedSuperClass.class)
+ .create(new ObjectPropertyAssertion.Creator<Class<?>>() {
+ @Override
+ public Class<?> create() {
+ return types2.next();
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(TypeDescription.Generic.AnnotationReader.Dispatcher.ForJava8CapableVm.AnnotatedTypeVariableType.class).apply();
+ ObjectPropertyAssertion.of(TypeDescription.Generic.AnnotationReader.Dispatcher.ForJava8CapableVm.AnnotatedExceptionType.class).create(new ObjectPropertyAssertion.Creator<AccessibleObject>() {
+ @Override
+ public AccessibleObject create() {
+ return iterator.next();
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(TypeDescription.Generic.AnnotationReader.Dispatcher.CreationAction.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericBuilderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericBuilderTest.java
new file mode 100644
index 0000000..dbbcd5c
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericBuilderTest.java
@@ -0,0 +1,231 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.lang.reflect.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class TypeDescriptionGenericBuilderTest extends AbstractTypeDescriptionGenericTest {
+
+ @Override
+ protected TypeDescription.Generic describeType(Field field) {
+ return describe(field.getGenericType(), TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveFieldType(field))
+ .accept(TypeDescription.Generic.Visitor.Substitutor.ForAttachment.of(new FieldDescription.ForLoadedField(field)));
+ }
+
+ @Override
+ protected TypeDescription.Generic describeReturnType(Method method) {
+ return describe(method.getGenericReturnType(), TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveReturnType(method))
+ .accept(TypeDescription.Generic.Visitor.Substitutor.ForAttachment.of(new MethodDescription.ForLoadedMethod(method)));
+ }
+
+ @Override
+ protected TypeDescription.Generic describeParameterType(Method method, int index) {
+ return describe(method.getGenericParameterTypes()[index], TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveParameterType(method, index))
+ .accept(TypeDescription.Generic.Visitor.Substitutor.ForAttachment.of(new MethodDescription.ForLoadedMethod(method)));
+ }
+
+ @Override
+ protected TypeDescription.Generic describeExceptionType(Method method, int index) {
+ return describe(method.getGenericExceptionTypes()[index], TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveExceptionType(method, index))
+ .accept(TypeDescription.Generic.Visitor.Substitutor.ForAttachment.of(new MethodDescription.ForLoadedMethod(method)));
+ }
+
+ @Override
+ protected TypeDescription.Generic describeSuperClass(Class<?> type) {
+ return describe(type.getGenericSuperclass(), TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveSuperClassType(type))
+ .accept(TypeDescription.Generic.Visitor.Substitutor.ForAttachment.of(new TypeDescription.ForLoadedType(type)));
+ }
+
+ @Override
+ protected TypeDescription.Generic describeInterfaceType(Class<?> type, int index) {
+ return describe(type.getGenericInterfaces()[index], TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveInterfaceType(type, index))
+ .accept(TypeDescription.Generic.Visitor.Substitutor.ForAttachment.of(new TypeDescription.ForLoadedType(type)));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNoOwnerTypeWhenRequired() throws Exception {
+ TypeDescription.Generic.Builder.parameterizedType(Foo.Inner.class, Object.class);
+ }
+
+ @Test
+ public void testImplicitOwnerTypeWhenRequired() throws Exception {
+ assertThat(TypeDescription.Generic.Builder.parameterizedType(Foo.class, Object.class).build().getOwnerType(),
+ is(TypeDefinition.Sort.describe(getClass())));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testOwnerTypeWhenNotRequired() throws Exception {
+ TypeDescription.Generic.Builder.parameterizedType(Foo.class, Object.class, Collections.<Type>singletonList(Object.class));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testIllegalOwnerType() throws Exception {
+ TypeDescription.Generic.Builder.parameterizedType(Foo.Inner.class, Object.class, Collections.<Type>singletonList(Foo.class));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNonGenericOwnerType() throws Exception {
+ TypeDescription.Generic.Builder.parameterizedType(Foo.Inner.class, Foo.class, Collections.<Type>singletonList(Foo.class));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testGenericOwnerType() throws Exception {
+ TypeDescription.Generic.Builder.parameterizedType(new TypeDescription.ForLoadedType(Foo.Nested.class),
+ TypeDescription.Generic.Builder.parameterizedType(Foo.class, Object.class).build(),
+ Collections.<TypeDefinition>singletonList(TypeDescription.OBJECT));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testIncompatibleParameterTypeNumber() throws Exception {
+ TypeDescription.Generic.Builder.parameterizedType(Foo.class);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testForbiddenZeroArity() throws Exception {
+ TypeDescription.Generic.Builder.rawType(Foo.class).asArray(0);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testForbiddenNegativeType() throws Exception {
+ TypeDescription.Generic.Builder.rawType(Foo.class).asArray(-1);
+ }
+
+ @Test
+ public void testMultipleArityArray() throws Exception {
+ assertThat(TypeDescription.Generic.Builder.rawType(Foo.class).asArray(2).build().getComponentType().getComponentType().represents(Foo.class), is(true));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testCannotAnnotateVoid() throws Exception {
+ TypeDescription.Generic.Builder.rawType(void.class).annotate(mock(AnnotationDescription.class)).build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNonGenericTypeAsParameterizedType() throws Exception {
+ TypeDescription.Generic.Builder.parameterizedType(Object.class).build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testMissingOwnerType() throws Exception {
+ TypeDescription.Generic.Builder.rawType(Bar.Inner.class, TypeDescription.Generic.UNDEFINED);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testIncompatibleType() throws Exception {
+ TypeDescription.Generic.Builder.rawType(Bar.Inner.class, TypeDescription.Generic.OBJECT);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testIncompatibleOwnerTypeWhenNonRequired() throws Exception {
+ TypeDescription.Generic.Builder.rawType(Object.class, TypeDescription.Generic.OBJECT);
+ }
+
+ @Test
+ public void testExplicitOwnerTypeOfNonGenericType() throws Exception {
+ TypeDescription.Generic ownerType = TypeDescription.Generic.Builder.rawType(Bar.class).build();
+ TypeDescription.Generic typeDescription = TypeDescription.Generic.Builder.rawType(Bar.Inner.class, ownerType).build();
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.represents(Bar.Inner.class), is(true));
+ assertThat(typeDescription.getOwnerType(), sameInstance(ownerType));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeDescription.Generic.Builder.OfGenericArrayType.class).apply();
+ ObjectPropertyAssertion.of(TypeDescription.Generic.Builder.OfNonGenericType.class).refine(new ObjectPropertyAssertion.Refinement<TypeDescription>() {
+ @Override
+ public void apply(TypeDescription mock) {
+ when(mock.asGenericType()).thenReturn(Mockito.mock(TypeDescription.Generic.class));
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(TypeDescription.Generic.Builder.OfParameterizedType.class).apply();
+ ObjectPropertyAssertion.of(TypeDescription.Generic.Builder.OfTypeVariable.class).apply();
+ }
+
+ @Override
+ @Test
+ @Ignore("The Java reflection API does not currently support owner types")
+ public void testTypeAnnotationOwnerType() throws Exception {
+ super.testTypeAnnotationOwnerType();
+ }
+
+ @Override
+ @Test
+ @Ignore("The Java reflection API does not currently support generic inner types")
+ public void testTypeAnnotationNonGenericInnerType() throws Exception {
+ super.testTypeAnnotationNonGenericInnerType();
+ }
+
+ private TypeDescription.Generic describe(Type type, TypeDescription.Generic.AnnotationReader annotationReader) {
+ if (type instanceof WildcardType) {
+ WildcardType wildcardType = (WildcardType) type;
+ return wildcardType.getLowerBounds().length == 0
+ ? builder(wildcardType.getUpperBounds()[0], annotationReader.ofWildcardUpperBoundType(0)).asWildcardUpperBound(annotationReader.asList())
+ : builder(wildcardType.getLowerBounds()[0], annotationReader.ofWildcardLowerBoundType(0)).asWildcardLowerBound(annotationReader.asList());
+ } else {
+ return builder(type, annotationReader).build();
+ }
+ }
+
+ private TypeDescription.Generic.Builder builder(Type type, TypeDescription.Generic.AnnotationReader annotationReader) {
+ if (type instanceof TypeVariable) {
+ return TypeDescription.Generic.Builder.typeVariable(((TypeVariable<?>) type).getName()).annotate(annotationReader.asList());
+ } else if (type instanceof Class) {
+ Class<?> rawType = (Class<?>) type;
+ return (rawType.isArray()
+ ? builder(rawType.getComponentType(), annotationReader.ofComponentType()).asArray()
+ : TypeDescription.Generic.Builder.rawType((Class<?>) type)).annotate(annotationReader.asList());
+ } else if (type instanceof GenericArrayType) {
+ return builder(((GenericArrayType) type).getGenericComponentType(), annotationReader.ofComponentType()).asArray().annotate(annotationReader.asList());
+ } else if (type instanceof ParameterizedType) {
+ ParameterizedType parameterizedType = (ParameterizedType) type;
+ List<TypeDescription.Generic> parameters = new ArrayList<TypeDescription.Generic>(parameterizedType.getActualTypeArguments().length);
+ int index = 0;
+ for (Type parameter : parameterizedType.getActualTypeArguments()) {
+ parameters.add(describe(parameter, annotationReader.ofTypeArgument(index++)));
+ }
+ return TypeDescription.Generic.Builder.parameterizedType(new TypeDescription.ForLoadedType((Class<?>) parameterizedType.getRawType()),
+ parameterizedType.getOwnerType() == null
+ ? null
+ : describe(parameterizedType.getOwnerType(), annotationReader.ofOwnerType()),
+ parameters).annotate(annotationReader.asList());
+ } else {
+ throw new AssertionError("Unexpected type: " + type);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private static class Foo<T> {
+
+ private class Inner<S> {
+ /* empty */
+ }
+
+ private static class Nested<S> {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private class Bar {
+
+ private class Inner {
+ /* empty */
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericLazyProjectionWithLazyNavigationTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericLazyProjectionWithLazyNavigationTest.java
new file mode 100644
index 0000000..4329b38
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericLazyProjectionWithLazyNavigationTest.java
@@ -0,0 +1,80 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class TypeDescriptionGenericLazyProjectionWithLazyNavigationTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription typeDescription, rawSuperType;
+
+ @Mock
+ private TypeDescription.Generic superType;
+
+ @Before
+ public void setUp() throws Exception {
+ when(typeDescription.asErasure()).thenReturn(typeDescription);
+ when(superType.asErasure()).thenReturn(rawSuperType);
+ when(superType.asGenericType()).thenReturn(superType);
+ }
+
+ @Test
+ public void testLazySuperClass() throws Exception {
+ when(typeDescription.getSuperClass()).thenReturn(superType);
+ assertThat(new AssertingLazyType(typeDescription).getSuperClass().asErasure(), is(rawSuperType));
+ }
+
+ @Test
+ public void testUndefinedLazySuperClass() throws Exception {
+ assertThat(new AssertingLazyType(typeDescription).getSuperClass(), nullValue(TypeDescription.Generic.class));
+ }
+
+ @Test
+ public void testInterfaceType() throws Exception {
+ when(typeDescription.getInterfaces()).thenReturn(new TypeList.Generic.Explicit(superType));
+ assertThat(new AssertingLazyType(typeDescription).getInterfaces().getOnly().asErasure(), is(rawSuperType));
+ }
+
+ @Test(expected = IndexOutOfBoundsException.class)
+ public void testInterfaceTypeOutOfBounds() throws Exception {
+ when(typeDescription.getInterfaces()).thenReturn(new TypeList.Generic.Explicit(superType));
+ new AssertingLazyType(typeDescription).getInterfaces().get(1);
+ }
+
+ private static class AssertingLazyType extends TypeDescription.Generic.LazyProjection.WithLazyNavigation {
+
+ private final TypeDescription typeDescription;
+
+ private AssertingLazyType(TypeDescription typeDescription) {
+ this.typeDescription = typeDescription;
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ throw new AssertionError();
+ }
+
+ @Override
+ public TypeDescription asErasure() {
+ return typeDescription;
+ }
+
+ @Override
+ protected TypeDescription.Generic resolve() {
+ throw new AssertionError();
+ }
+ }
+}
\ No newline at end of file
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericOfNonGenericTypeForReifiedErasureTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericOfNonGenericTypeForReifiedErasureTest.java
new file mode 100644
index 0000000..bee18c7
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericOfNonGenericTypeForReifiedErasureTest.java
@@ -0,0 +1,154 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.matcher.ElementMatchers;
+import org.junit.Test;
+
+import java.util.List;
+
+import static net.bytebuddy.matcher.ElementMatchers.isBridge;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.assertThat;
+
+public class TypeDescriptionGenericOfNonGenericTypeForReifiedErasureTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux";
+
+ @Test
+ public void testNonGenerifiedType() throws Exception {
+ TypeDescription.Generic typeDescription = TypeDescription.Generic.OfNonGenericType.ForReifiedErasure.of(TypeDescription.OBJECT);
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getSort(), not(instanceOf(TypeDescription.Generic.OfNonGenericType.ForReifiedErasure.class)));
+ }
+
+ @Test
+ public void testGenerifiedType() throws Exception {
+ TypeDescription.Generic typeDescription = TypeDescription.Generic.OfNonGenericType.ForReifiedErasure.of(new TypeDescription.ForLoadedType(Qux.class));
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(Qux.class)));
+ assertThat(typeDescription.getDeclaredFields().getOnly().getType().getSort(),
+ is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getDeclaredFields().getOnly().getType().asErasure(),
+ is((TypeDescription) new TypeDescription.ForLoadedType(Number.class)));
+ assertThat(typeDescription.getDeclaredMethods().filter(named(QUX)).getOnly().getReturnType().getSort(),
+ is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getDeclaredMethods().filter(named(QUX)).getOnly().getReturnType().asErasure(),
+ is((TypeDescription) new TypeDescription.ForLoadedType(Number.class)));
+ assertThat(typeDescription.getDeclaredMethods().filter(named(BAR)).getOnly().getReturnType().getSort(),
+ is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getDeclaredMethods().filter(named(BAR)).getOnly().getReturnType().asErasure(),
+ is((TypeDescription) new TypeDescription.ForLoadedType(List.class)));
+ assertThat(typeDescription.getSuperClass().getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getSuperClass().asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(Foo.class)));
+ assertThat(typeDescription.getSuperClass().getTypeArguments().size(), is(1));
+ assertThat(typeDescription.getSuperClass().getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getSuperClass().getTypeArguments().getOnly().asErasure(),
+ is((TypeDescription) new TypeDescription.ForLoadedType(Number.class)));
+ assertThat(typeDescription.getSuperClass().getDeclaredFields().getOnly().getType().getSort(),
+ is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getSuperClass().getDeclaredFields().getOnly().getType().asErasure(),
+ is((TypeDescription) new TypeDescription.ForLoadedType(Number.class)));
+ assertThat(typeDescription.getSuperClass().getDeclaredMethods().filter(named(FOO)).getOnly().getReturnType().getSort(),
+ is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getSuperClass().getDeclaredMethods().filter(named(FOO)).getOnly().getReturnType().asErasure(),
+ is((TypeDescription) new TypeDescription.ForLoadedType(Number.class)));
+ assertThat(typeDescription.getSuperClass().getDeclaredMethods().filter(named(BAR)).getOnly().getReturnType().getSort(),
+ is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getSuperClass().getDeclaredMethods().filter(named(BAR)).getOnly().getReturnType().asErasure(),
+ is((TypeDescription) new TypeDescription.ForLoadedType(List.class)));
+ assertThat(typeDescription.getInterfaces().getOnly().getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getInterfaces().getOnly().asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(Bar.class)));
+ assertThat(typeDescription.getInterfaces().getOnly().getTypeArguments().size(), is(1));
+ assertThat(typeDescription.getInterfaces().getOnly().getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getInterfaces().getOnly().getTypeArguments().getOnly().asErasure(),
+ is((TypeDescription) new TypeDescription.ForLoadedType(Number.class)));
+ assertThat(typeDescription.getInterfaces().getOnly().getDeclaredMethods().filter(named(FOO)).getOnly().getReturnType().getSort(),
+ is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getInterfaces().getOnly().getDeclaredMethods().filter(named(FOO)).getOnly().getReturnType().asErasure(),
+ is((TypeDescription) new TypeDescription.ForLoadedType(Number.class)));
+ assertThat(typeDescription.getInterfaces().getOnly().getDeclaredMethods().filter(named(BAR)).getOnly().getReturnType().getSort(),
+ is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getInterfaces().getOnly().getDeclaredMethods().filter(named(BAR)).getOnly().getReturnType().asErasure(),
+ is((TypeDescription) new TypeDescription.ForLoadedType(List.class)));
+ }
+
+ @Test
+ public void testNonGenericIntermediateType() throws Exception {
+ TypeDescription.Generic typeDescription = TypeDescription.Generic.OfNonGenericType.ForReifiedErasure.of(new TypeDescription.ForLoadedType(GenericIntermediate.class))
+ .getSuperClass();
+ assertThat(typeDescription.getSuperClass().getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getSuperClass().asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(Foo.class)));
+ assertThat(typeDescription.getSuperClass().getTypeArguments().size(), is(1));
+ assertThat(typeDescription.getSuperClass().getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getSuperClass().getTypeArguments().getOnly().asErasure(),
+ is((TypeDescription) new TypeDescription.ForLoadedType(Number.class)));
+ assertThat(typeDescription.getSuperClass().getDeclaredFields().getOnly().getType().getSort(),
+ is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getSuperClass().getDeclaredFields().getOnly().getType().asErasure(),
+ is((TypeDescription) new TypeDescription.ForLoadedType(Number.class)));
+ assertThat(typeDescription.getSuperClass().getDeclaredMethods().filter(named(FOO)).getOnly().getReturnType().getSort(),
+ is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getSuperClass().getDeclaredMethods().filter(named(FOO)).getOnly().getReturnType().asErasure(),
+ is((TypeDescription) new TypeDescription.ForLoadedType(Number.class)));
+ assertThat(typeDescription.getSuperClass().getDeclaredMethods().filter(named(BAR)).getOnly().getReturnType().getSort(),
+ is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getSuperClass().getDeclaredMethods().filter(named(BAR)).getOnly().getReturnType().asErasure(),
+ is((TypeDescription) new TypeDescription.ForLoadedType(List.class)));
+ assertThat(typeDescription.getInterfaces().getOnly().getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getInterfaces().getOnly().asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(Bar.class)));
+ assertThat(typeDescription.getInterfaces().getOnly().getTypeArguments().size(), is(1));
+ assertThat(typeDescription.getInterfaces().getOnly().getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getInterfaces().getOnly().getTypeArguments().getOnly().asErasure(),
+ is((TypeDescription) new TypeDescription.ForLoadedType(Number.class)));
+ assertThat(typeDescription.getInterfaces().getOnly().getDeclaredMethods().filter(named(FOO)).getOnly().getReturnType().getSort(),
+ is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getInterfaces().getOnly().getDeclaredMethods().filter(named(FOO)).getOnly().getReturnType().asErasure(),
+ is((TypeDescription) new TypeDescription.ForLoadedType(Number.class)));
+ assertThat(typeDescription.getInterfaces().getOnly().getDeclaredMethods().filter(named(BAR)).getOnly().getReturnType().getSort(),
+ is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getInterfaces().getOnly().getDeclaredMethods().filter(named(BAR)).getOnly().getReturnType().asErasure(),
+ is((TypeDescription) new TypeDescription.ForLoadedType(List.class)));
+ }
+
+ private static class Foo<T> {
+
+ T foo;
+
+ public T foo() {
+ return null;
+ }
+
+ public List<?> bar() {
+ return null;
+ }
+ }
+
+ private interface Bar<T> {
+
+ T foo();
+
+ List<?> bar();
+ }
+
+ private static class Qux<T extends Number> extends Foo<T> implements Bar<T>{
+
+ T foo;
+
+ public T qux() {
+ return null;
+ }
+
+ public List<?> bar() {
+ return null;
+ }
+ }
+
+ private static class NonGenericIntermediate extends Foo<Number> implements Bar<Number> {
+ /* empty */
+ }
+
+ private static class GenericIntermediate<T> extends NonGenericIntermediate {
+ /* empty */
+ }
+}
\ No newline at end of file
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericOfParameterizedTypeForGenerifiedErasureTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericOfParameterizedTypeForGenerifiedErasureTest.java
new file mode 100644
index 0000000..0a6ca3c
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericOfParameterizedTypeForGenerifiedErasureTest.java
@@ -0,0 +1,28 @@
+package net.bytebuddy.description.type;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class TypeDescriptionGenericOfParameterizedTypeForGenerifiedErasureTest {
+
+ @Test
+ public void testNonGenerifiedType() throws Exception {
+ TypeDescription.Generic typeDescription = TypeDescription.Generic.OfParameterizedType.ForGenerifiedErasure.of(TypeDescription.OBJECT);
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ }
+
+ @Test
+ public void testGenerifiedType() throws Exception {
+ TypeDescription.Generic typeDescription = TypeDescription.Generic.OfParameterizedType.ForGenerifiedErasure.of(new TypeDescription.ForLoadedType(Foo.class));
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getTypeArguments().size(), is(1));
+ assertThat(typeDescription.getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(typeDescription.getTypeArguments().getOnly().getSymbol(), is("T"));
+ }
+
+ public static class Foo<T> {
+ /* empty */
+ }
+}
\ No newline at end of file
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericOfParameterizedTypeForReifiedTypeTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericOfParameterizedTypeForReifiedTypeTest.java
new file mode 100644
index 0000000..c194c5f
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericOfParameterizedTypeForReifiedTypeTest.java
@@ -0,0 +1,150 @@
+package net.bytebuddy.description.type;
+
+import org.junit.Test;
+
+import java.util.List;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class TypeDescriptionGenericOfParameterizedTypeForReifiedTypeTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ @Test
+ public void testSuperType() throws Exception {
+ TypeDescription.Generic typeDescription = new TypeDescription.Generic.OfParameterizedType.ForReifiedType(new TypeDescription.ForLoadedType(Sample.class)
+ .getSuperClass());
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(Bar.class)));
+ assertThat(typeDescription.getTypeArguments().size(), is(1));
+ assertThat(typeDescription.getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getTypeArguments().getOnly().asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(Number.class)));
+ assertThat(typeDescription.getSuperClass().getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getSuperClass().asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(Foo.class)));
+ assertThat(typeDescription.getSuperClass().getTypeArguments().size(), is(1));
+ assertThat(typeDescription.getSuperClass().getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getSuperClass().getTypeArguments().getOnly().asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(Number.class)));
+ assertThat(typeDescription.getSuperClass().getDeclaredFields().getOnly().getType().getSort(),
+ is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getSuperClass().getDeclaredFields().getOnly().getType().asErasure(),
+ is((TypeDescription) new TypeDescription.ForLoadedType(Number.class)));
+ assertThat(typeDescription.getSuperClass().getDeclaredMethods().filter(named(FOO)).getOnly().getReturnType().getSort(),
+ is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getSuperClass().getDeclaredMethods().filter(named(FOO)).getOnly().getReturnType().asErasure(),
+ is((TypeDescription) new TypeDescription.ForLoadedType(Number.class)));
+ assertThat(typeDescription.getSuperClass().getDeclaredMethods().filter(named(BAR)).getOnly().getReturnType().getSort(),
+ is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getSuperClass().getDeclaredMethods().filter(named(BAR)).getOnly().getReturnType().asErasure(),
+ is((TypeDescription) new TypeDescription.ForLoadedType(List.class)));
+ }
+
+ @Test
+ public void testInterfaceType() throws Exception {
+ TypeDescription.Generic typeDescription = new TypeDescription.Generic.OfParameterizedType.ForReifiedType(new TypeDescription.ForLoadedType(Sample.class)
+ .getInterfaces().getOnly());
+ assertThat(typeDescription.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(Baz.class)));
+ assertThat(typeDescription.getTypeArguments().size(), is(1));
+ assertThat(typeDescription.getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getTypeArguments().getOnly().asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(Number.class)));
+ assertThat(typeDescription.getInterfaces().getOnly().getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getInterfaces().getOnly().asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(Qux.class)));
+ assertThat(typeDescription.getInterfaces().getOnly().getTypeArguments().size(), is(1));
+ assertThat(typeDescription.getInterfaces().getOnly().getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getInterfaces().getOnly().getTypeArguments().getOnly().asErasure(),
+ is((TypeDescription) new TypeDescription.ForLoadedType(Number.class)));
+ assertThat(typeDescription.getInterfaces().getOnly().getDeclaredMethods().filter(named(FOO)).getOnly().getReturnType().getSort(),
+ is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getInterfaces().getOnly().getDeclaredMethods().filter(named(FOO)).getOnly().getReturnType().asErasure(),
+ is((TypeDescription) new TypeDescription.ForLoadedType(Number.class)));
+ assertThat(typeDescription.getInterfaces().getOnly().getDeclaredMethods().filter(named(BAR)).getOnly().getReturnType().getSort(),
+ is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getInterfaces().getOnly().getDeclaredMethods().filter(named(BAR)).getOnly().getReturnType().asErasure(),
+ is((TypeDescription) new TypeDescription.ForLoadedType(List.class)));
+ }
+
+ @Test
+ public void testNonGenericIntermediateType() throws Exception {
+ TypeDescription.Generic typeDescription = TypeDescription.Generic.OfNonGenericType.ForReifiedErasure.of(new TypeDescription.ForLoadedType(NonGenericSample.class))
+ .getSuperClass()
+ .getSuperClass();
+ assertThat(typeDescription.getSuperClass().getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getSuperClass().asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(Foo.class)));
+ assertThat(typeDescription.getSuperClass().getTypeArguments().size(), is(1));
+ assertThat(typeDescription.getSuperClass().getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getSuperClass().getTypeArguments().getOnly().asErasure(),
+ is((TypeDescription) new TypeDescription.ForLoadedType(Number.class)));
+ assertThat(typeDescription.getSuperClass().getDeclaredFields().getOnly().getType().getSort(),
+ is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getSuperClass().getDeclaredFields().getOnly().getType().asErasure(),
+ is((TypeDescription) new TypeDescription.ForLoadedType(Number.class)));
+ assertThat(typeDescription.getSuperClass().getDeclaredMethods().filter(named(FOO)).getOnly().getReturnType().getSort(),
+ is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getSuperClass().getDeclaredMethods().filter(named(FOO)).getOnly().getReturnType().asErasure(),
+ is((TypeDescription) new TypeDescription.ForLoadedType(Number.class)));
+ assertThat(typeDescription.getSuperClass().getDeclaredMethods().filter(named(BAR)).getOnly().getReturnType().getSort(),
+ is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getSuperClass().getDeclaredMethods().filter(named(BAR)).getOnly().getReturnType().asErasure(),
+ is((TypeDescription) new TypeDescription.ForLoadedType(List.class)));
+ assertThat(typeDescription.getInterfaces().getOnly().getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getInterfaces().getOnly().asErasure(), is((TypeDescription) new TypeDescription.ForLoadedType(Qux.class)));
+ assertThat(typeDescription.getInterfaces().getOnly().getTypeArguments().size(), is(1));
+ assertThat(typeDescription.getInterfaces().getOnly().getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getInterfaces().getOnly().getTypeArguments().getOnly().asErasure(),
+ is((TypeDescription) new TypeDescription.ForLoadedType(Number.class)));
+ assertThat(typeDescription.getInterfaces().getOnly().getDeclaredMethods().filter(named(FOO)).getOnly().getReturnType().getSort(),
+ is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(typeDescription.getInterfaces().getOnly().getDeclaredMethods().filter(named(FOO)).getOnly().getReturnType().asErasure(),
+ is((TypeDescription) new TypeDescription.ForLoadedType(Number.class)));
+ assertThat(typeDescription.getInterfaces().getOnly().getDeclaredMethods().filter(named(BAR)).getOnly().getReturnType().getSort(),
+ is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(typeDescription.getInterfaces().getOnly().getDeclaredMethods().filter(named(BAR)).getOnly().getReturnType().asErasure(),
+ is((TypeDescription) new TypeDescription.ForLoadedType(List.class)));
+ }
+
+ private static class Foo<T> {
+
+ T foo;
+
+ public T foo() {
+ return null;
+ }
+
+ public List<?> bar() {
+ return null;
+ }
+ }
+
+ private static class Bar<T extends Number> extends Foo<T> {
+ /* empty */
+ }
+
+ private interface Qux<T> {
+
+ T foo();
+
+ List<?> bar();
+ }
+
+ private interface Baz<T extends Number> extends Qux<T> {
+ /* empty */
+ }
+
+ private abstract static class Sample extends Bar<Number> implements Baz<Number> {
+ /* empty */
+ }
+
+ private class NonGenericIntermediate extends Foo<Number> implements Qux<Number> {
+ /* empty */
+ }
+
+ private class RawTypeIntermediate<T> extends NonGenericIntermediate {
+ /* empty */
+ }
+
+ private class NonGenericSample extends RawTypeIntermediate<Number> {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericOfParameterizedTypeRenderingDelegateTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericOfParameterizedTypeRenderingDelegateTest.java
new file mode 100644
index 0000000..39748a5
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericOfParameterizedTypeRenderingDelegateTest.java
@@ -0,0 +1,69 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class TypeDescriptionGenericOfParameterizedTypeRenderingDelegateTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Mock
+ private TypeDescription.Generic ownerType;
+
+ @Before
+ public void setUp() throws Exception {
+ when(typeDescription.getName()).thenReturn(FOO + "." + BAR);
+ when(typeDescription.getSimpleName()).thenReturn(BAR);
+ }
+
+ @Test
+ public void testJava9Capable() throws Exception {
+ StringBuilder stringBuilder = new StringBuilder();
+ TypeDescription.Generic.OfParameterizedType.RenderingDelegate.JAVA_9_CAPABLE_VM.apply(stringBuilder, typeDescription, ownerType);
+ assertThat(stringBuilder.toString(), is("$" + BAR));
+ }
+
+ @Test
+ public void testLegacyParameterized() throws Exception {
+ when(ownerType.getSort()).thenReturn(TypeDefinition.Sort.PARAMETERIZED);
+ StringBuilder stringBuilder = new StringBuilder();
+ TypeDescription.Generic.OfParameterizedType.RenderingDelegate.LEGACY_VM.apply(stringBuilder, typeDescription, ownerType);
+ assertThat(stringBuilder.toString(), is("." + BAR));
+ }
+
+ @Test
+ public void testLegacyNonParameterized() throws Exception {
+ when(ownerType.getSort()).thenReturn(TypeDefinition.Sort.NON_GENERIC);
+ StringBuilder stringBuilder = new StringBuilder();
+ TypeDescription.Generic.OfParameterizedType.RenderingDelegate.LEGACY_VM.apply(stringBuilder, typeDescription, ownerType);
+ assertThat(stringBuilder.toString(), is("." + FOO + "." + BAR));
+ }
+
+ @Test
+ public void testCurrent() throws Exception {
+ assertThat(TypeDescription.Generic.OfParameterizedType.RenderingDelegate.CURRENT, is(ClassFileVersion.ofThisVm().isAtLeast(ClassFileVersion.JAVA_V9)
+ ? TypeDescription.Generic.OfParameterizedType.RenderingDelegate.JAVA_9_CAPABLE_VM
+ : TypeDescription.Generic.OfParameterizedType.RenderingDelegate.LEGACY_VM));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeDescription.Generic.OfParameterizedType.RenderingDelegate.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericOfTypeVariableSymbolicTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericOfTypeVariableSymbolicTest.java
new file mode 100644
index 0000000..287fbc4
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericOfTypeVariableSymbolicTest.java
@@ -0,0 +1,162 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationSource;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.assertThat;
+
+public class TypeDescriptionGenericOfTypeVariableSymbolicTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ private TypeDescription.Generic typeVariable;
+
+ @Mock
+ private AnnotationDescription annotationDescription;
+
+ @Before
+ public void setUp() throws Exception {
+ typeVariable = new TypeDescription.Generic.OfTypeVariable.Symbolic(FOO, new AnnotationSource.Explicit(annotationDescription));
+ }
+
+ @Test
+ public void testSymbol() throws Exception {
+ assertThat(typeVariable.getSymbol(), is(FOO));
+ }
+
+ @Test
+ public void testTypeName() throws Exception {
+ assertThat(typeVariable.getTypeName(), is(FOO));
+ }
+
+ @Test
+ public void testToString() throws Exception {
+ assertThat(typeVariable.toString(), is(FOO));
+ }
+
+ @Test
+ public void testSort() throws Exception {
+ assertThat(typeVariable.getSort(), is(TypeDefinition.Sort.VARIABLE_SYMBOLIC));
+ }
+
+ @Test
+ public void testStackSize() throws Exception {
+ assertThat(typeVariable.getStackSize(), is(StackSize.SINGLE));
+ }
+
+ @Test
+ public void testArray() throws Exception {
+ assertThat(typeVariable.isArray(), is(false));
+ }
+
+ @Test
+ public void testPrimitive() throws Exception {
+ assertThat(typeVariable.isPrimitive(), is(false));
+ }
+
+ @Test
+ public void testEquals() throws Exception {
+ assertThat(typeVariable, is(typeVariable));
+ assertThat(typeVariable, is((TypeDescription.Generic) new TypeDescription.Generic.OfTypeVariable.Symbolic(FOO, new AnnotationSource.Explicit(annotationDescription))));
+ assertThat(typeVariable, is((TypeDescription.Generic) new TypeDescription.Generic.OfTypeVariable.Symbolic(FOO, AnnotationSource.Empty.INSTANCE)));
+ assertThat(typeVariable, not((TypeDescription.Generic) new TypeDescription.Generic.OfTypeVariable.Symbolic(BAR, AnnotationSource.Empty.INSTANCE)));
+ assertThat(typeVariable, not(TypeDescription.Generic.OBJECT));
+ assertThat(typeVariable, not(new Object()));
+ assertThat(typeVariable, not(equalTo(null)));
+ }
+
+ @Test
+ public void testAnnotations() throws Exception {
+ assertThat(typeVariable.getDeclaredAnnotations().size(), is(1));
+ assertThat(typeVariable.getDeclaredAnnotations().contains(annotationDescription), is(true));
+ }
+
+ @Test
+ public void testHashCode() throws Exception {
+ assertThat(typeVariable.hashCode(), is(FOO.hashCode()));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testRawTypeThrowsException() throws Exception {
+ typeVariable.asRawType();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testErasureThrowsException() throws Exception {
+ typeVariable.asErasure();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testComponentTypeThrowsException() throws Exception {
+ typeVariable.getComponentType();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testDeclaredFieldsThrowsException() throws Exception {
+ typeVariable.getDeclaredFields();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testDeclaredMethodsThrowsException() throws Exception {
+ typeVariable.getDeclaredMethods();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testLowerBoundsThrowsException() throws Exception {
+ typeVariable.getLowerBounds();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUpperBoundsThrowsException() throws Exception {
+ typeVariable.getUpperBounds();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testParametersThrowsException() throws Exception {
+ typeVariable.getTypeArguments();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testVariableSourceThrowsException() throws Exception {
+ typeVariable.getTypeVariableSource();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void getOwnerTypeThrowsException() throws Exception {
+ typeVariable.getOwnerType();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testSuperClassThrowsException() throws Exception {
+ typeVariable.getSuperClass();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testInterfacesThrowsException() throws Exception {
+ typeVariable.getInterfaces();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIteratorThrowsException() throws Exception {
+ typeVariable.iterator();
+ }
+
+ @Test
+ public void testRepresents() throws Exception {
+ assertThat(typeVariable.represents(Object.class), is(false));
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testRepresentsNullPointer() throws Exception {
+ typeVariable.represents(null);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericOfTypeVariableWithAnnotationOverlayTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericOfTypeVariableWithAnnotationOverlayTest.java
new file mode 100644
index 0000000..c8314b6
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericOfTypeVariableWithAnnotationOverlayTest.java
@@ -0,0 +1,116 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.description.TypeVariableSource;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.annotation.AnnotationSource;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class TypeDescriptionGenericOfTypeVariableWithAnnotationOverlayTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ private TypeDescription.Generic typeVariable;
+
+ @Mock
+ private TypeDescription.Generic original, upperBound, lowerBound;
+
+ @Mock
+ private TypeVariableSource typeVariableSource;
+
+ @Mock
+ private AnnotationDescription annotationDescription;
+
+ @Before
+ public void setUp() throws Exception {
+ when(original.getSymbol()).thenReturn(FOO);
+ when(upperBound.asGenericType()).thenReturn(upperBound);
+ when(lowerBound.asGenericType()).thenReturn(lowerBound);
+ when(original.getUpperBounds()).thenReturn(new TypeList.Generic.Explicit(upperBound));
+ when(original.getLowerBounds()).thenReturn(new TypeList.Generic.Explicit(lowerBound));
+ when(original.getTypeVariableSource()).thenReturn(typeVariableSource);
+ typeVariable = new TypeDescription.Generic.OfTypeVariable.WithAnnotationOverlay(original, new AnnotationSource.Explicit(annotationDescription));
+ }
+
+ @Test
+ public void testSymbol() throws Exception {
+ assertThat(typeVariable.getSymbol(), is(FOO));
+ }
+
+ @Test
+ public void testTypeName() throws Exception {
+ assertThat(typeVariable.getTypeName(), is(FOO));
+ }
+
+ @Test
+ public void testToString() throws Exception {
+ assertThat(typeVariable.toString(), is(FOO));
+ }
+
+ @Test
+ public void testSort() throws Exception {
+ assertThat(typeVariable.getSort(), is(TypeDefinition.Sort.VARIABLE));
+ }
+
+ @Test
+ public void testStackSize() throws Exception {
+ assertThat(typeVariable.getStackSize(), is(StackSize.SINGLE));
+ }
+
+ @Test
+ public void testArray() throws Exception {
+ assertThat(typeVariable.isArray(), is(false));
+ }
+
+ @Test
+ public void testPrimitive() throws Exception {
+ assertThat(typeVariable.isPrimitive(), is(false));
+ }
+
+ @Test
+ public void testEquals() throws Exception {
+ assertThat(typeVariable, is(typeVariable));
+ assertThat(typeVariable, is(typeVariable(FOO, typeVariableSource, annotationDescription)));
+ assertThat(typeVariable, is(typeVariable(FOO, typeVariableSource)));
+ assertThat(typeVariable, not(typeVariable(BAR, typeVariableSource, annotationDescription)));
+ assertThat(typeVariable, not(typeVariable(FOO, mock(TypeVariableSource.class), annotationDescription)));
+ assertThat(typeVariable, not(TypeDescription.Generic.OBJECT));
+ assertThat(typeVariable, not(new Object()));
+ assertThat(typeVariable, not(equalTo(null)));
+ }
+
+ private static TypeDescription.Generic typeVariable(String symbol, TypeVariableSource typeVariableSource, AnnotationDescription... annotationDescription) {
+ TypeDescription.Generic typeVariable = mock(TypeDescription.Generic.class);
+ when(typeVariable.getSort()).thenReturn(TypeDefinition.Sort.VARIABLE);
+ when(typeVariable.getSymbol()).thenReturn(symbol);
+ when(typeVariable.getTypeVariableSource()).thenReturn(typeVariableSource);
+ when(typeVariable.getDeclaredAnnotations()).thenReturn(new AnnotationList.Explicit(annotationDescription));
+ return typeVariable;
+ }
+
+ @Test
+ public void testAnnotations() throws Exception {
+ assertThat(typeVariable.getDeclaredAnnotations().size(), is(1));
+ assertThat(typeVariable.getDeclaredAnnotations().contains(annotationDescription), is(true));
+ }
+
+ @Test
+ public void testHashCode() throws Exception {
+ assertThat(typeVariable.hashCode(), is(typeVariableSource.hashCode() ^ FOO.hashCode()));
+ }
+
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorAnnotationStripperTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorAnnotationStripperTest.java
new file mode 100644
index 0000000..72d0516
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorAnnotationStripperTest.java
@@ -0,0 +1,106 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class TypeDescriptionGenericVisitorAnnotationStripperTest {
+
+ private static final String FOO = "foo";
+
+ private AnnotationDescription annotationDescription;
+
+ @Before
+ public void setUp() throws Exception {
+ annotationDescription = AnnotationDescription.Builder.ofType(Foo.class).build();
+ }
+
+ @Test
+ public void testWildcardLowerBound() throws Exception {
+ TypeDescription.Generic typeDescription = TypeDescription.Generic.Builder.rawType(Object.class)
+ .annotate(annotationDescription)
+ .asWildcardLowerBound(annotationDescription);
+ assertThat(TypeDescription.Generic.Visitor.AnnotationStripper.INSTANCE.onWildcard(typeDescription), is(typeDescription));
+ assertThat(TypeDescription.Generic.Visitor.AnnotationStripper.INSTANCE.onWildcard(typeDescription).getDeclaredAnnotations().size(), is(0));
+ assertThat(TypeDescription.Generic.Visitor.AnnotationStripper.INSTANCE.onWildcard(typeDescription).getLowerBounds()
+ .getOnly().getDeclaredAnnotations().size(), is(0));
+ }
+
+ @Test
+ public void testWildcardUpperBound() throws Exception {
+ TypeDescription.Generic typeDescription = TypeDescription.Generic.Builder.rawType(Object.class)
+ .annotate(annotationDescription)
+ .asWildcardLowerBound(annotationDescription);
+ assertThat(TypeDescription.Generic.Visitor.AnnotationStripper.INSTANCE.onWildcard(typeDescription), is(typeDescription));
+ assertThat(TypeDescription.Generic.Visitor.AnnotationStripper.INSTANCE.onWildcard(typeDescription).getDeclaredAnnotations().size(), is(0));
+ assertThat(TypeDescription.Generic.Visitor.AnnotationStripper.INSTANCE.onWildcard(typeDescription).getUpperBounds()
+ .getOnly().getDeclaredAnnotations().size(), is(0));
+ }
+
+ @Test
+ public void testGenericArray() throws Exception {
+ TypeDescription.Generic typeDescription = TypeDescription.Generic.Builder.rawType(Object.class)
+ .annotate(annotationDescription)
+ .asArray()
+ .annotate(annotationDescription)
+ .build();
+ assertThat(TypeDescription.Generic.Visitor.AnnotationStripper.INSTANCE.onGenericArray(typeDescription), is(typeDescription));
+ assertThat(TypeDescription.Generic.Visitor.AnnotationStripper.INSTANCE.onGenericArray(typeDescription).getDeclaredAnnotations().size(), is(0));
+ assertThat(TypeDescription.Generic.Visitor.AnnotationStripper.INSTANCE.onGenericArray(typeDescription).getComponentType()
+ .getDeclaredAnnotations().size(), is(0));
+ }
+
+ @Test
+ public void testNonGenericArray() throws Exception {
+ TypeDescription.Generic typeDescription = TypeDescription.Generic.Builder.rawType(Object.class)
+ .annotate(annotationDescription)
+ .asArray()
+ .annotate(annotationDescription)
+ .build();
+ assertThat(TypeDescription.Generic.Visitor.AnnotationStripper.INSTANCE.onNonGenericType(typeDescription), is(typeDescription));
+ assertThat(TypeDescription.Generic.Visitor.AnnotationStripper.INSTANCE.onNonGenericType(typeDescription).getDeclaredAnnotations().size(), is(0));
+ assertThat(TypeDescription.Generic.Visitor.AnnotationStripper.INSTANCE.onNonGenericType(typeDescription).getComponentType()
+ .getDeclaredAnnotations().size(), is(0));
+ }
+
+ @Test
+ public void testNonGeneric() throws Exception {
+ TypeDescription.Generic typeDescription = TypeDescription.Generic.Builder.rawType(Object.class)
+ .annotate(annotationDescription)
+ .build();
+ assertThat(TypeDescription.Generic.Visitor.AnnotationStripper.INSTANCE.onNonGenericType(typeDescription), is(typeDescription));
+ assertThat(TypeDescription.Generic.Visitor.AnnotationStripper.INSTANCE.onNonGenericType(typeDescription).getDeclaredAnnotations().size(), is(0));
+ }
+
+ @Test
+ public void testTypeVariable() throws Exception {
+ TypeDescription.Generic typeDescription = TypeDescription.Generic.Builder.typeVariable(FOO).annotate(annotationDescription).build();
+ assertThat(TypeDescription.Generic.Visitor.AnnotationStripper.INSTANCE.onTypeVariable(typeDescription).getSymbol(), is(FOO));
+ assertThat(TypeDescription.Generic.Visitor.AnnotationStripper.INSTANCE.onTypeVariable(typeDescription).getDeclaredAnnotations().size(), is(0));
+ }
+
+ @Test
+ public void testParameterized() throws Exception {
+ TypeDescription.Generic typeDescription = TypeDescription.Generic.Builder.parameterizedType(new TypeDescription.ForLoadedType(Collection.class),
+ TypeDescription.Generic.Builder.rawType(Object.class).annotate(annotationDescription).build()).annotate(annotationDescription).build();
+ assertThat(TypeDescription.Generic.Visitor.AnnotationStripper.INSTANCE.onParameterizedType(typeDescription), is(typeDescription));
+ assertThat(TypeDescription.Generic.Visitor.AnnotationStripper.INSTANCE.onParameterizedType(typeDescription).getDeclaredAnnotations().size(), is(0));
+ assertThat(TypeDescription.Generic.Visitor.AnnotationStripper.INSTANCE.onParameterizedType(typeDescription).getTypeArguments()
+ .getOnly().getDeclaredAnnotations().size(), is(0));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeDescription.Generic.Visitor.AnnotationStripper.class).apply();
+ }
+
+ private @interface Foo {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorAssignerTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorAssignerTest.java
new file mode 100644
index 0000000..354a3e1
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorAssignerTest.java
@@ -0,0 +1,573 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.Serializable;
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class TypeDescriptionGenericVisitorAssignerTest {
+
+ private TypeDescription.Generic collectionWildcard, collectionRaw;
+
+ private TypeDescription.Generic collectionTypeVariableT, collectionTypeVariableS, collectionTypeVariableU;
+
+ private TypeDescription.Generic collectionUpperBoundTypeVariableT, collectionUpperBoundTypeVariableS, collectionUpperBoundTypeVariableU;
+
+ private TypeDescription.Generic collectionLowerBoundTypeVariableT, collectionLowerBoundTypeVariableS, collectionLowerBoundTypeVariableU;
+
+ private TypeDescription.Generic listRaw, listWildcard;
+
+ private TypeDescription.Generic abstractListRaw, arrayListRaw, arrayListWildcard;
+
+ private TypeDescription.Generic callableWildcard;
+
+ private TypeDescription.Generic arrayListTypeVariableT, arrayListTypeVariableS;
+
+ private TypeDescription.Generic collectionRawArray, listRawArray, listWildcardArray, arrayListRawArray;
+
+ private TypeDescription.Generic stringArray, objectArray, objectNestedArray;
+
+ private TypeDescription.Generic unboundWildcard;
+
+ private TypeDescription.Generic typeVariableT, typeVariableS, typeVariableU, typeVariableV;
+
+ private TypeDescription.Generic arrayTypeVariableT, arrayTypeVariableS, arrayTypeVariableU;
+
+ private TypeDescription.Generic arrayNestedTypeVariableT;
+
+ @Before
+ public void setUp() throws Exception {
+ FieldList<?> fields = new TypeDescription.ForLoadedType(GenericTypes.class).getDeclaredFields();
+ collectionRaw = fields.filter(named("collectionRaw")).getOnly().getType();
+ collectionWildcard = fields.filter(named("collectionWildcard")).getOnly().getType();
+ collectionTypeVariableT = fields.filter(named("collectionTypeVariableT")).getOnly().getType();
+ collectionTypeVariableS = fields.filter(named("collectionTypeVariableS")).getOnly().getType();
+ collectionTypeVariableU = fields.filter(named("collectionTypeVariableU")).getOnly().getType();
+ collectionUpperBoundTypeVariableT = fields.filter(named("collectionUpperBoundTypeVariableT")).getOnly().getType();
+ collectionUpperBoundTypeVariableS = fields.filter(named("collectionUpperBoundTypeVariableS")).getOnly().getType();
+ collectionUpperBoundTypeVariableU = fields.filter(named("collectionUpperBoundTypeVariableU")).getOnly().getType();
+ collectionLowerBoundTypeVariableT = fields.filter(named("collectionLowerBoundTypeVariableT")).getOnly().getType();
+ collectionLowerBoundTypeVariableS = fields.filter(named("collectionLowerBoundTypeVariableS")).getOnly().getType();
+ collectionLowerBoundTypeVariableU = fields.filter(named("collectionLowerBoundTypeVariableU")).getOnly().getType();
+ listRaw = fields.filter(named("listRaw")).getOnly().getType();
+ listWildcard = fields.filter(named("listWildcard")).getOnly().getType();
+ arrayListTypeVariableT = fields.filter(named("arrayListTypeVariableT")).getOnly().getType();
+ arrayListTypeVariableS = fields.filter(named("arrayListTypeVariableS")).getOnly().getType();
+ TypeDescription.Generic arrayListTypeVariableU = fields.filter(named("arrayListTypeVariableU")).getOnly().getType();
+ TypeDescription.Generic arrayListTypeVariableV = fields.filter(named("arrayListTypeVariableV")).getOnly().getType();
+ abstractListRaw = fields.filter(named("abstractListRaw")).getOnly().getType();
+ callableWildcard = fields.filter(named("callableWildcard")).getOnly().getType();
+ arrayListRaw = fields.filter(named("arrayListRaw")).getOnly().getType();
+ arrayListWildcard = fields.filter(named("arrayListWildcard")).getOnly().getType();
+ collectionRawArray = fields.filter(named("collectionRawArray")).getOnly().getType();
+ listRawArray = fields.filter(named("listRawArray")).getOnly().getType();
+ listWildcardArray = fields.filter(named("listWildcardArray")).getOnly().getType();
+ arrayListRawArray = fields.filter(named("arrayListRawArray")).getOnly().getType();
+ stringArray = new TypeDescription.Generic.OfNonGenericType.ForLoadedType(String[].class);
+ objectArray = new TypeDescription.Generic.OfNonGenericType.ForLoadedType(Object[].class);
+ objectNestedArray = new TypeDescription.Generic.OfNonGenericType.ForLoadedType(Object[][].class);
+ unboundWildcard = listWildcard.getTypeArguments().getOnly();
+ typeVariableT = arrayListTypeVariableT.getTypeArguments().getOnly();
+ typeVariableS = arrayListTypeVariableS.getTypeArguments().getOnly();
+ typeVariableU = arrayListTypeVariableU.getTypeArguments().getOnly();
+ typeVariableV = arrayListTypeVariableV.getTypeArguments().getOnly();
+ arrayTypeVariableT = fields.filter(named("arrayTypeVariableT")).getOnly().getType();
+ arrayTypeVariableS = fields.filter(named("arrayTypeVariableS")).getOnly().getType();
+ arrayTypeVariableU = fields.filter(named("arrayTypeVariableU")).getOnly().getType();
+ arrayNestedTypeVariableT = fields.filter(named("arrayNestedTypeVariableT")).getOnly().getType();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testAssignFromWildcardThrowsException() throws Exception {
+ unboundWildcard.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE);
+ }
+
+ @Test
+ public void testAssignNonGenericTypeFromAssignableNonGenericType() throws Exception {
+ assertThat(TypeDescription.Generic.OBJECT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(TypeDescription.STRING.asGenericType()), is(true));
+ }
+
+ @Test
+ public void testAssignNonGenericTypeFromNonAssignableNonGenericType() throws Exception {
+ assertThat(TypeDescription.STRING.asGenericType().accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(TypeDescription.Generic.OBJECT), is(false));
+ }
+
+ @Test
+ public void testAssignObjectTypeFromAssignableGenericType() throws Exception {
+ assertThat(TypeDescription.Generic.OBJECT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(listWildcard), is(true));
+ }
+
+ @Test
+ public void testAssignNonGenericTypeFromNonAssignableGenericType() throws Exception {
+ assertThat(TypeDescription.STRING.asGenericType().accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(listWildcard), is(false));
+ }
+
+ @Test
+ public void testAssignNonGenericSuperInterfaceTypeFromAssignableGenericInterfaceType() throws Exception {
+ assertThat(collectionRaw.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(listWildcard), is(true));
+ }
+
+ @Test
+ public void testAssignNonGenericSuperInterfaceTypeFromAssignableGenericType() throws Exception {
+ assertThat(collectionRaw.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(arrayListWildcard), is(true));
+ }
+
+ @Test
+ public void testAssignRawInterfaceTypeFromEqualGenericInterfaceType() throws Exception {
+ assertThat(listRaw.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(listWildcard), is(true));
+ }
+
+ @Test
+ public void testAssignRawTypeFromEqualGenericType() throws Exception {
+ assertThat(arrayListRaw.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(arrayListWildcard), is(true));
+ }
+
+ @Test
+ public void testAssignNonGenericSuperTypeFromAssignableGenericType() throws Exception {
+ assertThat(abstractListRaw.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(arrayListWildcard), is(true));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testAssignNonGenericTypeFromWildcardThrowsException() throws Exception {
+ TypeDescription.Generic.OBJECT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(unboundWildcard);
+ }
+
+ @Test
+ public void testAssignNonGenericTypeFromAssignableTypeVariable() throws Exception {
+ assertThat(TypeDescription.Generic.OBJECT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(typeVariableT), is(true));
+ }
+
+ @Test
+ public void testAssignNonGenericTypeFromNonAssignableTypeVariable() throws Exception {
+ assertThat(TypeDescription.STRING.asGenericType().accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(typeVariableT), is(false));
+ }
+
+ @Test
+ public void testAssignNonGenericSuperArrayTypeFromAssignableGenericArrayType() throws Exception {
+ assertThat(collectionRawArray.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(listWildcardArray), is(true));
+ }
+
+ @Test
+ public void testAssignRawArrayTypeFromEqualGenericArrayType() throws Exception {
+ assertThat(listRawArray.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(listWildcardArray), is(true));
+ }
+
+ @Test
+ public void testAssignNonGenericArrayFromNonAssignableGenericArrayType() throws Exception {
+ assertThat(stringArray.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(listWildcardArray), is(false));
+ }
+
+ @Test
+ public void testAssignNonGenericArrayFromAssignableGenericArrayType() throws Exception {
+ assertThat(objectArray.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(listWildcardArray), is(true));
+ }
+
+ @Test
+ public void testAssignNonGenericArrayFromGenericArrayTypeOfIncompatibleArity() throws Exception {
+ assertThat(objectNestedArray.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(listWildcardArray), is(false));
+ }
+
+ @Test
+ public void testAssignObjectTypeFromGenericArrayType() throws Exception {
+ assertThat(TypeDescription.Generic.OBJECT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(listWildcardArray), is(true));
+ }
+
+ @Test
+ public void testAssignCloneableTypeFromGenericArrayType() throws Exception {
+ assertThat(new TypeDescription.Generic.OfNonGenericType.ForLoadedType(Cloneable.class).accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(listWildcardArray), is(true));
+ }
+
+ @Test
+ public void testAssignSerializableTypeFromGenericArrayType() throws Exception {
+ assertThat(new TypeDescription.Generic.OfNonGenericType.ForLoadedType(Serializable.class).accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(listWildcardArray), is(true));
+ }
+
+ @Test
+ public void testAssignTypeVariableFromNonGenericType() throws Exception {
+ assertThat(typeVariableT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(TypeDescription.Generic.OBJECT), is(false));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testAssignTypeVariableFromWildcardTypeThrowsException() throws Exception {
+ typeVariableT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(unboundWildcard);
+ }
+
+ @Test
+ public void testAssignTypeVariableFromGenericArrayType() throws Exception {
+ assertThat(typeVariableT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(listWildcardArray), is(false));
+ }
+
+ @Test
+ public void testAssignTypeVariableFromParameterizedType() throws Exception {
+ assertThat(typeVariableT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(listWildcard), is(false));
+ }
+
+ @Test
+ public void testAssignTypeVariableFromEqualTypeVariable() throws Exception {
+ assertThat(typeVariableT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(typeVariableT), is(true));
+ }
+
+ @Test
+ public void testAssignTypeVariableFromNonAssignableWildcard() throws Exception {
+ assertThat(typeVariableT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(typeVariableS), is(false));
+ }
+
+ @Test
+ public void testAssignTypeVariableFromAssignableWildcard() throws Exception {
+ assertThat(typeVariableT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(typeVariableU), is(true));
+ }
+
+ @Test
+ public void testAssignGenericArrayFromAssignableGenericArray() throws Exception {
+ assertThat(arrayTypeVariableT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(arrayTypeVariableU), is(true));
+ }
+
+ @Test
+ public void testAssignGenericNestedArrayFromNonAssignableGenericArray() throws Exception {
+ assertThat(arrayTypeVariableT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(arrayNestedTypeVariableT), is(false));
+ }
+
+ @Test
+ public void testAssignGenericNestedArrayFromAssignableObjectArray() throws Exception {
+ assertThat(new TypeDescription.Generic.OfNonGenericType.ForLoadedType(Object[][].class).accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(arrayNestedTypeVariableT), is(true));
+ }
+
+ @Test
+ public void testAssignGenericArrayFromNonAssignableGenericArray() throws Exception {
+ assertThat(arrayTypeVariableT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(arrayTypeVariableS), is(false));
+ }
+
+ @Test
+ public void testAssignGenericArrayFromNonAssignableNonGenericNonArrayType() throws Exception {
+ assertThat(arrayTypeVariableT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(TypeDescription.Generic.OBJECT), is(false));
+ }
+
+ @Test
+ public void testAssignGenericArrayFromNonAssignableNonGenericArrayType() throws Exception {
+ assertThat(arrayTypeVariableT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(objectArray), is(false));
+ }
+
+ @Test
+ public void testAssignGenericArrayFromAssignableNonGenericArrayType() throws Exception {
+ assertThat(listWildcardArray.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(arrayListRawArray), is(true));
+ }
+
+ @Test
+ public void testAssignGenericArrayFromNonAssignableTypeVariable() throws Exception {
+ assertThat(arrayTypeVariableT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(typeVariableT), is(false));
+ }
+
+ @Test
+ public void testAssignGenericArrayFromNonAssignableParameterizedType() throws Exception {
+ assertThat(arrayTypeVariableT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(arrayListWildcard), is(false));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testAssignGenericArrayFromWildcardThrowsException() throws Exception {
+ arrayTypeVariableT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(unboundWildcard);
+ }
+
+ @Test
+ public void testAssignParameterizedWildcardTypeFromEqualType() throws Exception {
+ assertThat(collectionWildcard.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(collectionWildcard), is(true));
+ }
+
+ @Test
+ public void testAssignParameterizedWildcardTypeFromEqualRawType() throws Exception {
+ assertThat(collectionWildcard.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(collectionRaw), is(true));
+ }
+
+ @Test
+ public void testAssignParameterizedWildcardTypeFromAssignableParameterizedWildcardType() throws Exception {
+ assertThat(collectionWildcard.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(arrayListWildcard), is(true));
+ }
+
+ @Test
+ public void testAssignParameterizedWildcardTypeFromAssignableParameterizedNonWildcardTypeType() throws Exception {
+ assertThat(collectionWildcard.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(arrayListTypeVariableT), is(true));
+ }
+
+ @Test
+ public void testAssignParameterizedWildcardTypeFromAssignableTypeVariableType() throws Exception {
+ assertThat(collectionWildcard.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(typeVariableV), is(true));
+ }
+
+ @Test
+ public void testAssignParameterizedWildcardTypeFromNonAssignableRawType() throws Exception {
+ assertThat(collectionWildcard.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(TypeDescription.STRING.asGenericType()), is(false));
+ }
+
+ @Test
+ public void testAssignParameterizedWildcardTypeFromNonAssignableParameterizedType() throws Exception {
+ assertThat(collectionWildcard.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(callableWildcard), is(false));
+ }
+
+ @Test
+ public void testAssignParameterizedWildcardTypeFromNonAssignableGenericArrayType() throws Exception {
+ assertThat(collectionWildcard.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(arrayTypeVariableT), is(false));
+ }
+
+ @Test
+ public void testAssignParameterizedWildcardTypeFromNonAssignableTypeVariableType() throws Exception {
+ assertThat(collectionWildcard.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(typeVariableT), is(false));
+ }
+
+ @Test
+ public void testAssignParameterizedTypeVariableTypeFromEqualParameterizedTypeVariableTypeType() throws Exception {
+ assertThat(collectionTypeVariableT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(collectionTypeVariableT), is(true));
+ }
+
+ @Test
+ public void testAssignParameterizedTypeVariableTypeFromAssignableParameterizedTypeVariableTypeType() throws Exception {
+ assertThat(collectionTypeVariableT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(arrayListTypeVariableT), is(true));
+ }
+
+ @Test
+ public void testAssignParameterizedTypeVariableTypeFromNonAssignableParameterizedTypeVariableTypeType() throws Exception {
+ assertThat(collectionTypeVariableT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(arrayListTypeVariableS), is(false));
+ }
+
+ @Test
+ public void testAssignUpperBoundFromAssignableBound() throws Exception {
+ assertThat(collectionUpperBoundTypeVariableT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(collectionTypeVariableT), is(true));
+ }
+
+ @Test
+ public void testAssignUpperBoundFromAssignableBoundSuperType() throws Exception {
+ assertThat(collectionUpperBoundTypeVariableT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(collectionTypeVariableU), is(true));
+ }
+
+ @Test
+ public void testAssignUpperBoundFromAssignableUpperBoundSuperType() throws Exception {
+ assertThat(collectionUpperBoundTypeVariableT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(collectionUpperBoundTypeVariableU), is(true));
+ }
+
+ @Test
+ public void testAssignUpperBoundFromAssignableUpperBoundEqualType() throws Exception {
+ assertThat(collectionUpperBoundTypeVariableT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(collectionTypeVariableU), is(true));
+ }
+
+ @Test
+ public void testAssignUpperBoundFromNonAssignableBoundType() throws Exception {
+ assertThat(collectionUpperBoundTypeVariableT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(collectionTypeVariableS), is(false));
+ }
+
+ @Test
+ public void testAssignUpperBoundFromNonAssignableUpperBoundType() throws Exception {
+ assertThat(collectionUpperBoundTypeVariableT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(collectionUpperBoundTypeVariableS), is(false));
+ }
+
+ @Test
+ public void testAssignUpperBoundFromLowerpperBoundType() throws Exception {
+ assertThat(collectionUpperBoundTypeVariableT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(collectionLowerBoundTypeVariableT), is(false));
+ }
+
+ @Test
+ public void testAssignLowerBoundFromAssignableBound() throws Exception {
+ assertThat(collectionLowerBoundTypeVariableT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(collectionTypeVariableT), is(true));
+ }
+
+ @Test
+ public void testAssignLowerBoundFromAssignableBoundSuperType() throws Exception {
+ assertThat(collectionLowerBoundTypeVariableU.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(collectionTypeVariableT), is(true));
+ }
+
+ @Test
+ public void testAssignLowerBoundFromAssignableUpperBoundSuperType() throws Exception {
+ assertThat(collectionLowerBoundTypeVariableU.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(collectionLowerBoundTypeVariableT), is(true));
+ }
+
+ @Test
+ public void testAssigLowerBoundFromAssignableUpperBoundEqualType() throws Exception {
+ assertThat(collectionLowerBoundTypeVariableU.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(collectionTypeVariableT), is(true));
+ }
+
+ @Test
+ public void testAssignLowerBoundFromNonAssignableBoundType() throws Exception {
+ assertThat(collectionLowerBoundTypeVariableT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(collectionTypeVariableS), is(false));
+ }
+
+ @Test
+ public void testAssignLowerBoundFromNonAssignableUpperBoundType() throws Exception {
+ assertThat(collectionLowerBoundTypeVariableT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(collectionLowerBoundTypeVariableS), is(false));
+ }
+
+ @Test
+ public void testAssignLowerBoundFromLowerpperBoundType() throws Exception {
+ assertThat(collectionLowerBoundTypeVariableT.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(collectionUpperBoundTypeVariableT), is(false));
+ }
+
+ @Test
+ public void testAssignLowerBoundFromAssignableBoundSubType() throws Exception {
+ assertThat(collectionLowerBoundTypeVariableU.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(collectionTypeVariableT), is(true));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testAssignParameterizedTypeFromWildcardTypeThrowsException() throws Exception {
+ collectionWildcard.accept(TypeDescription.Generic.Visitor.Assigner.INSTANCE)
+ .isAssignableFrom(unboundWildcard);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testAssignIncompatibleParameterizedTypesThrowsException() throws Exception {
+ TypeDescription.Generic source = mock(TypeDescription.Generic.class), target = mock(TypeDescription.Generic.class);
+ TypeDescription erasure = mock(TypeDescription.class);
+ when(source.asErasure()).thenReturn(erasure);
+ when(target.asErasure()).thenReturn(erasure);
+ when(source.getTypeArguments()).thenReturn(new TypeList.Generic.Empty());
+ when(target.getTypeArguments()).thenReturn(new TypeList.Generic.Explicit(mock(TypeDescription.Generic.class)));
+ new TypeDescription.Generic.Visitor.Assigner.Dispatcher.ForParameterizedType(target).onParameterizedType(source);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeDescription.Generic.Visitor.Assigner.class).apply();
+ ObjectPropertyAssertion.of(TypeDescription.Generic.Visitor.Assigner.Dispatcher.ForGenericArray.class).apply();
+ ObjectPropertyAssertion.of(TypeDescription.Generic.Visitor.Assigner.Dispatcher.ForNonGenericType.class).apply();
+ ObjectPropertyAssertion.of(TypeDescription.Generic.Visitor.Assigner.Dispatcher.ForTypeVariable.class).apply();
+ ObjectPropertyAssertion.of(TypeDescription.Generic.Visitor.Assigner.Dispatcher.ForParameterizedType.class).apply();
+ ObjectPropertyAssertion.of(TypeDescription.Generic.Visitor.Assigner.Dispatcher.ForParameterizedType.ParameterAssigner.class).apply();
+ ObjectPropertyAssertion.of(TypeDescription.Generic.Visitor.Assigner.Dispatcher.ForParameterizedType.ParameterAssigner.InvariantBinding.class).apply();
+ ObjectPropertyAssertion.of(TypeDescription.Generic.Visitor.Assigner.Dispatcher.ForParameterizedType.ParameterAssigner.CovariantBinding.class).apply();
+ ObjectPropertyAssertion.of(TypeDescription.Generic.Visitor.Assigner.Dispatcher.ForParameterizedType.ParameterAssigner.ContravariantBinding.class).apply();
+ }
+
+ @SuppressWarnings({"unused", "unchecked"})
+ private static class GenericTypes<T, S, U extends T, V extends List<?>> {
+
+ private Collection collectionRaw;
+
+ private Collection<?> collectionWildcard;
+
+ private Collection<T> collectionTypeVariableT;
+
+ private Collection<S> collectionTypeVariableS;
+
+ private Collection<U> collectionTypeVariableU;
+
+ private Collection<? extends T> collectionUpperBoundTypeVariableT;
+
+ private Collection<? extends S> collectionUpperBoundTypeVariableS;
+
+ private Collection<? extends U> collectionUpperBoundTypeVariableU;
+
+ private Collection<? super T> collectionLowerBoundTypeVariableT;
+
+ private Collection<? super S> collectionLowerBoundTypeVariableS;
+
+ private Collection<? super U> collectionLowerBoundTypeVariableU;
+
+ private Collection[] collectionRawArray;
+
+ private List listRaw;
+
+ private List<?> listWildcard;
+
+ private List[] listRawArray;
+
+ private List<?>[] listWildcardArray;
+
+ private AbstractList abstractListRaw;
+
+ private ArrayList arrayListRaw;
+
+ private ArrayList<?> arrayListWildcard;
+
+ private ArrayList[] arrayListRawArray;
+
+ private ArrayList<T> arrayListTypeVariableT;
+
+ private ArrayList<S> arrayListTypeVariableS;
+
+ private ArrayList<U> arrayListTypeVariableU;
+
+ private ArrayList<V> arrayListTypeVariableV;
+
+ private Callable<?> callableWildcard;
+
+ private T[] arrayTypeVariableT;
+
+ private T[][] arrayNestedTypeVariableT;
+
+ private S[] arrayTypeVariableS;
+
+ private U[] arrayTypeVariableU;
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorForSignatureVisitorTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorForSignatureVisitorTest.java
new file mode 100644
index 0000000..2411599
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorForSignatureVisitorTest.java
@@ -0,0 +1,21 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+import org.objectweb.asm.signature.SignatureVisitor;
+
+import static org.mockito.Mockito.mock;
+
+public class TypeDescriptionGenericVisitorForSignatureVisitorTest {
+
+ @Test(expected = IllegalStateException.class)
+ public void testSignatureVisitorTypeVariableThrowsException() throws Exception {
+ new TypeDescription.Generic.Visitor.ForSignatureVisitor(mock(SignatureVisitor.class)).onWildcard(mock(TypeDescription.Generic.class));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeDescription.Generic.Visitor.ForSignatureVisitor.class).apply();
+ ObjectPropertyAssertion.of(TypeDescription.Generic.Visitor.ForSignatureVisitor.OfTypeArgument.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorNoOpTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorNoOpTest.java
new file mode 100644
index 0000000..2c0a073
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorNoOpTest.java
@@ -0,0 +1,50 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class TypeDescriptionGenericVisitorNoOpTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription.Generic typeDescription;
+
+ @Test
+ public void testVisitGenericArray() throws Exception {
+ assertThat(TypeDescription.Generic.Visitor.NoOp.INSTANCE.onGenericArray(typeDescription), is(typeDescription));
+ }
+
+ @Test
+ public void testVisitWildcard() throws Exception {
+ assertThat(TypeDescription.Generic.Visitor.NoOp.INSTANCE.onWildcard(typeDescription), is(typeDescription));
+ }
+
+ @Test
+ public void testVisitParameterized() throws Exception {
+ assertThat(TypeDescription.Generic.Visitor.NoOp.INSTANCE.onParameterizedType(typeDescription), is(typeDescription));
+ }
+
+ @Test
+ public void testVisitTypeVariable() throws Exception {
+ assertThat(TypeDescription.Generic.Visitor.NoOp.INSTANCE.onTypeVariable(typeDescription), is(typeDescription));
+ }
+
+ @Test
+ public void testVisitNonGenericType() throws Exception {
+ assertThat(TypeDescription.Generic.Visitor.NoOp.INSTANCE.onNonGenericType(typeDescription), is(typeDescription));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeDescription.Generic.Visitor.NoOp.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorReducingTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorReducingTest.java
new file mode 100644
index 0000000..2eff012
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorReducingTest.java
@@ -0,0 +1,114 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.dynamic.TargetType;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class TypeDescriptionGenericVisitorReducingTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription.Generic typeDescription, bound, genericTypeDescription;
+
+ @Mock
+ private TypeDescription declaringType, rawTypeDescription;
+
+ @Mock
+ private TypeVariableToken typeVariableToken;
+
+ private TypeDescription.Generic.Visitor<TypeDescription> visitor;
+
+ @Before
+ public void setUp() throws Exception {
+ when(bound.asGenericType()).thenReturn(bound);
+ visitor = new TypeDescription.Generic.Visitor.Reducing(declaringType, Collections.singletonList(typeVariableToken));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testWildcardThrowsException() throws Exception {
+ visitor.onWildcard(typeDescription);
+ }
+
+ @Test
+ public void testGenericArray() throws Exception {
+ when(typeDescription.asErasure()).thenReturn(rawTypeDescription);
+ assertThat(visitor.onGenericArray(typeDescription), is(rawTypeDescription));
+ verify(typeDescription).asErasure();
+ verifyNoMoreInteractions(typeDescription);
+ }
+
+ @Test
+ public void testParameterizedType() throws Exception {
+ when(typeDescription.asErasure()).thenReturn(rawTypeDescription);
+ assertThat(visitor.onParameterizedType(typeDescription), is(rawTypeDescription));
+ verify(typeDescription).asErasure();
+ verifyNoMoreInteractions(typeDescription);
+ }
+
+ @Test
+ public void testNonGenericType() throws Exception {
+ when(typeDescription.asErasure()).thenReturn(rawTypeDescription);
+ assertThat(visitor.onNonGenericType(typeDescription), is(rawTypeDescription));
+ verify(typeDescription).asErasure();
+ verifyNoMoreInteractions(typeDescription);
+ }
+
+ @Test
+ public void testTypeVariableSelfDeclared() throws Exception {
+ when(typeDescription.getSymbol()).thenReturn(FOO);
+ when(typeVariableToken.getSymbol()).thenReturn(FOO);
+ when(typeVariableToken.getBounds()).thenReturn(new TypeList.Generic.Explicit(bound));
+ when(bound.accept(visitor)).thenReturn(rawTypeDescription);
+ assertThat(visitor.onTypeVariable(typeDescription), is(rawTypeDescription));
+ verify(typeDescription).getSymbol();
+ verifyNoMoreInteractions(typeDescription);
+ verify(typeVariableToken).getSymbol();
+ verify(typeVariableToken).getBounds();
+ verifyNoMoreInteractions(typeVariableToken);
+ }
+
+ @Test
+ public void testTypeVariableContextDeclared() throws Exception {
+ when(typeDescription.getSymbol()).thenReturn(FOO);
+ when(typeVariableToken.getSymbol()).thenReturn(BAR);
+ when(declaringType.findVariable(FOO)).thenReturn(genericTypeDescription);
+ when(genericTypeDescription.asErasure()).thenReturn(rawTypeDescription);
+ assertThat(visitor.onTypeVariable(typeDescription), is(rawTypeDescription));
+ verify(typeDescription, times(2)).getSymbol();
+ verifyNoMoreInteractions(typeDescription);
+ verify(typeVariableToken).getSymbol();
+ verifyNoMoreInteractions(typeVariableToken);
+ verify(declaringType).findVariable(FOO);
+ verifyNoMoreInteractions(declaringType);
+ }
+
+ @Test
+ public void testTargetTypeResolution() throws Exception {
+ assertThat(visitor.onGenericArray(TargetType.DESCRIPTION.asGenericType()), is(declaringType));
+ assertThat(visitor.onParameterizedType(TargetType.DESCRIPTION.asGenericType()), is(declaringType));
+ assertThat(visitor.onNonGenericType(TargetType.DESCRIPTION.asGenericType()), is(declaringType));
+ when(typeDescription.getSymbol()).thenReturn(BAR);
+ when(declaringType.findVariable(BAR)).thenReturn(TargetType.DESCRIPTION.asGenericType());
+ assertThat(visitor.onTypeVariable(typeDescription), is(declaringType));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeDescription.Generic.Visitor.Reducing.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorReifyingTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorReifyingTest.java
new file mode 100644
index 0000000..5ea3c91
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorReifyingTest.java
@@ -0,0 +1,93 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class TypeDescriptionGenericVisitorReifyingTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription.Generic generic;
+
+ @Test
+ public void testInitiatingParameterizedType() throws Exception {
+ assertThat(TypeDescription.Generic.Visitor.Reifying.INITIATING.onParameterizedType(generic), sameInstance(generic));
+ }
+
+ @Test
+ public void testInitiatingGenerifiedNonGenericType() throws Exception {
+ when(generic.asErasure()).thenReturn(TypeDescription.OBJECT);
+ assertThat(TypeDescription.Generic.Visitor.Reifying.INITIATING.onNonGenericType(generic), sameInstance(generic));
+ }
+
+ @Test
+ public void testInitiatingNonGenerifiedNonGenericType() throws Exception {
+ when(generic.asErasure()).thenReturn(new TypeDescription.ForLoadedType(Foo.class));
+ assertThat(TypeDescription.Generic.Visitor.Reifying.INITIATING.onNonGenericType(generic), not(sameInstance(generic)));
+ assertThat(TypeDescription.Generic.Visitor.Reifying.INITIATING.onNonGenericType(generic).getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInitiatingTypeVariable() throws Exception {
+ TypeDescription.Generic.Visitor.Reifying.INITIATING.onTypeVariable(generic);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInitiatingGenericArray() throws Exception {
+ TypeDescription.Generic.Visitor.Reifying.INITIATING.onGenericArray(generic);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInitiatingWildcard() throws Exception {
+ TypeDescription.Generic.Visitor.Reifying.INITIATING.onWildcard(generic);
+ }
+
+ @Test
+ public void testInheritingParameterizedType() throws Exception {
+ assertThat(TypeDescription.Generic.Visitor.Reifying.INHERITING.onParameterizedType(generic), not(sameInstance(generic)));
+ }
+
+ @Test
+ public void testInheritingGenerifiedNonGenericType() throws Exception {
+ when(generic.asErasure()).thenReturn(TypeDescription.OBJECT);
+ assertThat(TypeDescription.Generic.Visitor.Reifying.INHERITING.onNonGenericType(generic), sameInstance(generic));
+ }
+
+ @Test
+ public void testInheritingNonGenerifiedNonGenericType() throws Exception {
+ when(generic.asErasure()).thenReturn(new TypeDescription.ForLoadedType(Foo.class));
+ assertThat(TypeDescription.Generic.Visitor.Reifying.INHERITING.onNonGenericType(generic), not(sameInstance(generic)));
+ assertThat(TypeDescription.Generic.Visitor.Reifying.INHERITING.onNonGenericType(generic).getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInheritingTypeVariable() throws Exception {
+ TypeDescription.Generic.Visitor.Reifying.INHERITING.onTypeVariable(generic);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInheritingGenericArray() throws Exception {
+ TypeDescription.Generic.Visitor.Reifying.INHERITING.onGenericArray(generic);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInheritingWildcard() throws Exception {
+ TypeDescription.Generic.Visitor.Reifying.INHERITING.onWildcard(generic);
+ }
+
+ private static class Foo<T> {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorSubstitutorForAttachmentTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorSubstitutorForAttachmentTest.java
new file mode 100644
index 0000000..8784b9e
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorSubstitutorForAttachmentTest.java
@@ -0,0 +1,79 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.matcher.ElementMatchers;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.util.List;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class TypeDescriptionGenericVisitorSubstitutorForAttachmentTest {
+
+ private static final String FOO = "foo";
+
+ @Test
+ public void testAttachment() throws Exception {
+ TypeDescription.Generic original = TypeDefinition.Sort.describe(Foo.Inner.class.getDeclaredField(FOO).getGenericType());
+ TypeDescription.Generic detached = original.accept(new TypeDescription.Generic.Visitor.Substitutor.ForDetachment(ElementMatchers.is(Foo.Inner.class)));
+ TypeDescription target = new TypeDescription.ForLoadedType(Bar.class);
+ TypeDescription.Generic attached = detached.accept(new TypeDescription.Generic.Visitor.Substitutor.ForAttachment(target.asGenericType(), target));
+ assertThat(attached.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(attached.asErasure(), sameInstance(target));
+ assertThat(attached.getTypeArguments().size(), is(4));
+ assertThat(attached.getTypeArguments().get(0).getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(attached.getTypeArguments().get(0).getSymbol(), is("T"));
+ assertThat(attached.getTypeArguments().get(0), is(target.getTypeVariables().filter(named("T")).getOnly()));
+ assertThat(attached.getTypeArguments().get(1).getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(attached.getTypeArguments().get(1).asErasure().represents(String.class), is(true));
+ assertThat(attached.getTypeArguments().get(2).getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(attached.getTypeArguments().get(2).getSymbol(), is("U"));
+ assertThat(attached.getTypeArguments().get(2), is(target.getTypeVariables().filter(named("U")).getOnly()));
+ assertThat(attached.getTypeArguments().get(3).getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(attached.getTypeArguments().get(3).asErasure().represents(List.class), is(true));
+ assertThat(attached.getTypeArguments().get(3).getTypeArguments().size(), is(1));
+ assertThat(attached.getTypeArguments().get(3).getTypeArguments().getOnly(), is(target.getTypeVariables().filter(named("S")).getOnly()));
+ assertThat(attached.getOwnerType(), notNullValue(TypeDescription.Generic.class));
+ assertThat(attached.getOwnerType().getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(attached.getOwnerType().getTypeArguments().size(), is(1));
+ assertThat(attached.getOwnerType().getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.VARIABLE));
+ assertThat(attached.getOwnerType().getTypeArguments().getOnly().getSymbol(), is("T"));
+ assertThat(attached.getOwnerType().getTypeArguments().getOnly(), is(target.getTypeVariables().filter(named("T")).getOnly()));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testIllegalAttachment() throws Exception {
+ TypeDescription.Generic original = TypeDefinition.Sort.describe(Foo.Inner.class.getDeclaredField(FOO).getGenericType());
+ TypeDescription.Generic detached = original.accept(new TypeDescription.Generic.Visitor.Substitutor.ForDetachment(ElementMatchers.is(Foo.Inner.class)));
+ detached.accept(new TypeDescription.Generic.Visitor.Substitutor.ForAttachment(TypeDescription.Generic.OBJECT, TypeDescription.OBJECT));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeDescription.Generic.Visitor.Substitutor.ForAttachment.class)
+ .refine(new ObjectPropertyAssertion.Refinement<TypeDefinition>() {
+ @Override
+ public void apply(TypeDefinition mock) {
+ when(mock.asErasure()).thenReturn(Mockito.mock(TypeDescription.class));
+ }
+ }).apply();
+ }
+
+ @SuppressWarnings("unused")
+ public static class Foo<O> {
+
+ public abstract class Inner<T, S extends CharSequence, U extends T, V> {
+
+ Foo<T>.Inner<T, String, U, List<S>> foo;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public abstract static class Bar<U, T, S, V extends Number> {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorSubstitutorForDetachmentTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorSubstitutorForDetachmentTest.java
new file mode 100644
index 0000000..3d29c45
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorSubstitutorForDetachmentTest.java
@@ -0,0 +1,67 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.dynamic.TargetType;
+import net.bytebuddy.matcher.ElementMatchers;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class TypeDescriptionGenericVisitorSubstitutorForDetachmentTest {
+
+ private static final String FOO = "foo";
+
+ @Test
+ public void testDetachment() throws Exception {
+ TypeDescription.Generic original = TypeDefinition.Sort.describe(Foo.Inner.class.getDeclaredField(FOO).getGenericType());
+ TypeDescription.Generic detached = original.accept(new TypeDescription.Generic.Visitor.Substitutor.ForDetachment(ElementMatchers.is(Foo.Inner.class)));
+ assertThat(detached, not(sameInstance(original)));
+ assertThat(detached.getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(detached.asErasure(), is(TargetType.DESCRIPTION));
+ assertThat(detached.getTypeArguments().size(), is(4));
+ assertThat(detached.getTypeArguments().get(0).getSort(), is(TypeDefinition.Sort.VARIABLE_SYMBOLIC));
+ assertThat(detached.getTypeArguments().get(0).getSymbol(), is("T"));
+ assertThat(detached.getTypeArguments().get(1).getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(detached.getTypeArguments().get(1).asErasure().represents(String.class), is(true));
+ assertThat(detached.getTypeArguments().get(2).getSort(), is(TypeDefinition.Sort.VARIABLE_SYMBOLIC));
+ assertThat(detached.getTypeArguments().get(2).getSymbol(), is("U"));
+ assertThat(detached.getTypeArguments().get(3).getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(detached.getTypeArguments().get(3).getTypeArguments().size(), is(1));
+ assertThat(detached.getTypeArguments().get(3).getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.VARIABLE_SYMBOLIC));
+ assertThat(detached.getTypeArguments().get(3).getTypeArguments().getOnly().getSymbol(), is("S"));
+ assertThat(detached.getOwnerType(), notNullValue(TypeDescription.Generic.class));
+ assertThat(detached.getOwnerType().getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(detached.getOwnerType().getTypeArguments().size(), is(1));
+ assertThat(detached.getOwnerType().getTypeArguments().getOnly().getSort(), is(TypeDefinition.Sort.VARIABLE_SYMBOLIC));
+ assertThat(detached.getOwnerType().getTypeArguments().getOnly().getSymbol(), is("T"));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testDetachedNoSource() throws Exception {
+ TypeDescription.Generic original = TypeDefinition.Sort.describe(Foo.Inner.class.getDeclaredField(FOO).getGenericType());
+ TypeDescription.Generic detached = original.accept(new TypeDescription.Generic.Visitor.Substitutor.ForDetachment(ElementMatchers.is(Foo.Inner.class)));
+ detached.getTypeArguments().get(0).getTypeVariableSource();
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeDescription.Generic.Visitor.Substitutor.ForDetachment.class).apply();
+ }
+
+ @SuppressWarnings("unused")
+ public static class Foo<O> {
+
+ public abstract class Inner<T, S extends CharSequence, U extends T, V> {
+
+ Foo<T>.Inner<T, String, U, List<S>> foo;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public abstract static class Bar<A, T, S, V extends Number> {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorSubstitutorForTokenNormalizationTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorSubstitutorForTokenNormalizationTest.java
new file mode 100644
index 0000000..b07ac2c
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorSubstitutorForTokenNormalizationTest.java
@@ -0,0 +1,75 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.description.annotation.AnnotationSource;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.dynamic.TargetType;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class TypeDescriptionGenericVisitorSubstitutorForTokenNormalizationTest {
+
+ private static final String FOO = "foo";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription target;
+
+ @Mock
+ private TypeDescription.Generic source;
+
+ @Mock
+ private AnnotationDescription annotationDescription;
+
+ @Before
+ public void setUp() throws Exception {
+ when(source.getSymbol()).thenReturn(FOO);
+ when(source.getDeclaredAnnotations()).thenReturn(new AnnotationList.Explicit(annotationDescription));
+ }
+
+ @Test
+ public void testTargetType() throws Exception {
+ TypeDescription.Generic typeDescription = new TypeDescription.Generic.Visitor.Substitutor.ForTokenNormalization(target)
+ .onSimpleType(new TypeDescription.Generic.OfNonGenericType.Latent(TargetType.DESCRIPTION, new AnnotationSource.Explicit(annotationDescription)));
+ assertThat(typeDescription.asErasure(), is(target));
+ assertThat(typeDescription.getDeclaredAnnotations(), is(Collections.singletonList(annotationDescription)));
+ }
+
+ @Test
+ public void testNotTargetType() throws Exception {
+ assertThat(new TypeDescription.Generic.Visitor.Substitutor.ForTokenNormalization(target).onSimpleType(source), sameInstance(source));
+ }
+
+ @Test
+ public void testTypeVariable() throws Exception {
+ TypeDescription.Generic typeDescription = new TypeDescription.Generic.Visitor.Substitutor.ForTokenNormalization(target).onTypeVariable(source);
+ assertThat(typeDescription, is((TypeDescription.Generic) new TypeDescription.Generic.OfTypeVariable.Symbolic(FOO, new AnnotationSource.Explicit(annotationDescription))));
+ assertThat(typeDescription.getDeclaredAnnotations(), is(Collections.singletonList(annotationDescription)));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeDescription.Generic.Visitor.Substitutor.ForTokenNormalization.class)
+ .refine(new ObjectPropertyAssertion.Refinement<TypeDescription>() {
+ @Override
+ public void apply(TypeDescription mock) {
+ when(mock.asGenericType()).thenReturn(Mockito.mock(TypeDescription.Generic.class));
+ }
+ }).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorSubstitutorForTypeVariableBindingTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorSubstitutorForTypeVariableBindingTest.java
new file mode 100644
index 0000000..22177f1
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorSubstitutorForTypeVariableBindingTest.java
@@ -0,0 +1,89 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.description.TypeVariableSource;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class TypeDescriptionGenericVisitorSubstitutorForTypeVariableBindingTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription.Generic parameterizedType, source, target, unknown, substituted;
+
+ @Mock
+ private TypeDescription.AbstractBase typeDefinition;
+
+ @Mock
+ private MethodDescription.AbstractBase methodDefinition;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ when(parameterizedType.findBindingOf(source)).thenReturn(target);
+ when(typeDefinition.accept(any(TypeVariableSource.Visitor.class))).thenCallRealMethod();
+ when(methodDefinition.accept(any(TypeVariableSource.Visitor.class))).thenReturn(substituted);
+ }
+
+ @Test
+ public void testSimpleType() throws Exception {
+ assertThat(new TypeDescription.Generic.Visitor.Substitutor.ForTypeVariableBinding(parameterizedType).onSimpleType(source), is(source));
+ }
+
+ @Test
+ public void testNonGenericType() throws Exception {
+ assertThat(new TypeDescription.Generic.Visitor.Substitutor.ForTypeVariableBinding(parameterizedType).onNonGenericType(source), is(source));
+ }
+
+ @Test
+ public void testTypeVariableKnownOnType() throws Exception {
+ when(source.getTypeVariableSource()).thenReturn(typeDefinition);
+ assertThat(new TypeDescription.Generic.Visitor.Substitutor.ForTypeVariableBinding(parameterizedType).onTypeVariable(source), is(target));
+ }
+
+ @Test
+ public void testTypeVariableUnknownOnType() throws Exception {
+ when(unknown.getTypeVariableSource()).thenReturn(typeDefinition);
+ TypeDescription.Generic rawType = mock(TypeDescription.Generic.class);
+ when(unknown.asRawType()).thenReturn(rawType);
+ assertThat(new TypeDescription.Generic.Visitor.Substitutor.ForTypeVariableBinding(parameterizedType).onTypeVariable(unknown), is(rawType));
+ }
+
+ @Test
+ public void testTypeVariableKnownOnMethod() throws Exception {
+ when(source.getTypeVariableSource()).thenReturn(methodDefinition);
+ assertThat(new TypeDescription.Generic.Visitor.Substitutor.ForTypeVariableBinding(parameterizedType).onTypeVariable(source), is(substituted));
+ }
+
+ @Test
+ public void testTypeVariableUnknownOnMethod() throws Exception {
+ when(unknown.getTypeVariableSource()).thenReturn(methodDefinition);
+ assertThat(new TypeDescription.Generic.Visitor.Substitutor.ForTypeVariableBinding(parameterizedType).onTypeVariable(unknown), is(substituted));
+ }
+
+ @Test
+ public void testUnequalVariablesAndParameters() throws Exception {
+ TypeDescription.Generic typeDescription = mock(TypeDescription.Generic.class);
+ when(typeDescription.getTypeArguments()).thenReturn(new TypeList.Generic.Explicit(mock(TypeDescription.Generic.class)));
+ TypeDescription rawTypeDescription = mock(TypeDescription.class);
+ when(typeDescription.asErasure()).thenReturn(rawTypeDescription);
+ when(rawTypeDescription.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeDescription.Generic.Visitor.Substitutor.ForTypeVariableBinding.class).apply();
+ ObjectPropertyAssertion.of(TypeDescription.Generic.Visitor.Substitutor.ForTypeVariableBinding.TypeVariableSubstitutor.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorTypeErasingTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorTypeErasingTest.java
new file mode 100644
index 0000000..a9333dd
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorTypeErasingTest.java
@@ -0,0 +1,57 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class TypeDescriptionGenericVisitorTypeErasingTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription.Generic typeDescription, rawType;
+
+ @Before
+ public void setUp() throws Exception {
+ when(typeDescription.asRawType()).thenReturn(rawType);
+ }
+
+ @Test
+ public void testGenericArray() throws Exception {
+ assertThat(TypeDescription.Generic.Visitor.TypeErasing.INSTANCE.onGenericArray(typeDescription), is(rawType));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testWildcard() throws Exception {
+ TypeDescription.Generic.Visitor.TypeErasing.INSTANCE.onWildcard(typeDescription);
+ }
+
+ @Test
+ public void testParameterized() throws Exception {
+ assertThat(TypeDescription.Generic.Visitor.TypeErasing.INSTANCE.onParameterizedType(typeDescription), is(rawType));
+ }
+
+ @Test
+ public void testTypeVariable() throws Exception {
+ assertThat(TypeDescription.Generic.Visitor.TypeErasing.INSTANCE.onTypeVariable(typeDescription), is(rawType));
+ }
+
+ @Test
+ public void testNonGeneric() throws Exception {
+ assertThat(TypeDescription.Generic.Visitor.TypeErasing.INSTANCE.onNonGenericType(typeDescription), is(rawType));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeDescription.Generic.Visitor.TypeErasing.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorValidatorForTypeAnnotations.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorValidatorForTypeAnnotations.java
new file mode 100644
index 0000000..b8023ce
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorValidatorForTypeAnnotations.java
@@ -0,0 +1,221 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.lang.annotation.ElementType;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class TypeDescriptionGenericVisitorValidatorForTypeAnnotations {
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private AnnotationDescription legalAnnotation, illegalAnnotation, duplicateAnnotation;
+
+ @Mock
+ private TypeDescription legalType, illegalType;
+
+ @Mock
+ private TypeDescription.Generic legal, illegal, duplicate, otherLegal, otherIllegal;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ when(otherLegal.accept(any(TypeDescription.Generic.Visitor.class))).thenReturn(true);
+ when(otherIllegal.accept(any(TypeDescription.Generic.Visitor.class))).thenReturn(false);
+ when(illegal.getDeclaredAnnotations()).thenReturn(new AnnotationList.Explicit(illegalAnnotation));
+ when(illegalAnnotation.getElementTypes()).thenReturn(new HashSet<ElementType>());
+ when(illegalAnnotation.getAnnotationType()).thenReturn(illegalType);
+ when(otherLegal.asGenericType()).thenReturn(otherLegal);
+ when(otherIllegal.asGenericType()).thenReturn(otherIllegal);
+ try {
+ Enum<?> typeUse = Enum.valueOf(ElementType.class, "TYPE_USE");
+ Enum<?> typeParameter = Enum.valueOf(ElementType.class, "TYPE_PARAMETER");
+ when(legalAnnotation.getElementTypes()).thenReturn(new HashSet(Arrays.asList(typeUse, typeParameter)));
+ when(duplicateAnnotation.getElementTypes()).thenReturn(new HashSet(Arrays.asList(typeUse, typeParameter)));
+ } catch (IllegalArgumentException ignored) {
+ when(legalAnnotation.getElementTypes()).thenReturn(Collections.<ElementType>emptySet());
+ when(duplicateAnnotation.getElementTypes()).thenReturn(Collections.<ElementType>emptySet());
+ }
+ when(legal.getDeclaredAnnotations()).thenReturn(new AnnotationList.Explicit(legalAnnotation));
+ when(duplicate.getDeclaredAnnotations()).thenReturn(new AnnotationList.Explicit(legalAnnotation, duplicateAnnotation));
+ when(legalAnnotation.getAnnotationType()).thenReturn(legalType);
+ when(duplicateAnnotation.getAnnotationType()).thenReturn(legalType);
+ }
+
+ @Test
+ public void testIllegalGenericArray() throws Exception {
+ assertThat(TypeDescription.Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE.onGenericArray(illegal), is(false));
+ }
+
+ @Test
+ public void testDuplicateGenericArray() throws Exception {
+ assertThat(TypeDescription.Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE.onGenericArray(duplicate), is(false));
+ }
+
+ @Test
+ public void testIllegalDelegatedGenericArray() throws Exception {
+ when(legal.getComponentType()).thenReturn(otherIllegal);
+ assertThat(TypeDescription.Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE.onGenericArray(legal), is(false));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testLegalGenericArray() throws Exception {
+ when(legal.getComponentType()).thenReturn(otherLegal);
+ assertThat(TypeDescription.Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE.onGenericArray(legal), is(true));
+ verify(otherLegal).accept(TypeDescription.Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE);
+ }
+
+ @Test
+ public void testIllegalNonGenericArray() throws Exception {
+ assertThat(TypeDescription.Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE.onNonGenericType(illegal), is(false));
+ }
+
+ @Test
+ public void testDuplicateNonGenericArray() throws Exception {
+ assertThat(TypeDescription.Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE.onNonGenericType(duplicate), is(false));
+ }
+
+ @Test
+ public void testIllegalDelegatedNonGenericArray() throws Exception {
+ when(legal.isArray()).thenReturn(true);
+ when(legal.getComponentType()).thenReturn(otherIllegal);
+ assertThat(TypeDescription.Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE.onNonGenericType(legal), is(false));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testLegalNonGenericArray() throws Exception {
+ when(legal.isArray()).thenReturn(true);
+ when(legal.getComponentType()).thenReturn(otherLegal);
+ assertThat(TypeDescription.Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE.onNonGenericType(legal), is(true));
+ verify(otherLegal).accept(TypeDescription.Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE);
+ }
+
+ @Test
+ public void testIllegalNonGeneric() throws Exception {
+ assertThat(TypeDescription.Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE.onNonGenericType(illegal), is(false));
+ }
+
+ @Test
+ public void testDuplicateNonGeneric() throws Exception {
+ assertThat(TypeDescription.Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE.onNonGenericType(duplicate), is(false));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testLegalNonGeneric() throws Exception {
+ assertThat(TypeDescription.Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE.onNonGenericType(legal), is(true));
+ }
+
+ @Test
+ public void testIllegalTypeVariable() throws Exception {
+ assertThat(TypeDescription.Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE.onTypeVariable(illegal), is(false));
+ }
+
+ @Test
+ public void testDuplicateTypeVariable() throws Exception {
+ assertThat(TypeDescription.Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE.onTypeVariable(duplicate), is(false));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testLegalTypeVariable() throws Exception {
+ assertThat(TypeDescription.Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE.onTypeVariable(legal), is(true));
+ }
+
+ @Test
+ public void testIllegalParameterized() throws Exception {
+ assertThat(TypeDescription.Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE.onParameterizedType(illegal), is(false));
+ }
+
+ @Test
+ public void testDuplicateParameterized() throws Exception {
+ assertThat(TypeDescription.Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE.onParameterizedType(duplicate), is(false));
+ }
+
+ @Test
+ public void testIllegalDelegatedOwnerTypeParameterized() throws Exception {
+ when(legal.getOwnerType()).thenReturn(otherIllegal);
+ assertThat(TypeDescription.Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE.onParameterizedType(legal), is(false));
+ }
+
+ @Test
+ public void testIllegalDelegatedTypeArgumentParameterized() throws Exception {
+ when(legal.getTypeArguments()).thenReturn(new TypeList.Generic.Explicit(otherIllegal));
+ assertThat(TypeDescription.Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE.onParameterizedType(legal), is(false));
+ }
+
+ @Test
+ public void testIllegalDuplicateParameterized() throws Exception {
+ assertThat(TypeDescription.Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE.onParameterizedType(duplicate), is(false));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testLegalParameterized() throws Exception {
+ when(legal.isArray()).thenReturn(true);
+ when(legal.getTypeArguments()).thenReturn(new TypeList.Generic.Explicit(otherLegal));
+ when(legal.getOwnerType()).thenReturn(otherLegal);
+ assertThat(TypeDescription.Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE.onParameterizedType(legal), is(true));
+ verify(otherLegal, times(2)).accept(TypeDescription.Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE);
+ }
+
+ @Test
+ public void testWildcardIllegal() throws Exception {
+ assertThat(TypeDescription.Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE.onWildcard(illegal), is(false));
+ }
+
+ @Test
+ public void testWildcardDuplicate() throws Exception {
+ assertThat(TypeDescription.Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE.onWildcard(duplicate), is(false));
+ }
+
+ @Test
+ public void testWildcardIllegalUpperBounds() throws Exception {
+ when(legal.getUpperBounds()).thenReturn(new TypeList.Generic.Explicit(otherIllegal));
+ when(legal.getLowerBounds()).thenReturn(new TypeList.Generic.Empty());
+ assertThat(TypeDescription.Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE.onWildcard(legal), is(false));
+ }
+
+ @Test
+ public void testWildcardIllegalLowerBounds() throws Exception {
+ when(legal.getUpperBounds()).thenReturn(new TypeList.Generic.Explicit(TypeDescription.Generic.OBJECT));
+ when(legal.getLowerBounds()).thenReturn(new TypeList.Generic.Explicit(otherIllegal));
+ assertThat(TypeDescription.Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE.onWildcard(legal), is(false));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testWildcardLegal() throws Exception {
+ when(legal.getUpperBounds()).thenReturn(new TypeList.Generic.Explicit(TypeDescription.Generic.OBJECT));
+ when(legal.getLowerBounds()).thenReturn(new TypeList.Generic.Explicit(otherLegal));
+ assertThat(TypeDescription.Generic.Visitor.Validator.ForTypeAnnotations.INSTANCE.onWildcard(legal), is(true));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeDescription.Generic.Visitor.Validator.ForTypeAnnotations.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorValidatorTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorValidatorTest.java
new file mode 100644
index 0000000..ac31a43
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionGenericVisitorValidatorTest.java
@@ -0,0 +1,115 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.io.Serializable;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class TypeDescriptionGenericVisitorValidatorTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription.Generic typeDescription;
+
+ @Test
+ public void testWildcardNotValidated() throws Exception {
+ assertThat(TypeDescription.Generic.Visitor.Validator.SUPER_CLASS.onWildcard(typeDescription), is(false));
+ assertThat(TypeDescription.Generic.Visitor.Validator.INTERFACE.onWildcard(typeDescription), is(false));
+ assertThat(TypeDescription.Generic.Visitor.Validator.TYPE_VARIABLE.onWildcard(typeDescription), is(false));
+ assertThat(TypeDescription.Generic.Visitor.Validator.FIELD.onWildcard(typeDescription), is(false));
+ assertThat(TypeDescription.Generic.Visitor.Validator.METHOD_RETURN.onWildcard(typeDescription), is(false));
+ assertThat(TypeDescription.Generic.Visitor.Validator.METHOD_PARAMETER.onWildcard(typeDescription), is(false));
+ assertThat(TypeDescription.Generic.Visitor.Validator.EXCEPTION.onWildcard(typeDescription), is(false));
+ }
+
+ @Test
+ public void testExceptionType() throws Exception {
+ assertThat(TypeDescription.Generic.Visitor.Validator.EXCEPTION.onNonGenericType(TypeDefinition.Sort.describe(Exception.class)), is(true));
+ TypeDescription.Generic typeVariable = mock(TypeDescription.Generic.class), bound = mock(TypeDescription.Generic.class);
+ when(typeVariable.getUpperBounds()).thenReturn(new TypeList.Generic.Explicit(bound));
+ when(bound.asGenericType()).thenReturn(bound);
+ when(bound.accept(TypeDescription.Generic.Visitor.Validator.EXCEPTION)).thenReturn(false);
+ assertThat(TypeDescription.Generic.Visitor.Validator.EXCEPTION.onTypeVariable(typeVariable), is(false));
+ when(bound.accept(TypeDescription.Generic.Visitor.Validator.EXCEPTION)).thenReturn(true);
+ assertThat(TypeDescription.Generic.Visitor.Validator.EXCEPTION.onTypeVariable(typeVariable), is(true));
+ assertThat(TypeDescription.Generic.Visitor.Validator.EXCEPTION.onGenericArray(mock(TypeDescription.Generic.class)), is(false));
+ assertThat(TypeDescription.Generic.Visitor.Validator.EXCEPTION.onParameterizedType(mock(TypeDescription.Generic.class)), is(false));
+ }
+
+ @Test
+ public void testSuperClassType() throws Exception {
+ assertThat(TypeDescription.Generic.Visitor.Validator.SUPER_CLASS.onNonGenericType(TypeDescription.Generic.OBJECT), is(true));
+ assertThat(TypeDescription.Generic.Visitor.Validator.SUPER_CLASS.onNonGenericType(TypeDefinition.Sort.describe(Serializable.class)), is(false));
+ assertThat(TypeDescription.Generic.Visitor.Validator.SUPER_CLASS.onNonGenericType(TypeDefinition.Sort.describe(void.class)), is(false));
+ assertThat(TypeDescription.Generic.Visitor.Validator.SUPER_CLASS.onNonGenericType(TypeDefinition.Sort.describe(int.class)), is(false));
+ assertThat(TypeDescription.Generic.Visitor.Validator.SUPER_CLASS.onNonGenericType(TypeDefinition.Sort.describe(void.class)), is(false));
+ assertThat(TypeDescription.Generic.Visitor.Validator.SUPER_CLASS.onNonGenericType(TypeDefinition.Sort.describe(Object[].class)), is(false));
+ assertThat(TypeDescription.Generic.Visitor.Validator.SUPER_CLASS.onParameterizedType(TypeDescription.Generic.OBJECT), is(true));
+ assertThat(TypeDescription.Generic.Visitor.Validator.SUPER_CLASS.onParameterizedType(TypeDefinition.Sort.describe(Serializable.class)), is(false));
+ assertThat(TypeDescription.Generic.Visitor.Validator.SUPER_CLASS.onTypeVariable(mock(TypeDescription.Generic.class)), is(false));
+ assertThat(TypeDescription.Generic.Visitor.Validator.SUPER_CLASS.onGenericArray(mock(TypeDescription.Generic.class)), is(false));
+ }
+
+ @Test
+ public void testInterfaceType() throws Exception {
+ assertThat(TypeDescription.Generic.Visitor.Validator.INTERFACE.onNonGenericType(TypeDescription.Generic.OBJECT), is(false));
+ assertThat(TypeDescription.Generic.Visitor.Validator.INTERFACE.onNonGenericType(TypeDefinition.Sort.describe(Serializable.class)), is(true));
+ assertThat(TypeDescription.Generic.Visitor.Validator.INTERFACE.onNonGenericType(TypeDefinition.Sort.describe(void.class)), is(false));
+ assertThat(TypeDescription.Generic.Visitor.Validator.INTERFACE.onNonGenericType(TypeDefinition.Sort.describe(int.class)), is(false));
+ assertThat(TypeDescription.Generic.Visitor.Validator.INTERFACE.onNonGenericType(TypeDefinition.Sort.describe(void.class)), is(false));
+ assertThat(TypeDescription.Generic.Visitor.Validator.INTERFACE.onNonGenericType(TypeDefinition.Sort.describe(Object[].class)), is(false));
+ assertThat(TypeDescription.Generic.Visitor.Validator.INTERFACE.onParameterizedType(TypeDescription.Generic.OBJECT), is(false));
+ assertThat(TypeDescription.Generic.Visitor.Validator.INTERFACE.onParameterizedType(TypeDefinition.Sort.describe(Serializable.class)), is(true));
+ assertThat(TypeDescription.Generic.Visitor.Validator.INTERFACE.onTypeVariable(mock(TypeDescription.Generic.class)), is(false));
+ assertThat(TypeDescription.Generic.Visitor.Validator.INTERFACE.onGenericArray(mock(TypeDescription.Generic.class)), is(false));
+ }
+
+ @Test
+ public void testFieldType() throws Exception {
+ assertThat(TypeDescription.Generic.Visitor.Validator.FIELD.onNonGenericType(TypeDescription.Generic.OBJECT), is(true));
+ assertThat(TypeDescription.Generic.Visitor.Validator.FIELD.onNonGenericType(TypeDefinition.Sort.describe(Object[].class)), is(true));
+ assertThat(TypeDescription.Generic.Visitor.Validator.FIELD.onNonGenericType(TypeDefinition.Sort.describe(int.class)), is(true));
+ assertThat(TypeDescription.Generic.Visitor.Validator.FIELD.onNonGenericType(TypeDefinition.Sort.describe(void.class)), is(false));
+ assertThat(TypeDescription.Generic.Visitor.Validator.FIELD.onGenericArray(mock(TypeDescription.Generic.class)), is(true));
+ assertThat(TypeDescription.Generic.Visitor.Validator.FIELD.onParameterizedType(mock(TypeDescription.Generic.class)), is(true));
+ assertThat(TypeDescription.Generic.Visitor.Validator.FIELD.onTypeVariable(mock(TypeDescription.Generic.class)), is(true));
+ }
+
+ @Test
+ public void testMethodParameterType() throws Exception {
+ assertThat(TypeDescription.Generic.Visitor.Validator.METHOD_PARAMETER.onNonGenericType(TypeDescription.Generic.OBJECT), is(true));
+ assertThat(TypeDescription.Generic.Visitor.Validator.METHOD_PARAMETER.onNonGenericType(TypeDefinition.Sort.describe(Object[].class)), is(true));
+ assertThat(TypeDescription.Generic.Visitor.Validator.METHOD_PARAMETER.onNonGenericType(TypeDefinition.Sort.describe(int.class)), is(true));
+ assertThat(TypeDescription.Generic.Visitor.Validator.METHOD_PARAMETER.onNonGenericType(TypeDefinition.Sort.describe(void.class)), is(false));
+ assertThat(TypeDescription.Generic.Visitor.Validator.METHOD_PARAMETER.onGenericArray(mock(TypeDescription.Generic.class)), is(true));
+ assertThat(TypeDescription.Generic.Visitor.Validator.METHOD_PARAMETER.onParameterizedType(mock(TypeDescription.Generic.class)), is(true));
+ assertThat(TypeDescription.Generic.Visitor.Validator.METHOD_PARAMETER.onTypeVariable(mock(TypeDescription.Generic.class)), is(true));
+ }
+
+ @Test
+ public void testMethodReturnType() throws Exception {
+ assertThat(TypeDescription.Generic.Visitor.Validator.METHOD_RETURN.onNonGenericType(TypeDescription.Generic.OBJECT), is(true));
+ assertThat(TypeDescription.Generic.Visitor.Validator.METHOD_RETURN.onNonGenericType(TypeDefinition.Sort.describe(Object[].class)), is(true));
+ assertThat(TypeDescription.Generic.Visitor.Validator.METHOD_RETURN.onNonGenericType(TypeDefinition.Sort.describe(int.class)), is(true));
+ assertThat(TypeDescription.Generic.Visitor.Validator.METHOD_RETURN.onNonGenericType(TypeDefinition.Sort.describe(void.class)), is(true));
+ assertThat(TypeDescription.Generic.Visitor.Validator.METHOD_RETURN.onGenericArray(mock(TypeDescription.Generic.class)), is(true));
+ assertThat(TypeDescription.Generic.Visitor.Validator.METHOD_RETURN.onParameterizedType(mock(TypeDescription.Generic.class)), is(true));
+ assertThat(TypeDescription.Generic.Visitor.Validator.METHOD_RETURN.onTypeVariable(mock(TypeDescription.Generic.class)), is(true));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeDescription.Generic.Visitor.Validator.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionLatentTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionLatentTest.java
new file mode 100644
index 0000000..a25186b
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeDescriptionLatentTest.java
@@ -0,0 +1,103 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class TypeDescriptionLatentTest {
+
+ private static final String FOO = "foo";
+
+ private static final int MODIFIERS = 42;
+
+ @Rule
+ public MockitoRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription.Generic superClass, interfaceType;
+
+ @Before
+ public void setUp() throws Exception {
+ when(superClass.asGenericType()).thenReturn(superClass);
+ when(interfaceType.asGenericType()).thenReturn(interfaceType);
+ }
+
+ @Test
+ public void testName() throws Exception {
+ assertThat(new TypeDescription.Latent(FOO, MODIFIERS, superClass, Collections.singletonList(interfaceType)).getName(), is(FOO));
+ }
+
+ @Test
+ public void testModifiers() throws Exception {
+ assertThat(new TypeDescription.Latent(FOO, MODIFIERS, superClass, Collections.singletonList(interfaceType)).getModifiers(), is(MODIFIERS));
+ }
+
+ @Test
+ public void testSuperType() throws Exception {
+ assertThat(new TypeDescription.Latent(FOO, MODIFIERS, superClass, Collections.singletonList(interfaceType)).getSuperClass(), is(superClass));
+ }
+
+ @Test
+ public void testInterfaceTypes() throws Exception {
+ assertThat(new TypeDescription.Latent(FOO, MODIFIERS, superClass, Collections.singletonList(interfaceType)).getInterfaces().size(), is(1));
+ assertThat(new TypeDescription.Latent(FOO, MODIFIERS, superClass, Collections.singletonList(interfaceType)).getInterfaces().getOnly(), is(interfaceType));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testFields() throws Exception {
+ new TypeDescription.Latent(FOO, MODIFIERS, superClass, Collections.singletonList(interfaceType)).getDeclaredFields();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethods() throws Exception {
+ new TypeDescription.Latent(FOO, MODIFIERS, superClass, Collections.singletonList(interfaceType)).getDeclaredMethods();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAnnotations() throws Exception {
+ new TypeDescription.Latent(FOO, MODIFIERS, superClass, Collections.singletonList(interfaceType)).getDeclaredAnnotations();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testTypeVariables() throws Exception {
+ new TypeDescription.Latent(FOO, MODIFIERS, superClass, Collections.singletonList(interfaceType)).getTypeVariables();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMemberClass() throws Exception {
+ new TypeDescription.Latent(FOO, MODIFIERS, superClass, Collections.singletonList(interfaceType)).isMemberClass();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAnoynmousClass() throws Exception {
+ new TypeDescription.Latent(FOO, MODIFIERS, superClass, Collections.singletonList(interfaceType)).isAnonymousClass();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testLocalClass() throws Exception {
+ new TypeDescription.Latent(FOO, MODIFIERS, superClass, Collections.singletonList(interfaceType)).isLocalClass();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testEnclosingMethod() throws Exception {
+ new TypeDescription.Latent(FOO, MODIFIERS, superClass, Collections.singletonList(interfaceType)).getEnclosingMethod();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testEnclosingType() throws Exception {
+ new TypeDescription.Latent(FOO, MODIFIERS, superClass, Collections.singletonList(interfaceType)).getEnclosingType();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testDeclaredTypes() throws Exception {
+ new TypeDescription.Latent(FOO, MODIFIERS, superClass, Collections.singletonList(interfaceType)).getDeclaredTypes();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeInitializerTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeInitializerTest.java
new file mode 100644
index 0000000..b0a2b2c
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeInitializerTest.java
@@ -0,0 +1,90 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.dynamic.scaffold.TypeInitializer;
+import net.bytebuddy.dynamic.scaffold.TypeWriter;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.MethodVisitor;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class TypeInitializerTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeWriter.MethodPool.Record record, expanded;
+
+ @Mock
+ private ByteCodeAppender byteCodeAppender;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @Mock
+ private MethodDescription methodDescription;
+
+ @Test
+ public void testNoneExpansion() throws Exception {
+ assertThat(TypeInitializer.None.INSTANCE.expandWith(byteCodeAppender), is((TypeInitializer) new TypeInitializer.Simple(byteCodeAppender)));
+ }
+
+ @Test
+ public void testNoneDefined() throws Exception {
+ assertThat(TypeInitializer.None.INSTANCE.isDefined(), is(false));
+ }
+
+ @Test
+ public void testNoneThrowsExceptionOnApplication() throws Exception {
+ ByteCodeAppender.Size size = TypeInitializer.None.INSTANCE.apply(methodVisitor, implementationContext, methodDescription);
+ assertThat(size.getOperandStackSize(), is(0));
+ assertThat(size.getLocalVariableSize(), is(0));
+ verifyZeroInteractions(methodDescription);
+ }
+
+ @Test
+ public void testNoneWrap() throws Exception {
+ assertThat(TypeInitializer.None.INSTANCE.wrap(record), is(record));
+ }
+
+ @Test
+ public void testSimpleExpansion() throws Exception {
+ assertThat(new TypeInitializer.Simple(byteCodeAppender).expandWith(byteCodeAppender),
+ is((TypeInitializer) new TypeInitializer.Simple(new ByteCodeAppender.Compound(byteCodeAppender, byteCodeAppender))));
+ }
+
+ @Test
+ public void testSimpleApplication() throws Exception {
+ TypeInitializer typeInitializer = new TypeInitializer.Simple(byteCodeAppender);
+ assertThat(typeInitializer.isDefined(), is(true));
+ typeInitializer.apply(methodVisitor, implementationContext, methodDescription);
+ verify(byteCodeAppender).apply(methodVisitor, implementationContext, methodDescription);
+ verifyZeroInteractions(byteCodeAppender);
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testSimpleWrap() throws Exception {
+ when(record.prepend(byteCodeAppender)).thenReturn(expanded);
+ assertThat(new TypeInitializer.Simple(byteCodeAppender).wrap(record), is(expanded));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeInitializer.None.class).apply();
+ ObjectPropertyAssertion.of(TypeInitializer.Simple.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeListEmptyTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeListEmptyTest.java
new file mode 100644
index 0000000..79fd613
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeListEmptyTest.java
@@ -0,0 +1,20 @@
+package net.bytebuddy.description.type;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class TypeListEmptyTest {
+
+ @Test
+ public void testInternalName() throws Exception {
+ assertThat(new TypeList.Empty().toInternalNames(), nullValue(String[].class));
+ }
+
+ @Test
+ public void testSize() throws Exception {
+ assertThat(new TypeList.Empty().getStackSize(), is(0));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeListExplicitTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeListExplicitTest.java
new file mode 100644
index 0000000..dd5b6e6
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeListExplicitTest.java
@@ -0,0 +1,26 @@
+package net.bytebuddy.description.type;
+
+import java.util.List;
+
+public class TypeListExplicitTest extends AbstractTypeListTest<TypeDescription> {
+
+ @Override
+ protected TypeDescription getFirst() throws Exception {
+ return new TypeDescription.ForLoadedType(Foo.class);
+ }
+
+ @Override
+ protected TypeDescription getSecond() throws Exception {
+ return new TypeDescription.ForLoadedType(Bar.class);
+ }
+
+ @Override
+ protected TypeList asList(List<TypeDescription> elements) {
+ return new TypeList.Explicit(elements);
+ }
+
+ @Override
+ protected TypeDescription asElement(TypeDescription element) {
+ return element;
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeListForLoadedTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeListForLoadedTest.java
new file mode 100644
index 0000000..b80630f
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeListForLoadedTest.java
@@ -0,0 +1,26 @@
+package net.bytebuddy.description.type;
+
+import java.util.List;
+
+public class TypeListForLoadedTest extends AbstractTypeListTest<Class<?>> {
+
+ @Override
+ protected Class<?> getFirst() throws Exception {
+ return Foo.class;
+ }
+
+ @Override
+ protected Class<?> getSecond() throws Exception {
+ return Bar.class;
+ }
+
+ @Override
+ protected TypeList asList(List<Class<?>> elements) {
+ return new TypeList.ForLoadedTypes(elements);
+ }
+
+ @Override
+ protected TypeDescription asElement(Class<?> element) {
+ return new TypeDescription.ForLoadedType(element);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeListGenericEmptyTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeListGenericEmptyTest.java
new file mode 100644
index 0000000..134b8b1
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeListGenericEmptyTest.java
@@ -0,0 +1,24 @@
+package net.bytebuddy.description.type;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class TypeListGenericEmptyTest {
+
+ @Test
+ public void testRawTypes() throws Exception {
+ assertThat(new TypeList.Generic.Empty().asErasures().size(), is(0));
+ }
+
+ @Test
+ public void testVisitor() throws Exception {
+ assertThat(new TypeList.Generic.Empty().accept(TypeDescription.Generic.Visitor.NoOp.INSTANCE).size(), is(0));
+ }
+
+ @Test
+ public void testSize() throws Exception {
+ assertThat(new TypeList.Generic.Empty().getStackSize(), is(0));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeListGenericExplicitTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeListGenericExplicitTest.java
new file mode 100644
index 0000000..cfbe442
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeListGenericExplicitTest.java
@@ -0,0 +1,26 @@
+package net.bytebuddy.description.type;
+
+import java.util.List;
+
+public class TypeListGenericExplicitTest extends AbstractTypeListGenericTest<TypeDescription.Generic> {
+
+ @Override
+ protected TypeDescription.Generic getFirst() throws Exception {
+ return TypeDefinition.Sort.describe(Holder.class.getGenericInterfaces()[0]);
+ }
+
+ @Override
+ protected TypeDescription.Generic getSecond() throws Exception {
+ return TypeDefinition.Sort.describe(Holder.class.getGenericInterfaces()[1]);
+ }
+
+ @Override
+ protected TypeList.Generic asList(List<TypeDescription.Generic> elements) {
+ return new TypeList.Generic.Explicit(elements);
+ }
+
+ @Override
+ protected TypeDescription.Generic asElement(TypeDescription.Generic element) {
+ return element;
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeListGenericForLoadedTypesTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeListGenericForLoadedTypesTest.java
new file mode 100644
index 0000000..0b62ea8
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeListGenericForLoadedTypesTest.java
@@ -0,0 +1,27 @@
+package net.bytebuddy.description.type;
+
+import java.lang.reflect.Type;
+import java.util.List;
+
+public class TypeListGenericForLoadedTypesTest extends AbstractTypeListGenericTest<Type> {
+
+ @Override
+ protected Type getFirst() throws Exception {
+ return Holder.class.getGenericInterfaces()[0];
+ }
+
+ @Override
+ protected Type getSecond() throws Exception {
+ return Holder.class.getGenericInterfaces()[1];
+ }
+
+ @Override
+ protected TypeList.Generic asList(List<Type> elements) {
+ return new TypeList.Generic.ForLoadedTypes(elements);
+ }
+
+ @Override
+ protected TypeDescription.Generic asElement(Type element) {
+ return TypeDefinition.Sort.describe(element);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeVariableTokenTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeVariableTokenTest.java
new file mode 100644
index 0000000..adec5f1
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/description/type/TypeVariableTokenTest.java
@@ -0,0 +1,67 @@
+package net.bytebuddy.description.type;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class TypeVariableTokenTest {
+
+ private static final String FOO = "foo";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription.Generic bound, visitedBound;
+
+ @Mock
+ private AnnotationDescription annotation;
+
+ @Mock
+ private TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ when(bound.asGenericType()).thenReturn(bound);
+ when(visitedBound.asGenericType()).thenReturn(visitedBound);
+ when(bound.accept((TypeDescription.Generic.Visitor) visitor)).thenReturn(visitedBound);
+ }
+
+ @Test
+ public void testPropertiesSimple() throws Exception {
+ assertThat(new TypeVariableToken(FOO, Collections.singletonList(bound)).getSymbol(), is(FOO));
+ assertThat(new TypeVariableToken(FOO, Collections.singletonList(bound)).getBounds(), is(Collections.singletonList(bound)));
+ assertThat(new TypeVariableToken(FOO, Collections.singletonList(bound)).getAnnotations().size(), is(0));
+ }
+
+ @Test
+ public void testProperties() throws Exception {
+ assertThat(new TypeVariableToken(FOO, Collections.singletonList(bound), Collections.singletonList(annotation)).getSymbol(), is(FOO));
+ assertThat(new TypeVariableToken(FOO, Collections.singletonList(bound), Collections.singletonList(annotation)).getBounds(),
+ is(Collections.singletonList(bound)));
+ assertThat(new TypeVariableToken(FOO, Collections.singletonList(bound), Collections.singletonList(annotation)).getAnnotations(),
+ is(Collections.singletonList(annotation)));
+ }
+
+ @Test
+ public void testVisitor() throws Exception {
+ assertThat(new TypeVariableToken(FOO, Collections.singletonList(bound)).accept(visitor), is(new TypeVariableToken(FOO, Collections.singletonList(visitedBound))));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeVariableToken.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/AbstractDynamicTypeBuilderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/AbstractDynamicTypeBuilderTest.java
new file mode 100644
index 0000000..a097b98
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/AbstractDynamicTypeBuilderTest.java
@@ -0,0 +1,1213 @@
+package net.bytebuddy.dynamic;
+
+import net.bytebuddy.asm.AsmVisitorWrapper;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationValue;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.modifier.*;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeVariableToken;
+import net.bytebuddy.dynamic.loading.ByteArrayClassLoader;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.implementation.*;
+import net.bytebuddy.implementation.bind.annotation.SuperCall;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.constant.NullConstant;
+import net.bytebuddy.implementation.bytecode.constant.TextConstant;
+import net.bytebuddy.implementation.bytecode.member.FieldAccess;
+import net.bytebuddy.implementation.bytecode.member.MethodReturn;
+import net.bytebuddy.pool.TypePool;
+import net.bytebuddy.test.utility.CallTraceable;
+import net.bytebuddy.test.utility.ClassFileExtraction;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.hamcrest.CoreMatchers;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.*;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import static net.bytebuddy.matcher.ElementMatchers.isTypeInitializer;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+public abstract class AbstractDynamicTypeBuilderTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux", TO_STRING = "toString";
+
+ private static final String TYPE_VARIABLE_NAME = "net.bytebuddy.test.precompiled.TypeAnnotation", VALUE = "value";
+
+ private static final int MODIFIERS = Opcodes.ACC_PUBLIC;
+
+ private static final boolean BOOLEAN_VALUE = true;
+
+ private static final int INTEGER_VALUE = 42;
+
+ private static final long LONG_VALUE = 42L;
+
+ private static final float FLOAT_VALUE = 42f;
+
+ private static final double DOUBLE_VALUE = 42d;
+
+ private static final String BOOLEAN_FIELD = "booleanField";
+
+ private static final String BYTE_FIELD = "byteField";
+
+ private static final String CHARACTER_FIELD = "characterField";
+
+ private static final String SHORT_FIELD = "shortField";
+
+ private static final String INTEGER_FIELD = "integerField";
+
+ private static final String LONG_FIELD = "longField";
+
+ private static final String FLOAT_FIELD = "floatField";
+
+ private static final String DOUBLE_FIELD = "doubleField";
+
+ private static final String STRING_FIELD = "stringField";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ private Type list, fooVariable;
+
+ protected abstract DynamicType.Builder<?> createPlain();
+
+ protected abstract DynamicType.Builder<?> createPlainWithoutValidation();
+
+ @Before
+ public void setUp() throws Exception {
+ list = Holder.class.getDeclaredField("list").getGenericType();
+ fooVariable = ((ParameterizedType) Holder.class.getDeclaredField("fooList").getGenericType()).getActualTypeArguments()[0];
+ }
+
+ @Test
+ public void testMethodDefinition() throws Exception {
+ Class<?> type = createPlain()
+ .defineMethod(FOO, Object.class, Visibility.PUBLIC)
+ .throwing(Exception.class)
+ .intercept(new Implementation.Simple(new TextConstant(FOO), MethodReturn.REFERENCE))
+ .make()
+ .load(new URLClassLoader(new URL[0], null), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Method method = type.getDeclaredMethod(FOO);
+ assertThat(method.getReturnType(), CoreMatchers.<Class<?>>is(Object.class));
+ assertThat(method.getExceptionTypes(), is(new Class<?>[]{Exception.class}));
+ assertThat(method.getModifiers(), is(Modifier.PUBLIC));
+ assertThat(method.invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ }
+
+ @Test
+ public void testAbstractMethodDefinition() throws Exception {
+ Class<?> type = createPlain()
+ .modifiers(Visibility.PUBLIC, TypeManifestation.ABSTRACT)
+ .defineMethod(FOO, Object.class, Visibility.PUBLIC)
+ .throwing(Exception.class)
+ .withoutCode()
+ .make()
+ .load(new URLClassLoader(new URL[0], null), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Method method = type.getDeclaredMethod(FOO);
+ assertThat(method.getReturnType(), CoreMatchers.<Class<?>>is(Object.class));
+ assertThat(method.getExceptionTypes(), is(new Class<?>[]{Exception.class}));
+ assertThat(method.getModifiers(), is(Modifier.PUBLIC | Modifier.ABSTRACT));
+ }
+
+ @Test
+ public void testConstructorDefinition() throws Exception {
+ Class<?> type = createPlain()
+ .defineConstructor(Visibility.PUBLIC).withParameters(Void.class)
+ .throwing(Exception.class)
+ .intercept(MethodCall.invoke(Object.class.getDeclaredConstructor()))
+ .make()
+ .load(new URLClassLoader(new URL[0], null), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Constructor<?> constructor = type.getDeclaredConstructor(Void.class);
+ assertThat(constructor.getExceptionTypes(), is(new Class<?>[]{Exception.class}));
+ assertThat(constructor.getModifiers(), is(Modifier.PUBLIC));
+ assertThat(constructor.newInstance((Object) null), notNullValue(Object.class));
+ }
+
+ @Test
+ public void testFieldDefinition() throws Exception {
+ Class<?> type = createPlain()
+ .defineField(FOO, Void.class, Visibility.PUBLIC)
+ .make()
+ .load(new URLClassLoader(new URL[0], null), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Field field = type.getDeclaredField(FOO);
+ assertThat(field.getType(), CoreMatchers.<Class<?>>is(Void.class));
+ assertThat(field.getModifiers(), is(Modifier.PUBLIC));
+ }
+
+ @Test
+ public void testFieldDefaultValueDefinition() throws Exception {
+ Class<?> type = createPlain()
+ .defineField(BOOLEAN_FIELD, boolean.class, Visibility.PUBLIC, Ownership.STATIC).value(BOOLEAN_VALUE)
+ .defineField(BYTE_FIELD, byte.class, Visibility.PUBLIC, Ownership.STATIC).value(INTEGER_VALUE)
+ .defineField(SHORT_FIELD, short.class, Visibility.PUBLIC, Ownership.STATIC).value(INTEGER_VALUE)
+ .defineField(CHARACTER_FIELD, char.class, Visibility.PUBLIC, Ownership.STATIC).value(INTEGER_VALUE)
+ .defineField(INTEGER_FIELD, int.class, Visibility.PUBLIC, Ownership.STATIC).value(INTEGER_VALUE)
+ .defineField(LONG_FIELD, long.class, Visibility.PUBLIC, Ownership.STATIC).value(LONG_VALUE)
+ .defineField(FLOAT_FIELD, float.class, Visibility.PUBLIC, Ownership.STATIC).value(FLOAT_VALUE)
+ .defineField(DOUBLE_FIELD, double.class, Visibility.PUBLIC, Ownership.STATIC).value(DOUBLE_VALUE)
+ .defineField(STRING_FIELD, String.class, Visibility.PUBLIC, Ownership.STATIC).value(FOO)
+ .make()
+ .load(new URLClassLoader(new URL[0], null), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredField(BOOLEAN_FIELD).get(null), is((Object) BOOLEAN_VALUE));
+ assertThat(type.getDeclaredField(BYTE_FIELD).get(null), is((Object) (byte) INTEGER_VALUE));
+ assertThat(type.getDeclaredField(SHORT_FIELD).get(null), is((Object) (short) INTEGER_VALUE));
+ assertThat(type.getDeclaredField(CHARACTER_FIELD).get(null), is((Object) (char) INTEGER_VALUE));
+ assertThat(type.getDeclaredField(INTEGER_FIELD).get(null), is((Object) INTEGER_VALUE));
+ assertThat(type.getDeclaredField(LONG_FIELD).get(null), is((Object) LONG_VALUE));
+ assertThat(type.getDeclaredField(FLOAT_FIELD).get(null), is((Object) FLOAT_VALUE));
+ assertThat(type.getDeclaredField(DOUBLE_FIELD).get(null), is((Object) DOUBLE_VALUE));
+ assertThat(type.getDeclaredField(STRING_FIELD).get(null), is((Object) FOO));
+ }
+
+ @Test
+ public void testApplicationOrder() throws Exception {
+ assertThat(createPlain()
+ .method(named(TO_STRING)).intercept(new Implementation.Simple(new TextConstant(FOO), MethodReturn.REFERENCE))
+ .method(named(TO_STRING)).intercept(new Implementation.Simple(new TextConstant(BAR), MethodReturn.REFERENCE))
+ .make()
+ .load(new URLClassLoader(new URL[0], null), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance()
+ .toString(), is(BAR));
+ }
+
+ @Test
+ public void testTypeInitializer() throws Exception {
+ ClassLoader classLoader = new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassFileExtraction.of(Bar.class));
+ Class<?> type = createPlain()
+ .invokable(isTypeInitializer()).intercept(MethodCall.invoke(Bar.class.getDeclaredMethod("invoke")))
+ .make()
+ .load(classLoader, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredConstructor().newInstance(), notNullValue(Object.class));
+ Class<?> foo = classLoader.loadClass(Bar.class.getName());
+ assertThat(foo.getDeclaredField(FOO).get(null), is((Object) FOO));
+ }
+
+ @Test
+ public void testConstructorInvokingMethod() throws Exception {
+ Class<?> type = createPlain()
+ .defineMethod(FOO, Object.class, Visibility.PUBLIC)
+ .intercept(new Implementation.Simple(new TextConstant(FOO), MethodReturn.REFERENCE))
+ .make()
+ .load(new URLClassLoader(new URL[0], null), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Method method = type.getDeclaredMethod(FOO);
+ assertThat(method.invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ }
+
+ @Test
+ public void testMethodTransformation() throws Exception {
+ Class<?> type = createPlain()
+ .method(named(TO_STRING))
+ .intercept(new Implementation.Simple(new TextConstant(FOO), MethodReturn.REFERENCE))
+ .transform(Transformer.ForMethod.withModifiers(MethodManifestation.FINAL))
+ .make()
+ .load(new URLClassLoader(new URL[0], null), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredConstructor().newInstance().toString(), is(FOO));
+ assertThat(type.getDeclaredMethod(TO_STRING).getModifiers(), is(Opcodes.ACC_FINAL | Opcodes.ACC_PUBLIC));
+ }
+
+ @Test
+ public void testFieldTransformation() throws Exception {
+ Class<?> type = createPlain()
+ .defineField(FOO, Void.class)
+ .field(named(FOO))
+ .transform(Transformer.ForField.withModifiers(Visibility.PUBLIC))
+ .make()
+ .load(new URLClassLoader(new URL[0], null), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredField(FOO).getModifiers(), is(Opcodes.ACC_PUBLIC));
+ }
+
+ @Test
+ public void testIgnoredMethod() throws Exception {
+ Class<?> type = createPlain()
+ .ignoreAlso(named(TO_STRING))
+ .method(named(TO_STRING))
+ .intercept(new Implementation.Simple(new TextConstant(FOO), MethodReturn.REFERENCE))
+ .make()
+ .load(new URLClassLoader(new URL[0], null), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredConstructor().newInstance().toString(), CoreMatchers.not(FOO));
+ }
+
+ @Test
+ public void testIgnoredMethodDoesNotApplyForDefined() throws Exception {
+ Class<?> type = createPlain()
+ .ignoreAlso(named(FOO))
+ .defineMethod(FOO, String.class, Visibility.PUBLIC)
+ .intercept(new Implementation.Simple(new TextConstant(FOO), MethodReturn.REFERENCE))
+ .make()
+ .load(new URLClassLoader(new URL[0], null), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ }
+
+ @Test
+ public void testPreparedField() throws Exception {
+ ClassLoader classLoader = new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassFileExtraction.of(SampleAnnotation.class));
+ Class<?> type = createPlain()
+ .defineMethod(BAR, String.class, Visibility.PUBLIC)
+ .intercept(new PreparedField())
+ .make()
+ .load(classLoader, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredFields().length, is(1));
+ assertThat(type.getDeclaredField(FOO).getName(), is(FOO));
+ assertThat(type.getDeclaredField(FOO).getType(), CoreMatchers.<Class<?>>is(Object.class));
+ assertThat(type.getDeclaredField(FOO).getModifiers(), is(MODIFIERS));
+ assertThat(type.getDeclaredField(FOO).getAnnotations().length, is(1));
+ Annotation annotation = type.getDeclaredField(FOO).getAnnotations()[0];
+ assertThat(annotation.annotationType().getName(), is(SampleAnnotation.class.getName()));
+ Method foo = annotation.annotationType().getDeclaredMethod(FOO);
+ assertThat(foo.invoke(annotation), is((Object) BAR));
+ }
+
+ @Test
+ public void testPreparedMethod() throws Exception {
+ ClassLoader classLoader = new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassFileExtraction.of(SampleAnnotation.class));
+ Class<?> type = createPlain()
+ .defineMethod(BAR, String.class, Visibility.PUBLIC)
+ .intercept(new PreparedMethod())
+ .make()
+ .load(classLoader, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethods().length, is(2));
+ assertThat(type.getDeclaredMethod(FOO, Object.class).getName(), is(FOO));
+ assertThat(type.getDeclaredMethod(FOO, Object.class).getReturnType(), CoreMatchers.<Class<?>>is(Object.class));
+ assertThat(type.getDeclaredMethod(FOO, Object.class).getParameterTypes().length, is(1));
+ assertThat(type.getDeclaredMethod(FOO, Object.class).getParameterTypes()[0], CoreMatchers.<Class<?>>is(Object.class));
+ assertThat(type.getDeclaredMethod(FOO, Object.class).getModifiers(), is(MODIFIERS));
+ assertThat(type.getDeclaredMethod(FOO, Object.class).getAnnotations().length, is(1));
+ Annotation methodAnnotation = type.getDeclaredMethod(FOO, Object.class).getAnnotations()[0];
+ assertThat(methodAnnotation.annotationType().getName(), is(SampleAnnotation.class.getName()));
+ Method methodMethod = methodAnnotation.annotationType().getDeclaredMethod(FOO);
+ assertThat(methodMethod.invoke(methodAnnotation), is((Object) BAR));
+ assertThat(type.getDeclaredMethod(FOO, Object.class).getParameterAnnotations()[0].length, is(1));
+ Annotation parameterAnnotation = type.getDeclaredMethod(FOO, Object.class).getParameterAnnotations()[0][0];
+ assertThat(parameterAnnotation.annotationType().getName(), is(SampleAnnotation.class.getName()));
+ Method parameterMethod = parameterAnnotation.annotationType().getDeclaredMethod(FOO);
+ assertThat(parameterMethod.invoke(parameterAnnotation), is((Object) QUX));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testWriterHint() throws Exception {
+ AsmVisitorWrapper asmVisitorWrapper = mock(AsmVisitorWrapper.class);
+ when(asmVisitorWrapper.wrap(any(TypeDescription.class),
+ any(ClassVisitor.class),
+ any(Implementation.Context.class),
+ any(TypePool.class),
+ any(FieldList.class),
+ any(MethodList.class),
+ anyInt(),
+ anyInt())).then(new Answer<ClassVisitor>() {
+ @Override
+ public ClassVisitor answer(InvocationOnMock invocationOnMock) throws Throwable {
+ return new ClassVisitor(Opcodes.ASM5, (ClassVisitor) invocationOnMock.getArguments()[1]) {
+ @Override
+ public void visitEnd() {
+ MethodVisitor mv = visitMethod(Opcodes.ACC_PUBLIC, FOO, "()Ljava/lang/String;", null, null);
+ mv.visitCode();
+ mv.visitLdcInsn(FOO);
+ mv.visitInsn(Opcodes.ARETURN);
+ mv.visitMaxs(-1, -1);
+ mv.visitEnd();
+ }
+ };
+ }
+ });
+ when(asmVisitorWrapper.mergeWriter(0)).thenReturn(ClassWriter.COMPUTE_MAXS);
+ Class<?> type = createPlain()
+ .visit(asmVisitorWrapper)
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ verify(asmVisitorWrapper).mergeWriter(0);
+ verify(asmVisitorWrapper, atMost(1)).mergeReader(0);
+ verify(asmVisitorWrapper).wrap(any(TypeDescription.class),
+ any(ClassVisitor.class),
+ any(Implementation.Context.class),
+ any(TypePool.class),
+ any(FieldList.class),
+ any(MethodList.class),
+ anyInt(),
+ anyInt());
+ verifyNoMoreInteractions(asmVisitorWrapper);
+ }
+
+ @Test
+ public void testExplicitTypeInitializer() throws Exception {
+ assertThat(createPlain()
+ .defineField(FOO, String.class, Ownership.STATIC, Visibility.PUBLIC)
+ .initializer(new ByteCodeAppender() {
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodDescription instrumentedMethod) {
+ return new Size(new StackManipulation.Compound(
+ new TextConstant(FOO),
+ FieldAccess.forField(instrumentedMethod.getDeclaringType().getDeclaredFields().filter(named(FOO)).getOnly()).write()
+ ).apply(methodVisitor, implementationContext).getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+ }).make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded()
+ .getDeclaredField(FOO)
+ .get(null), is((Object) FOO));
+ }
+
+ @Test
+ public void testSerialVersionUid() throws Exception {
+ Class<?> type = createPlain()
+ .serialVersionUid(42L)
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Field field = type.getDeclaredField("serialVersionUID");
+ field.setAccessible(true);
+ assertThat((Long) field.get(null), is(42L));
+ assertThat(field.getType(), is((Object) long.class));
+ assertThat(field.getModifiers(), is(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL));
+ }
+
+ @Test
+ public void testTypeVariable() throws Exception {
+ Class<?> type = createPlain()
+ .typeVariable(FOO)
+ .typeVariable(BAR, String.class)
+ .make()
+ .load(new URLClassLoader(new URL[0], null), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getTypeParameters().length, is(2));
+ assertThat(type.getTypeParameters()[0].getName(), is(FOO));
+ assertThat(type.getTypeParameters()[0].getBounds().length, is(1));
+ assertThat(type.getTypeParameters()[0].getBounds()[0], is((Object) Object.class));
+ assertThat(type.getTypeParameters()[1].getName(), is(BAR));
+ assertThat(type.getTypeParameters()[1].getBounds().length, is(1));
+ assertThat(type.getTypeParameters()[1].getBounds()[0], is((Object) String.class));
+ }
+
+ @Test
+ public void testTypeVariableTransformation() throws Exception {
+ Class<?> type = createPlain()
+ .typeVariable(FOO)
+ .typeVariable(BAR, String.class)
+ .transform(named(BAR), new Transformer<TypeVariableToken>() {
+ @Override
+ public TypeVariableToken transform(TypeDescription instrumentedType, TypeVariableToken target) {
+ return new TypeVariableToken(target.getSymbol(), Collections.singletonList(new TypeDescription.Generic.OfNonGenericType.ForLoadedType(Integer.class)));
+ }
+ })
+ .make()
+ .load(new URLClassLoader(new URL[0], null), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getTypeParameters().length, is(2));
+ assertThat(type.getTypeParameters()[0].getName(), is(FOO));
+ assertThat(type.getTypeParameters()[0].getBounds().length, is(1));
+ assertThat(type.getTypeParameters()[0].getBounds()[0], is((Object) Object.class));
+ assertThat(type.getTypeParameters()[1].getName(), is(BAR));
+ assertThat(type.getTypeParameters()[1].getBounds().length, is(1));
+ assertThat(type.getTypeParameters()[1].getBounds()[0], is((Object) Integer.class));
+ }
+
+ @Test
+ public void testGenericFieldDefinition() throws Exception {
+ Class<?> type = createPlain()
+ .defineField(QUX, list)
+ .make()
+ .load(new URLClassLoader(new URL[0], null), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredField(QUX).getGenericType(), is(list));
+ }
+
+ @Test
+ public void testGenericMethodDefinition() throws Exception {
+ Class<?> type = createPlain()
+ .defineMethod(QUX, list)
+ .withParameter(list, BAR, ProvisioningState.MANDATED)
+ .throwing(fooVariable)
+ .typeVariable(FOO, Exception.class)
+ .intercept(StubMethod.INSTANCE)
+ .make()
+ .load(new URLClassLoader(new URL[0], null), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(QUX, List.class).getTypeParameters().length, is(1));
+ assertThat(type.getDeclaredMethod(QUX, List.class).getTypeParameters()[0].getName(), is(FOO));
+ assertThat(type.getDeclaredMethod(QUX, List.class).getTypeParameters()[0].getBounds().length, is(1));
+ assertThat(type.getDeclaredMethod(QUX, List.class).getTypeParameters()[0].getBounds()[0], is((Object) Exception.class));
+ assertThat(type.getDeclaredMethod(QUX, List.class).getGenericReturnType(), is(list));
+ assertThat(type.getDeclaredMethod(QUX, List.class).getGenericExceptionTypes()[0], is((Type) type.getDeclaredMethod(QUX, List.class).getTypeParameters()[0]));
+ assertThat(type.getDeclaredMethod(QUX, List.class).getGenericParameterTypes().length, is(1));
+ assertThat(type.getDeclaredMethod(QUX, List.class).getGenericParameterTypes()[0], is(list));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testGenericMethodDefinitionMetaDataParameter() throws Exception {
+ Class<?> type = createPlain()
+ .defineMethod(QUX, list)
+ .withParameter(list, BAR, ProvisioningState.MANDATED)
+ .throwing(fooVariable)
+ .typeVariable(FOO, Exception.class)
+ .intercept(StubMethod.INSTANCE)
+ .make()
+ .load(new URLClassLoader(new URL[0], null), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(TypeDefinition.Sort.describe(type).getDeclaredMethods().filter(named(QUX)).getOnly().getParameters().getOnly().getName(), is(BAR));
+ assertThat(TypeDefinition.Sort.describe(type).getDeclaredMethods().filter(named(QUX)).getOnly().getParameters().getOnly().getModifiers(),
+ is(ProvisioningState.MANDATED.getMask()));
+ }
+
+ @Test(expected = ClassFormatError.class)
+ public void testUnvalidated() throws Exception {
+ createPlainWithoutValidation()
+ .defineField(FOO, void.class)
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER);
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testTypeVariableOnTypeAnnotationClassBound() throws Exception {
+ Class<? extends Annotation> typeAnnotationType = (Class<? extends Annotation>) Class.forName(TYPE_VARIABLE_NAME);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotationType).getDeclaredMethods().filter(named(VALUE)).getOnly();
+ Class<?> type = createPlain()
+ .typeVariable(FOO, TypeDescription.Generic.Builder.rawType(Object.class)
+ .build(AnnotationDescription.Builder.ofType(typeAnnotationType).define(VALUE, INTEGER_VALUE * 2).build()))
+ .annotateTypeVariable(AnnotationDescription.Builder.ofType(typeAnnotationType).define(VALUE, INTEGER_VALUE).build())
+ .make()
+ .load(typeAnnotationType.getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded();
+ assertThat(type.getTypeParameters().length, is(1));
+ assertThat(type.getTypeParameters()[0].getBounds().length, is(1));
+ assertThat(type.getTypeParameters()[0].getBounds()[0], is((Object) Object.class));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(type.getTypeParameters()[0]).asList().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(type.getTypeParameters()[0]).asList().ofType(typeAnnotationType)
+ .getValue(value).resolve(Integer.class), is(INTEGER_VALUE));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(type.getTypeParameters()[0]).ofTypeVariableBoundType(0)
+ .asList().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(type.getTypeParameters()[0]).ofTypeVariableBoundType(0)
+ .asList().ofType(typeAnnotationType).getValue(value).resolve(Integer.class), is(INTEGER_VALUE * 2));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testTypeVariableOnTypeAnnotationInterfaceBound() throws Exception {
+ Class<? extends Annotation> typeAnnotationType = (Class<? extends Annotation>) Class.forName(TYPE_VARIABLE_NAME);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotationType).getDeclaredMethods().filter(named(VALUE)).getOnly();
+ Class<?> type = createPlain()
+ .typeVariable(FOO, TypeDescription.Generic.Builder.rawType(Runnable.class)
+ .build(AnnotationDescription.Builder.ofType(typeAnnotationType).define(VALUE, INTEGER_VALUE * 2).build()))
+ .annotateTypeVariable(AnnotationDescription.Builder.ofType(typeAnnotationType).define(VALUE, INTEGER_VALUE).build())
+ .make()
+ .load(typeAnnotationType.getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded();
+ assertThat(type.getTypeParameters().length, is(1));
+ assertThat(type.getTypeParameters()[0].getBounds().length, is(1));
+ assertThat(type.getTypeParameters()[0].getBounds()[0], is((Object) Runnable.class));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(type.getTypeParameters()[0]).asList().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(type.getTypeParameters()[0]).asList().ofType(typeAnnotationType)
+ .getValue(value).resolve(Integer.class), is(INTEGER_VALUE));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(type.getTypeParameters()[0]).ofTypeVariableBoundType(0)
+ .asList().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(type.getTypeParameters()[0]).ofTypeVariableBoundType(0)
+ .asList().ofType(typeAnnotationType).getValue(value).resolve(Integer.class), is(INTEGER_VALUE * 2));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testTypeVariableOnTypeAnnotationTypeVariableBound() throws Exception {
+ Class<? extends Annotation> typeAnnotationType = (Class<? extends Annotation>) Class.forName(TYPE_VARIABLE_NAME);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotationType).getDeclaredMethods().filter(named(VALUE)).getOnly();
+ Class<?> type = createPlain()
+ .typeVariable(FOO)
+ .annotateTypeVariable(AnnotationDescription.Builder.ofType(typeAnnotationType).define(VALUE, INTEGER_VALUE).build())
+ .typeVariable(BAR, TypeDescription.Generic.Builder.rawType(Object.class)
+ .build(AnnotationDescription.Builder.ofType(typeAnnotationType).define(VALUE, INTEGER_VALUE * 3).build()))
+ .annotateTypeVariable(AnnotationDescription.Builder.ofType(typeAnnotationType).define(VALUE, INTEGER_VALUE * 2).build())
+ .make()
+ .load(typeAnnotationType.getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded();
+ assertThat(type.getTypeParameters().length, is(2));
+ assertThat(type.getTypeParameters()[0].getBounds().length, is(1));
+ assertThat(type.getTypeParameters()[0].getBounds()[0], is((Object) Object.class));
+ assertThat(type.getTypeParameters()[1].getBounds().length, is(1));
+ assertThat(type.getTypeParameters()[1].getBounds()[0], is((Object) Object.class));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(type.getTypeParameters()[0]).asList().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(type.getTypeParameters()[0]).asList().ofType(typeAnnotationType)
+ .getValue(value).resolve(Integer.class), is(INTEGER_VALUE));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(type.getTypeParameters()[1]).asList().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(type.getTypeParameters()[1]).asList().ofType(typeAnnotationType)
+ .getValue(value).resolve(Integer.class), is(INTEGER_VALUE * 2));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(type.getTypeParameters()[1]).ofTypeVariableBoundType(0)
+ .asList().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(type.getTypeParameters()[1]).ofTypeVariableBoundType(0)
+ .asList().ofType(typeAnnotationType).getValue(value).resolve(Integer.class), is(INTEGER_VALUE * 3));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testTypeAnnotationOnInterfaceType() throws Exception {
+ Class<? extends Annotation> typeAnnotationType = (Class<? extends Annotation>) Class.forName(TYPE_VARIABLE_NAME);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotationType).getDeclaredMethods().filter(named(VALUE)).getOnly();
+ Class<?> type = createPlain()
+ .merge(TypeManifestation.ABSTRACT)
+ .implement(TypeDescription.Generic.Builder.rawType(Runnable.class)
+ .build(AnnotationDescription.Builder.ofType(typeAnnotationType).define(VALUE, INTEGER_VALUE).build()))
+ .make()
+ .load(typeAnnotationType.getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded();
+ assertThat(type.getInterfaces().length, is(1));
+ assertThat(type.getInterfaces()[0], is((Object) Runnable.class));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveInterfaceType(type, 0).asList().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveInterfaceType(type, 0).asList().ofType(typeAnnotationType)
+ .getValue(value).resolve(Integer.class), is(INTEGER_VALUE));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testAnnotationTypeOnFieldType() throws Exception {
+ Class<? extends Annotation> typeAnnotationType = (Class<? extends Annotation>) Class.forName(TYPE_VARIABLE_NAME);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotationType).getDeclaredMethods().filter(named(VALUE)).getOnly();
+ Field field = createPlain()
+ .defineField(FOO, TypeDescription.Generic.Builder.rawType(Object.class)
+ .build(AnnotationDescription.Builder.ofType(typeAnnotationType).define(VALUE, INTEGER_VALUE).build()))
+ .make()
+ .load(typeAnnotationType.getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded()
+ .getDeclaredField(FOO);
+ assertThat(field.getType(), is((Object) Object.class));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveFieldType(field).asList().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveFieldType(field).asList().ofType(typeAnnotationType)
+ .getValue(value).resolve(Integer.class), is(INTEGER_VALUE));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testTypeVariableOnMethodAnnotationClassBound() throws Exception {
+ Class<? extends Annotation> typeAnnotationType = (Class<? extends Annotation>) Class.forName(TYPE_VARIABLE_NAME);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotationType).getDeclaredMethods().filter(named(VALUE)).getOnly();
+ Method method = createPlain()
+ .merge(TypeManifestation.ABSTRACT)
+ .defineMethod(FOO, void.class)
+ .typeVariable(FOO, TypeDescription.Generic.Builder.rawType(Object.class)
+ .build(AnnotationDescription.Builder.ofType(typeAnnotationType).define(VALUE, INTEGER_VALUE * 2).build()))
+ .annotateTypeVariable(AnnotationDescription.Builder.ofType(typeAnnotationType).define(VALUE, INTEGER_VALUE).build())
+ .withoutCode()
+ .make()
+ .load(typeAnnotationType.getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded()
+ .getDeclaredMethod(FOO);
+ assertThat(method.getTypeParameters().length, is(1));
+ assertThat(method.getTypeParameters()[0].getBounds().length, is(1));
+ assertThat(method.getTypeParameters()[0].getBounds()[0], is((Object) Object.class));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(method.getTypeParameters()[0]).asList().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(method.getTypeParameters()[0]).asList().ofType(typeAnnotationType)
+ .getValue(value).resolve(Integer.class), is(INTEGER_VALUE));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(method.getTypeParameters()[0]).ofTypeVariableBoundType(0)
+ .asList().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(method.getTypeParameters()[0]).ofTypeVariableBoundType(0)
+ .asList().ofType(typeAnnotationType).getValue(value).resolve(Integer.class), is(INTEGER_VALUE * 2));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testTypeVariableOnMethodAnnotationInterfaceBound() throws Exception {
+ Class<? extends Annotation> typeAnnotationType = (Class<? extends Annotation>) Class.forName(TYPE_VARIABLE_NAME);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotationType).getDeclaredMethods().filter(named(VALUE)).getOnly();
+ Method method = createPlain()
+ .merge(TypeManifestation.ABSTRACT)
+ .defineMethod(FOO, void.class)
+ .typeVariable(FOO, TypeDescription.Generic.Builder.rawType(Runnable.class)
+ .build(AnnotationDescription.Builder.ofType(typeAnnotationType).define(VALUE, INTEGER_VALUE * 2).build()))
+ .annotateTypeVariable(AnnotationDescription.Builder.ofType(typeAnnotationType).define(VALUE, INTEGER_VALUE).build())
+ .withoutCode()
+ .make()
+ .load(typeAnnotationType.getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded()
+ .getDeclaredMethod(FOO);
+ assertThat(method.getTypeParameters().length, is(1));
+ assertThat(method.getTypeParameters()[0].getBounds().length, is(1));
+ assertThat(method.getTypeParameters()[0].getBounds()[0], is((Object) Runnable.class));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(method.getTypeParameters()[0]).asList().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(method.getTypeParameters()[0]).asList().ofType(typeAnnotationType)
+ .getValue(value).resolve(Integer.class), is(INTEGER_VALUE));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(method.getTypeParameters()[0]).ofTypeVariableBoundType(0)
+ .asList().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(method.getTypeParameters()[0]).ofTypeVariableBoundType(0)
+ .asList().ofType(typeAnnotationType).getValue(value).resolve(Integer.class), is(INTEGER_VALUE * 2));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testTypeVariableOnMethodAnnotationTypeVariableBound() throws Exception {
+ Class<? extends Annotation> typeAnnotationType = (Class<? extends Annotation>) Class.forName(TYPE_VARIABLE_NAME);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotationType).getDeclaredMethods().filter(named(VALUE)).getOnly();
+ Method method = createPlain()
+ .merge(TypeManifestation.ABSTRACT)
+ .defineMethod(FOO, void.class)
+ .typeVariable(FOO).annotateTypeVariable(AnnotationDescription.Builder.ofType(typeAnnotationType).define(VALUE, INTEGER_VALUE).build())
+ .typeVariable(BAR, TypeDescription.Generic.Builder.rawType(Object.class)
+ .build(AnnotationDescription.Builder.ofType(typeAnnotationType).define(VALUE, INTEGER_VALUE * 3).build()))
+ .annotateTypeVariable(AnnotationDescription.Builder.ofType(typeAnnotationType).define(VALUE, INTEGER_VALUE * 2).build())
+ .withoutCode()
+ .make()
+ .load(typeAnnotationType.getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded()
+ .getDeclaredMethod(FOO);
+ assertThat(method.getTypeParameters().length, is(2));
+ assertThat(method.getTypeParameters()[0].getBounds().length, is(1));
+ assertThat(method.getTypeParameters()[0].getBounds()[0], is((Object) Object.class));
+ assertThat(method.getTypeParameters()[1].getBounds().length, is(1));
+ assertThat(method.getTypeParameters()[1].getBounds()[0], is((Object) Object.class));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(method.getTypeParameters()[0]).asList().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(method.getTypeParameters()[0]).asList().ofType(typeAnnotationType)
+ .getValue(value).resolve(Integer.class), is(INTEGER_VALUE));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(method.getTypeParameters()[1]).asList().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(method.getTypeParameters()[1]).asList().ofType(typeAnnotationType)
+ .getValue(value).resolve(Integer.class), is(INTEGER_VALUE * 2));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(method.getTypeParameters()[1]).ofTypeVariableBoundType(0)
+ .asList().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(method.getTypeParameters()[1]).ofTypeVariableBoundType(0)
+ .asList().ofType(typeAnnotationType).getValue(value).resolve(Integer.class), is(INTEGER_VALUE * 3));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testAnnotationTypeOnMethodReturnType() throws Exception {
+ Class<? extends Annotation> typeAnnotationType = (Class<? extends Annotation>) Class.forName(TYPE_VARIABLE_NAME);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotationType).getDeclaredMethods().filter(named(VALUE)).getOnly();
+ Method method = createPlain()
+ .merge(TypeManifestation.ABSTRACT)
+ .defineMethod(FOO, TypeDescription.Generic.Builder.rawType(Object.class)
+ .build(AnnotationDescription.Builder.ofType(typeAnnotationType).define(VALUE, INTEGER_VALUE).build()))
+ .withoutCode()
+ .make()
+ .load(typeAnnotationType.getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded()
+ .getDeclaredMethod(FOO);
+ assertThat(method.getReturnType(), is((Object) Object.class));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveReturnType(method).asList().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveReturnType(method).asList().ofType(typeAnnotationType)
+ .getValue(value).resolve(Integer.class), is(INTEGER_VALUE));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testAnnotationTypeOnMethodParameterType() throws Exception {
+ Class<? extends Annotation> typeAnnotationType = (Class<? extends Annotation>) Class.forName(TYPE_VARIABLE_NAME);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotationType).getDeclaredMethods().filter(named(VALUE)).getOnly();
+ Method method = createPlain()
+ .merge(TypeManifestation.ABSTRACT)
+ .defineMethod(FOO, void.class).withParameters(TypeDescription.Generic.Builder.rawType(Object.class)
+ .build(AnnotationDescription.Builder.ofType(typeAnnotationType).define(VALUE, INTEGER_VALUE).build()))
+ .withoutCode()
+ .make()
+ .load(typeAnnotationType.getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded()
+ .getDeclaredMethod(FOO, Object.class);
+ assertThat(method.getParameterTypes().length, is(1));
+ assertThat(method.getParameterTypes()[0], is((Object) Object.class));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveParameterType(method, 0).asList().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveParameterType(method, 0).asList().ofType(typeAnnotationType)
+ .getValue(value).resolve(Integer.class), is(INTEGER_VALUE));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testAnnotationTypeOnMethodExceptionType() throws Exception {
+ Class<? extends Annotation> typeAnnotationType = (Class<? extends Annotation>) Class.forName(TYPE_VARIABLE_NAME);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotationType).getDeclaredMethods().filter(named(VALUE)).getOnly();
+ Method method = createPlain()
+ .merge(TypeManifestation.ABSTRACT)
+ .defineMethod(FOO, void.class).throwing(TypeDescription.Generic.Builder.rawType(Exception.class)
+ .build(AnnotationDescription.Builder.ofType(typeAnnotationType).define(VALUE, INTEGER_VALUE).build()))
+ .withoutCode()
+ .make()
+ .load(typeAnnotationType.getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded()
+ .getDeclaredMethod(FOO);
+ assertThat(method.getExceptionTypes().length, is(1));
+ assertThat(method.getExceptionTypes()[0], is((Object) Exception.class));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveExceptionType(method, 0).asList().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveExceptionType(method, 0).asList().ofType(typeAnnotationType)
+ .getValue(value).resolve(Integer.class), is(INTEGER_VALUE));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testAnnotationTypeOnWildcardWithoutBound() throws Exception {
+ Class<? extends Annotation> typeAnnotationType = (Class<? extends Annotation>) Class.forName(TYPE_VARIABLE_NAME);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotationType).getDeclaredMethods().filter(named(VALUE)).getOnly();
+ Field field = createPlain()
+ .defineField(FOO, TypeDescription.Generic.Builder.parameterizedType(new TypeDescription.ForLoadedType(Collection.class),
+ TypeDescription.Generic.Builder.unboundWildcard(AnnotationDescription.Builder.ofType(typeAnnotationType)
+ .define(VALUE, INTEGER_VALUE).build())).build())
+ .make()
+ .load(typeAnnotationType.getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded()
+ .getDeclaredField(FOO);
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveFieldType(field).ofTypeArgument(0).asList().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveFieldType(field).ofTypeArgument(0).asList().ofType(typeAnnotationType)
+ .getValue(value).resolve(Integer.class), is(INTEGER_VALUE));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testAnnotationTypeOnWildcardUpperBoundBound() throws Exception {
+ Class<? extends Annotation> typeAnnotationType = (Class<? extends Annotation>) Class.forName(TYPE_VARIABLE_NAME);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotationType).getDeclaredMethods().filter(named(VALUE)).getOnly();
+ Field field = createPlain()
+ .defineField(FOO, TypeDescription.Generic.Builder.parameterizedType(new TypeDescription.ForLoadedType(Collection.class),
+ TypeDescription.Generic.Builder.rawType(Object.class)
+ .annotate(AnnotationDescription.Builder.ofType(typeAnnotationType).define(VALUE, INTEGER_VALUE).build())
+ .asWildcardUpperBound()).build())
+ .make()
+ .load(typeAnnotationType.getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded()
+ .getDeclaredField(FOO);
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveFieldType(field).ofTypeArgument(0).ofWildcardUpperBoundType(0).asList().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveFieldType(field).ofTypeArgument(0).ofWildcardUpperBoundType(0).asList()
+ .ofType(typeAnnotationType).getValue(value).resolve(Integer.class), is(INTEGER_VALUE));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testAnnotationTypeOnWildcardLowerBoundBound() throws Exception {
+ Class<? extends Annotation> typeAnnotationType = (Class<? extends Annotation>) Class.forName(TYPE_VARIABLE_NAME);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotationType).getDeclaredMethods().filter(named(VALUE)).getOnly();
+ Field field = createPlain()
+ .defineField(FOO, TypeDescription.Generic.Builder.parameterizedType(new TypeDescription.ForLoadedType(Collection.class),
+ TypeDescription.Generic.Builder.rawType(Object.class)
+ .annotate(AnnotationDescription.Builder.ofType(typeAnnotationType).define(VALUE, INTEGER_VALUE).build())
+ .asWildcardLowerBound()).build())
+ .make()
+ .load(typeAnnotationType.getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded()
+ .getDeclaredField(FOO);
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveFieldType(field).ofTypeArgument(0).ofWildcardLowerBoundType(0).asList().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveFieldType(field).ofTypeArgument(0).ofWildcardLowerBoundType(0).asList()
+ .ofType(typeAnnotationType).getValue(value).resolve(Integer.class), is(INTEGER_VALUE));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testAnnotationTypeOnGenericComponentType() throws Exception {
+ Class<? extends Annotation> typeAnnotationType = (Class<? extends Annotation>) Class.forName(TYPE_VARIABLE_NAME);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotationType).getDeclaredMethods().filter(named(VALUE)).getOnly();
+ Field field = createPlain()
+ .defineField(FOO, TypeDescription.Generic.Builder.parameterizedType(new TypeDescription.ForLoadedType(Collection.class),
+ TypeDescription.Generic.Builder.unboundWildcard())
+ .annotate(AnnotationDescription.Builder.ofType(typeAnnotationType).define(VALUE, INTEGER_VALUE).build())
+ .asArray()
+ .build())
+ .make()
+ .load(typeAnnotationType.getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded()
+ .getDeclaredField(FOO);
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveFieldType(field).ofComponentType().asList().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveFieldType(field).ofComponentType().asList()
+ .ofType(typeAnnotationType).getValue(value).resolve(Integer.class), is(INTEGER_VALUE));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testAnnotationTypeOnNonGenericComponentType() throws Exception {
+ Class<? extends Annotation> typeAnnotationType = (Class<? extends Annotation>) Class.forName(TYPE_VARIABLE_NAME);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotationType).getDeclaredMethods().filter(named(VALUE)).getOnly();
+ Field field = createPlain()
+ .defineField(FOO, TypeDescription.Generic.Builder.rawType(Object.class)
+ .annotate(AnnotationDescription.Builder.ofType(typeAnnotationType).define(VALUE, INTEGER_VALUE).build())
+ .asArray()
+ .build())
+ .make()
+ .load(typeAnnotationType.getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded()
+ .getDeclaredField(FOO);
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveFieldType(field).ofComponentType().asList().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveFieldType(field).ofComponentType().asList()
+ .ofType(typeAnnotationType).getValue(value).resolve(Integer.class), is(INTEGER_VALUE));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testAnnotationTypeOnParameterizedType() throws Exception {
+ Class<? extends Annotation> typeAnnotationType = (Class<? extends Annotation>) Class.forName(TYPE_VARIABLE_NAME);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotationType).getDeclaredMethods().filter(named(VALUE)).getOnly();
+ Field field = createPlain()
+ .defineField(FOO, TypeDescription.Generic.Builder.parameterizedType(new TypeDescription.ForLoadedType(Collection.class),
+ TypeDescription.Generic.Builder.unboundWildcard(AnnotationDescription.Builder.ofType(typeAnnotationType)
+ .define(VALUE, INTEGER_VALUE).build()))
+ .build())
+ .make()
+ .load(typeAnnotationType.getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded()
+ .getDeclaredField(FOO);
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveFieldType(field).ofTypeArgument(0).asList().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveFieldType(field).ofTypeArgument(0).asList()
+ .ofType(typeAnnotationType).getValue(value).resolve(Integer.class), is(INTEGER_VALUE));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testAnnotationTypeOnNestedType() throws Exception {
+ Class<? extends Annotation> typeAnnotationType = (Class<? extends Annotation>) Class.forName(TYPE_VARIABLE_NAME);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotationType).getDeclaredMethods().filter(named(VALUE)).getOnly();
+ Field field = createPlain()
+ .defineField(FOO, TypeDescription.Generic.Builder.rawType(new TypeDescription.ForLoadedType(Nested.Inner.class),
+ TypeDescription.Generic.Builder.rawType(Nested.class).build())
+ .annotate(AnnotationDescription.Builder.ofType(typeAnnotationType).define(VALUE, INTEGER_VALUE).build())
+ .build())
+ .make()
+ .load(typeAnnotationType.getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded()
+ .getDeclaredField(FOO);
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveFieldType(field).asList().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveFieldType(field).asList()
+ .ofType(typeAnnotationType).getValue(value).resolve(Integer.class), is(INTEGER_VALUE));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ @Ignore("The Java reflection API does not currently support nested generic types")
+ public void testAnnotationTypeOnNestedParameterizedType() throws Exception {
+ Class<? extends Annotation> typeAnnotationType = (Class<? extends Annotation>) Class.forName(TYPE_VARIABLE_NAME);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotationType).getDeclaredMethods().filter(named(VALUE)).getOnly();
+ Field field = createPlain()
+ .defineField(FOO, TypeDescription.Generic.Builder.parameterizedType(new TypeDescription.ForLoadedType(GenericNested.Inner.class),
+ TypeDescription.Generic.Builder.parameterizedType(GenericNested.class, Void.class).build(),
+ Collections.<TypeDefinition>emptyList())
+ .annotate(AnnotationDescription.Builder.ofType(typeAnnotationType).define(VALUE, INTEGER_VALUE).build())
+ .build())
+ .make()
+ .load(typeAnnotationType.getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded()
+ .getDeclaredField(FOO);
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveFieldType(field).asList().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveFieldType(field).asList()
+ .ofType(typeAnnotationType).getValue(value).resolve(Integer.class), is(INTEGER_VALUE));
+ }
+
+ @Test
+ public void testBridgeResolutionAmbiguous() throws Exception {
+ Class<?> type = createPlain()
+ .defineMethod(QUX, String.class, Visibility.PUBLIC)
+ .intercept(FixedValue.value(FOO))
+ .defineMethod(QUX, Object.class, Visibility.PUBLIC)
+ .intercept(FixedValue.value(BAR))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded();
+ for (Method method : type.getDeclaredMethods()) {
+ if (method.getReturnType() == String.class) {
+ assertThat(method.getName(), is(QUX));
+ assertThat(method.getParameterTypes().length, is(0));
+ assertThat(method.invoke(type.getDeclaredConstructor().newInstance()), is((Object) BAR));
+ } else if (method.getReturnType() == Object.class) {
+ assertThat(method.getName(), is(QUX));
+ assertThat(method.getParameterTypes().length, is(0));
+ assertThat(method.invoke(type.getDeclaredConstructor().newInstance()), is((Object) BAR));
+ } else {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @Test
+ public void testCanOverloadMethodByReturnType() throws Exception {
+ Class<?> type = createPlain()
+ .defineMethod(QUX, String.class, Visibility.PUBLIC)
+ .intercept(FixedValue.value(FOO))
+ .defineMethod(QUX, Object.class, Ownership.STATIC, Visibility.PUBLIC) // Is static to avoid method graph compiler.
+ .intercept(FixedValue.value(BAR))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded();
+ for (Method method : type.getDeclaredMethods()) {
+ if (method.getReturnType() == String.class) {
+ assertThat(method.getName(), is(QUX));
+ assertThat(method.getParameterTypes().length, is(0));
+ assertThat(method.invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ } else if (method.getReturnType() == Object.class) {
+ assertThat(method.getName(), is(QUX));
+ assertThat(method.getParameterTypes().length, is(0));
+ assertThat(method.invoke(null), is((Object) BAR));
+ } else {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @Test
+ public void testCanOverloadFieldByType() throws Exception {
+ Class<?> type = createPlain()
+ .defineField(QUX, String.class, Ownership.STATIC, Visibility.PUBLIC)
+ .value(FOO)
+ .defineField(QUX, long.class, Ownership.STATIC, Visibility.PUBLIC)
+ .value(42L)
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded();
+ for (Field field : type.getDeclaredFields()) {
+ if (field.getType() == String.class) {
+ assertThat(field.getName(), is(QUX));
+ assertThat(field.get(null), is((Object) FOO));
+ } else if (field.getType() == long.class) {
+ assertThat(field.getName(), is(QUX));
+ assertThat(field.get(null), is((Object) 42L));
+ } else {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ @Test
+ public void testInterfaceInterception() throws Exception {
+ assertThat(((SampleInterface) createPlain()
+ .implement(SampleInterface.class)
+ .intercept(FixedValue.value(FOO))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance()).foo(), is(FOO));
+ }
+
+ @Test
+ public void testInterfaceInterceptionPreviousSuperseeded() throws Exception {
+ assertThat(((SampleInterface) createPlain()
+ .method(named(FOO))
+ .intercept(ExceptionMethod.throwing(AssertionError.class))
+ .implement(SampleInterface.class)
+ .intercept(FixedValue.value(FOO))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance()).foo(), is(FOO));
+ }
+
+ @Test
+ public void testInterfaceInterceptionLaterSuperseeding() throws Exception {
+ assertThat(((SampleInterface) createPlain()
+ .implement(SampleInterface.class)
+ .intercept(ExceptionMethod.throwing(AssertionError.class))
+ .method(named(FOO))
+ .intercept(FixedValue.value(FOO))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance()).foo(), is(FOO));
+ }
+
+ @Test
+ public void testInterfaceInterceptionSubClass() throws Exception {
+ assertThat(((SampleInterface) createPlain()
+ .implement(SampleInterface.SubInterface.class)
+ .intercept(FixedValue.value(FOO))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance()).foo(), is(FOO));
+ }
+
+ @Test
+ public void testInterfaceMakesClassMethodPublic() throws Exception {
+ Class<?> type = createPlain()
+ .implement(Cloneable.class)
+ .method(named("clone"))
+ .intercept(FixedValue.self())
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded();
+ Cloneable cloneable = (Cloneable) type.getDeclaredConstructor().newInstance();
+ assertThat(cloneable.clone(), sameInstance((Object) cloneable));
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface SampleAnnotation {
+
+ String foo();
+ }
+
+ public static class Foo {
+ /* empty */
+ }
+
+ public static class Bar {
+
+ public static String foo;
+
+ public static void invoke() {
+ foo = FOO;
+ }
+ }
+
+ public static class BridgeRetention<T> extends CallTraceable {
+
+ public T foo() {
+ register(FOO);
+ return null;
+ }
+
+ public static class Inner extends BridgeRetention<String> {
+ /* empty */
+ }
+ }
+
+ public static class CallSuperMethod<T> extends CallTraceable {
+
+ public T foo(T value) {
+ register(FOO);
+ return value;
+ }
+
+ public static class Inner extends CallSuperMethod<String> {
+ /* empty */
+ }
+ }
+
+ private static class PreparedField implements Implementation {
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType.withField(new FieldDescription.Token(FOO,
+ MODIFIERS,
+ TypeDescription.Generic.OBJECT,
+ Collections.singletonList(AnnotationDescription.Builder.ofType(SampleAnnotation.class).define(FOO, BAR).build())));
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return new ByteCodeAppender.Simple(NullConstant.INSTANCE, MethodReturn.REFERENCE);
+ }
+ }
+
+ private static class PreparedMethod implements Implementation {
+
+ @Override
+ public InstrumentedType prepare(InstrumentedType instrumentedType) {
+ return instrumentedType.withMethod(new MethodDescription.Token(FOO,
+ MODIFIERS,
+ Collections.<TypeVariableToken>emptyList(),
+ TypeDescription.Generic.OBJECT,
+ Collections.singletonList(new ParameterDescription.Token(TypeDescription.Generic.OBJECT,
+ Collections.singletonList(AnnotationDescription.Builder.ofType(SampleAnnotation.class).define(FOO, QUX).build()))),
+ Collections.singletonList(new TypeDescription.Generic.OfNonGenericType.ForLoadedType(Exception.class)),
+ Collections.singletonList(AnnotationDescription.Builder.ofType(SampleAnnotation.class).define(FOO, BAR).build()),
+ AnnotationValue.UNDEFINED,
+ TypeDescription.Generic.UNDEFINED));
+ }
+
+ @Override
+ public ByteCodeAppender appender(Target implementationTarget) {
+ return new ByteCodeAppender.Simple(NullConstant.INSTANCE, MethodReturn.REFERENCE);
+ }
+ }
+
+ public static class InterfaceOverrideInterceptor {
+
+ public static String intercept(@SuperCall Callable<String> zuper) throws Exception {
+ return zuper.call() + BAR;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private static class Holder<foo> {
+
+ List<?> list;
+
+ List<foo> fooList;
+ }
+
+ @SuppressWarnings("unused")
+ public static class Nested {
+
+ public class Inner {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class GenericNested<T> {
+
+ public class Inner {
+ /* empty */
+ }
+ }
+
+ public interface Cloneable {
+
+ Object clone();
+ }
+
+ public interface SampleInterface {
+
+ String foo();
+
+ interface SubInterface extends SampleInterface {
+
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorAgentBasedTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorAgentBasedTest.java
new file mode 100644
index 0000000..cd28950
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorAgentBasedTest.java
@@ -0,0 +1,144 @@
+package net.bytebuddy.dynamic;
+
+import net.bytebuddy.agent.ByteBuddyAgent;
+import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
+import net.bytebuddy.test.utility.AgentAttachmentRule;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.hamcrest.CoreMatchers;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+
+import java.lang.instrument.Instrumentation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.ProtectionDomain;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ClassFileLocatorAgentBasedTest {
+
+ private static final String FOO = "foo";
+
+ @Rule
+ public MethodRule agentAttachmentRule = new AgentAttachmentRule();
+
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ @Test
+ @AgentAttachmentRule.Enforce(redefinesClasses = true)
+ public void testStrategyCreation() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ assertThat(ClassReloadingStrategy.fromInstalledAgent(), notNullValue());
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce(retransformsClasses = true)
+ public void testExtraction() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ ClassFileLocator classFileLocator = ClassFileLocator.AgentBased.fromInstalledAgent(getClass().getClassLoader());
+ ClassFileLocator.Resolution resolution = classFileLocator.locate(Foo.class.getName());
+ assertThat(resolution.isResolved(), is(true));
+ assertThat(resolution.resolve(), notNullValue(byte[].class));
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce(retransformsClasses = true)
+ public void testExtractionOfInflatedMethodAccessor() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ Method bar = Foo.class.getDeclaredMethod("bar");
+ for (int i = 0; i < 20; i++) {
+ bar.invoke(new Foo());
+ }
+ Field field = Method.class.getDeclaredField("methodAccessor");
+ field.setAccessible(true);
+ Object methodAccessor = field.get(bar);
+ Field delegate = methodAccessor.getClass().getDeclaredField("delegate");
+ delegate.setAccessible(true);
+ Class<?> delegateClass = delegate.get(methodAccessor).getClass();
+ ClassFileLocator classFileLocator = ClassFileLocator.AgentBased.fromInstalledAgent(delegateClass.getClassLoader());
+ ClassFileLocator.Resolution resolution = classFileLocator.locate(delegateClass.getName());
+ assertThat(resolution.isResolved(), is(true));
+ assertThat(resolution.resolve(), notNullValue(byte[].class));
+ }
+
+ @Test
+ public void testExplicitLookupBootstrapClassLoader() throws Exception {
+ ClassFileLocator.AgentBased.ClassLoadingDelegate classLoadingDelegate = ClassFileLocator.AgentBased.ClassLoadingDelegate.Explicit.of(Object.class);
+ assertThat(classLoadingDelegate.getClassLoader(), is(ClassLoader.getSystemClassLoader()));
+ assertThat(classLoadingDelegate.locate(Object.class.getName()), CoreMatchers.<Class<?>>is(Object.class));
+ assertThat(classLoadingDelegate.locate(String.class.getName()), CoreMatchers.<Class<?>>is(String.class));
+ }
+
+ @Test
+ public void testExplicitLookup() throws Exception {
+ ClassFileLocator.AgentBased.ClassLoadingDelegate classLoadingDelegate = ClassFileLocator.AgentBased.ClassLoadingDelegate.Explicit.of(Foo.class);
+ assertThat(classLoadingDelegate.getClassLoader(), is(Foo.class.getClassLoader()));
+ assertThat(classLoadingDelegate.locate(Foo.class.getName()), CoreMatchers.<Class<?>>is(Foo.class));
+ assertThat(classLoadingDelegate.locate(Object.class.getName()), CoreMatchers.<Class<?>>is(Object.class));
+ }
+
+ @Test
+ public void testExtractingTransformerHandlesNullValue() throws Exception {
+ assertThat(new ClassFileLocator.AgentBased.ExtractionClassFileTransformer(mock(ClassLoader.class), FOO).transform(mock(ClassLoader.class),
+ FOO,
+ Object.class,
+ mock(ProtectionDomain.class),
+ new byte[0]), nullValue(byte[].class));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ClassFileLocator.AgentBased.class).refine(new ObjectPropertyAssertion.Refinement<Instrumentation>() {
+ @Override
+ public void apply(Instrumentation mock) {
+ when(mock.isRetransformClassesSupported()).thenReturn(true);
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(ClassFileLocator.AgentBased.ClassLoadingDelegate.Default.class).apply();
+ ObjectPropertyAssertion.of(ClassFileLocator.AgentBased.ClassLoadingDelegate.ForDelegatingClassLoader.class).apply();
+ final Iterator<Field> iterator = Arrays.asList(Foo.class.getDeclaredFields()).iterator();
+ ObjectPropertyAssertion.of(ClassFileLocator.AgentBased.ClassLoadingDelegate.ForDelegatingClassLoader.Dispatcher.Resolved.class)
+ .create(new ObjectPropertyAssertion.Creator<Field>() {
+ @Override
+ public Field create() {
+ return iterator.next();
+ }
+ })
+ .apply();
+ ObjectPropertyAssertion.of(ClassFileLocator.AgentBased.ClassLoadingDelegate.ForDelegatingClassLoader.Dispatcher.Unresolved.class).apply();
+ final Iterator<Class<?>> otherIterator = Arrays.<Class<?>>asList(Integer.class, String.class, Object.class, Byte.class).iterator();
+ ObjectPropertyAssertion.of(ClassFileLocator.AgentBased.ClassLoadingDelegate.Explicit.class).create(new ObjectPropertyAssertion.Creator<Collection<Class<?>>>() {
+ @Override
+ public Collection<Class<?>> create() {
+ return Collections.<Class<?>>singletonList(otherIterator.next());
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(ClassFileLocator.AgentBased.ClassLoadingDelegate.ForDelegatingClassLoader.Dispatcher.CreationAction.class).apply();
+ }
+
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNonCompatible() throws Exception {
+ new ClassFileLocator.AgentBased(mock(Instrumentation.class), getClass().getClassLoader());
+ }
+
+ private static class Foo {
+
+ int foo, bar;
+
+ void bar() {
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorCompoundTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorCompoundTest.java
new file mode 100644
index 0000000..b4392b1
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorCompoundTest.java
@@ -0,0 +1,79 @@
+package net.bytebuddy.dynamic;
+
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.io.Closeable;
+import java.util.Collections;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class ClassFileLocatorCompoundTest {
+
+ private static final String FOO = "foo";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private ClassFileLocator classFileLocator;
+
+ @Mock
+ private ClassFileLocator otherClassFileLocator;
+
+ @Mock
+ private ClassFileLocator.Resolution legal, illegal;
+
+ @Before
+ public void setUp() throws Exception {
+ when(legal.isResolved()).thenReturn(true);
+ }
+
+ @Test
+ public void testApplicationOrderCallsSecond() throws Exception {
+ when(classFileLocator.locate(FOO)).thenReturn(illegal);
+ when(otherClassFileLocator.locate(FOO)).thenReturn(legal);
+ assertThat(new ClassFileLocator.Compound(classFileLocator, otherClassFileLocator).locate(FOO), is(legal));
+ verify(classFileLocator).locate(FOO);
+ verifyNoMoreInteractions(classFileLocator);
+ verify(otherClassFileLocator).locate(FOO);
+ verifyNoMoreInteractions(otherClassFileLocator);
+ }
+
+ @Test
+ public void testApplicationOrderDoesNotCallSecond() throws Exception {
+ when(classFileLocator.locate(FOO)).thenReturn(legal);
+ assertThat(new ClassFileLocator.Compound(classFileLocator, otherClassFileLocator).locate(FOO), is(legal));
+ verify(classFileLocator).locate(FOO);
+ verifyNoMoreInteractions(classFileLocator);
+ verifyZeroInteractions(otherClassFileLocator);
+ }
+
+ @Test
+ public void testClosable() throws Exception {
+ when(classFileLocator.locate(FOO)).thenReturn(legal);
+ new ClassFileLocator.Compound(classFileLocator, otherClassFileLocator).close();
+ verify(classFileLocator).close();
+ verifyNoMoreInteractions(classFileLocator);
+ verify(otherClassFileLocator).close();
+ verifyNoMoreInteractions(otherClassFileLocator);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ClassFileLocator.Compound.class).create(new ObjectPropertyAssertion.Creator<List<?>>() {
+ @Override
+ public List<?> create() {
+ return Collections.singletonList(mock(ClassFileLocator.class));
+ }
+ }).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorForClassLoaderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorForClassLoaderTest.java
new file mode 100644
index 0000000..1abcb1a
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorForClassLoaderTest.java
@@ -0,0 +1,132 @@
+package net.bytebuddy.dynamic;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.utility.JavaModule;
+import net.bytebuddy.utility.StreamDrainer;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.io.ByteArrayInputStream;
+import java.io.Closeable;
+import java.io.InputStream;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.*;
+
+public class ClassFileLocatorForClassLoaderTest {
+
+ private static final String FOOBAR = "foo/bar";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private ClosableClassLoader classLoader;
+
+ @Test
+ public void testCreation() throws Exception {
+ assertThat(ClassFileLocator.ForClassLoader.of(classLoader), is((ClassFileLocator) new ClassFileLocator.ForClassLoader(classLoader)));
+ assertThat(ClassFileLocator.ForClassLoader.of(null), is((ClassFileLocator) new ClassFileLocator.ForClassLoader(ClassLoader.getSystemClassLoader())));
+ }
+
+ @Test
+ public void testLocatable() throws Exception {
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(new byte[]{1, 2, 3});
+ when(classLoader.getResourceAsStream(FOOBAR + ".class")).thenReturn(inputStream);
+ ClassFileLocator.Resolution resolution = new ClassFileLocator.ForClassLoader(classLoader)
+ .locate(FOOBAR);
+ assertThat(resolution.isResolved(), is(true));
+ assertThat(resolution.resolve(), is(new byte[]{1, 2, 3}));
+ verify(classLoader).getResourceAsStream(FOOBAR + ".class");
+ verifyNoMoreInteractions(classLoader);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonLocatable() throws Exception {
+ ClassFileLocator.Resolution resolution = new ClassFileLocator.ForClassLoader(classLoader)
+ .locate(FOOBAR);
+ assertThat(resolution.isResolved(), is(false));
+ verify(classLoader).getResourceAsStream(FOOBAR + ".class");
+ verifyNoMoreInteractions(classLoader);
+ resolution.resolve();
+ fail();
+ }
+
+ @Test
+ public void testReadTypeBootstrapClassLoader() throws Exception {
+ ClassFileLocator.Resolution resolution = ClassFileLocator.ForClassLoader.read(Object.class);
+ assertThat(resolution.isResolved(), is(true));
+ JavaModule module = JavaModule.ofType(Object.class);
+ InputStream inputStream = module == null
+ ? Object.class.getResourceAsStream(Object.class.getSimpleName() + ".class")
+ : module.getResourceAsStream(Object.class.getName().replace('.', '/') + ".class");
+ try {
+ assertThat(resolution.resolve(), is(StreamDrainer.DEFAULT.drain(inputStream)));
+ } finally {
+ inputStream.close();
+ }
+ }
+
+ @Test
+ public void testReadTypeNonBootstrapClassLoader() throws Exception {
+ ClassFileLocator.Resolution resolution = ClassFileLocator.ForClassLoader.read(Foo.class);
+ assertThat(resolution.isResolved(), is(true));
+ InputStream inputStream = getClass().getClassLoader().getResourceAsStream(Foo.class.getName().replace('.', '/') + ".class");
+ try {
+ assertThat(resolution.resolve(), is(StreamDrainer.DEFAULT.drain(inputStream)));
+ } finally {
+ inputStream.close();
+ }
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testReadTypeIllegal() throws Exception {
+ Class<?> nonClassFileType = new ByteBuddy().subclass(Object.class).make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER).getLoaded();
+ ClassFileLocator.Resolution resolution = ClassFileLocator.ForClassLoader.read(nonClassFileType);
+ assertThat(resolution.isResolved(), is(false));
+ resolution.resolve();
+ }
+
+ @Test
+ public void testClose() throws Exception {
+ ClassFileLocator.ForClassLoader.of(classLoader).close();
+ verifyZeroInteractions(classLoader);
+ }
+
+ @Test
+ public void testSystemClassLoader() throws Exception {
+ assertThat(ClassFileLocator.ForClassLoader.ofClassPath(), is(ClassFileLocator.ForClassLoader.of(ClassLoader.getSystemClassLoader())));
+ }
+
+ @Test
+ public void testPlatformLoader() throws Exception {
+ assertThat(ClassFileLocator.ForClassLoader.of(ClassLoader.getSystemClassLoader().getParent()),
+ is(ClassFileLocator.ForClassLoader.of(ClassLoader.getSystemClassLoader().getParent())));
+ }
+
+ @Test
+ public void testBootLoader() throws Exception {
+ assertThat(ClassFileLocator.ForClassLoader.of(null), is(ClassFileLocator.ForClassLoader.of(ClassLoader.getSystemClassLoader())));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ClassFileLocator.ForClassLoader.class).apply();
+ }
+
+ private static class Foo {
+ /* empty */
+ }
+
+ private abstract static class ClosableClassLoader extends ClassLoader implements Closeable {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorForClassLoaderWeaklyReferencedTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorForClassLoaderWeaklyReferencedTest.java
new file mode 100644
index 0000000..a12f6df
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorForClassLoaderWeaklyReferencedTest.java
@@ -0,0 +1,91 @@
+package net.bytebuddy.dynamic;
+
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.io.ByteArrayInputStream;
+import java.io.Closeable;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.*;
+
+public class ClassFileLocatorForClassLoaderWeaklyReferencedTest {
+
+ private static final String FOOBAR = "foo/bar";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private ClosableClassLoader classLoader;
+
+ @Test
+ public void testCreation() throws Exception {
+ assertThat(ClassFileLocator.ForClassLoader.WeaklyReferenced.of(classLoader),
+ is((ClassFileLocator) new ClassFileLocator.ForClassLoader.WeaklyReferenced(classLoader)));
+ assertThat(ClassFileLocator.ForClassLoader.WeaklyReferenced.of(null),
+ is((ClassFileLocator) new ClassFileLocator.ForClassLoader(ClassLoader.getSystemClassLoader())));
+ assertThat(ClassFileLocator.ForClassLoader.WeaklyReferenced.of(ClassLoader.getSystemClassLoader()),
+ is((ClassFileLocator) new ClassFileLocator.ForClassLoader(ClassLoader.getSystemClassLoader())));
+ }
+
+ @Test
+ public void testLocatable() throws Exception {
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(new byte[]{1, 2, 3});
+ when(classLoader.getResourceAsStream(FOOBAR + ".class")).thenReturn(inputStream);
+ ClassFileLocator.Resolution resolution = new ClassFileLocator.ForClassLoader.WeaklyReferenced(classLoader)
+ .locate(FOOBAR);
+ assertThat(resolution.isResolved(), is(true));
+ assertThat(resolution.resolve(), is(new byte[]{1, 2, 3}));
+ verify(classLoader).getResourceAsStream(FOOBAR + ".class");
+ verifyNoMoreInteractions(classLoader);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonLocatable() throws Exception {
+ ClassFileLocator.Resolution resolution = new ClassFileLocator.ForClassLoader.WeaklyReferenced(classLoader)
+ .locate(FOOBAR);
+ assertThat(resolution.isResolved(), is(false));
+ verify(classLoader).getResourceAsStream(FOOBAR + ".class");
+ verifyNoMoreInteractions(classLoader);
+ resolution.resolve();
+ fail();
+ }
+
+ @Test
+ public void testClose() throws Exception {
+ ClassFileLocator.ForClassLoader.WeaklyReferenced.of(classLoader).close();
+ verifyZeroInteractions(classLoader);
+ }
+
+ @Test
+ public void testSystemClassLoader() throws Exception {
+ assertThat(ClassFileLocator.ForClassLoader.WeaklyReferenced.of(ClassLoader.getSystemClassLoader()), is(ClassFileLocator.ForClassLoader.of(ClassLoader.getSystemClassLoader())));
+ }
+
+ @Test
+ public void testPlatformLoader() throws Exception {
+ assertThat(ClassFileLocator.ForClassLoader.WeaklyReferenced.of(ClassLoader.getSystemClassLoader().getParent()),
+ is(ClassFileLocator.ForClassLoader.of(ClassLoader.getSystemClassLoader().getParent())));
+ }
+
+ @Test
+ public void testBootLoader() throws Exception {
+ assertThat(ClassFileLocator.ForClassLoader.WeaklyReferenced.of(null), is(ClassFileLocator.ForClassLoader.of(ClassLoader.getSystemClassLoader())));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ClassFileLocator.ForClassLoader.WeaklyReferenced.class).apply();
+ }
+
+ private abstract static class ClosableClassLoader extends ClassLoader implements Closeable {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorForFolderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorForFolderTest.java
new file mode 100644
index 0000000..00d1d26
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorForFolderTest.java
@@ -0,0 +1,73 @@
+package net.bytebuddy.dynamic;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.Random;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class ClassFileLocatorForFolderTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ private static final int VALUE = 42;
+
+ private File folder;
+
+ @Before
+ public void setUp() throws Exception {
+ File file = File.createTempFile(FOO, BAR);
+ assertThat(file.delete(), is(true));
+ folder = new File(file.getParentFile(), FOO + new Random().nextInt());
+ assertThat(folder.mkdir(), is(true));
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ assertThat(folder.delete(), is(true));
+ }
+
+ @Test
+ public void testSuccessfulLocation() throws Exception {
+ File packageFolder = new File(folder, FOO);
+ assertThat(packageFolder.mkdir(), is(true));
+ File file = new File(packageFolder, BAR + ".class");
+ assertThat(file.createNewFile(), is(true));
+ FileOutputStream fileOutputStream = new FileOutputStream(file);
+ try {
+ fileOutputStream.write(VALUE);
+ fileOutputStream.write(VALUE * 2);
+ } finally {
+ fileOutputStream.close();
+ }
+ ClassFileLocator classFileLocator = new ClassFileLocator.ForFolder(folder);
+ ClassFileLocator.Resolution resolution = classFileLocator.locate(FOO + "." + BAR);
+ assertThat(resolution.isResolved(), is(true));
+ assertThat(resolution.resolve(), is(new byte[]{VALUE, VALUE * 2}));
+ assertThat(file.delete(), is(true));
+ assertThat(packageFolder.delete(), is(true));
+ }
+
+ @Test
+ public void testNonSuccessfulLocation() throws Exception {
+ ClassFileLocator classFileLocator = new ClassFileLocator.ForFolder(folder);
+ ClassFileLocator.Resolution resolution = classFileLocator.locate(FOO + "." + BAR);
+ assertThat(resolution.isResolved(), is(false));
+ }
+
+ @Test
+ public void testClose() throws Exception {
+ new ClassFileLocator.ForFolder(folder).close();
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ClassFileLocator.ForFolder.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorForJarFileTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorForJarFileTest.java
new file mode 100644
index 0000000..cbc693e
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorForJarFileTest.java
@@ -0,0 +1,132 @@
+package net.bytebuddy.dynamic;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+import org.objectweb.asm.ClassVisitor;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class ClassFileLocatorForJarFileTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ private static final int VALUE = 42;
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ private File file;
+
+ @Before
+ public void setUp() throws Exception {
+ file = File.createTempFile(FOO, BAR);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ assertThat(file.delete(), is(true));
+ }
+
+ @Test
+ public void testSuccessfulLocation() throws Exception {
+ JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(file));
+ try {
+ JarEntry jarEntry = new JarEntry(FOO + "/" + BAR + ".class");
+ jarOutputStream.putNextEntry(jarEntry);
+ jarOutputStream.write(VALUE);
+ jarOutputStream.write(VALUE * 2);
+ jarOutputStream.closeEntry();
+ } finally {
+ jarOutputStream.close();
+ }
+ JarFile jarFile = new JarFile(file);
+ try {
+ ClassFileLocator classFileLocator = new ClassFileLocator.ForJarFile(jarFile);
+ ClassFileLocator.Resolution resolution = classFileLocator.locate(FOO + "." + BAR);
+ assertThat(resolution.isResolved(), is(true));
+ assertThat(resolution.resolve(), is(new byte[]{VALUE, VALUE * 2}));
+ } finally {
+ jarFile.close();
+ }
+ }
+
+ @Test
+ public void testJarFileClosable() throws Exception {
+ JarFile jarFile = mock(JarFile.class);
+ Closeable classFileLocator = new ClassFileLocator.ForJarFile(jarFile);
+ classFileLocator.close();
+ verify(jarFile).close();
+ }
+
+ @Test
+ public void testClassPath() throws Exception {
+ ClassFileLocator classFileLocator = ClassFileLocator.ForJarFile.ofClassPath();
+ try {
+ assertThat(classFileLocator.locate(ByteBuddy.class.getName()).isResolved(), is(true)); // As file.
+ assertThat(classFileLocator.locate(ClassVisitor.class.getName()).isResolved(), is(true)); // On path.
+ } finally {
+ classFileLocator.close();
+ }
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(value = 8, sort = JavaVersionRule.Sort.AT_MOST)
+ public void testRuntimeJar() throws Exception {
+ ClassFileLocator classFileLocator = ClassFileLocator.ForJarFile.ofRuntimeJar();
+ try {
+ // java.lang.Object is not contained in the rt.jar for some JVMs.
+ assertThat(classFileLocator.locate(Void.class.getName()).isResolved(), is(true));
+ } finally {
+ classFileLocator.close();
+ }
+ }
+
+ @Test
+ public void testNonSuccessfulLocation() throws Exception {
+ JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(file));
+ try {
+ JarEntry jarEntry = new JarEntry("noop.class");
+ jarOutputStream.putNextEntry(jarEntry);
+ jarOutputStream.write(VALUE);
+ jarOutputStream.closeEntry();
+ } finally {
+ jarOutputStream.close();
+ }
+ JarFile jarFile = new JarFile(file);
+ try {
+ ClassFileLocator classFileLocator = new ClassFileLocator.ForJarFile(jarFile);
+ ClassFileLocator.Resolution resolution = classFileLocator.locate(FOO + "." + BAR);
+ assertThat(resolution.isResolved(), is(false));
+ } finally {
+ jarFile.close();
+ }
+ }
+
+ @Test
+ public void testClose() throws Exception {
+ JarFile jarFile = mock(JarFile.class);
+ new ClassFileLocator.ForJarFile(jarFile).close();
+ verify(jarFile).close();
+ verifyNoMoreInteractions(jarFile);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ClassFileLocator.ForJarFile.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorForModuleFileTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorForModuleFileTest.java
new file mode 100644
index 0000000..208e754
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorForModuleFileTest.java
@@ -0,0 +1,121 @@
+package net.bytebuddy.dynamic;
+
+import net.bytebuddy.test.utility.JavaVersionRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+public class ClassFileLocatorForModuleFileTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ private static final int VALUE = 42;
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ private File file;
+
+ @Before
+ public void setUp() throws Exception {
+ file = File.createTempFile(FOO, BAR);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ assertThat(file.delete(), is(true));
+ }
+
+ @Test
+ public void testSuccessfulLocation() throws Exception {
+ ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(file));
+ try {
+ ZipEntry zipEntry = new ZipEntry("classes/" + FOO + "/" + BAR + ".class");
+ zipOutputStream.putNextEntry(zipEntry);
+ zipOutputStream.write(VALUE);
+ zipOutputStream.write(VALUE * 2);
+ zipOutputStream.closeEntry();
+ } finally {
+ zipOutputStream.close();
+ }
+ ZipFile zipFile = new ZipFile(file);
+ try {
+ ClassFileLocator classFileLocator = new ClassFileLocator.ForModuleFile(zipFile);
+ ClassFileLocator.Resolution resolution = classFileLocator.locate(FOO + "." + BAR);
+ assertThat(resolution.isResolved(), is(true));
+ assertThat(resolution.resolve(), is(new byte[]{VALUE, VALUE * 2}));
+ } finally {
+ zipFile.close();
+ }
+ }
+
+ @Test
+ public void testZipFileClosable() throws Exception {
+ ZipFile zipFile = mock(ZipFile.class);
+ Closeable classFileLocator = new ClassFileLocator.ForModuleFile(zipFile);
+ classFileLocator.close();
+ verify(zipFile).close();
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(9)
+ public void testBootJar() throws Exception {
+ ClassFileLocator classFileLocator = ClassFileLocator.ForModuleFile.ofBootPath();
+ try {
+ assertThat(classFileLocator.locate(Object.class.getName()).isResolved(), is(true));
+ } finally {
+ classFileLocator.close();
+ }
+ }
+
+ @Test
+ public void testNonSuccessfulLocation() throws Exception {
+ ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(file));
+ try {
+ ZipEntry zipEntry = new ZipEntry("noop.class");
+ zipOutputStream.putNextEntry(zipEntry);
+ zipOutputStream.write(VALUE);
+ zipOutputStream.closeEntry();
+ } finally {
+ zipOutputStream.close();
+ }
+ ZipFile zipFile = new ZipFile(file);
+ try {
+ ClassFileLocator classFileLocator = new ClassFileLocator.ForModuleFile(zipFile);
+ ClassFileLocator.Resolution resolution = classFileLocator.locate(FOO + "." + BAR);
+ assertThat(resolution.isResolved(), is(false));
+ } finally {
+ zipFile.close();
+ }
+ }
+
+ @Test
+ public void testClose() throws Exception {
+ ZipFile zipFile = mock(ZipFile.class);
+ new ClassFileLocator.ForModuleFile(zipFile).close();
+ verify(zipFile).close();
+ verifyNoMoreInteractions(zipFile);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ClassFileLocator.ForModuleFile.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorForModuleTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorForModuleTest.java
new file mode 100644
index 0000000..df1cd21
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorForModuleTest.java
@@ -0,0 +1,96 @@
+package net.bytebuddy.dynamic;
+
+import net.bytebuddy.test.utility.JavaVersionRule;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.utility.JavaModule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.io.ByteArrayInputStream;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.*;
+
+public class ClassFileLocatorForModuleTest {
+
+ private static final String FOOBAR = "foo/bar";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ @Mock
+ private JavaModule module;
+
+ @Mock
+ private ClassLoader classLoader;
+
+ @Before
+ public void setUp() throws Exception {
+ when(module.isNamed()).thenReturn(true);
+ when(module.getClassLoader()).thenReturn(classLoader);
+ }
+
+ @Test
+ public void testCreationNamed() throws Exception {
+ when(module.isNamed()).thenReturn(true);
+ assertThat(ClassFileLocator.ForModule.of(module), is((ClassFileLocator) new ClassFileLocator.ForModule(module)));
+ }
+
+ @Test
+ public void testCreationUnnamed() throws Exception {
+ when(module.isNamed()).thenReturn(false);
+ assertThat(ClassFileLocator.ForModule.of(module), is((ClassFileLocator) new ClassFileLocator.ForClassLoader(classLoader)));
+ }
+
+ @Test
+ public void testLocatable() throws Exception {
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(new byte[]{1, 2, 3});
+ when(module.getResourceAsStream(FOOBAR + ".class")).thenReturn(inputStream);
+ ClassFileLocator.Resolution resolution = new ClassFileLocator.ForModule(module)
+ .locate(FOOBAR);
+ assertThat(resolution.isResolved(), is(true));
+ assertThat(resolution.resolve(), is(new byte[]{1, 2, 3}));
+ verify(module).getResourceAsStream(FOOBAR + ".class");
+ verifyNoMoreInteractions(module);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonLocatable() throws Exception {
+ ClassFileLocator.Resolution resolution = new ClassFileLocator.ForModule(module)
+ .locate(FOOBAR);
+ assertThat(resolution.isResolved(), is(false));
+ verify(module).getResourceAsStream(FOOBAR + ".class");
+ verifyNoMoreInteractions(module);
+ resolution.resolve();
+ fail();
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(9)
+ public void testBootPath() throws Exception {
+ ClassFileLocator classFileLocator = ClassFileLocator.ForModule.ofBootLayer();
+ assertThat(classFileLocator.locate(Object.class.getName()).isResolved(), is(true));
+ assertThat(classFileLocator.locate(getClass().getName()).isResolved(), is(false));
+ }
+
+ @Test
+ public void testClose() throws Exception {
+ new ClassFileLocator.ForModule(module).close();
+ verifyZeroInteractions(module);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ClassFileLocator.ForModule.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorForModuleWeaklyReferencedTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorForModuleWeaklyReferencedTest.java
new file mode 100644
index 0000000..0d81250
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorForModuleWeaklyReferencedTest.java
@@ -0,0 +1,115 @@
+package net.bytebuddy.dynamic;
+
+import net.bytebuddy.test.utility.JavaVersionRule;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.utility.JavaModule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+public class ClassFileLocatorForModuleWeaklyReferencedTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ @Mock
+ private JavaModule module;
+
+ @Mock
+ private ClassLoader classLoader;
+
+ @Mock
+ private Object unwrapped;
+
+ @Before
+ public void setUp() throws Exception {
+ when(module.isNamed()).thenReturn(true);
+ when(module.getClassLoader()).thenReturn(classLoader);
+ when(module.unwrap()).thenReturn(unwrapped);
+ }
+
+ @Test
+ public void testCreationNamed() throws Exception {
+ when(module.isNamed()).thenReturn(true);
+ assertThat(ClassFileLocator.ForModule.WeaklyReferenced.of(module), is((ClassFileLocator) new ClassFileLocator.ForModule.WeaklyReferenced(unwrapped)));
+ }
+
+ @Test
+ public void testCreationUnnamed() throws Exception {
+ when(module.isNamed()).thenReturn(false);
+ assertThat(ClassFileLocator.ForModule.WeaklyReferenced.of(module), is((ClassFileLocator) new ClassFileLocator.ForClassLoader.WeaklyReferenced(classLoader)));
+ }
+
+ @Test
+ public void testCreationNamedSystem() throws Exception {
+ when(module.isNamed()).thenReturn(true);
+ when(module.getClassLoader()).thenReturn(ClassLoader.getSystemClassLoader());
+ assertThat(ClassFileLocator.ForModule.WeaklyReferenced.of(module), is((ClassFileLocator) new ClassFileLocator.ForModule(module)));
+ }
+
+ @Test
+ public void testCreationUnnamedSystem() throws Exception {
+ when(module.isNamed()).thenReturn(false);
+ when(module.getClassLoader()).thenReturn(ClassLoader.getSystemClassLoader());
+ assertThat(ClassFileLocator.ForModule.of(module), is((ClassFileLocator) new ClassFileLocator.ForClassLoader(ClassLoader.getSystemClassLoader())));
+ }
+
+ @Test
+ public void testCreationNamedPlatform() throws Exception {
+ when(module.isNamed()).thenReturn(true);
+ when(module.getClassLoader()).thenReturn(ClassLoader.getSystemClassLoader().getParent());
+ assertThat(ClassFileLocator.ForModule.WeaklyReferenced.of(module), is((ClassFileLocator) new ClassFileLocator.ForModule(module)));
+ }
+
+ @Test
+ public void testCreationUnnamedPlatform() throws Exception {
+ when(module.isNamed()).thenReturn(false);
+ when(module.getClassLoader()).thenReturn(ClassLoader.getSystemClassLoader().getParent());
+ assertThat(ClassFileLocator.ForModule.of(module), is((ClassFileLocator) new ClassFileLocator.ForClassLoader(ClassLoader.getSystemClassLoader().getParent())));
+ }
+
+ @Test
+ public void testCreationNamedBoot() throws Exception {
+ when(module.isNamed()).thenReturn(true);
+ when(module.getClassLoader()).thenReturn(null);
+ assertThat(ClassFileLocator.ForModule.WeaklyReferenced.of(module), is((ClassFileLocator) new ClassFileLocator.ForModule(module)));
+ }
+
+ @Test
+ public void testCreationUnnamedBoot() throws Exception {
+ when(module.isNamed()).thenReturn(false);
+ when(module.getClassLoader()).thenReturn(null);
+ assertThat(ClassFileLocator.ForModule.of(module), is((ClassFileLocator) new ClassFileLocator.ForClassLoader(ClassLoader.getSystemClassLoader())));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(9)
+ public void testLocateModules() throws Exception {
+ ClassFileLocator classFileLocator = new ClassFileLocator.ForModule.WeaklyReferenced(JavaModule.ofType(Object.class).unwrap());
+ assertThat(classFileLocator.locate(Object.class.getName()).isResolved(), is(true));
+ assertThat(classFileLocator.locate(getClass().getName()).isResolved(), is(false));
+ }
+
+ @Test
+ public void testClose() throws Exception {
+ new ClassFileLocator.ForModule.WeaklyReferenced(module).close();
+ verifyZeroInteractions(module);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ClassFileLocator.ForModule.WeaklyReferenced.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorNoOpTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorNoOpTest.java
new file mode 100644
index 0000000..98fcf63
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorNoOpTest.java
@@ -0,0 +1,27 @@
+package net.bytebuddy.dynamic;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class ClassFileLocatorNoOpTest {
+
+ private static final String FOO = "foo";
+
+ @Test
+ public void testLocation() throws Exception {
+ assertThat(ClassFileLocator.NoOp.INSTANCE.locate(FOO).isResolved(), is(false));
+ }
+
+ @Test
+ public void testClose() throws Exception {
+ ClassFileLocator.NoOp.INSTANCE.close();
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ClassFileLocator.NoOp.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorPackageDiscriminatingTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorPackageDiscriminatingTest.java
new file mode 100644
index 0000000..ddd5d17
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorPackageDiscriminatingTest.java
@@ -0,0 +1,72 @@
+package net.bytebuddy.dynamic;
+
+import net.bytebuddy.description.NamedElement;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class ClassFileLocatorPackageDiscriminatingTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ private ClassFileLocator classFileLocator;
+
+ @Mock
+ private ClassFileLocator foo, bar;
+
+ @Mock
+ private ClassFileLocator.Resolution fooResulution, barResolution;
+
+ @Before
+ public void setUp() throws Exception {
+ when(foo.locate(FOO + "." + BAR)).thenReturn(fooResulution);
+ when(bar.locate(BAR)).thenReturn(barResolution);
+ Map<String, ClassFileLocator> map = new HashMap<String, ClassFileLocator>();
+ map.put(FOO, foo);
+ map.put(NamedElement.EMPTY_NAME, bar);
+ classFileLocator = new ClassFileLocator.PackageDiscriminating(map);
+ }
+
+ @Test
+ public void testValidLocation() throws Exception {
+ assertThat(classFileLocator.locate(FOO + "." + BAR), is(fooResulution));
+ }
+
+ @Test
+ public void testValidLocationDefaultPackage() throws Exception {
+ assertThat(classFileLocator.locate(BAR), is(barResolution));
+ }
+
+ @Test
+ public void testInvalidLocation() throws Exception {
+ assertThat(classFileLocator.locate(BAR + "." + FOO).isResolved(), is(false));
+ }
+
+ @Test
+ public void testClose() throws Exception {
+ classFileLocator.close();
+ verify(foo).close();
+ verifyNoMoreInteractions(foo);
+ verify(bar).close();
+ verifyNoMoreInteractions(bar);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ClassFileLocator.PackageDiscriminating.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorResolutionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorResolutionTest.java
new file mode 100644
index 0000000..999b3e2
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorResolutionTest.java
@@ -0,0 +1,41 @@
+package net.bytebuddy.dynamic;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.hamcrest.MatcherAssert;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class ClassFileLocatorResolutionTest {
+
+ private static final String FOO = "foo";
+
+ private static final byte[] DATA = new byte[]{1, 2, 3};
+
+ @Test
+ public void testIllegal() throws Exception {
+ MatcherAssert.assertThat(new ClassFileLocator.Resolution.Illegal(FOO).isResolved(), is(false));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIllegalThrowsException() throws Exception {
+ new ClassFileLocator.Resolution.Illegal(FOO).resolve();
+ }
+
+ @Test
+ public void testExplicit() throws Exception {
+ assertThat(new ClassFileLocator.Resolution.Explicit(DATA).isResolved(), is(true));
+ }
+
+ @Test
+ public void testExplicitGetData() throws Exception {
+ assertThat(new ClassFileLocator.Resolution.Explicit(DATA).resolve(), is(DATA));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ClassFileLocator.Resolution.Explicit.class).apply();
+ ObjectPropertyAssertion.of(ClassFileLocator.Resolution.Illegal.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorSimpleTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorSimpleTest.java
new file mode 100644
index 0000000..ba4a6e2
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/ClassFileLocatorSimpleTest.java
@@ -0,0 +1,54 @@
+package net.bytebuddy.dynamic;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ClassFileLocatorSimpleTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ private static final byte[] QUX = new byte[]{1, 2, 3};
+
+ @Test
+ public void testSuccessfulLocation() throws Exception {
+ ClassFileLocator.Resolution resolution = ClassFileLocator.Simple.of(FOO, QUX).locate(FOO);
+ assertThat(resolution.isResolved(), is(true));
+ assertThat(resolution.resolve(), is(QUX));
+ }
+
+ @Test
+ public void testInSuccessfulLocation() throws Exception {
+ ClassFileLocator.Resolution resolution = ClassFileLocator.Simple.of(FOO, QUX).locate(BAR);
+ assertThat(resolution.isResolved(), is(false));
+ }
+
+ @Test
+ public void testClose() throws Exception {
+ ClassFileLocator.Simple.of(FOO, QUX).close();
+ }
+
+ @Test
+ public void testDynamicType() throws Exception {
+ DynamicType dynamicType = mock(DynamicType.class);
+ TypeDescription typeDescription = mock(TypeDescription.class);
+ when(typeDescription.getName()).thenReturn(FOO);
+ when(dynamicType.getAllTypes()).thenReturn(Collections.singletonMap(typeDescription, QUX));
+ ClassFileLocator classFileLocator = ClassFileLocator.Simple.of(dynamicType);
+ assertThat(classFileLocator.locate(FOO).isResolved(), is(true));
+ assertThat(classFileLocator.locate(FOO).resolve(), is(QUX));
+ assertThat(classFileLocator.locate(BAR).isResolved(), is(false));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ClassFileLocator.Simple.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/DynamicTypeBuilderObjectPropertiesTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/DynamicTypeBuilderObjectPropertiesTest.java
new file mode 100644
index 0000000..e9c12db
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/DynamicTypeBuilderObjectPropertiesTest.java
@@ -0,0 +1,22 @@
+package net.bytebuddy.dynamic;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+public class DynamicTypeBuilderObjectPropertiesTest {
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(DynamicType.Builder.AbstractBase.Adapter.TypeVariableDefinitionAdapter.class).apply();
+ ObjectPropertyAssertion.of(DynamicType.Builder.AbstractBase.Adapter.FieldDefinitionAdapter.class).apply();
+ ObjectPropertyAssertion.of(DynamicType.Builder.AbstractBase.Adapter.FieldMatchAdapter.class).apply();
+ ObjectPropertyAssertion.of(DynamicType.Builder.AbstractBase.Adapter.MethodDefinitionAdapter.class).apply();
+ ObjectPropertyAssertion.of(DynamicType.Builder.AbstractBase.Adapter.MethodDefinitionAdapter.TypeVariableAnnotationAdapter.class).apply();
+ ObjectPropertyAssertion.of(DynamicType.Builder.AbstractBase.Adapter.MethodDefinitionAdapter.AnnotationAdapter.class).apply();
+ ObjectPropertyAssertion.of(DynamicType.Builder.AbstractBase.Adapter.MethodDefinitionAdapter.ParameterAnnotationAdapter.class).apply();
+ ObjectPropertyAssertion.of(DynamicType.Builder.AbstractBase.Adapter.MethodDefinitionAdapter.SimpleParameterAnnotationAdapter.class).apply();
+ ObjectPropertyAssertion.of(DynamicType.Builder.AbstractBase.Adapter.MethodMatchAdapter.class).apply();
+ ObjectPropertyAssertion.of(DynamicType.Builder.AbstractBase.Adapter.MethodMatchAdapter.AnnotationAdapter.class).apply();
+ ObjectPropertyAssertion.of(DynamicType.Builder.AbstractBase.Adapter.OptionalMethodMatchAdapter.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/DynamicTypeDefaultLoadedTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/DynamicTypeDefaultLoadedTest.java
new file mode 100644
index 0000000..8cf0cb6
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/DynamicTypeDefaultLoadedTest.java
@@ -0,0 +1,66 @@
+package net.bytebuddy.dynamic;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.LoadedTypeInitializer;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.hamcrest.CoreMatchers;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class DynamicTypeDefaultLoadedTest {
+
+ private static final Class<?> MAIN_TYPE = Void.class, AUXILIARY_TYPE = Object.class;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private LoadedTypeInitializer mainLoadedTypeInitializer, auxiliaryLoadedTypeInitializer;
+
+ @Mock
+ private TypeDescription mainTypeDescription, auxiliaryTypeDescription;
+
+ private DynamicType.Loaded<?> dynamicType;
+
+ @Before
+ public void setUp() throws Exception {
+ Map<TypeDescription, Class<?>> loadedTypes = new HashMap<TypeDescription, Class<?>>();
+ loadedTypes.put(mainTypeDescription, MAIN_TYPE);
+ loadedTypes.put(auxiliaryTypeDescription, AUXILIARY_TYPE);
+ DynamicType auxiliaryType = mock(DynamicType.class);
+ dynamicType = new DynamicType.Default.Loaded<Object>(mainTypeDescription,
+ new byte[0],
+ mainLoadedTypeInitializer,
+ Collections.singletonList(auxiliaryType),
+ loadedTypes);
+ when(auxiliaryType.getTypeDescription()).thenReturn(mainTypeDescription);
+ }
+
+ @Test
+ public void testLoadedTypeDescription() throws Exception {
+ assertThat(dynamicType.getLoaded(), CoreMatchers.<Class<?>>is(MAIN_TYPE));
+ assertThat(dynamicType.getTypeDescription(), is(mainTypeDescription));
+ assertThat(dynamicType.getLoadedAuxiliaryTypes().size(), is(1));
+ assertThat(dynamicType.getLoadedAuxiliaryTypes().keySet(), hasItem(auxiliaryTypeDescription));
+ assertThat(dynamicType.getLoadedAuxiliaryTypes().get(auxiliaryTypeDescription), CoreMatchers.<Class<?>>is(AUXILIARY_TYPE));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(DynamicType.Default.Loaded.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/DynamicTypeDefaultTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/DynamicTypeDefaultTest.java
new file mode 100644
index 0000000..66e2712
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/DynamicTypeDefaultTest.java
@@ -0,0 +1,296 @@
+package net.bytebuddy.dynamic;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.LoadedTypeInitializer;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.utility.RandomString;
+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.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.*;
+import java.util.jar.*;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class DynamicTypeDefaultTest {
+
+ private static final String CLASS_FILE_EXTENSION = ".class";
+
+ private static final String FOOBAR = "foo/bar", QUXBAZ = "qux/baz", BARBAZ = "bar/baz", FOO = "foo", BAR = "bar", TEMP = "tmp";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ private static final byte[] BINARY_FIRST = new byte[]{1, 2, 3}, BINARY_SECOND = new byte[]{4, 5, 6}, BINARY_THIRD = new byte[]{7, 8, 9};
+
+ @Mock
+ private LoadedTypeInitializer mainLoadedTypeInitializer, auxiliaryLoadedTypeInitializer;
+
+ @Mock
+ private DynamicType auxiliaryType;
+
+ @Mock
+ private TypeDescription typeDescription, auxiliaryTypeDescription;
+
+ private DynamicType dynamicType;
+
+ private static void assertFile(File file, byte[] binaryRepresentation) throws IOException {
+ FileInputStream fileInputStream = new FileInputStream(file);
+ try {
+ byte[] buffer = new byte[binaryRepresentation.length + 1];
+ assertThat(fileInputStream.read(buffer), is(binaryRepresentation.length));
+ int index = 0;
+ for (byte b : binaryRepresentation) {
+ assertThat(buffer[index++], is(b));
+ }
+ assertThat(buffer[index], is((byte) 0));
+ } finally {
+ fileInputStream.close();
+ }
+ assertThat(file.delete(), is(true));
+ }
+
+ private static File makeTemporaryFolder() throws IOException {
+ File file = File.createTempFile(TEMP, TEMP);
+ try {
+ File folder = new File(file.getParentFile(), TEMP + RandomString.make());
+ assertThat(folder.mkdir(), is(true));
+ return folder;
+ } finally {
+ assertThat(file.delete(), is(true));
+ }
+ }
+
+ private static void assertJarFile(File file, Manifest manifest, Map<String, byte[]> expectedEntries) throws IOException {
+ JarInputStream jarInputStream = new JarInputStream(new FileInputStream(file));
+ try {
+ assertThat(jarInputStream.getManifest(), is(manifest));
+ JarEntry jarEntry;
+ while ((jarEntry = jarInputStream.getNextJarEntry()) != null) {
+ byte[] binary = expectedEntries.remove(jarEntry.getName());
+ assertThat(binary, notNullValue());
+ byte[] buffer = new byte[binary.length];
+ assertThat(jarInputStream.read(buffer), is(buffer.length));
+ assertThat(Arrays.equals(buffer, binary), is(true));
+ assertThat(jarInputStream.read(buffer), is(-1));
+ jarInputStream.closeEntry();
+ }
+ assertThat(expectedEntries.size(), is(0));
+ } finally {
+ jarInputStream.close();
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ dynamicType = new DynamicType.Default(typeDescription,
+ BINARY_FIRST,
+ mainLoadedTypeInitializer,
+ Collections.singletonList(auxiliaryType));
+ when(typeDescription.getName()).thenReturn(FOOBAR.replace('/', '.'));
+ when(typeDescription.getInternalName()).thenReturn(FOOBAR);
+ when(auxiliaryType.saveIn(any(File.class))).thenReturn(Collections.<TypeDescription, File>emptyMap());
+ when(auxiliaryTypeDescription.getName()).thenReturn(QUXBAZ.replace('/', '.'));
+ when(auxiliaryTypeDescription.getInternalName()).thenReturn(QUXBAZ);
+ when(auxiliaryType.getTypeDescription()).thenReturn(auxiliaryTypeDescription);
+ when(auxiliaryType.getBytes()).thenReturn(BINARY_SECOND);
+ when(auxiliaryType.getLoadedTypeInitializers()).thenReturn(Collections.singletonMap(auxiliaryTypeDescription, auxiliaryLoadedTypeInitializer));
+ when(auxiliaryType.getAuxiliaryTypes()).thenReturn(Collections.<TypeDescription, byte[]>emptyMap());
+ when(auxiliaryType.getAllTypes()).thenReturn(Collections.singletonMap(auxiliaryTypeDescription, BINARY_SECOND));
+ }
+
+ @Test
+ public void testByteArray() throws Exception {
+ assertThat(dynamicType.getBytes(), is(BINARY_FIRST));
+ }
+
+ @Test
+ public void testTypeDescription() throws Exception {
+ assertThat(dynamicType.getTypeDescription(), is(typeDescription));
+ }
+
+ @Test
+ public void testRawAuxiliaryTypes() throws Exception {
+ assertThat(dynamicType.getAuxiliaryTypes().size(), is(1));
+ assertThat(dynamicType.getAuxiliaryTypes().get(auxiliaryTypeDescription), is(BINARY_SECOND));
+ }
+
+ @Test
+ public void testTypeInitializersNotAlive() throws Exception {
+ assertThat(dynamicType.hasAliveLoadedTypeInitializers(), is(false));
+ }
+
+ @Test
+ public void testTypeInitializersAliveMain() throws Exception {
+ when(mainLoadedTypeInitializer.isAlive()).thenReturn(true);
+ assertThat(dynamicType.hasAliveLoadedTypeInitializers(), is(true));
+ }
+
+ @Test
+ public void testTypeInitializersAliveAuxiliary() throws Exception {
+ when(auxiliaryLoadedTypeInitializer.isAlive()).thenReturn(true);
+ assertThat(dynamicType.hasAliveLoadedTypeInitializers(), is(true));
+ }
+
+ @Test
+ public void testTypeInitializers() throws Exception {
+ assertThat(dynamicType.getLoadedTypeInitializers().size(), is(2));
+ assertThat(dynamicType.getLoadedTypeInitializers().get(typeDescription), is(mainLoadedTypeInitializer));
+ assertThat(dynamicType.getLoadedTypeInitializers().get(auxiliaryTypeDescription), is(auxiliaryLoadedTypeInitializer));
+ }
+
+ @Test
+ public void testFileSaving() throws Exception {
+ File folder = makeTemporaryFolder();
+ boolean folderDeletion, fileDeletion;
+ try {
+ Map<TypeDescription, File> files = dynamicType.saveIn(folder);
+ assertThat(files.size(), is(1));
+ assertFile(files.get(typeDescription), BINARY_FIRST);
+ } finally {
+ folderDeletion = new File(folder, FOO).delete();
+ fileDeletion = folder.delete();
+ }
+ assertThat(folderDeletion, is(true));
+ assertThat(fileDeletion, is(true));
+ verify(auxiliaryType).saveIn(folder);
+ }
+
+ @Test
+ public void testJarCreation() throws Exception {
+ File file = File.createTempFile(FOO, TEMP);
+ assertThat(file.delete(), is(true));
+ boolean fileDeletion;
+ try {
+ assertThat(dynamicType.toJar(file), is(file));
+ assertThat(file.exists(), is(true));
+ assertThat(file.isFile(), is(true));
+ assertThat(file.length() > 0L, is(true));
+ Map<String, byte[]> bytes = new HashMap<String, byte[]>();
+ bytes.put(FOOBAR + CLASS_FILE_EXTENSION, BINARY_FIRST);
+ bytes.put(QUXBAZ + CLASS_FILE_EXTENSION, BINARY_SECOND);
+ Manifest manifest = new Manifest();
+ manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
+ assertJarFile(file, manifest, bytes);
+ } finally {
+ fileDeletion = file.delete();
+ }
+ assertThat(fileDeletion, is(true));
+ }
+
+ @Test
+ public void testJarWithExplicitManifestCreation() throws Exception {
+ File file = File.createTempFile(FOO, TEMP);
+ assertThat(file.delete(), is(true));
+ boolean fileDeletion;
+ try {
+ Manifest manifest = new Manifest();
+ manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, BAR);
+ assertThat(dynamicType.toJar(file, manifest), is(file));
+ assertThat(file.exists(), is(true));
+ assertThat(file.isFile(), is(true));
+ assertThat(file.length() > 0L, is(true));
+ Map<String, byte[]> bytes = new HashMap<String, byte[]>();
+ bytes.put(FOOBAR + CLASS_FILE_EXTENSION, BINARY_FIRST);
+ bytes.put(QUXBAZ + CLASS_FILE_EXTENSION, BINARY_SECOND);
+ assertJarFile(file, manifest, bytes);
+ } finally {
+ fileDeletion = file.delete();
+ }
+ assertThat(fileDeletion, is(true));
+ }
+
+ @Test
+ public void testJarTargetInjection() throws Exception {
+ File sourceFile = File.createTempFile(BAR, TEMP);
+ Manifest manifest = new Manifest();
+ manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, BAR);
+ JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(sourceFile), manifest);
+ try {
+ jarOutputStream.putNextEntry(new JarEntry(BARBAZ + CLASS_FILE_EXTENSION));
+ jarOutputStream.write(BINARY_THIRD);
+ jarOutputStream.closeEntry();
+ jarOutputStream.putNextEntry(new JarEntry(FOOBAR + CLASS_FILE_EXTENSION));
+ jarOutputStream.write(BINARY_THIRD);
+ jarOutputStream.closeEntry();
+ } finally {
+ jarOutputStream.close();
+ }
+ File file = File.createTempFile(FOO, TEMP);
+ assertThat(file.delete(), is(true));
+ boolean fileDeletion;
+ try {
+ assertThat(dynamicType.inject(sourceFile, file), is(file));
+ assertThat(file.exists(), is(true));
+ assertThat(file.isFile(), is(true));
+ assertThat(file.length() > 0L, is(true));
+ Map<String, byte[]> bytes = new HashMap<String, byte[]>();
+ bytes.put(FOOBAR + CLASS_FILE_EXTENSION, BINARY_FIRST);
+ bytes.put(QUXBAZ + CLASS_FILE_EXTENSION, BINARY_SECOND);
+ bytes.put(BARBAZ + CLASS_FILE_EXTENSION, BINARY_THIRD);
+ assertJarFile(file, manifest, bytes);
+ } finally {
+ fileDeletion = file.delete() & sourceFile.delete();
+ }
+ assertThat(fileDeletion, is(true));
+ }
+
+ @Test
+ public void testJarSelfInjection() throws Exception {
+ File file = File.createTempFile(BAR, TEMP);
+ Manifest manifest = new Manifest();
+ manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, BAR);
+ JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(file), manifest);
+ try {
+ jarOutputStream.putNextEntry(new JarEntry(BARBAZ + CLASS_FILE_EXTENSION));
+ jarOutputStream.write(BINARY_THIRD);
+ jarOutputStream.closeEntry();
+ jarOutputStream.putNextEntry(new JarEntry(FOOBAR + CLASS_FILE_EXTENSION));
+ jarOutputStream.write(BINARY_THIRD);
+ jarOutputStream.closeEntry();
+ } finally {
+ jarOutputStream.close();
+ }
+ boolean fileDeletion;
+ try {
+ assertThat(dynamicType.inject(file), is(file));
+ assertThat(file.exists(), is(true));
+ assertThat(file.isFile(), is(true));
+ assertThat(file.length() > 0L, is(true));
+ Map<String, byte[]> bytes = new HashMap<String, byte[]>();
+ bytes.put(FOOBAR + CLASS_FILE_EXTENSION, BINARY_FIRST);
+ bytes.put(QUXBAZ + CLASS_FILE_EXTENSION, BINARY_SECOND);
+ bytes.put(BARBAZ + CLASS_FILE_EXTENSION, BINARY_THIRD);
+ assertJarFile(file, manifest, bytes);
+ } finally {
+ fileDeletion = file.delete();
+ }
+ assertThat(fileDeletion, is(true));
+ }
+
+ @Test
+ public void testIterationOrder() throws Exception {
+ Iterator<TypeDescription> types = dynamicType.getAllTypes().keySet().iterator();
+ assertThat(types.hasNext(), is(true));
+ assertThat(types.next(), is(typeDescription));
+ assertThat(types.hasNext(), is(true));
+ assertThat(types.next(), is(auxiliaryTypeDescription));
+ assertThat(types.hasNext(), is(false));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(DynamicType.Default.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/DynamicTypeDefaultUnloadedTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/DynamicTypeDefaultUnloadedTest.java
new file mode 100644
index 0000000..19fcc20
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/DynamicTypeDefaultUnloadedTest.java
@@ -0,0 +1,107 @@
+package net.bytebuddy.dynamic;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.LoadedTypeInitializer;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.hamcrest.CoreMatchers;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class DynamicTypeDefaultUnloadedTest {
+
+ private static final Class<?> MAIN_TYPE = Void.class, AUXILIARY_TYPE = Object.class;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private LoadedTypeInitializer mainLoadedTypeInitializer, auxiliaryLoadedTypeInitializer;
+
+ @Mock
+ private DynamicType auxiliaryType;
+
+ @Mock
+ private ClassLoader classLoader;
+
+ @Mock
+ private ClassLoadingStrategy<ClassLoader> classLoadingStrategy;
+
+ @Mock
+ private TypeResolutionStrategy.Resolved typeResolutionStrategy;
+
+ @Mock
+ private TypeDescription typeDescription, auxiliaryTypeDescription;
+
+ private byte[] binaryRepresentation, auxiliaryTypeByte;
+
+ private DynamicType.Unloaded<?> unloaded;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ binaryRepresentation = new byte[]{0, 1, 2};
+ auxiliaryTypeByte = new byte[]{4, 5, 6};
+ unloaded = new DynamicType.Default.Unloaded<Object>(typeDescription,
+ binaryRepresentation,
+ mainLoadedTypeInitializer,
+ Collections.singletonList(auxiliaryType),
+ typeResolutionStrategy);
+ Map<TypeDescription, Class<?>> loadedTypes = new HashMap<TypeDescription, Class<?>>();
+ loadedTypes.put(typeDescription, MAIN_TYPE);
+ loadedTypes.put(auxiliaryTypeDescription, AUXILIARY_TYPE);
+ when(auxiliaryType.getTypeDescription()).thenReturn(auxiliaryTypeDescription);
+ when(auxiliaryType.getBytes()).thenReturn(auxiliaryTypeByte);
+ when(auxiliaryType.getLoadedTypeInitializers()).thenReturn(Collections.singletonMap(auxiliaryTypeDescription, auxiliaryLoadedTypeInitializer));
+ when(auxiliaryType.getAuxiliaryTypes()).thenReturn(Collections.<TypeDescription, byte[]>emptyMap());
+ when(typeResolutionStrategy.initialize(unloaded, classLoader, classLoadingStrategy)).thenReturn(loadedTypes);
+ when(typeDescription.getName()).thenReturn(MAIN_TYPE.getName());
+ when(auxiliaryTypeDescription.getName()).thenReturn(AUXILIARY_TYPE.getName());
+ }
+
+ @Test
+ public void testQueries() throws Exception {
+ DynamicType.Loaded<?> loaded = unloaded.load(classLoader, classLoadingStrategy);
+ assertThat(loaded.getTypeDescription(), is(typeDescription));
+ assertThat(loaded.getBytes(), is(binaryRepresentation));
+ assertThat(loaded.getAuxiliaryTypes(), is(Collections.singletonMap(auxiliaryTypeDescription, auxiliaryTypeByte)));
+ }
+
+ @Test
+ public void testTypeLoading() throws Exception {
+ DynamicType.Loaded<?> loaded = unloaded.load(classLoader, classLoadingStrategy);
+ assertThat(loaded.getLoaded(), CoreMatchers.<Class<?>>is(MAIN_TYPE));
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(1));
+ assertThat(loaded.getLoadedAuxiliaryTypes().get(auxiliaryTypeDescription), CoreMatchers.<Class<?>>is(AUXILIARY_TYPE));
+ verify(typeResolutionStrategy).initialize(unloaded, classLoader, classLoadingStrategy);
+ verifyNoMoreInteractions(typeResolutionStrategy);
+ }
+
+ @Test
+ public void testTypeInclusion() throws Exception {
+ DynamicType additionalType = mock(DynamicType.class);
+ TypeDescription additionalTypeDescription = mock(TypeDescription.class);
+ when(additionalType.getTypeDescription()).thenReturn(additionalTypeDescription);
+ DynamicType.Unloaded<?> dynamicType = unloaded.include(additionalType);
+ assertThat(dynamicType.getAuxiliaryTypes().size(), is(2));
+ assertThat(dynamicType.getAuxiliaryTypes().containsKey(additionalTypeDescription), is(true));
+ assertThat(dynamicType.getAuxiliaryTypes().containsKey(auxiliaryTypeDescription), is(true));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(DynamicType.Default.Unloaded.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/NexusTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/NexusTest.java
new file mode 100644
index 0000000..ddf35ab
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/NexusTest.java
@@ -0,0 +1,231 @@
+package net.bytebuddy.dynamic;
+
+import net.bytebuddy.dynamic.loading.ByteArrayClassLoader;
+import net.bytebuddy.dynamic.loading.PackageDefinitionStrategy;
+import net.bytebuddy.implementation.LoadedTypeInitializer;
+import net.bytebuddy.test.utility.ClassFileExtraction;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Map;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class NexusTest {
+
+ private static final String FOO = "foo";
+
+ private static final int BAR = 42;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private LoadedTypeInitializer loadedTypeInitializer;
+
+ @Mock
+ private ClassLoader classLoader;
+
+ @Test
+ public void testNexusIsPublic() throws Exception {
+ assertThat(Modifier.isPublic(Nexus.class.getModifiers()), is(true));
+ }
+
+ @Test
+ public void testNexusHasNoDeclaringType() throws Exception {
+ assertThat(Nexus.class.getDeclaringClass(), nullValue(Class.class));
+ }
+
+ @Test
+ public void testNexusHasNoDeclaredTypes() throws Exception {
+ assertThat(Nexus.class.getDeclaredClasses().length, is(0));
+ }
+
+ @Test
+ public void testNexusAccessorNonActive() throws Exception {
+ ClassLoader classLoader = new ByteArrayClassLoader.ChildFirst(getClass().getClassLoader(),
+ ClassFileExtraction.of(Nexus.class,
+ NexusAccessor.class,
+ NexusAccessor.Dispatcher.class,
+ NexusAccessor.Dispatcher.CreationAction.class,
+ NexusAccessor.Dispatcher.Available.class,
+ NexusAccessor.Dispatcher.Unavailable.class),
+ null,
+ ByteArrayClassLoader.PersistenceHandler.MANIFEST,
+ PackageDefinitionStrategy.NoOp.INSTANCE);
+ Field duplicateInitializers = classLoader.loadClass(Nexus.class.getName()).getDeclaredField("TYPE_INITIALIZERS");
+ duplicateInitializers.setAccessible(true);
+ assertThat(((Map<?, ?>) duplicateInitializers.get(null)).size(), is(0));
+ Field actualInitializers = Nexus.class.getDeclaredField("TYPE_INITIALIZERS");
+ actualInitializers.setAccessible(true);
+ assertThat(((Map<?, ?>) actualInitializers.get(null)).size(), is(0));
+ Class<?> accessor = classLoader.loadClass(NexusAccessor.class.getName());
+ ClassLoader qux = mock(ClassLoader.class);
+ when(loadedTypeInitializer.isAlive()).thenReturn(false);
+ assertThat(accessor
+ .getDeclaredMethod("register", String.class, ClassLoader.class, int.class, LoadedTypeInitializer.class)
+ .invoke(accessor.getDeclaredConstructor().newInstance(), FOO, qux, BAR, loadedTypeInitializer), nullValue(Object.class));
+ try {
+ assertThat(((Map<?, ?>) duplicateInitializers.get(null)).size(), is(0));
+ assertThat(((Map<?, ?>) actualInitializers.get(null)).size(), is(0));
+ } finally {
+ Constructor<Nexus> constructor = Nexus.class.getDeclaredConstructor(String.class, ClassLoader.class, ReferenceQueue.class, int.class);
+ constructor.setAccessible(true);
+ Object value = ((Map<?, ?>) actualInitializers.get(null)).remove(constructor.newInstance(FOO, qux, Nexus.NO_QUEUE, BAR));
+ assertThat(value, nullValue());
+ }
+ }
+
+ @Test
+ public void testNexusAccessorClassLoaderBoundary() throws Exception {
+ ClassLoader classLoader = new ByteArrayClassLoader.ChildFirst(getClass().getClassLoader(),
+ ClassFileExtraction.of(Nexus.class,
+ NexusAccessor.class,
+ NexusAccessor.Dispatcher.class,
+ NexusAccessor.Dispatcher.CreationAction.class,
+ NexusAccessor.Dispatcher.Available.class,
+ NexusAccessor.Dispatcher.Unavailable.class),
+ null,
+ ByteArrayClassLoader.PersistenceHandler.MANIFEST,
+ PackageDefinitionStrategy.NoOp.INSTANCE);
+ Field duplicateInitializers = classLoader.loadClass(Nexus.class.getName()).getDeclaredField("TYPE_INITIALIZERS");
+ duplicateInitializers.setAccessible(true);
+ assertThat(((Map<?, ?>) duplicateInitializers.get(null)).size(), is(0));
+ Field actualInitializers = Nexus.class.getDeclaredField("TYPE_INITIALIZERS");
+ actualInitializers.setAccessible(true);
+ assertThat(((Map<?, ?>) actualInitializers.get(null)).size(), is(0));
+ Class<?> accessor = classLoader.loadClass(NexusAccessor.class.getName());
+ ClassLoader qux = mock(ClassLoader.class);
+ when(loadedTypeInitializer.isAlive()).thenReturn(true);
+ assertThat(accessor
+ .getDeclaredMethod("register", String.class, ClassLoader.class, int.class, LoadedTypeInitializer.class)
+ .invoke(accessor.getDeclaredConstructor().newInstance(), FOO, qux, BAR, loadedTypeInitializer), nullValue(Object.class));
+ try {
+ assertThat(((Map<?, ?>) duplicateInitializers.get(null)).size(), is(0));
+ assertThat(((Map<?, ?>) actualInitializers.get(null)).size(), is(1));
+ } finally {
+ Constructor<Nexus> constructor = Nexus.class.getDeclaredConstructor(String.class, ClassLoader.class, ReferenceQueue.class, int.class);
+ constructor.setAccessible(true);
+ Object value = ((Map<?, ?>) actualInitializers.get(null)).remove(constructor.newInstance(FOO, qux, Nexus.NO_QUEUE, BAR));
+ assertThat(value, is((Object) loadedTypeInitializer));
+ }
+ }
+
+ @Test
+ public void testNexusAccessorClassLoaderNoResource() throws Exception {
+ ClassLoader classLoader = new ByteArrayClassLoader.ChildFirst(getClass().getClassLoader(),
+ ClassFileExtraction.of(Nexus.class,
+ NexusAccessor.class,
+ NexusAccessor.Dispatcher.class,
+ NexusAccessor.Dispatcher.CreationAction.class,
+ NexusAccessor.Dispatcher.Available.class,
+ NexusAccessor.Dispatcher.Unavailable.class),
+ null,
+ ByteArrayClassLoader.PersistenceHandler.LATENT,
+ PackageDefinitionStrategy.NoOp.INSTANCE);
+ Field duplicateInitializers = classLoader.loadClass(Nexus.class.getName()).getDeclaredField("TYPE_INITIALIZERS");
+ duplicateInitializers.setAccessible(true);
+ assertThat(((Map<?, ?>) duplicateInitializers.get(null)).size(), is(0));
+ Field actualInitializers = Nexus.class.getDeclaredField("TYPE_INITIALIZERS");
+ actualInitializers.setAccessible(true);
+ assertThat(((Map<?, ?>) actualInitializers.get(null)).size(), is(0));
+ Class<?> accessor = classLoader.loadClass(NexusAccessor.class.getName());
+ ClassLoader qux = mock(ClassLoader.class);
+ when(loadedTypeInitializer.isAlive()).thenReturn(true);
+ assertThat(accessor
+ .getDeclaredMethod("register", String.class, ClassLoader.class, int.class, LoadedTypeInitializer.class)
+ .invoke(accessor.getDeclaredConstructor().newInstance(), FOO, qux, BAR, loadedTypeInitializer), nullValue(Object.class));
+ try {
+ assertThat(((Map<?, ?>) duplicateInitializers.get(null)).size(), is(0));
+ assertThat(((Map<?, ?>) actualInitializers.get(null)).size(), is(1));
+ } finally {
+ Constructor<Nexus> constructor = Nexus.class.getDeclaredConstructor(String.class, ClassLoader.class, ReferenceQueue.class, int.class);
+ constructor.setAccessible(true);
+ Object value = ((Map<?, ?>) actualInitializers.get(null)).remove(constructor.newInstance(FOO, qux, Nexus.NO_QUEUE, BAR));
+ assertThat(value, is((Object) loadedTypeInitializer));
+ }
+ }
+
+ @Test
+ public void testNexusClean() throws Exception {
+ Field typeInitializers = ClassLoader.getSystemClassLoader().loadClass(Nexus.class.getName()).getDeclaredField("TYPE_INITIALIZERS");
+ typeInitializers.setAccessible(true);
+ ClassLoader classLoader = new URLClassLoader(new URL[0]);
+ when(loadedTypeInitializer.isAlive()).thenReturn(true);
+ assertThat(((Map<?, ?>) typeInitializers.get(null)).isEmpty(), is(true));
+ ReferenceQueue<ClassLoader> referenceQueue = new ReferenceQueue<ClassLoader>();
+ NexusAccessor nexusAccessor = new NexusAccessor(referenceQueue);
+ nexusAccessor.register(FOO, classLoader, BAR, loadedTypeInitializer);
+ assertThat(((Map<?, ?>) typeInitializers.get(null)).isEmpty(), is(false));
+ classLoader = null;
+ System.gc();
+ NexusAccessor.clean(referenceQueue.remove(100L));
+ assertThat(((Map<?, ?>) typeInitializers.get(null)).isEmpty(), is(true));
+ }
+
+ @Test
+ public void testNexusAccessorIsAvailable() throws Exception {
+ assertThat(NexusAccessor.isAlive(), is(true));
+ }
+
+ @Test
+ public void testUnavailableState() throws Exception {
+ assertThat(new NexusAccessor.Dispatcher.Unavailable(new Exception()).isAlive(), is(false));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUnavailableDispatcherRegisterThrowsException() throws Exception {
+ new NexusAccessor.Dispatcher.Unavailable(new Exception()).register(FOO, classLoader, Nexus.NO_QUEUE, BAR, loadedTypeInitializer);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ @SuppressWarnings("unchecked")
+ public void testUnavailableDispatcherCleanThrowsException() throws Exception {
+ new NexusAccessor.Dispatcher.Unavailable(new Exception()).clean(mock(Reference.class));
+ }
+
+ @Test
+ public void testNexusEquality() throws Exception {
+ Constructor<Nexus> constructor = Nexus.class.getDeclaredConstructor(String.class, ClassLoader.class, ReferenceQueue.class, int.class);
+ constructor.setAccessible(true);
+ assertThat(constructor.newInstance(FOO, classLoader, Nexus.NO_QUEUE, BAR),
+ is(constructor.newInstance(FOO, classLoader, Nexus.NO_QUEUE, BAR)));
+ assertThat(constructor.newInstance(FOO, classLoader, Nexus.NO_QUEUE, BAR).hashCode(),
+ is(constructor.newInstance(FOO, classLoader, Nexus.NO_QUEUE, BAR).hashCode()));
+
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(NexusAccessor.class).apply();
+ ObjectPropertyAssertion.of(NexusAccessor.Dispatcher.CreationAction.class).apply();
+ final Iterator<Method> methods = Arrays.asList(Object.class.getDeclaredMethods()).iterator();
+ ObjectPropertyAssertion.of(NexusAccessor.Dispatcher.Available.class)
+ .create(new ObjectPropertyAssertion.Creator<Method>() {
+ @Override
+ public Method create() {
+ return methods.next();
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(NexusAccessor.Dispatcher.Unavailable.class).apply();
+ ObjectPropertyAssertion.of(NexusAccessor.InitializationAppender.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/TargetTypeTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/TargetTypeTest.java
new file mode 100644
index 0000000..29a6b5c
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/TargetTypeTest.java
@@ -0,0 +1,76 @@
+package net.bytebuddy.dynamic;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.hamcrest.CoreMatchers;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Modifier;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.when;
+
+public class TargetTypeTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription typeDescription, targetType, componentType;
+
+ @Test
+ public void testIsNotTargetType() throws Exception {
+ when(typeDescription.represents(TargetType.class)).thenReturn(false);
+ assertThat(TargetType.resolve(typeDescription, targetType), is(typeDescription));
+ }
+
+ @Test
+ public void testIsTargetType() throws Exception {
+ when(typeDescription.represents(TargetType.class)).thenReturn(true);
+ assertThat(TargetType.resolve(typeDescription, targetType), is(targetType));
+ }
+
+ @Test
+ public void testIsNotTargetTypeArray() throws Exception {
+ when(typeDescription.isArray()).thenReturn(true);
+ when(typeDescription.getComponentType()).thenReturn(componentType);
+ when(componentType.represents(TargetType.class)).thenReturn(false);
+ assertThat(TargetType.resolve(typeDescription, targetType), is(typeDescription));
+ }
+
+ @Test
+ public void testIsTargetTypeArray() throws Exception {
+ when(typeDescription.isArray()).thenReturn(true);
+ when(typeDescription.getComponentType()).thenReturn(componentType);
+ when(componentType.represents(TargetType.class)).thenReturn(true);
+ TypeDescription resolvedType = TargetType.resolve(typeDescription, targetType);
+ assertThat(resolvedType.isArray(), is(true));
+ assertThat(resolvedType.getComponentType(), is(targetType));
+ }
+
+ @Test
+ public void testConstructorIsHidden() throws Exception {
+ assertThat(TargetType.class.getDeclaredConstructors().length, is(1));
+ Constructor<?> constructor = TargetType.class.getDeclaredConstructor();
+ assertThat(Modifier.isPrivate(constructor.getModifiers()), is(true));
+ constructor.setAccessible(true);
+ try {
+ constructor.newInstance();
+ fail();
+ } catch (InvocationTargetException exception) {
+ assertThat(exception.getCause().getClass(), CoreMatchers.<Class<?>>is(UnsupportedOperationException.class));
+ }
+ }
+
+ @Test
+ public void testTypeIsFinal() throws Exception {
+ assertThat(Modifier.isFinal(TargetType.class.getModifiers()), is(true));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/TransformerCompoundTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/TransformerCompoundTest.java
new file mode 100644
index 0000000..ac1ada5
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/TransformerCompoundTest.java
@@ -0,0 +1,55 @@
+package net.bytebuddy.dynamic;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.Collections;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class TransformerCompoundTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private Transformer<Object> first, second;
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Mock
+ private Object firstTarget, secondTarget, finalTarget;
+
+ @Before
+ public void setUp() throws Exception {
+ when(first.transform(typeDescription, firstTarget)).thenReturn(secondTarget);
+ when(second.transform(typeDescription, secondTarget)).thenReturn(finalTarget);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked") // In absence of @SafeVarargs for Java 6
+ public void testTransformation() throws Exception {
+ assertThat(new Transformer.Compound<Object>(first, second).transform(typeDescription, firstTarget), is(finalTarget));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(Transformer.Compound.class).create(new ObjectPropertyAssertion.Creator<List<?>>() {
+ @Override
+ public List<?> create() {
+ return Collections.singletonList(mock(Transformer.class));
+ }
+ }).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/TransformerForFieldTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/TransformerForFieldTest.java
new file mode 100644
index 0000000..7750c21
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/TransformerForFieldTest.java
@@ -0,0 +1,132 @@
+package net.bytebuddy.dynamic;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.modifier.ModifierContributor;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.Collections;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.none;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class TransformerForFieldTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ private static final int MODIFIERS = 42, RANGE = 3, MASK = 1;
+
+ @Rule
+ public TestRule mocktioRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription instrumentedType, rawDeclaringType, rawReturnType, rawParameterType;
+
+ @Mock
+ private Transformer<FieldDescription.Token> tokenTransformer;
+
+ @Mock
+ private FieldDescription fieldDescription;
+
+ @Mock
+ private FieldDescription.InDefinedShape definedField;
+
+ @Mock
+ private FieldDescription.Token fieldToken;
+
+ @Mock
+ private TypeDescription.Generic fieldType, declaringType;
+
+ @Mock
+ private AnnotationDescription fieldAnnotation;
+
+ @Mock
+ private ModifierContributor.ForField modifierContributor;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ when(fieldType.accept(any(TypeDescription.Generic.Visitor.class))).thenReturn(fieldType);
+ when(fieldDescription.asToken(none())).thenReturn(fieldToken);
+ when(fieldDescription.getDeclaringType()).thenReturn(declaringType);
+ when(fieldDescription.asDefined()).thenReturn(definedField);
+ when(fieldToken.getName()).thenReturn(FOO);
+ when(fieldToken.getModifiers()).thenReturn(MODIFIERS);
+ when(fieldToken.getType()).thenReturn(fieldType);
+ when(fieldToken.getAnnotations()).thenReturn(new AnnotationList.Explicit(fieldAnnotation));
+ when(modifierContributor.getMask()).thenReturn(MASK);
+ when(modifierContributor.getRange()).thenReturn(RANGE);
+ }
+
+ @Test
+ public void testSimpleTransformation() throws Exception {
+ when(tokenTransformer.transform(instrumentedType, fieldToken)).thenReturn(fieldToken);
+ FieldDescription transformed = new Transformer.ForField(tokenTransformer).transform(instrumentedType, fieldDescription);
+ assertThat(transformed.getDeclaringType(), is((TypeDefinition) declaringType));
+ assertThat(transformed.getInternalName(), is(FOO));
+ assertThat(transformed.getModifiers(), is(MODIFIERS));
+ assertThat(transformed.getDeclaredAnnotations(), is(Collections.singletonList(fieldAnnotation)));
+ assertThat(transformed.getType(), is(fieldType));
+ assertThat(transformed.asDefined(), is(definedField));
+ }
+
+ @Test
+ public void testModifierTransformation() throws Exception {
+ FieldDescription.Token transformed = new Transformer.ForField.FieldModifierTransformer(ModifierContributor.Resolver.of(modifierContributor))
+ .transform(instrumentedType, fieldToken);
+ assertThat(transformed.getName(), is(FOO));
+ assertThat(transformed.getModifiers(), is((MODIFIERS & ~RANGE) | MASK));
+ assertThat(transformed.getType(), is(fieldType));
+ }
+
+ @Test
+ public void testNoChangesUnlessSpecified() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(Bar.class);
+ FieldDescription fieldDescription = typeDescription.getSuperClass().getDeclaredFields().filter(named(FOO)).getOnly();
+ FieldDescription transformed = Transformer.ForField.withModifiers().transform(typeDescription, fieldDescription);
+ assertThat(transformed, is(fieldDescription));
+ assertThat(transformed.getModifiers(), is(fieldDescription.getModifiers()));
+ }
+
+ @Test
+ public void testRetainsInstrumentedType() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(Bar.class);
+ FieldDescription fieldDescription = typeDescription.getSuperClass().getDeclaredFields().filter(named(BAR)).getOnly();
+ FieldDescription transformed = Transformer.ForField.withModifiers().transform(typeDescription, fieldDescription);
+ assertThat(transformed, is(fieldDescription));
+ assertThat(transformed.getModifiers(), is(fieldDescription.getModifiers()));
+ assertThat(transformed.getType().asErasure(), is(typeDescription));
+ assertThat(transformed.getType().getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(transformed.getType().getTypeArguments().size(), is(1));
+ assertThat(transformed.getType().getTypeArguments().getOnly(), is(typeDescription.getSuperClass().getDeclaredFields().filter(named(FOO)).getOnly().getType()));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(Transformer.ForField.class).apply();
+ ObjectPropertyAssertion.of(Transformer.ForField.FieldModifierTransformer.class).apply();
+ }
+
+ private static class Foo<T> {
+
+ T foo;
+
+ Bar<T> bar;
+ }
+
+ private static class Bar<S> extends Foo<S> {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/TransformerForMethodTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/TransformerForMethodTest.java
new file mode 100644
index 0000000..3cc1705
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/TransformerForMethodTest.java
@@ -0,0 +1,188 @@
+package net.bytebuddy.dynamic;
+
+import net.bytebuddy.description.ByteCodeElement;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.modifier.ModifierContributor;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.description.type.TypeVariableToken;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.Collections;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.none;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class TransformerForMethodTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux";
+
+ private static final int MODIFIERS = 42, RANGE = 3, MASK = 1;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription instrumentedType, rawDeclaringType, rawReturnType, rawParameterType;
+
+ @Mock
+ private Transformer<MethodDescription.Token> tokenTransformer;
+
+ @Mock
+ private MethodDescription methodDescription;
+
+ @Mock
+ private MethodDescription.InDefinedShape definedMethod;
+
+ @Mock
+ private MethodDescription.Token methodToken;
+
+ @Mock
+ private ParameterDescription.Token parameterToken;
+
+ @Mock
+ private ParameterDescription.InDefinedShape definedParameter;
+
+ @Mock
+ private TypeDescription.Generic returnType, typeVariableBound, parameterType, exceptionType, declaringType;
+
+ @Mock
+ private AnnotationDescription methodAnnotation, parameterAnnotation;
+
+ @Mock
+ private ModifierContributor.ForMethod modifierContributor;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ when(returnType.accept(any(TypeDescription.Generic.Visitor.class))).thenReturn(returnType);
+ when(typeVariableBound.accept(any(TypeDescription.Generic.Visitor.class))).thenReturn(typeVariableBound);
+ when(parameterType.accept(any(TypeDescription.Generic.Visitor.class))).thenReturn(parameterType);
+ when(exceptionType.accept(any(TypeDescription.Generic.Visitor.class))).thenReturn(exceptionType);
+ when(typeVariableBound.getSymbol()).thenReturn(QUX);
+ when(typeVariableBound.getSort()).thenReturn(TypeDefinition.Sort.VARIABLE);
+ when(typeVariableBound.asGenericType()).thenReturn(typeVariableBound);
+ when(methodDescription.asToken(none())).thenReturn(methodToken);
+ when(methodDescription.getDeclaringType()).thenReturn(declaringType);
+ when(methodDescription.asDefined()).thenReturn(definedMethod);
+ when(methodToken.getName()).thenReturn(FOO);
+ when(methodToken.getModifiers()).thenReturn(MODIFIERS);
+ when(methodToken.getReturnType()).thenReturn(returnType);
+ when(methodToken.getTypeVariableTokens()).thenReturn(new ByteCodeElement.Token.TokenList<TypeVariableToken>(new TypeVariableToken(QUX,
+ new TypeList.Generic.Explicit(typeVariableBound))));
+ when(methodToken.getExceptionTypes()).thenReturn(new TypeList.Generic.Explicit(exceptionType));
+ when(methodToken.getParameterTokens())
+ .thenReturn(new ByteCodeElement.Token.TokenList<ParameterDescription.Token>(parameterToken));
+ when(methodToken.getAnnotations()).thenReturn(new AnnotationList.Explicit(methodAnnotation));
+ when(modifierContributor.getMask()).thenReturn(MASK);
+ when(modifierContributor.getRange()).thenReturn(RANGE);
+ when(parameterToken.getType()).thenReturn(parameterType);
+ when(parameterToken.getAnnotations()).thenReturn(new AnnotationList.Explicit(parameterAnnotation));
+ when(parameterToken.getName()).thenReturn(BAR);
+ when(parameterToken.getModifiers()).thenReturn(MODIFIERS * 2);
+ when(definedMethod.getParameters())
+ .thenReturn(new ParameterList.Explicit<ParameterDescription.InDefinedShape>(definedParameter));
+ when(declaringType.asErasure()).thenReturn(rawDeclaringType);
+ when(returnType.asErasure()).thenReturn(rawReturnType);
+ when(parameterType.asErasure()).thenReturn(rawParameterType);
+ when(exceptionType.asGenericType()).thenReturn(exceptionType);
+ }
+
+ @Test
+ public void testSimpleTransformation() throws Exception {
+ when(tokenTransformer.transform(instrumentedType, methodToken)).thenReturn(methodToken);
+ MethodDescription transformed = new Transformer.ForMethod(tokenTransformer).transform(instrumentedType, methodDescription);
+ assertThat(transformed.getDeclaringType(), is((TypeDefinition) declaringType));
+ assertThat(transformed.getInternalName(), is(FOO));
+ assertThat(transformed.getModifiers(), is(MODIFIERS));
+ assertThat(transformed.getReturnType(), is(returnType));
+ assertThat(transformed.getTypeVariables().size(), is(1));
+ assertThat(transformed.getTypeVariables().getOnly().getSymbol(), is(QUX));
+ assertThat(transformed.getExceptionTypes().size(), is(1));
+ assertThat(transformed.getExceptionTypes().getOnly(), is(exceptionType));
+ assertThat(transformed.getDeclaredAnnotations(), is(Collections.singletonList(methodAnnotation)));
+ assertThat(transformed.getParameters().size(), is(1));
+ assertThat(transformed.getParameters().getOnly().getType(), is(parameterType));
+ assertThat(transformed.getParameters().getOnly().getName(), is(BAR));
+ assertThat(transformed.getParameters().getOnly().getModifiers(), is(MODIFIERS * 2));
+ assertThat(transformed.getParameters().getOnly().getDeclaredAnnotations().size(), is(1));
+ assertThat(transformed.getParameters().getOnly().getDeclaredAnnotations().getOnly(), is(parameterAnnotation));
+ assertThat(transformed.getParameters().getOnly().asDefined(), is(definedParameter));
+ assertThat(transformed.getParameters().getOnly().getDeclaredAnnotations(), is(Collections.singletonList(parameterAnnotation)));
+ assertThat(transformed.getParameters().getOnly().getDeclaringMethod(), is(transformed));
+ assertThat(transformed.asDefined(), is(definedMethod));
+ }
+
+ @Test
+ public void testModifierTransformation() throws Exception {
+ MethodDescription.Token transformed = new Transformer.ForMethod.MethodModifierTransformer(ModifierContributor.Resolver.of(modifierContributor))
+ .transform(instrumentedType, methodToken);
+ assertThat(transformed.getName(), is(FOO));
+ assertThat(transformed.getModifiers(), is((MODIFIERS & ~RANGE) | MASK));
+ assertThat(transformed.getReturnType(), is(returnType));
+ assertThat(transformed.getTypeVariableTokens().size(), is(1));
+ assertThat(transformed.getTypeVariableTokens().get(0), is(new TypeVariableToken(QUX, Collections.singletonList(typeVariableBound))));
+ assertThat(transformed.getExceptionTypes().size(), is(1));
+ assertThat(transformed.getExceptionTypes().getOnly(), is(exceptionType));
+ assertThat(transformed.getParameterTokens().size(), is(1));
+ assertThat(transformed.getParameterTokens().getOnly(), is(parameterToken));
+ }
+
+ @Test
+ public void testNoChangesUnlessSpecified() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(Bar.class);
+ MethodDescription methodDescription = typeDescription.getSuperClass().getDeclaredMethods().filter(named(FOO)).getOnly();
+ MethodDescription transformed = Transformer.ForMethod.withModifiers().transform(typeDescription, methodDescription);
+ assertThat(transformed, is(methodDescription));
+ assertThat(transformed.getModifiers(), is(methodDescription.getModifiers()));
+ }
+
+ @Test
+ public void testRetainsInstrumentedType() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(Bar.class);
+ MethodDescription methodDescription = typeDescription.getSuperClass().getDeclaredMethods().filter(named(BAR)).getOnly();
+ MethodDescription transformed = Transformer.ForMethod.withModifiers().transform(typeDescription, methodDescription);
+ assertThat(transformed, is(methodDescription));
+ assertThat(transformed.getModifiers(), is(methodDescription.getModifiers()));
+ assertThat(transformed.getReturnType().asErasure(), is(typeDescription));
+ assertThat(transformed.getReturnType().getSort(), is(TypeDefinition.Sort.PARAMETERIZED));
+ assertThat(transformed.getReturnType().getTypeArguments().size(), is(1));
+ assertThat(transformed.getReturnType().getTypeArguments().getOnly(), is(typeDescription.getSuperClass().getDeclaredMethods().filter(named(FOO)).getOnly().getReturnType()));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(Transformer.ForMethod.class).apply();
+ ObjectPropertyAssertion.of(Transformer.ForMethod.MethodModifierTransformer.class).apply();
+ ObjectPropertyAssertion.of(Transformer.ForMethod.TransformedMethod.AttachmentVisitor.class).apply();
+ }
+
+ private static class Foo<T> {
+
+ T foo() {
+ return null;
+ }
+
+ Bar<T> bar() {
+ return null;
+ }
+ }
+
+ private static class Bar<S> extends Foo<S> {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/TransformerNoOpTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/TransformerNoOpTest.java
new file mode 100644
index 0000000..69642df
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/TransformerNoOpTest.java
@@ -0,0 +1,23 @@
+package net.bytebuddy.dynamic;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class TransformerNoOpTest {
+
+ @Test
+ public void testTransformation() throws Exception {
+ Object target = mock(Object.class);
+ assertThat(Transformer.NoOp.INSTANCE.transform(mock(TypeDescription.class), target), is(target));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(Transformer.NoOp.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/TypeResolutionStrategyTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/TypeResolutionStrategyTest.java
new file mode 100644
index 0000000..706558b
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/TypeResolutionStrategyTest.java
@@ -0,0 +1,133 @@
+package net.bytebuddy.dynamic;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.dynamic.scaffold.TypeInitializer;
+import net.bytebuddy.implementation.LoadedTypeInitializer;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.hamcrest.CoreMatchers;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.lang.ref.ReferenceQueue;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.util.Collections;
+import java.util.Map;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class TypeResolutionStrategyTest {
+
+ private static final byte[] FOO = new byte[]{1, 2, 3};
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeInitializer typeInitializer, otherTypeInitializer;
+
+ @Mock
+ private DynamicType dynamicType;
+
+ @Mock
+ private ClassLoader classLoader;
+
+ @Mock
+ private ClassLoadingStrategy<ClassLoader> classLoadingStrategy;
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Mock
+ private LoadedTypeInitializer loadedTypeInitializer;
+
+ @Before
+ public void setUp() throws Exception {
+ when(dynamicType.getTypeDescription()).thenReturn(typeDescription);
+ when(dynamicType.getAllTypes()).thenReturn(Collections.singletonMap(typeDescription, FOO));
+ when(dynamicType.getLoadedTypeInitializers()).thenReturn(Collections.singletonMap(typeDescription, loadedTypeInitializer));
+ when(classLoadingStrategy.load(classLoader, Collections.singletonMap(typeDescription, FOO)))
+ .thenReturn(Collections.<TypeDescription, Class<?>>singletonMap(typeDescription, Foo.class));
+ when(loadedTypeInitializer.isAlive()).thenReturn(true);
+ when(typeDescription.getName()).thenReturn(Foo.class.getName());
+ }
+
+ @Test
+ public void testPassive() throws Exception {
+ TypeResolutionStrategy.Resolved resolved = TypeResolutionStrategy.Passive.INSTANCE.resolve();
+ assertThat(resolved.injectedInto(typeInitializer), is(typeInitializer));
+ assertThat(resolved.initialize(dynamicType, classLoader, classLoadingStrategy),
+ is(Collections.<TypeDescription, Class<?>>singletonMap(typeDescription, Foo.class)));
+ verify(classLoadingStrategy).load(classLoader, Collections.singletonMap(typeDescription, FOO));
+ verifyNoMoreInteractions(classLoadingStrategy);
+ verify(loadedTypeInitializer).onLoad(Foo.class);
+ verifyNoMoreInteractions(loadedTypeInitializer);
+ }
+
+ @Test
+ public void testActive() throws Exception {
+ TypeResolutionStrategy.Resolved resolved = new TypeResolutionStrategy.Active().resolve();
+ Field field = TypeResolutionStrategy.Active.Resolved.class.getDeclaredField("identification");
+ field.setAccessible(true);
+ int identification = (Integer) field.get(resolved);
+ when(typeInitializer.expandWith(new NexusAccessor.InitializationAppender(identification))).thenReturn(otherTypeInitializer);
+ assertThat(resolved.injectedInto(typeInitializer), is(otherTypeInitializer));
+ assertThat(resolved.initialize(dynamicType, classLoader, classLoadingStrategy),
+ is(Collections.<TypeDescription, Class<?>>singletonMap(typeDescription, Foo.class)));
+ try {
+ verify(classLoadingStrategy).load(classLoader, Collections.singletonMap(typeDescription, FOO));
+ verifyNoMoreInteractions(classLoadingStrategy);
+ verify(loadedTypeInitializer).isAlive();
+ verifyNoMoreInteractions(loadedTypeInitializer);
+ } finally {
+ Field initializers = Nexus.class.getDeclaredField("TYPE_INITIALIZERS");
+ initializers.setAccessible(true);
+ Constructor<Nexus> constructor = Nexus.class.getDeclaredConstructor(String.class, ClassLoader.class, ReferenceQueue.class, int.class);
+ constructor.setAccessible(true);
+ Object value = ((Map<?, ?>) initializers.get(null)).remove(constructor.newInstance(Foo.class.getName(), Foo.class.getClassLoader(), null, identification));
+ assertThat(value, CoreMatchers.is((Object) loadedTypeInitializer));
+ }
+ }
+
+ @Test
+ public void testLazy() throws Exception {
+ TypeResolutionStrategy.Resolved resolved = TypeResolutionStrategy.Lazy.INSTANCE.resolve();
+ assertThat(resolved.injectedInto(typeInitializer), is(typeInitializer));
+ assertThat(resolved.initialize(dynamicType, classLoader, classLoadingStrategy),
+ is(Collections.<TypeDescription, Class<?>>singletonMap(typeDescription, Foo.class)));
+ verify(classLoadingStrategy).load(classLoader, Collections.singletonMap(typeDescription, FOO));
+ verifyNoMoreInteractions(classLoadingStrategy);
+ verifyNoMoreInteractions(loadedTypeInitializer);
+ }
+
+ @Test
+ public void testDisabled() throws Exception {
+ TypeResolutionStrategy.Resolved resolved = TypeResolutionStrategy.Disabled.INSTANCE.resolve();
+ assertThat(resolved.injectedInto(typeInitializer), is(typeInitializer));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testDisabledCannotBeApplied() throws Exception {
+ TypeResolutionStrategy.Disabled.INSTANCE.resolve().initialize(dynamicType, classLoader, classLoadingStrategy);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeResolutionStrategy.Active.class).apply();
+ ObjectPropertyAssertion.of(TypeResolutionStrategy.Active.Resolved.class).apply();
+ ObjectPropertyAssertion.of(TypeResolutionStrategy.Passive.class).apply();
+ ObjectPropertyAssertion.of(TypeResolutionStrategy.Lazy.class).apply();
+ ObjectPropertyAssertion.of(TypeResolutionStrategy.Disabled.class).apply();
+ }
+
+ private static class Foo {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ByteArrayClassLoaderChildFirstPrependingEnumerationTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ByteArrayClassLoaderChildFirstPrependingEnumerationTest.java
new file mode 100644
index 0000000..0f26cbb
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ByteArrayClassLoaderChildFirstPrependingEnumerationTest.java
@@ -0,0 +1,59 @@
+package net.bytebuddy.dynamic.loading;
+
+import net.bytebuddy.test.utility.IntegrationRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+
+import java.net.URL;
+import java.util.*;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class ByteArrayClassLoaderChildFirstPrependingEnumerationTest {
+
+ @Rule
+ public MethodRule integrationRule = new IntegrationRule();
+
+ private URL first, second, third;
+
+ @Before
+ public void setUp() throws Exception {
+ first = new URL("file://foo");
+ second = new URL("file://bar");
+ third = new URL("file://qux");
+ }
+
+ @Test
+ @IntegrationRule.Enforce
+ public void testPrepending() throws Exception {
+ Vector<URL> vector = new Vector<URL>();
+ vector.add(second);
+ vector.add(third);
+ vector.add(first);
+ Enumeration<URL> enumeration = new ByteArrayClassLoader.ChildFirst.PrependingEnumeration(first, vector.elements());
+ assertThat(enumeration.hasMoreElements(), is(true));
+ assertThat(enumeration.nextElement(), is(first));
+ assertThat(enumeration.hasMoreElements(), is(true));
+ assertThat(enumeration.nextElement(), is(second));
+ assertThat(enumeration.hasMoreElements(), is(true));
+ assertThat(enumeration.nextElement(), is(third));
+ assertThat(enumeration.hasMoreElements(), is(false));
+ }
+
+ @Test(expected = NoSuchElementException.class)
+ public void testNextElementThrowsException() throws Exception {
+ Vector<URL> vector = new Vector<URL>();
+ vector.add(second);
+ vector.add(third);
+ vector.add(first);
+ Enumeration<URL> enumeration = new ByteArrayClassLoader.ChildFirst.PrependingEnumeration(first, vector.elements());
+ enumeration.nextElement();
+ enumeration.nextElement();
+ enumeration.nextElement();
+ enumeration.nextElement();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ByteArrayClassLoaderChildFirstTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ByteArrayClassLoaderChildFirstTest.java
new file mode 100644
index 0000000..258edbc
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ByteArrayClassLoaderChildFirstTest.java
@@ -0,0 +1,237 @@
+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;
+
+ at 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));
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ByteArrayClassLoaderEmptyEnumerationTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ByteArrayClassLoaderEmptyEnumerationTest.java
new file mode 100644
index 0000000..1ce1534
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ByteArrayClassLoaderEmptyEnumerationTest.java
@@ -0,0 +1,27 @@
+package net.bytebuddy.dynamic.loading;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.util.NoSuchElementException;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class ByteArrayClassLoaderEmptyEnumerationTest {
+
+ @Test
+ public void testNoFurtherElements() throws Exception {
+ assertThat(ByteArrayClassLoader.EmptyEnumeration.INSTANCE.hasMoreElements(), is(false));
+ }
+
+ @Test(expected = NoSuchElementException.class)
+ public void testNextElementThrowsException() throws Exception {
+ ByteArrayClassLoader.EmptyEnumeration.INSTANCE.nextElement();
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ByteArrayClassLoader.EmptyEnumeration.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ByteArrayClassLoaderObjectPropertiesTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ByteArrayClassLoaderObjectPropertiesTest.java
new file mode 100644
index 0000000..5bb6840
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ByteArrayClassLoaderObjectPropertiesTest.java
@@ -0,0 +1,24 @@
+package net.bytebuddy.dynamic.loading;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.AccessControlContext;
+import java.security.ProtectionDomain;
+import java.util.Arrays;
+import java.util.Iterator;
+
+import static org.mockito.Mockito.mock;
+
+public class ByteArrayClassLoaderObjectPropertiesTest {
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ByteArrayClassLoader.ClassDefinitionAction.class).apply();
+ ObjectPropertyAssertion.of(ByteArrayClassLoader.PersistenceHandler.class).apply();
+ ObjectPropertyAssertion.of(ByteArrayClassLoader.PersistenceHandler.UrlDefinitionAction.class).apply();
+ ObjectPropertyAssertion.of(ByteArrayClassLoader.PersistenceHandler.UrlDefinitionAction.ByteArrayUrlStreamHandler.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ByteArrayClassLoaderPackageLookupStrategy.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ByteArrayClassLoaderPackageLookupStrategy.java
new file mode 100644
index 0000000..efc3f15
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ByteArrayClassLoaderPackageLookupStrategy.java
@@ -0,0 +1,40 @@
+package net.bytebuddy.dynamic.loading;
+
+import net.bytebuddy.test.utility.ClassFileExtraction;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Iterator;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class ByteArrayClassLoaderPackageLookupStrategy {
+
+ @Test
+ public void testGetPackage() throws Exception {
+ ByteArrayClassLoader byteArrayClassLoader = new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassFileExtraction.of(Foo.class));
+ byteArrayClassLoader.loadClass(Foo.class.getName());
+ assertThat(ByteArrayClassLoader.PackageLookupStrategy.ForLegacyVm.INSTANCE.apply(byteArrayClassLoader, Foo.class.getPackage().getName()).getName(),
+ is(Foo.class.getPackage().getName()));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ByteArrayClassLoader.PackageLookupStrategy.CreationAction.class).apply();
+ ObjectPropertyAssertion.of(ByteArrayClassLoader.PackageLookupStrategy.ForLegacyVm.class).apply();
+ final Iterator<Method> iterator = Arrays.asList(Object.class.getDeclaredMethods()).iterator();
+ ObjectPropertyAssertion.of(ByteArrayClassLoader.PackageLookupStrategy.ForJava9CapableVm.class).create(new ObjectPropertyAssertion.Creator<Method>() {
+ @Override
+ public Method create() {
+ return iterator.next();
+ }
+ }).apply();
+ }
+
+ private static class Foo {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ByteArrayClassLoaderSingletonEnumerationTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ByteArrayClassLoaderSingletonEnumerationTest.java
new file mode 100644
index 0000000..8dcf885
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ByteArrayClassLoaderSingletonEnumerationTest.java
@@ -0,0 +1,39 @@
+package net.bytebuddy.dynamic.loading;
+
+import net.bytebuddy.test.utility.IntegrationRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class ByteArrayClassLoaderSingletonEnumerationTest {
+
+ @Rule
+ public MethodRule integrationRule = new IntegrationRule();
+
+ @Test
+ @IntegrationRule.Enforce
+ public void testIteration() throws Exception {
+ URL url = new URL("file://foo");
+ Enumeration<URL> enumeration = new ByteArrayClassLoader.SingletonEnumeration(url);
+ assertThat(enumeration.hasMoreElements(), is(true));
+ assertThat(enumeration.nextElement(), is(url));
+ assertThat(enumeration.hasMoreElements(), is(false));
+ }
+
+ @Test(expected = NoSuchElementException.class)
+ public void testSecondElementThrowsException() throws Exception {
+ Enumeration<URL> enumeration = new ByteArrayClassLoader.SingletonEnumeration(new URL("file://foo"));
+ enumeration.nextElement();
+ enumeration.nextElement();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ByteArrayClassLoaderSynchronizationStrategyTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ByteArrayClassLoaderSynchronizationStrategyTest.java
new file mode 100644
index 0000000..fa0979d
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ByteArrayClassLoaderSynchronizationStrategyTest.java
@@ -0,0 +1,50 @@
+package net.bytebuddy.dynamic.loading;
+
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Iterator;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class ByteArrayClassLoaderSynchronizationStrategyTest {
+
+ private static final String FOO = "foo";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private ClassLoader classLoader;
+
+ @Test
+ public void testInitialize() throws Exception {
+ assertThat(ByteArrayClassLoader.SynchronizationStrategy.ForLegacyVm.INSTANCE.initialize(),
+ is((ByteArrayClassLoader.SynchronizationStrategy) ByteArrayClassLoader.SynchronizationStrategy.ForLegacyVm.INSTANCE));
+ }
+
+ @Test
+ public void testLegacyVm() throws Exception {
+ assertThat(ByteArrayClassLoader.SynchronizationStrategy.ForLegacyVm.INSTANCE.getClassLoadingLock(classLoader, FOO), is((Object) classLoader));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ByteArrayClassLoader.SynchronizationStrategy.CreationAction.class).apply();
+ ObjectPropertyAssertion.of(ByteArrayClassLoader.SynchronizationStrategy.ForLegacyVm.class).apply();
+ final Iterator<Method> iterator = Arrays.asList(Object.class.getDeclaredMethods()).iterator();
+ ObjectPropertyAssertion.of(ByteArrayClassLoader.SynchronizationStrategy.ForJava7CapableVm.class).create(new ObjectPropertyAssertion.Creator<Method>() {
+ @Override
+ public Method create() {
+ return iterator.next();
+ }
+ }).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ByteArrayClassLoaderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ByteArrayClassLoaderTest.java
new file mode 100644
index 0000000..5ed79fe
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ByteArrayClassLoaderTest.java
@@ -0,0 +1,258 @@
+package net.bytebuddy.dynamic.loading;
+
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.test.utility.ClassFileExtraction;
+import net.bytebuddy.test.utility.IntegrationRule;
+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.MethodRule;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.InputStream;
+import java.lang.instrument.ClassFileTransformer;
+import java.net.URL;
+import java.security.ProtectionDomain;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Enumeration;
+
+import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+ at RunWith(Parameterized.class)
+public class ByteArrayClassLoaderTest {
+
+ private static final ProtectionDomain DEFAULT_PROTECTION_DOMAIN = null;
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux", CLASS_FILE = ".class";
+
+ private final ByteArrayClassLoader.PersistenceHandler persistenceHandler;
+
+ private final boolean expectedResourceLookup;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Rule
+ public MethodRule integrationRule = new IntegrationRule();
+
+ private InjectionClassLoader classLoader;
+
+ private URL sealBase;
+
+ @Mock
+ private PackageDefinitionStrategy packageDefinitionStrategy;
+
+ @Mock
+ private ClassFileTransformer classFileTransformer;
+
+ public ByteArrayClassLoaderTest(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 {
+ classLoader = new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER,
+ ClassFileExtraction.of(Foo.class),
+ DEFAULT_PROTECTION_DOMAIN,
+ persistenceHandler,
+ packageDefinitionStrategy,
+ classFileTransformer);
+ sealBase = new URL("file://foo");
+ when(packageDefinitionStrategy.define(classLoader, Foo.class.getPackage().getName(), Foo.class.getName()))
+ .thenReturn(new PackageDefinitionStrategy.Definition.Simple(FOO, BAR, QUX, QUX, FOO, BAR, sealBase));
+ when(packageDefinitionStrategy.define(classLoader, Bar.class.getPackage().getName(), Bar.class.getName()))
+ .thenReturn(PackageDefinitionStrategy.Definition.Trivial.INSTANCE);
+ when(classFileTransformer.transform(eq(classLoader),
+ anyString(),
+ Mockito.any(Class.class),
+ Mockito.any(ProtectionDomain.class),
+ Mockito.any(byte[].class))).thenAnswer(new Answer<byte[]>() {
+ @Override
+ public byte[] answer(InvocationOnMock invocation) throws Throwable {
+ return (byte[]) invocation.getArguments()[4];
+ }
+ });
+ }
+
+ @Test
+ public void testLoading() throws Exception {
+ Class<?> type = classLoader.loadClass(Foo.class.getName());
+ assertThat(type.getClassLoader(), is((ClassLoader) classLoader));
+ assertEquals(classLoader.loadClass(Foo.class.getName()), type);
+ assertThat(type, not(CoreMatchers.<Class<?>>is(Foo.class)));
+ }
+
+ @Test
+ @IntegrationRule.Enforce
+ public void testPackageDefinition() throws Exception {
+ Class<?> type = classLoader.loadClass(Foo.class.getName());
+ assertThat(type.getPackage(), notNullValue(Package.class));
+ assertThat(type.getPackage(), not(Foo.class.getPackage()));
+ assertThat(type.getPackage().getName(), is(Foo.class.getPackage().getName()));
+ assertThat(type.getPackage().getSpecificationTitle(), is(FOO));
+ assertThat(type.getPackage().getSpecificationVersion(), is(BAR));
+ assertThat(type.getPackage().getSpecificationVendor(), is(QUX));
+ assertThat(type.getPackage().getImplementationTitle(), is(QUX));
+ assertThat(type.getPackage().getImplementationVersion(), is(FOO));
+ assertThat(type.getPackage().getImplementationVendor(), is(BAR));
+ assertThat(type.getPackage().isSealed(), is(true));
+ assertThat(type.getPackage().isSealed(sealBase), is(true));
+ }
+
+ @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) 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) 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(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) 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
+ 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) 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) 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);
+ }
+
+ @Test
+ public void testPackage() throws Exception {
+ assertThat(classLoader.loadClass(Foo.class.getName()).getPackage().getName(), is(Foo.class.getPackage().getName()));
+ assertThat(classLoader.loadClass(Foo.class.getName()).getPackage(), not(Foo.class.getPackage()));
+ }
+
+ @Test
+ public void testInjection() throws Exception {
+ assertThat(classLoader.defineClass(Bar.class.getName(), ClassFileLocator.ForClassLoader.read(Bar.class).resolve()).getName(), is(Bar.class.getName()));
+ }
+
+ @Test
+ public void testDuplicateInjection() throws Exception {
+ Class<?> type = classLoader.defineClass(Bar.class.getName(), ClassFileLocator.ForClassLoader.read(Bar.class).resolve());
+ assertThat(classLoader.defineClass(Bar.class.getName(), ClassFileLocator.ForClassLoader.read(Bar.class).resolve()), is((Object) type));
+ }
+
+ @Test
+ public void testPredefinedInjection() throws Exception {
+ Class<?> type = classLoader.defineClass(Foo.class.getName(), ClassFileLocator.ForClassLoader.read(Foo.class).resolve());
+ assertThat(type, is((Object) classLoader.loadClass(Foo.class.getName())));
+ }
+
+ private static class Foo {
+ /* Note: Foo is know to the system class loader but not to the bootstrap class loader */
+ }
+
+ private static class Bar {
+ /* Note: Bar is know to the system class loader but not to the bootstrap class loader */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ClassInjectorUsingInstrumentationTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ClassInjectorUsingInstrumentationTest.java
new file mode 100644
index 0000000..ef46856
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ClassInjectorUsingInstrumentationTest.java
@@ -0,0 +1,81 @@
+package net.bytebuddy.dynamic.loading;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.agent.ByteBuddyAgent;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.test.utility.AgentAttachmentRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.utility.RandomString;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+
+import java.io.File;
+import java.security.AccessControlContext;
+import java.security.ProtectionDomain;
+import java.util.Collections;
+import java.util.Map;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class ClassInjectorUsingInstrumentationTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ @Rule
+ public MethodRule agentAttachmentRule = new AgentAttachmentRule();
+
+ private File folder;
+
+ @Before
+ public void setUp() throws Exception {
+ File file = File.createTempFile(FOO, BAR);
+ assertThat(file.delete(), is(true));
+ folder = new File(file.getParentFile(), RandomString.make());
+ assertThat(folder.mkdir(), is(true));
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce
+ public void testBootstrapInjection() throws Exception {
+ ClassInjector classInjector = ClassInjector.UsingInstrumentation.of(folder,
+ ClassInjector.UsingInstrumentation.Target.BOOTSTRAP,
+ ByteBuddyAgent.install());
+ String name = FOO + RandomString.make();
+ DynamicType dynamicType = new ByteBuddy().subclass(Object.class).name(name).make();
+ Map<TypeDescription, Class<?>> types = classInjector.inject(Collections.singletonMap(dynamicType.getTypeDescription(), dynamicType.getBytes()));
+ assertThat(types.size(), is(1));
+ assertThat(types.get(dynamicType.getTypeDescription()).getName(), is(name));
+ assertThat(types.get(dynamicType.getTypeDescription()).getClassLoader(), nullValue(ClassLoader.class));
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce
+ public void testSystemInjection() throws Exception {
+ ClassInjector classInjector = ClassInjector.UsingInstrumentation.of(folder,
+ ClassInjector.UsingInstrumentation.Target.SYSTEM,
+ ByteBuddyAgent.install());
+ String name = BAR + RandomString.make();
+ DynamicType dynamicType = new ByteBuddy().subclass(Object.class).name(name).make();
+ Map<TypeDescription, Class<?>> types = classInjector.inject(Collections.singletonMap(dynamicType.getTypeDescription(), dynamicType.getBytes()));
+ assertThat(types.size(), is(1));
+ assertThat(types.get(dynamicType.getTypeDescription()).getName(), is(name));
+ assertThat(types.get(dynamicType.getTypeDescription()).getClassLoader(), is(ClassLoader.getSystemClassLoader()));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ClassInjector.UsingInstrumentation.class).create(new ObjectPropertyAssertion.Creator<AccessControlContext>() {
+ @Override
+ public AccessControlContext create() {
+ return new AccessControlContext(new ProtectionDomain[]{mock(ProtectionDomain.class)});
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(ClassInjector.UsingInstrumentation.Target.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ClassInjectorUsingReflectionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ClassInjectorUsingReflectionTest.java
new file mode 100644
index 0000000..a023d0a
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ClassInjectorUsingReflectionTest.java
@@ -0,0 +1,206 @@
+package net.bytebuddy.dynamic.loading;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.TargetType;
+import net.bytebuddy.implementation.MethodDelegation;
+import net.bytebuddy.implementation.bind.annotation.AllArguments;
+import net.bytebuddy.implementation.bind.annotation.Origin;
+import net.bytebuddy.implementation.bind.annotation.RuntimeType;
+import net.bytebuddy.implementation.bind.annotation.Super;
+import net.bytebuddy.test.utility.ClassFileExtraction;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class ClassInjectorUsingReflectionTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ private ClassLoader classLoader;
+
+ @Before
+ public void setUp() throws Exception {
+ classLoader = new URLClassLoader(new URL[0], null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testBootstrapClassLoader() throws Exception {
+ new ClassInjector.UsingReflection(ClassLoadingStrategy.BOOTSTRAP_LOADER);
+ }
+
+ @Test
+ public void testInjection() throws Exception {
+ new ClassInjector.UsingReflection(classLoader)
+ .inject(Collections.<TypeDescription, byte[]>singletonMap(new TypeDescription.ForLoadedType(Foo.class), ClassFileExtraction.extract(Foo.class)));
+ assertThat(classLoader.loadClass(Foo.class.getName()).getClassLoader(), is(classLoader));
+ }
+
+ @Test
+ public void testDirectInjection() throws Exception {
+ ClassInjector.UsingReflection.Dispatcher dispatcher = ClassInjector.UsingReflection.Dispatcher.Direct.make().initialize();
+ assertThat(dispatcher.findClass(classLoader, Foo.class.getName()), nullValue(Class.class));
+ assertThat(dispatcher.defineClass(classLoader, Foo.class.getName(), ClassFileExtraction.extract(Foo.class), null), notNullValue(Class.class));
+ assertThat(dispatcher.getPackage(classLoader, Foo.class.getPackage().getName()), nullValue(Package.class));
+ assertThat(dispatcher.definePackage(classLoader,
+ Foo.class.getPackage().getName(),
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null), notNullValue(Package.class));
+ assertThat(classLoader.loadClass(Foo.class.getName()).getClassLoader(), is(classLoader));
+ }
+
+ @Test
+ public void testIndirectInjection() throws Exception {
+ ClassInjector.UsingReflection.Dispatcher dispatcher = ClassInjector.UsingReflection.Dispatcher.Indirect.make().initialize();
+ assertThat(dispatcher.findClass(classLoader, Foo.class.getName()), nullValue(Class.class));
+ assertThat(dispatcher.defineClass(classLoader, Foo.class.getName(), ClassFileExtraction.extract(Foo.class), null), notNullValue(Class.class));
+ assertThat(dispatcher.getPackage(classLoader, Foo.class.getPackage().getName()), nullValue(Package.class));
+ assertThat(dispatcher.definePackage(classLoader,
+ Foo.class.getPackage().getName(),
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null), notNullValue(Package.class));
+ assertThat(classLoader.loadClass(Foo.class.getName()).getClassLoader(), is(classLoader));
+ }
+
+ @Test
+ public void testDispatcherFaultyInitializationGetClass() throws Exception {
+ assertThat(new ClassInjector.UsingReflection.Dispatcher.Unavailable(new Exception()).initialize().findClass(getClass().getClassLoader(),
+ Object.class.getName()), is((Object) Object.class));
+ }
+
+ @Test
+ public void testDispatcherFaultyInitializationGetClassInexistant() throws Exception {
+ assertThat(new ClassInjector.UsingReflection.Dispatcher.Unavailable(new Exception()).initialize().findClass(getClass().getClassLoader(),
+ FOO), nullValue(Class.class));
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testDispatcherFaultyInitializationDefineClass() throws Exception {
+ new ClassInjector.UsingReflection.Dispatcher.Unavailable(new Exception()).initialize().defineClass(null,
+ null,
+ null,
+ null);
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testDispatcherFaultyInitializationGetPackage() throws Exception {
+ new ClassInjector.UsingReflection.Dispatcher.Unavailable(new Exception()).initialize().getPackage(null, null);
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testDispatcherFaultyInitializationDefinePackage() throws Exception {
+ new ClassInjector.UsingReflection.Dispatcher.Unavailable(new Exception()).initialize().definePackage(null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null);
+ }
+
+ @Test
+ public void testLegacyDispatcherGetLock() throws Exception {
+ ClassLoader classLoader = mock(ClassLoader.class);
+ assertThat(new ClassInjector.UsingReflection.Dispatcher.Direct.ForLegacyVm(null,
+ null,
+ null,
+ null).getClassLoadingLock(classLoader, FOO), is((Object) classLoader));
+ }
+
+ @Test
+ public void testFaultyDispatcherGetLock() throws Exception {
+ ClassLoader classLoader = mock(ClassLoader.class);
+ assertThat(new ClassInjector.UsingReflection.Dispatcher.Unavailable(null).getClassLoadingLock(classLoader, FOO), is((Object) classLoader));
+ }
+
+ @Test
+ public void testInjectionOrderNoPrematureAuxiliaryInjection() throws Exception {
+ ClassLoader classLoader = new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER,
+ ClassFileExtraction.of(Bar.class, Interceptor.class));
+ Class<?> type = new ByteBuddy().rebase(Bar.class)
+ .method(named(BAR))
+ .intercept(MethodDelegation.to(Interceptor.class)).make()
+ .load(classLoader, ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(BAR, String.class).invoke(type.getDeclaredConstructor().newInstance(), FOO), is((Object) BAR));
+ }
+
+ @Test
+ public void testAvailability() throws Exception {
+ assertThat(ClassInjector.UsingReflection.isAvailable(), is(true));
+ assertThat(new ClassInjector.UsingReflection.Dispatcher.Unavailable(null).isAvailable(), is(false));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ClassInjector.UsingReflection.class).apply();
+ final Iterator<Method> methods = Arrays.asList(String.class.getDeclaredMethods()).iterator();
+ ObjectPropertyAssertion.of(ClassInjector.UsingReflection.Dispatcher.Direct.ForLegacyVm.class).create(new ObjectPropertyAssertion.Creator<Method>() {
+ @Override
+ public Method create() {
+ return methods.next();
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(ClassInjector.UsingReflection.Dispatcher.Direct.ForJava7CapableVm.class).create(new ObjectPropertyAssertion.Creator<Method>() {
+ @Override
+ public Method create() {
+ return methods.next();
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(ClassInjector.UsingReflection.Dispatcher.Indirect.class).create(new ObjectPropertyAssertion.Creator<Method>() {
+ @Override
+ public Method create() {
+ return methods.next();
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(ClassInjector.UsingReflection.Dispatcher.Unavailable.class).apply();
+ ObjectPropertyAssertion.of(ClassInjector.UsingReflection.Dispatcher.Unavailable.class).apply();
+ ObjectPropertyAssertion.of(ClassInjector.UsingReflection.Dispatcher.CreationAction.class).apply();
+ }
+
+ private static class Foo {
+ /* Note: Foo is know to the system class loader but not to the bootstrap class loader */
+ }
+
+ public static class Bar {
+
+ public String bar(String value) {
+ return value;
+ }
+ }
+
+ public static class Interceptor {
+
+ @RuntimeType
+ public static Object intercept(@Super(proxyType = TargetType.class) Object zuper,
+ @AllArguments Object[] args,
+ @Origin Method method) throws Throwable {
+ args[0] = BAR;
+ return method.invoke(zuper, args);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ClassInjectorUsingUnsafeTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ClassInjectorUsingUnsafeTest.java
new file mode 100644
index 0000000..0109ecf
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ClassInjectorUsingUnsafeTest.java
@@ -0,0 +1,78 @@
+package net.bytebuddy.dynamic.loading;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.ClassFileExtraction;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.CoreMatchers.is;
+
+public class ClassInjectorUsingUnsafeTest {
+
+ private ClassLoader classLoader;
+
+ @Before
+ public void setUp() throws Exception {
+ classLoader = new URLClassLoader(new URL[0], ClassLoadingStrategy.BOOTSTRAP_LOADER);
+ }
+
+ @Test
+ public void testUnsafeInjection() throws Exception {
+ assertThat(new ClassInjector.UsingUnsafe(classLoader)
+ .inject(Collections.singletonMap(new TypeDescription.ForLoadedType(Foo.class), ClassFileExtraction.extract(Foo.class)))
+ .get(new TypeDescription.ForLoadedType(Foo.class)), notNullValue(Class.class));
+ assertThat(Class.forName(Foo.class.getName(), false, classLoader).getName(), is(Foo.class.getName()));
+ }
+
+ @Test
+ public void testAvailability() throws Exception {
+ assertThat(ClassInjector.UsingUnsafe.isAvailable(), is(true));
+ assertThat(new ClassInjector.UsingUnsafe.Dispatcher.Disabled(null).isAvailable(), is(false));
+ }
+
+ @Test(expected = RuntimeException.class)
+ public void testUnavailableThrowsException() throws Exception {
+ new ClassInjector.UsingUnsafe.Dispatcher.Disabled(new RuntimeException()).initialize();
+ }
+
+ @Test
+ public void testHelperMethods() throws Exception {
+ assertThat(ClassInjector.UsingUnsafe.ofBootstrapLoader(), is((ClassInjector) new ClassInjector.UsingUnsafe(ClassLoadingStrategy.BOOTSTRAP_LOADER)));
+ assertThat(ClassInjector.UsingUnsafe.ofClassPath(), is((ClassInjector) new ClassInjector.UsingUnsafe(ClassLoader.getSystemClassLoader())));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ClassInjector.UsingUnsafe.class).apply();
+ ObjectPropertyAssertion.of(ClassInjector.UsingUnsafe.Dispatcher.CreationAction.class).apply();
+ final Iterator<Method> methods = Arrays.asList(String.class.getDeclaredMethods()).iterator();
+ final Iterator<Field> fields = Arrays.asList(String.class.getDeclaredFields()).iterator();
+ ObjectPropertyAssertion.of(ClassInjector.UsingUnsafe.Dispatcher.Enabled.class).create(new ObjectPropertyAssertion.Creator<Method>() {
+ @Override
+ public Method create() {
+ return methods.next();
+ }
+ }).create(new ObjectPropertyAssertion.Creator<Field>() {
+ @Override
+ public Field create() {
+ return fields.next();
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(ClassInjector.UsingUnsafe.Dispatcher.Disabled.class).apply();
+ }
+
+ private static class Foo {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ClassLoadingStrategyDefaultTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ClassLoadingStrategyDefaultTest.java
new file mode 100644
index 0000000..265742a
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ClassLoadingStrategyDefaultTest.java
@@ -0,0 +1,265 @@
+package net.bytebuddy.dynamic.loading;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.ClassFileExtraction;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.AccessControlContext;
+import java.security.ProtectionDomain;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class ClassLoadingStrategyDefaultTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ private ClassLoader classLoader;
+
+ private TypeDescription typeDescription;
+
+ private Map<TypeDescription, byte[]> binaryRepresentations;
+
+ private ProtectionDomain protectionDomain;
+
+ @Mock
+ private PackageDefinitionStrategy packageDefinitionStrategy;
+
+ @Before
+ public void setUp() throws Exception {
+ classLoader = new URLClassLoader(new URL[0], null);
+ binaryRepresentations = new LinkedHashMap<TypeDescription, byte[]>();
+ typeDescription = new TypeDescription.ForLoadedType(Foo.class);
+ binaryRepresentations.put(typeDescription, ClassFileExtraction.extract(Foo.class));
+ protectionDomain = getClass().getProtectionDomain();
+ when(packageDefinitionStrategy.define(any(ClassLoader.class), any(String.class), any(String.class)))
+ .thenReturn(PackageDefinitionStrategy.Definition.Undefined.INSTANCE);
+ }
+
+ @Test
+ public void testWrapper() throws Exception {
+ Map<TypeDescription, Class<?>> loaded = ClassLoadingStrategy.Default.WRAPPER.load(classLoader, binaryRepresentations);
+ assertThat(loaded.size(), is(1));
+ Class<?> type = loaded.get(typeDescription);
+ assertThat(type.getClassLoader().getParent(), is(classLoader));
+ assertThat(type.getName(), is(Foo.class.getName()));
+ }
+
+ @Test
+ public void testWrapperPersistent() throws Exception {
+ Map<TypeDescription, Class<?>> loaded = ClassLoadingStrategy.Default.WRAPPER_PERSISTENT.load(classLoader, binaryRepresentations);
+ assertThat(loaded.size(), is(1));
+ Class<?> type = loaded.get(typeDescription);
+ assertThat(type.getClassLoader().getParent(), is(classLoader));
+ assertThat(type.getName(), is(Foo.class.getName()));
+ }
+
+ @Test
+ public void testChildFirst() throws Exception {
+ Map<TypeDescription, Class<?>> loaded = ClassLoadingStrategy.Default.CHILD_FIRST.load(classLoader, binaryRepresentations);
+ assertThat(loaded.size(), is(1));
+ Class<?> type = loaded.get(typeDescription);
+ assertThat(type.getClassLoader().getParent(), is(classLoader));
+ assertThat(type.getName(), is(Foo.class.getName()));
+ }
+
+ @Test
+ public void testChildFirstPersistent() throws Exception {
+ Map<TypeDescription, Class<?>> loaded = ClassLoadingStrategy.Default.CHILD_FIRST_PERSISTENT.load(classLoader, binaryRepresentations);
+ assertThat(loaded.size(), is(1));
+ Class<?> type = loaded.get(typeDescription);
+ assertThat(type.getClassLoader().getParent(), is(classLoader));
+ assertThat(type.getName(), is(Foo.class.getName()));
+ }
+
+ @Test
+ public void testInjection() throws Exception {
+ Map<TypeDescription, Class<?>> loaded = ClassLoadingStrategy.Default.INJECTION.load(classLoader, binaryRepresentations);
+ assertThat(loaded.size(), is(1));
+ Class<?> type = loaded.get(typeDescription);
+ assertThat(type.getClassLoader(), is(classLoader));
+ assertThat(type.getName(), is(Foo.class.getName()));
+ }
+
+ @Test
+ public void testWrapperWithProtectionDomain() throws Exception {
+ Map<TypeDescription, Class<?>> loaded = ClassLoadingStrategy.Default.WRAPPER.with(protectionDomain)
+ .load(classLoader, binaryRepresentations);
+ assertThat(loaded.size(), is(1));
+ Class<?> type = loaded.get(typeDescription);
+ assertThat(type.getClassLoader().getParent(), is(classLoader));
+ assertThat(type.getName(), is(Foo.class.getName()));
+ }
+
+ @Test
+ public void testWrapperPersistentWithProtectionDomain() throws Exception {
+ Map<TypeDescription, Class<?>> loaded = ClassLoadingStrategy.Default.WRAPPER_PERSISTENT.with(protectionDomain)
+ .load(classLoader, binaryRepresentations);
+ assertThat(loaded.size(), is(1));
+ Class<?> type = loaded.get(typeDescription);
+ assertThat(type.getClassLoader().getParent(), is(classLoader));
+ assertThat(type.getName(), is(Foo.class.getName()));
+ }
+
+ @Test
+ public void testChildFirstWithProtectionDomain() throws Exception {
+ Map<TypeDescription, Class<?>> loaded = ClassLoadingStrategy.Default.CHILD_FIRST.with(protectionDomain)
+ .load(classLoader, binaryRepresentations);
+ assertThat(loaded.size(), is(1));
+ Class<?> type = loaded.get(typeDescription);
+ assertThat(type.getClassLoader().getParent(), is(classLoader));
+ assertThat(type.getName(), is(Foo.class.getName()));
+ }
+
+ @Test
+ public void testChildFirstPersistentWithProtectionDomain() throws Exception {
+ Map<TypeDescription, Class<?>> loaded = ClassLoadingStrategy.Default.CHILD_FIRST_PERSISTENT.with(protectionDomain)
+ .load(classLoader, binaryRepresentations);
+ assertThat(loaded.size(), is(1));
+ Class<?> type = loaded.get(typeDescription);
+ assertThat(type.getClassLoader().getParent(), is(classLoader));
+ assertThat(type.getName(), is(Foo.class.getName()));
+ }
+
+ @Test
+ public void testInjectionWithProtectionDomain() throws Exception {
+ Map<TypeDescription, Class<?>> loaded = ClassLoadingStrategy.Default.INJECTION.with(protectionDomain)
+ .load(classLoader, binaryRepresentations);
+ assertThat(loaded.size(), is(1));
+ Class<?> type = loaded.get(typeDescription);
+ assertThat(type.getClassLoader(), is(classLoader));
+ assertThat(type.getName(), is(Foo.class.getName()));
+ }
+
+ @Test
+ public void testWrapperWithPackageDefiner() throws Exception {
+ Map<TypeDescription, Class<?>> loaded = ClassLoadingStrategy.Default.WRAPPER.with(packageDefinitionStrategy)
+ .load(classLoader, binaryRepresentations);
+ assertThat(loaded.size(), is(1));
+ Class<?> type = loaded.get(typeDescription);
+ assertThat(type.getClassLoader().getParent(), is(classLoader));
+ assertThat(type.getName(), is(Foo.class.getName()));
+ verify(packageDefinitionStrategy).define(any(ClassLoader.class), eq(Foo.class.getPackage().getName()), eq(Foo.class.getName()));
+ }
+
+ @Test
+ public void testWrapperPersistentWithPackageDefinitionStrategy() throws Exception {
+ Map<TypeDescription, Class<?>> loaded = ClassLoadingStrategy.Default.WRAPPER_PERSISTENT.with(packageDefinitionStrategy)
+ .load(classLoader, binaryRepresentations);
+ assertThat(loaded.size(), is(1));
+ Class<?> type = loaded.get(typeDescription);
+ assertThat(type.getClassLoader().getParent(), is(classLoader));
+ assertThat(type.getName(), is(Foo.class.getName()));
+ verify(packageDefinitionStrategy).define(any(ClassLoader.class), eq(Foo.class.getPackage().getName()), eq(Foo.class.getName()));
+ }
+
+ @Test
+ public void testChildFirstWithPackageDefinitionStrategy() throws Exception {
+ Map<TypeDescription, Class<?>> loaded = ClassLoadingStrategy.Default.CHILD_FIRST.with(packageDefinitionStrategy)
+ .load(classLoader, binaryRepresentations);
+ assertThat(loaded.size(), is(1));
+ Class<?> type = loaded.get(typeDescription);
+ assertThat(type.getClassLoader().getParent(), is(classLoader));
+ assertThat(type.getName(), is(Foo.class.getName()));
+ verify(packageDefinitionStrategy).define(any(ClassLoader.class), eq(Foo.class.getPackage().getName()), eq(Foo.class.getName()));
+ }
+
+ @Test
+ public void testChildFirstPersistentWithPackageDefinitionStrategy() throws Exception {
+ Map<TypeDescription, Class<?>> loaded = ClassLoadingStrategy.Default.CHILD_FIRST_PERSISTENT.with(packageDefinitionStrategy)
+ .load(classLoader, binaryRepresentations);
+ assertThat(loaded.size(), is(1));
+ Class<?> type = loaded.get(typeDescription);
+ assertThat(type.getClassLoader().getParent(), is(classLoader));
+ assertThat(type.getName(), is(Foo.class.getName()));
+ verify(packageDefinitionStrategy).define(any(ClassLoader.class), eq(Foo.class.getPackage().getName()), eq(Foo.class.getName()));
+ }
+
+ @Test
+ public void testInjectionWithPackageDefinitionStrategy() throws Exception {
+ Map<TypeDescription, Class<?>> loaded = ClassLoadingStrategy.Default.INJECTION.with(packageDefinitionStrategy)
+ .load(classLoader, binaryRepresentations);
+ assertThat(loaded.size(), is(1));
+ Class<?> type = loaded.get(typeDescription);
+ assertThat(type.getClassLoader(), is(classLoader));
+ assertThat(type.getName(), is(Foo.class.getName()));
+ verify(packageDefinitionStrategy).define(any(ClassLoader.class), eq(Foo.class.getPackage().getName()), eq(Foo.class.getName()));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testWrapperThrowsExceptionOnExistingClass() throws Exception {
+ ClassLoadingStrategy.Default.WRAPPER.load(ClassLoader.getSystemClassLoader(), Collections.singletonMap(TypeDescription.STRING, new byte[0]));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testWrapperPersistentThrowsExceptionOnExistingClass() throws Exception {
+ ClassLoadingStrategy.Default.WRAPPER_PERSISTENT.load(ClassLoader.getSystemClassLoader(), Collections.singletonMap(TypeDescription.STRING, new byte[0]));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testInjectionThrowsExceptionOnExistingClass() throws Exception {
+ ClassLoadingStrategy.Default.INJECTION.load(ClassLoader.getSystemClassLoader(), Collections.singletonMap(TypeDescription.STRING, new byte[0]));
+ }
+
+ @Test
+ public void testWrapperDoesNotThrowExceptionOnExistingClassWhenSupressed() throws Exception {
+ Map<TypeDescription, Class<?>> types = ClassLoadingStrategy.Default.WRAPPER
+ .allowExistingTypes()
+ .load(ClassLoader.getSystemClassLoader(), Collections.singletonMap(TypeDescription.STRING, new byte[0]));
+ assertThat(types.size(), is(1));
+ assertEquals(String.class, types.get(TypeDescription.STRING));
+ }
+
+ @Test
+ public void testWrapperPersistentDoesNotThrowExceptionOnExistingClassWhenSupressed() throws Exception {
+ Map<TypeDescription, Class<?>> types = ClassLoadingStrategy.Default.WRAPPER_PERSISTENT
+ .allowExistingTypes()
+ .load(ClassLoader.getSystemClassLoader(), Collections.singletonMap(TypeDescription.STRING, new byte[0]));
+ assertThat(types.size(), is(1));
+ assertEquals(String.class, types.get(TypeDescription.STRING));
+ }
+
+ @Test
+ public void testInjectionDoesNotThrowExceptionOnExistingClassWhenSupressed() throws Exception {
+ Map<TypeDescription, Class<?>> types = ClassLoadingStrategy.Default.INJECTION
+ .allowExistingTypes()
+ .load(ClassLoader.getSystemClassLoader(), Collections.singletonMap(TypeDescription.STRING, new byte[0]));
+ assertThat(types.size(), is(1));
+ assertEquals(String.class, types.get(TypeDescription.STRING));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ClassLoadingStrategy.Default.class).apply();
+ ObjectPropertyAssertion.of(ClassLoadingStrategy.Default.WrappingDispatcher.class).create(new ObjectPropertyAssertion.Creator<AccessControlContext>() {
+ @Override
+ public AccessControlContext create() {
+ return new AccessControlContext(new ProtectionDomain[]{mock(ProtectionDomain.class)});
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(ClassLoadingStrategy.Default.InjectionDispatcher.class).create(new ObjectPropertyAssertion.Creator<AccessControlContext>() {
+ @Override
+ public AccessControlContext create() {
+ return new AccessControlContext(new ProtectionDomain[]{mock(ProtectionDomain.class)});
+ }
+ }).skipSynthetic().apply();
+ }
+
+ private static class Foo {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ClassLoadingStrategyForBootstrapInjectionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ClassLoadingStrategyForBootstrapInjectionTest.java
new file mode 100644
index 0000000..12311a6
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ClassLoadingStrategyForBootstrapInjectionTest.java
@@ -0,0 +1,71 @@
+package net.bytebuddy.dynamic.loading;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.agent.ByteBuddyAgent;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.test.utility.AgentAttachmentRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.utility.RandomString;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+
+import java.io.File;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Collections;
+import java.util.Map;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class ClassLoadingStrategyForBootstrapInjectionTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ @Rule
+ public MethodRule agentAttachmentRule = new AgentAttachmentRule();
+
+ private File file;
+
+ @Before
+ public void setUp() throws Exception {
+ file = File.createTempFile(FOO, BAR);
+ assertThat(file.delete(), is(true));
+ file = new File(file.getParentFile(), RandomString.make());
+ assertThat(file.mkdir(), is(true));
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce
+ public void testBootstrapInjection() throws Exception {
+ ClassLoadingStrategy<ClassLoader> bootstrapStrategy = new ClassLoadingStrategy.ForBootstrapInjection(ByteBuddyAgent.install(), file);
+ String name = FOO + RandomString.make();
+ DynamicType dynamicType = new ByteBuddy().subclass(Object.class).name(name).make();
+ Map<TypeDescription, Class<?>> loaded = bootstrapStrategy.load(ClassLoadingStrategy.BOOTSTRAP_LOADER, Collections.singletonMap(dynamicType.getTypeDescription(), dynamicType.getBytes()));
+ assertThat(loaded.size(), is(1));
+ assertThat(loaded.get(dynamicType.getTypeDescription()).getName(), is(name));
+ assertThat(loaded.get(dynamicType.getTypeDescription()).getClassLoader(), nullValue(ClassLoader.class));
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce
+ public void testClassLoaderInjection() throws Exception {
+ ClassLoadingStrategy<ClassLoader> bootstrapStrategy = new ClassLoadingStrategy.ForBootstrapInjection(ByteBuddyAgent.install(), file);
+ String name = BAR + RandomString.make();
+ ClassLoader classLoader = new URLClassLoader(new URL[0], null);
+ DynamicType dynamicType = new ByteBuddy().subclass(Object.class).name(name).make();
+ Map<TypeDescription, Class<?>> loaded = bootstrapStrategy.load(classLoader, Collections.singletonMap(dynamicType.getTypeDescription(), dynamicType.getBytes()));
+ assertThat(loaded.size(), is(1));
+ assertThat(loaded.get(dynamicType.getTypeDescription()).getName(), is(name));
+ assertThat(loaded.get(dynamicType.getTypeDescription()).getClassLoader(), is(classLoader));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ClassLoadingStrategy.ForBootstrapInjection.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ClassLoadingStrategyForUnsafeInjectionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ClassLoadingStrategyForUnsafeInjectionTest.java
new file mode 100644
index 0000000..07466ce
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ClassLoadingStrategyForUnsafeInjectionTest.java
@@ -0,0 +1,74 @@
+package net.bytebuddy.dynamic.loading;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.ClassFileExtraction;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.ProtectionDomain;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class ClassLoadingStrategyForUnsafeInjectionTest {
+
+ private ClassLoader classLoader;
+
+ private TypeDescription typeDescription;
+
+ private Map<TypeDescription, byte[]> binaryRepresentations;
+
+ private ProtectionDomain protectionDomain;
+
+ @Before
+ public void setUp() throws Exception {
+ classLoader = new URLClassLoader(new URL[0], null);
+ binaryRepresentations = new LinkedHashMap<TypeDescription, byte[]>();
+ typeDescription = new TypeDescription.ForLoadedType(Foo.class);
+ binaryRepresentations.put(typeDescription, ClassFileExtraction.extract(Foo.class));
+ protectionDomain = getClass().getProtectionDomain();
+ }
+
+ @Test
+ public void testInjection() throws Exception {
+ Map<TypeDescription, Class<?>> loaded = new ClassLoadingStrategy.ForUnsafeInjection().load(classLoader, binaryRepresentations);
+ assertThat(loaded.size(), is(1));
+ Class<?> type = loaded.get(typeDescription);
+ assertThat(type.getClassLoader(), is(classLoader));
+ assertThat(type.getName(), is(Foo.class.getName()));
+ }
+
+ @Test
+ public void testInjectionWithProtectionDomain() throws Exception {
+ Map<TypeDescription, Class<?>> loaded = new ClassLoadingStrategy.ForUnsafeInjection(protectionDomain)
+ .load(classLoader, binaryRepresentations);
+ assertThat(loaded.size(), is(1));
+ Class<?> type = loaded.get(typeDescription);
+ assertThat(type.getClassLoader(), is(classLoader));
+ assertThat(type.getName(), is(Foo.class.getName()));
+ }
+
+ @Test
+ public void testInjectionDoesNotThrowExceptionOnExistingClass() throws Exception {
+ Map<TypeDescription, Class<?>> types = new ClassLoadingStrategy.ForUnsafeInjection(protectionDomain)
+ .load(ClassLoader.getSystemClassLoader(), Collections.singletonMap(TypeDescription.STRING, new byte[0]));
+ assertThat(types.size(), is(1));
+ assertEquals(String.class, types.get(TypeDescription.STRING));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ClassLoadingStrategy.ForUnsafeInjection.class).apply();
+ }
+
+ private static class Foo {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ClassReloadingStrategyTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ClassReloadingStrategyTest.java
new file mode 100644
index 0000000..7cfa2c0
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/ClassReloadingStrategyTest.java
@@ -0,0 +1,315 @@
+package net.bytebuddy.dynamic.loading;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.agent.ByteBuddyAgent;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.implementation.FixedValue;
+import net.bytebuddy.test.utility.AgentAttachmentRule;
+import net.bytebuddy.test.utility.ClassFileExtraction;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.utility.RandomString;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+import org.mockito.ArgumentCaptor;
+
+import java.lang.instrument.ClassDefinition;
+import java.lang.instrument.Instrumentation;
+import java.security.ProtectionDomain;
+import java.util.Collections;
+import java.util.concurrent.Callable;
+
+import static junit.framework.TestCase.assertEquals;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class ClassReloadingStrategyTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ private static final String LAMBDA_SAMPLE_FACTORY = "net.bytebuddy.test.precompiled.LambdaSampleFactory";
+
+ @Rule
+ public MethodRule agentAttachmentRule = new AgentAttachmentRule();
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ @Test
+ @AgentAttachmentRule.Enforce(redefinesClasses = true)
+ public void testStrategyCreation() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ assertThat(ClassReloadingStrategy.fromInstalledAgent(), notNullValue());
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce(redefinesClasses = true)
+ public void testFromAgentClassReloadingStrategy() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ Foo foo = new Foo();
+ assertThat(foo.foo(), is(FOO));
+ ClassReloadingStrategy classReloadingStrategy = ClassReloadingStrategy.fromInstalledAgent();
+ new ByteBuddy()
+ .redefine(Foo.class)
+ .method(named(FOO))
+ .intercept(FixedValue.value(BAR))
+ .make()
+ .load(Foo.class.getClassLoader(), classReloadingStrategy);
+ try {
+ assertThat(foo.foo(), is(BAR));
+ } finally {
+ classReloadingStrategy.reset(Foo.class);
+ assertThat(foo.foo(), is(FOO));
+ }
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce(redefinesClasses = true)
+ public void testFromAgentClassWithAuxiliaryReloadingStrategy() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ Foo foo = new Foo();
+ assertThat(foo.foo(), is(FOO));
+ ClassReloadingStrategy classReloadingStrategy = ClassReloadingStrategy.fromInstalledAgent();
+ String randomName = FOO + RandomString.make();
+ new ByteBuddy()
+ .redefine(Foo.class)
+ .method(named(FOO))
+ .intercept(FixedValue.value(BAR))
+ .make()
+ .include(new ByteBuddy().subclass(Object.class).name(randomName).make())
+ .load(Foo.class.getClassLoader(), classReloadingStrategy);
+ try {
+ assertThat(foo.foo(), is(BAR));
+ } finally {
+ classReloadingStrategy.reset(Foo.class);
+ assertThat(foo.foo(), is(FOO));
+ }
+ assertThat(Class.forName(randomName), notNullValue(Class.class));
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce(redefinesClasses = true)
+ public void testClassRedefinitionRenamingWithStackMapFrames() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ ClassReloadingStrategy classReloadingStrategy = ClassReloadingStrategy.fromInstalledAgent();
+ Bar bar = new Bar();
+ new ByteBuddy().redefine(Qux.class)
+ .name(Bar.class.getName())
+ .make()
+ .load(Bar.class.getClassLoader(), classReloadingStrategy);
+ try {
+ assertThat(bar.foo(), is(BAR));
+ } finally {
+ classReloadingStrategy.reset(Bar.class);
+ assertThat(bar.foo(), is(FOO));
+ }
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce(redefinesClasses = true)
+ public void testRedefinitionReloadingStrategy() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ Foo foo = new Foo();
+ assertThat(foo.foo(), is(FOO));
+ ClassReloadingStrategy classReloadingStrategy = new ClassReloadingStrategy(ByteBuddyAgent.getInstrumentation(), ClassReloadingStrategy.Strategy.REDEFINITION);
+ new ByteBuddy()
+ .redefine(Foo.class)
+ .method(named(FOO))
+ .intercept(FixedValue.value(BAR))
+ .make()
+ .load(Foo.class.getClassLoader(), classReloadingStrategy);
+ try {
+ assertThat(foo.foo(), is(BAR));
+ } finally {
+ classReloadingStrategy.reset(Foo.class);
+ assertThat(foo.foo(), is(FOO));
+ }
+ }
+
+ @Test
+ @AgentAttachmentRule.Enforce(retransformsClasses = true)
+ public void testRetransformationReloadingStrategy() throws Exception {
+ assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
+ Foo foo = new Foo();
+ assertThat(foo.foo(), is(FOO));
+ ClassReloadingStrategy classReloadingStrategy = new ClassReloadingStrategy(ByteBuddyAgent.getInstrumentation(), ClassReloadingStrategy.Strategy.RETRANSFORMATION);
+ new ByteBuddy()
+ .redefine(Foo.class)
+ .method(named(FOO))
+ .intercept(FixedValue.value(BAR))
+ .make()
+ .load(Foo.class.getClassLoader(), classReloadingStrategy);
+ try {
+ assertThat(foo.foo(), is(BAR));
+ } finally {
+ classReloadingStrategy.reset(Foo.class);
+ assertThat(foo.foo(), is(FOO));
+ }
+ }
+
+ @Test
+ public void testPreregisteredType() throws Exception {
+ Instrumentation instrumentation = mock(Instrumentation.class);
+ ClassLoader classLoader = mock(ClassLoader.class);
+ when(instrumentation.isRedefineClassesSupported()).thenReturn(true);
+ when(instrumentation.getInitiatedClasses(classLoader)).thenReturn(new Class<?>[0]);
+ ClassReloadingStrategy classReloadingStrategy = ClassReloadingStrategy.of(instrumentation).preregistered(Object.class);
+ ArgumentCaptor<ClassDefinition> classDefinition = ArgumentCaptor.forClass(ClassDefinition.class);
+ classReloadingStrategy.load(classLoader, Collections.singletonMap(TypeDescription.OBJECT, new byte[]{1, 2, 3}));
+ verify(instrumentation).redefineClasses(classDefinition.capture());
+ assertEquals(Object.class, classDefinition.getValue().getDefinitionClass());
+ assertThat(classDefinition.getValue().getDefinitionClassFile(), is(new byte[]{1, 2, 3}));
+ }
+
+ @Test
+ public void testRetransformationDiscovery() throws Exception {
+ Instrumentation instrumentation = mock(Instrumentation.class);
+ when(instrumentation.isRetransformClassesSupported()).thenReturn(true);
+ assertThat(ClassReloadingStrategy.of(instrumentation), notNullValue(ClassReloadingStrategy.class));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNonCompatible() throws Exception {
+ ClassReloadingStrategy.of(mock(Instrumentation.class));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNoRedefinition() throws Exception {
+ new ClassReloadingStrategy(mock(Instrumentation.class), ClassReloadingStrategy.Strategy.REDEFINITION);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNoRetransformation() throws Exception {
+ new ClassReloadingStrategy(mock(Instrumentation.class), ClassReloadingStrategy.Strategy.RETRANSFORMATION);
+ }
+
+ @Test
+ public void testResetNotSupported() throws Exception {
+ Instrumentation instrumentation = mock(Instrumentation.class);
+ when(instrumentation.isRetransformClassesSupported()).thenReturn(true);
+ new ClassReloadingStrategy(instrumentation, ClassReloadingStrategy.Strategy.RETRANSFORMATION).reset();
+ }
+
+ @Test
+ public void testEngineSelfReport() throws Exception {
+ assertThat(ClassReloadingStrategy.Strategy.REDEFINITION.isRedefinition(), is(true));
+ assertThat(ClassReloadingStrategy.Strategy.RETRANSFORMATION.isRedefinition(), is(false));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @AgentAttachmentRule.Enforce(retransformsClasses = true)
+ public void testAnonymousType() throws Exception {
+ ClassLoader classLoader = new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER,
+ ClassFileExtraction.of(Class.forName(LAMBDA_SAMPLE_FACTORY)),
+ ByteArrayClassLoader.PersistenceHandler.MANIFEST);
+ Instrumentation instrumentation = ByteBuddyAgent.install();
+ Class<?> factory = classLoader.loadClass(LAMBDA_SAMPLE_FACTORY);
+ @SuppressWarnings("unchecked")
+ Callable<String> instance = (Callable<String>) factory.getDeclaredMethod("nonCapturing").invoke(factory.getDeclaredConstructor().newInstance());
+ // Anonymous types can only be reset to their original format, if a retransformation is applied.
+ ClassReloadingStrategy classReloadingStrategy = new ClassReloadingStrategy(instrumentation,
+ ClassReloadingStrategy.Strategy.RETRANSFORMATION).preregistered(instance.getClass());
+ ClassFileLocator classFileLocator = ClassFileLocator.AgentBased.of(instrumentation, instance.getClass());
+ try {
+ assertThat(instance.call(), is(FOO));
+ new ByteBuddy()
+ .redefine(instance.getClass(), classFileLocator)
+ .method(named("call"))
+ .intercept(FixedValue.value(BAR))
+ .make()
+ .load(instance.getClass().getClassLoader(), classReloadingStrategy);
+ assertThat(instance.call(), is(BAR));
+ } finally {
+ classReloadingStrategy.reset(classFileLocator, instance.getClass());
+ assertThat(instance.call(), is(FOO));
+ }
+ }
+
+ @Test
+ public void testResetEmptyNoEffectImplicitLocator() throws Exception {
+ Instrumentation instrumentation = mock(Instrumentation.class);
+ when(instrumentation.isRedefineClassesSupported()).thenReturn(true);
+ ClassReloadingStrategy.of(instrumentation).reset();
+ verify(instrumentation, times(2)).isRedefineClassesSupported();
+ verifyNoMoreInteractions(instrumentation);
+ }
+
+ @Test
+ public void testResetEmptyNoEffect() throws Exception {
+ Instrumentation instrumentation = mock(Instrumentation.class);
+ ClassFileLocator classFileLocator = mock(ClassFileLocator.class);
+ when(instrumentation.isRedefineClassesSupported()).thenReturn(true);
+ ClassReloadingStrategy.of(instrumentation).reset(classFileLocator);
+ verify(instrumentation, times(2)).isRedefineClassesSupported();
+ verifyNoMoreInteractions(instrumentation);
+ verifyZeroInteractions(classFileLocator);
+ }
+
+ @Test
+ public void testTransformerHandlesNullValue() throws Exception {
+ assertThat(new ClassReloadingStrategy.Strategy.ClassRedefinitionTransformer(Collections.<Class<?>, ClassDefinition>emptyMap()).transform(mock(ClassLoader.class),
+ FOO,
+ Object.class,
+ mock(ProtectionDomain.class),
+ new byte[0]), nullValue(byte[].class));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ClassReloadingStrategy.class).refine(new ObjectPropertyAssertion.Refinement<Instrumentation>() {
+ @Override
+ public void apply(Instrumentation mock) {
+ when(mock.isRedefineClassesSupported()).thenReturn(true);
+ when(mock.isRetransformClassesSupported()).thenReturn(true);
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(ClassReloadingStrategy.BootstrapInjection.Enabled.class).apply();
+ ObjectPropertyAssertion.of(ClassReloadingStrategy.Strategy.class).apply();
+ ObjectPropertyAssertion.of(ClassReloadingStrategy.Strategy.ClassResettingTransformer.class).apply();
+ ObjectPropertyAssertion.of(ClassReloadingStrategy.BootstrapInjection.Enabled.class).apply();
+ ObjectPropertyAssertion.of(ClassReloadingStrategy.BootstrapInjection.Disabled.class).apply();
+ }
+
+ @SuppressWarnings("unused")
+ public static class Foo {
+
+ /**
+ * Padding to increase class file size.
+ */
+ private long a1, a2, a3, a4, a5, a6, a7, a8, a9, a10,
+ b2, b3, b4, b5, b6, b7, b8, b9, b10,
+ c1, c2, c3, c4, c5, c6, c7, c8, c9, c10,
+ d1, d2, d3, d4, d5, d6, d7, d8, d9, d10;
+
+ public String foo() {
+ return FOO;
+ }
+ }
+
+ public static class Bar {
+
+ @SuppressWarnings("unused")
+ public String foo() {
+ Bar bar = new Bar();
+ return Math.random() < 0
+ ? FOO
+ : FOO;
+ }
+ }
+
+ public static class Qux {
+
+ @SuppressWarnings("unused")
+ public String foo() {
+ Qux qux = new Qux();
+ return Math.random() < 0
+ ? BAR
+ : BAR;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/InjectionClassLoaderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/InjectionClassLoaderTest.java
new file mode 100644
index 0000000..4c38808
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/InjectionClassLoaderTest.java
@@ -0,0 +1,50 @@
+package net.bytebuddy.dynamic.loading;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class InjectionClassLoaderTest {
+
+ private static final String FOO = "foo";
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testBootrap() throws Exception {
+ InjectionClassLoader.Strategy.INSTANCE.load(null, Collections.<TypeDescription, byte[]>emptyMap());
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testInjection() throws Exception {
+ InjectionClassLoader classLoader = mock(InjectionClassLoader.class);
+ TypeDescription typeDescription = mock(TypeDescription.class);
+ byte[] binaryRepresentation = new byte[0];
+ when(typeDescription.getName()).thenReturn(FOO);
+ when(classLoader.defineClass(FOO, binaryRepresentation)).thenReturn((Class) Object.class);
+ assertThat(InjectionClassLoader.Strategy.INSTANCE.load(classLoader, Collections.singletonMap(typeDescription, binaryRepresentation)),
+ is(Collections.<TypeDescription, Class<?>>singletonMap(typeDescription, Object.class)));
+ verify(classLoader).defineClass(FOO, binaryRepresentation);
+ verifyNoMoreInteractions(classLoader);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testInjectionException() throws Exception {
+ InjectionClassLoader classLoader = mock(InjectionClassLoader.class);
+ TypeDescription typeDescription = mock(TypeDescription.class);
+ byte[] binaryRepresentation = new byte[0];
+ when(typeDescription.getName()).thenReturn(FOO);
+ when(classLoader.defineClass(FOO, binaryRepresentation)).thenThrow(new ClassNotFoundException(FOO));
+ InjectionClassLoader.Strategy.INSTANCE.load(classLoader, Collections.singletonMap(typeDescription, binaryRepresentation));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(InjectionClassLoader.Strategy.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/MultipleParentClassLoaderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/MultipleParentClassLoaderTest.java
new file mode 100644
index 0000000..d8eaf5e
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/MultipleParentClassLoaderTest.java
@@ -0,0 +1,226 @@
+package net.bytebuddy.dynamic.loading;
+
+import net.bytebuddy.test.utility.IntegrationRule;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.hamcrest.CoreMatchers;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.NoSuchElementException;
+
+import static net.bytebuddy.matcher.ElementMatchers.isBootstrapClassLoader;
+import static net.bytebuddy.matcher.ElementMatchers.not;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class MultipleParentClassLoaderTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux", BAZ = "baz", SCHEME = "http://";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Rule
+ public MethodRule integrationRule = new IntegrationRule();
+
+ @Mock
+ private ClassLoader first, second;
+
+ private URL fooUrl, barFirstUrl, barSecondUrl, quxUrl;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ when(first.loadClass(FOO)).thenReturn((Class) Foo.class);
+ when(first.loadClass(BAR)).thenReturn((Class) BarFirst.class);
+ when(first.loadClass(QUX)).thenThrow(new ClassNotFoundException());
+ when(first.loadClass(BAZ)).thenThrow(new ClassNotFoundException());
+ when(second.loadClass(BAR)).thenReturn((Class) BarSecond.class);
+ when(second.loadClass(QUX)).thenReturn((Class) Qux.class);
+ when(second.loadClass(BAZ)).thenThrow(new ClassNotFoundException());
+ fooUrl = new URL(SCHEME + FOO);
+ barFirstUrl = new URL(SCHEME + BAR);
+ barSecondUrl = new URL(SCHEME + BAZ);
+ quxUrl = new URL(SCHEME + QUX);
+ when(first.getResource(FOO)).thenReturn(fooUrl);
+ when(first.getResource(BAR)).thenReturn(barFirstUrl);
+ when(second.getResource(BAR)).thenReturn(barSecondUrl);
+ when(second.getResource(QUX)).thenReturn(quxUrl);
+ when(first.getResources(FOO)).thenReturn(new SingleElementEnumeration(fooUrl));
+ when(first.getResources(BAR)).thenReturn(new SingleElementEnumeration(barFirstUrl));
+ when(second.getResources(BAR)).thenReturn(new SingleElementEnumeration(barSecondUrl));
+ when(second.getResources(QUX)).thenReturn(new SingleElementEnumeration(quxUrl));
+ }
+
+ @Test
+ public void testSingleParentReturnsOriginal() throws Exception {
+ assertThat(new MultipleParentClassLoader.Builder()
+ .append(getClass().getClassLoader(), getClass().getClassLoader())
+ .build(), is(ClassLoader.getSystemClassLoader()));
+ }
+
+ @Test
+ public void testSingleParentReturnsOriginalChained() throws Exception {
+ assertThat(new MultipleParentClassLoader.Builder()
+ .append(ClassLoader.getSystemClassLoader())
+ .append(ClassLoader.getSystemClassLoader())
+ .build(), is(ClassLoader.getSystemClassLoader()));
+ }
+
+ @Test
+ public void testClassLoaderFilter() throws Exception {
+ assertThat(new MultipleParentClassLoader.Builder()
+ .append(getClass().getClassLoader(), null)
+ .filter(not(isBootstrapClassLoader()))
+ .build(), is(getClass().getClassLoader()));
+ }
+
+ @Test
+ public void testMultipleParentClassLoading() throws Exception {
+ ClassLoader classLoader = new MultipleParentClassLoader.Builder().append(first, second, null).build();
+ assertThat(classLoader.loadClass(FOO), CoreMatchers.<Class<?>>is(Foo.class));
+ assertThat(classLoader.loadClass(BAR), CoreMatchers.<Class<?>>is(BarFirst.class));
+ assertThat(classLoader.loadClass(QUX), CoreMatchers.<Class<?>>is(Qux.class));
+ verify(first).loadClass(FOO);
+ verify(first).loadClass(BAR);
+ verify(first).loadClass(QUX);
+ verifyNoMoreInteractions(first);
+ verify(second).loadClass(QUX);
+ verifyNoMoreInteractions(second);
+ }
+
+ @Test(expected = ClassNotFoundException.class)
+ public void testMultipleParentClassLoadingNotFound() throws Exception {
+ new MultipleParentClassLoader.Builder().append(first, second, null).build().loadClass(BAZ);
+ }
+
+ @Test
+ @IntegrationRule.Enforce
+ public void testMultipleParentURL() throws Exception {
+ ClassLoader classLoader = new MultipleParentClassLoader.Builder().append(first, second, null).build();
+ assertThat(classLoader.getResource(FOO), is(fooUrl));
+ assertThat(classLoader.getResource(BAR), is(barFirstUrl));
+ assertThat(classLoader.getResource(QUX), is(quxUrl));
+ verify(first).getResource(FOO);
+ verify(first).getResource(BAR);
+ verify(first).getResource(QUX);
+ verifyNoMoreInteractions(first);
+ verify(second).getResource(QUX);
+ verifyNoMoreInteractions(second);
+ }
+
+ @Test
+ public void testMultipleParentURLNotFound() throws Exception {
+ assertThat(new MultipleParentClassLoader.Builder().append(first, second, null).build().getResource(BAZ), nullValue(URL.class));
+ }
+
+ @Test
+ @IntegrationRule.Enforce
+ public void testMultipleParentEnumerationURL() throws Exception {
+ ClassLoader classLoader = new MultipleParentClassLoader.Builder().append(first, second, null).build();
+ Enumeration<URL> foo = classLoader.getResources(FOO);
+ assertThat(foo.hasMoreElements(), is(true));
+ assertThat(foo.nextElement(), is(fooUrl));
+ assertThat(foo.hasMoreElements(), is(false));
+ Enumeration<URL> bar = classLoader.getResources(BAR);
+ assertThat(bar.hasMoreElements(), is(true));
+ assertThat(bar.nextElement(), is(barFirstUrl));
+ assertThat(bar.hasMoreElements(), is(true));
+ assertThat(bar.nextElement(), is(barSecondUrl));
+ assertThat(bar.hasMoreElements(), is(false));
+ Enumeration<URL> qux = classLoader.getResources(QUX);
+ assertThat(qux.hasMoreElements(), is(true));
+ assertThat(qux.nextElement(), is(quxUrl));
+ assertThat(qux.hasMoreElements(), is(false));
+ }
+
+ @Test(expected = NoSuchElementException.class)
+ public void testMultipleParentEnumerationNotFound() throws Exception {
+ ClassLoader classLoader = new MultipleParentClassLoader.Builder().append(first, second, null).build();
+ Enumeration<URL> enumeration = classLoader.getResources(BAZ);
+ assertThat(enumeration.hasMoreElements(), is(false));
+ enumeration.nextElement();
+ }
+
+ @Test
+ public void testMultipleParentClassLoaderExplicitParentOnly() throws Exception {
+ assertThat(new MultipleParentClassLoader.Builder().build(first), is(first));
+ }
+
+ @Test
+ public void testMultipleParentClassLoaderExplicitParentPreIncluded() throws Exception {
+ assertThat(new MultipleParentClassLoader.Builder().append(first).build(first), is(first));
+ }
+
+ @Test
+ public void testMultipleParentClassLoaderExplicitParentPreIncludedWithOther() throws Exception {
+ ClassLoader classLoader = new MultipleParentClassLoader.Builder().append(first, second).build(first);;
+ assertThat(classLoader, CoreMatchers.not(first));
+ assertThat(classLoader, CoreMatchers.not(second));
+ assertThat(classLoader.getParent(), is(first));
+ }
+
+ @Test
+ public void testMultipleParentClassLoaderExplicitParentNotPreIncludedWithOther() throws Exception {
+ ClassLoader classLoader = new MultipleParentClassLoader.Builder().append(second).build(first);;
+ assertThat(classLoader, CoreMatchers.not(second));
+ assertThat(classLoader.getParent(), is(first));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MultipleParentClassLoader.Builder.class).apply();
+ }
+
+ public static class Foo {
+ /* empty */
+ }
+
+ public static class BarFirst {
+ /* empty */
+ }
+
+ public static class BarSecond {
+ /* empty */
+ }
+
+ public static class Qux {
+ /* empty */
+ }
+
+ private static class SingleElementEnumeration implements Enumeration<URL> {
+
+ private URL element;
+
+ public SingleElementEnumeration(URL element) {
+ this.element = element;
+ }
+
+ @Override
+ public boolean hasMoreElements() {
+ return element != null;
+ }
+
+ @Override
+ public URL nextElement() {
+ if (!hasMoreElements()) {
+ throw new AssertionError();
+ }
+ try {
+ return element;
+ } finally {
+ element = null;
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/NoOpClassFileTransformerTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/NoOpClassFileTransformerTest.java
new file mode 100644
index 0000000..7061af5
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/NoOpClassFileTransformerTest.java
@@ -0,0 +1,20 @@
+package net.bytebuddy.dynamic.loading;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class NoOpClassFileTransformerTest {
+
+ @Test
+ public void testNoTransformation() throws Exception {
+ assertThat(NoOpClassFileTransformer.INSTANCE.transform(mock(ClassLoader.class),
+ "foo",
+ null,
+ null,
+ new byte[0]), nullValue(byte[].class));
+ }
+}
\ No newline at end of file
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/PackageDefinitionStrategyTypeSimpleTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/PackageDefinitionStrategyTypeSimpleTest.java
new file mode 100644
index 0000000..698decf
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/PackageDefinitionStrategyTypeSimpleTest.java
@@ -0,0 +1,136 @@
+package net.bytebuddy.dynamic.loading;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.test.utility.IntegrationRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+
+import java.io.File;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class PackageDefinitionStrategyTypeSimpleTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux", BAZ = "baz";
+
+ @Rule
+ public MethodRule integrationRule = new IntegrationRule();
+
+ private PackageDefinitionStrategy.Definition definition;
+
+ private URL sealBase;
+
+ @Before
+ public void setUp() throws Exception {
+ sealBase = new URL("file://foo");
+ definition = new PackageDefinitionStrategy.Definition.Simple(FOO, BAR, QUX, BAZ, FOO + BAR, QUX + BAZ, sealBase);
+ }
+
+ @Test
+ public void testIsDefined() throws Exception {
+ assertThat(definition.isDefined(), is(true));
+ }
+
+ @Test
+ public void testSpecificationTitle() throws Exception {
+ assertThat(definition.getSpecificationTitle(), is(FOO));
+ }
+
+ @Test
+ public void testSpecificationVersion() throws Exception {
+ assertThat(definition.getSpecificationVersion(), is(BAR));
+ }
+
+ @Test
+ public void testSpecificationVendor() throws Exception {
+ assertThat(definition.getSpecificationVendor(), is(QUX));
+ }
+
+ @Test
+ public void testImplementationTitle() throws Exception {
+ assertThat(definition.getImplementationTitle(), is(BAZ));
+ }
+
+ @Test
+ public void testImplementationVersion() throws Exception {
+ assertThat(definition.getImplementationVersion(), is(FOO + BAR));
+ }
+
+ @Test
+ public void testImplementationVendor() throws Exception {
+ assertThat(definition.getImplementationVendor(), is(QUX + BAZ));
+ }
+
+ @Test
+ @IntegrationRule.Enforce
+ public void testSealBase() throws Exception {
+ assertThat(definition.getSealBase(), is(sealBase));
+ }
+
+ @Test
+ public void testSealedNotCompatibleToUnsealed() throws Exception {
+ assertThat(definition.isCompatibleTo(getClass().getPackage()), is(false));
+ }
+
+ @Test
+ public void testNonSealedIsCompatibleToUnsealed() throws Exception {
+ assertThat(new PackageDefinitionStrategy.Definition.Simple(FOO, BAR, QUX, BAZ, FOO + BAR, QUX + BAZ, null)
+ .isCompatibleTo(getClass().getPackage()), is(true));
+ }
+
+ @Test
+ public void testNonSealedIsCompatibleToSealed() throws Exception {
+ File file = File.createTempFile(FOO, BAR);
+ try {
+ Manifest manifest = new Manifest();
+ manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
+ manifest.getMainAttributes().put(Attributes.Name.SEALED, Boolean.TRUE.toString());
+ URL url = new ByteBuddy().subclass(Object.class).name("foo.Bar").make().toJar(file, manifest).toURI().toURL();
+ ClassLoader classLoader = new URLClassLoader(new URL[]{url}, null);
+ Package definedPackage = classLoader.loadClass("foo.Bar").getPackage();
+ assertThat(new PackageDefinitionStrategy.Definition.Simple(FOO, BAR, QUX, BAZ, FOO + BAR, QUX + BAZ, null)
+ .isCompatibleTo(definedPackage), is(false));
+ } finally {
+ file.deleteOnExit();
+ }
+ }
+
+ @Test
+ public void testSealedIsCompatibleToSealed() throws Exception {
+ File file = File.createTempFile(FOO, BAR);
+ try {
+ Manifest manifest = new Manifest();
+ manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
+ manifest.getMainAttributes().put(Attributes.Name.SEALED, Boolean.TRUE.toString());
+ URL url = new ByteBuddy().subclass(Object.class).name("foo.Bar").make().toJar(file, manifest).toURI().toURL();
+ ClassLoader classLoader = new URLClassLoader(new URL[]{url}, null);
+ Package definedPackage = classLoader.loadClass("foo.Bar").getPackage();
+ assertThat(new PackageDefinitionStrategy.Definition.Simple(FOO, BAR, QUX, BAZ, FOO + BAR, QUX + BAZ, url)
+ .isCompatibleTo(definedPackage), is(true));
+ } finally {
+ file.deleteOnExit();
+ }
+ }
+
+ @Test
+ @IntegrationRule.Enforce
+ public void testObjectProperties() throws Exception {
+ final Iterator<URL> urls = Arrays.asList(new URL("file:/foo"), new URL("file:/bar")).iterator();
+ ObjectPropertyAssertion.of(PackageDefinitionStrategy.Definition.Simple.class).create(new ObjectPropertyAssertion.Creator<URL>() {
+ @Override
+ public URL create() {
+ return urls.next();
+ }
+ }).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/PackageDefinitionStrategyTypeTrivialTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/PackageDefinitionStrategyTypeTrivialTest.java
new file mode 100644
index 0000000..6dcc6cd
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/PackageDefinitionStrategyTypeTrivialTest.java
@@ -0,0 +1,63 @@
+package net.bytebuddy.dynamic.loading;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.net.URL;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class PackageDefinitionStrategyTypeTrivialTest {
+
+ @Test
+ public void testIsDefined() throws Exception {
+ assertThat(PackageDefinitionStrategy.Definition.Trivial.INSTANCE.isDefined(), is(true));
+ }
+
+ @Test
+ public void testSpecificationTitle() throws Exception {
+ assertThat(PackageDefinitionStrategy.Definition.Trivial.INSTANCE.getSpecificationTitle(), nullValue(String.class));
+ }
+
+ @Test
+ public void testSpecificationVersion() throws Exception {
+ assertThat(PackageDefinitionStrategy.Definition.Trivial.INSTANCE.getSpecificationVersion(), nullValue(String.class));
+ }
+
+ @Test
+ public void testSpecificationVendor() throws Exception {
+ assertThat(PackageDefinitionStrategy.Definition.Trivial.INSTANCE.getSpecificationVendor(), nullValue(String.class));
+ }
+
+ @Test
+ public void testImplementationTitle() throws Exception {
+ assertThat(PackageDefinitionStrategy.Definition.Trivial.INSTANCE.getImplementationTitle(), nullValue(String.class));
+ }
+
+ @Test
+ public void testImplementationVersion() throws Exception {
+ assertThat(PackageDefinitionStrategy.Definition.Trivial.INSTANCE.getImplementationVersion(), nullValue(String.class));
+ }
+
+ @Test
+ public void testImplementationVendor() throws Exception {
+ assertThat(PackageDefinitionStrategy.Definition.Trivial.INSTANCE.getImplementationVendor(), nullValue(String.class));
+ }
+
+ @Test
+ public void testSealBase() throws Exception {
+ assertThat(PackageDefinitionStrategy.Definition.Trivial.INSTANCE.getSealBase(), nullValue(URL.class));
+ }
+
+ @Test
+ public void testIsCompatibleTo() throws Exception {
+ assertThat(PackageDefinitionStrategy.Definition.Trivial.INSTANCE.isCompatibleTo(getClass().getPackage()), is(true));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(PackageDefinitionStrategy.Definition.Trivial.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/PackageDefinitionStrategyTypeUndefinedTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/PackageDefinitionStrategyTypeUndefinedTest.java
new file mode 100644
index 0000000..af1900e
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/PackageDefinitionStrategyTypeUndefinedTest.java
@@ -0,0 +1,60 @@
+package net.bytebuddy.dynamic.loading;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class PackageDefinitionStrategyTypeUndefinedTest {
+
+ @Test
+ public void testIsUndefined() throws Exception {
+ assertThat(PackageDefinitionStrategy.Definition.Undefined.INSTANCE.isDefined(), is(false));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testSpecificationTitleThrowsException() throws Exception {
+ PackageDefinitionStrategy.Definition.Undefined.INSTANCE.getSpecificationTitle();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testSpecificationVersionThrowsException() throws Exception {
+ PackageDefinitionStrategy.Definition.Undefined.INSTANCE.getSpecificationVersion();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testSpecificationVendorThrowsException() throws Exception {
+ PackageDefinitionStrategy.Definition.Undefined.INSTANCE.getSpecificationVendor();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testImplementationTitleThrowsException() throws Exception {
+ PackageDefinitionStrategy.Definition.Undefined.INSTANCE.getImplementationTitle();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testImplementationVersionThrowsException() throws Exception {
+ PackageDefinitionStrategy.Definition.Undefined.INSTANCE.getImplementationVersion();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testImplementationVendorThrowsException() throws Exception {
+ PackageDefinitionStrategy.Definition.Undefined.INSTANCE.getImplementationVendor();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testSealBaseThrowsException() throws Exception {
+ PackageDefinitionStrategy.Definition.Undefined.INSTANCE.getSealBase();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIsCompatibleToThrowsException() throws Exception {
+ PackageDefinitionStrategy.Definition.Undefined.INSTANCE.isCompatibleTo(getClass().getPackage());
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(PackageDefinitionStrategy.Definition.Undefined.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/PackageDefinitionTrivialTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/PackageDefinitionTrivialTest.java
new file mode 100644
index 0000000..60f6a86
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/PackageDefinitionTrivialTest.java
@@ -0,0 +1,34 @@
+package net.bytebuddy.dynamic.loading;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.net.URL;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class PackageDefinitionTrivialTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ @Test
+ public void testPackageNotDefined() throws Exception {
+ PackageDefinitionStrategy.Definition definition = PackageDefinitionStrategy.Trivial.INSTANCE.define(getClass().getClassLoader(), FOO, BAR);
+ assertThat(definition.isDefined(), is(true));
+ assertThat(definition.getImplementationTitle(), nullValue(String.class));
+ assertThat(definition.getImplementationVersion(), nullValue(String.class));
+ assertThat(definition.getImplementationVendor(), nullValue(String.class));
+ assertThat(definition.getSpecificationTitle(), nullValue(String.class));
+ assertThat(definition.getSpecificationVersion(), nullValue(String.class));
+ assertThat(definition.getSpecificationVendor(), nullValue(String.class));
+ assertThat(definition.getSealBase(), nullValue(URL.class));
+ assertThat(definition.isCompatibleTo(getClass().getPackage()), is(true));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(PackageDefinitionStrategy.Trivial.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/PackageTypeStrategyManifestReadingTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/PackageTypeStrategyManifestReadingTest.java
new file mode 100644
index 0000000..7dfe68f
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/PackageTypeStrategyManifestReadingTest.java
@@ -0,0 +1,271 @@
+package net.bytebuddy.dynamic.loading;
+
+import net.bytebuddy.test.utility.IntegrationRule;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class PackageTypeStrategyManifestReadingTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux", BAZ = "baz";
+
+ @Rule
+ public MethodRule integrationRule = new IntegrationRule();
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private PackageDefinitionStrategy.ManifestReading.SealBaseLocator sealBaseLocator;
+
+ @Mock
+ private ClassLoader classLoader;
+
+ private URL url;
+
+ @Before
+ public void setUp() throws Exception {
+ url = new URL("file:/foo");
+ }
+
+ @Test
+ public void testNoManifest() throws Exception {
+ PackageDefinitionStrategy packageDefinitionStrategy = new PackageDefinitionStrategy.ManifestReading(sealBaseLocator);
+ PackageDefinitionStrategy.Definition definition = packageDefinitionStrategy.define(classLoader, FOO, BAR);
+ assertThat(definition.isDefined(), is(true));
+ assertThat(definition.getImplementationTitle(), nullValue(String.class));
+ assertThat(definition.getImplementationVersion(), nullValue(String.class));
+ assertThat(definition.getImplementationVendor(), nullValue(String.class));
+ assertThat(definition.getSpecificationTitle(), nullValue(String.class));
+ assertThat(definition.getSpecificationVersion(), nullValue(String.class));
+ assertThat(definition.getSpecificationVendor(), nullValue(String.class));
+ assertThat(definition.getSealBase(), nullValue(URL.class));
+ assertThat(definition.isCompatibleTo(getClass().getPackage()), is(true));
+ verifyZeroInteractions(sealBaseLocator);
+ }
+
+ @Test
+ public void testManifestMainAttributesNotSealed() throws Exception {
+ final Manifest manifest = new Manifest();
+ manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
+ manifest.getMainAttributes().put(Attributes.Name.SPECIFICATION_TITLE, FOO);
+ manifest.getMainAttributes().put(Attributes.Name.SPECIFICATION_VERSION, BAR);
+ manifest.getMainAttributes().put(Attributes.Name.SPECIFICATION_VENDOR, QUX);
+ manifest.getMainAttributes().put(Attributes.Name.IMPLEMENTATION_TITLE, BAZ);
+ manifest.getMainAttributes().put(Attributes.Name.IMPLEMENTATION_VERSION, FOO + BAR);
+ manifest.getMainAttributes().put(Attributes.Name.IMPLEMENTATION_VENDOR, QUX + BAZ);
+ manifest.getMainAttributes().put(Attributes.Name.SEALED, Boolean.FALSE.toString());
+ when(classLoader.getResourceAsStream("/META-INF/MANIFEST.MF")).then(new Answer<InputStream>() {
+ @Override
+ public InputStream answer(InvocationOnMock invocationOnMock) throws Throwable {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ manifest.write(outputStream);
+ return new ByteArrayInputStream(outputStream.toByteArray());
+ }
+ });
+ PackageDefinitionStrategy packageDefinitionStrategy = new PackageDefinitionStrategy.ManifestReading(sealBaseLocator);
+ PackageDefinitionStrategy.Definition definition = packageDefinitionStrategy.define(classLoader, FOO + "." + BAR, FOO + "." + BAR + "." + QUX);
+ assertThat(definition.isDefined(), is(true));
+ assertThat(definition.getSpecificationTitle(), is(FOO));
+ assertThat(definition.getSpecificationVersion(), is(BAR));
+ assertThat(definition.getSpecificationVendor(), is(QUX));
+ assertThat(definition.getImplementationTitle(), is(BAZ));
+ assertThat(definition.getImplementationVersion(), is(FOO + BAR));
+ assertThat(definition.getImplementationVendor(), is(QUX + BAZ));
+ assertThat(definition.getSealBase(), nullValue(URL.class));
+ assertThat(definition.isCompatibleTo(getClass().getPackage()), is(true));
+ verifyZeroInteractions(sealBaseLocator);
+ }
+
+ @Test
+ public void testManifestPackageAttributesNotSealed() throws Exception {
+ final Manifest manifest = new Manifest();
+ manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
+ manifest.getMainAttributes().put(Attributes.Name.SPECIFICATION_TITLE, FOO + QUX);
+ manifest.getMainAttributes().put(Attributes.Name.SPECIFICATION_VERSION, FOO + QUX);
+ manifest.getMainAttributes().put(Attributes.Name.SPECIFICATION_VENDOR, FOO + QUX);
+ manifest.getMainAttributes().put(Attributes.Name.IMPLEMENTATION_TITLE, FOO + QUX);
+ manifest.getMainAttributes().put(Attributes.Name.IMPLEMENTATION_VERSION, FOO + QUX);
+ manifest.getMainAttributes().put(Attributes.Name.IMPLEMENTATION_VENDOR, FOO + QUX);
+ manifest.getMainAttributes().put(Attributes.Name.SEALED, FOO + QUX);
+ manifest.getEntries().put(FOO + "/" + BAR + "/", new Attributes());
+ manifest.getAttributes(FOO + "/" + BAR + "/").put(Attributes.Name.SPECIFICATION_TITLE, FOO);
+ manifest.getAttributes(FOO + "/" + BAR + "/").put(Attributes.Name.SPECIFICATION_VERSION, BAR);
+ manifest.getAttributes(FOO + "/" + BAR + "/").put(Attributes.Name.SPECIFICATION_VENDOR, QUX);
+ manifest.getAttributes(FOO + "/" + BAR + "/").put(Attributes.Name.IMPLEMENTATION_TITLE, BAZ);
+ manifest.getAttributes(FOO + "/" + BAR + "/").put(Attributes.Name.IMPLEMENTATION_VERSION, FOO + BAR);
+ manifest.getAttributes(FOO + "/" + BAR + "/").put(Attributes.Name.IMPLEMENTATION_VENDOR, QUX + BAZ);
+ manifest.getAttributes(FOO + "/" + BAR + "/").put(Attributes.Name.SEALED, Boolean.FALSE.toString());
+ when(classLoader.getResourceAsStream("/META-INF/MANIFEST.MF")).then(new Answer<InputStream>() {
+ @Override
+ public InputStream answer(InvocationOnMock invocationOnMock) throws Throwable {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ manifest.write(outputStream);
+ return new ByteArrayInputStream(outputStream.toByteArray());
+ }
+ });
+ PackageDefinitionStrategy packageDefinitionStrategy = new PackageDefinitionStrategy.ManifestReading(sealBaseLocator);
+ PackageDefinitionStrategy.Definition definition = packageDefinitionStrategy.define(classLoader, FOO + "." + BAR, FOO + "." + BAR + "." + QUX);
+ assertThat(definition.isDefined(), is(true));
+ assertThat(definition.getSpecificationTitle(), is(FOO));
+ assertThat(definition.getSpecificationVersion(), is(BAR));
+ assertThat(definition.getSpecificationVendor(), is(QUX));
+ assertThat(definition.getImplementationTitle(), is(BAZ));
+ assertThat(definition.getImplementationVersion(), is(FOO + BAR));
+ assertThat(definition.getImplementationVendor(), is(QUX + BAZ));
+ assertThat(definition.getSealBase(), nullValue(URL.class));
+ assertThat(definition.isCompatibleTo(getClass().getPackage()), is(true));
+ verifyZeroInteractions(sealBaseLocator);
+ }
+
+ @Test
+ @IntegrationRule.Enforce
+ public void testManifestMainAttributesSealed() throws Exception {
+ final Manifest manifest = new Manifest();
+ manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
+ manifest.getMainAttributes().put(Attributes.Name.SEALED, Boolean.TRUE.toString());
+ when(classLoader.getResourceAsStream("/META-INF/MANIFEST.MF")).then(new Answer<InputStream>() {
+ @Override
+ public InputStream answer(InvocationOnMock invocationOnMock) throws Throwable {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ manifest.write(outputStream);
+ return new ByteArrayInputStream(outputStream.toByteArray());
+ }
+ });
+ when(sealBaseLocator.findSealBase(classLoader, FOO + "." + BAR + "." + QUX)).thenReturn(url);
+ PackageDefinitionStrategy packageDefinitionStrategy = new PackageDefinitionStrategy.ManifestReading(sealBaseLocator);
+ PackageDefinitionStrategy.Definition definition = packageDefinitionStrategy.define(classLoader, FOO + "." + BAR, FOO + "." + BAR + "." + QUX);
+ assertThat(definition.isDefined(), is(true));
+ assertThat(definition.getSealBase(), is(url));
+ verify(sealBaseLocator).findSealBase(classLoader, FOO + "." + BAR + "." + QUX);
+ verifyNoMoreInteractions(sealBaseLocator);
+ }
+
+ @Test
+ @IntegrationRule.Enforce
+ public void testManifestPackageAttributesSealed() throws Exception {
+ final Manifest manifest = new Manifest();
+ manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
+ manifest.getEntries().put(FOO + "/" + BAR + "/", new Attributes());
+ manifest.getAttributes(FOO + "/" + BAR + "/").put(Attributes.Name.SEALED, Boolean.TRUE.toString());
+ when(classLoader.getResourceAsStream("/META-INF/MANIFEST.MF")).then(new Answer<InputStream>() {
+ @Override
+ public InputStream answer(InvocationOnMock invocationOnMock) throws Throwable {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ manifest.write(outputStream);
+ return new ByteArrayInputStream(outputStream.toByteArray());
+ }
+ });
+ when(sealBaseLocator.findSealBase(classLoader, FOO + "." + BAR + "." + QUX)).thenReturn(url);
+ PackageDefinitionStrategy packageDefinitionStrategy = new PackageDefinitionStrategy.ManifestReading(sealBaseLocator);
+ PackageDefinitionStrategy.Definition definition = packageDefinitionStrategy.define(classLoader, FOO + "." + BAR, FOO + "." + BAR + "." + QUX);
+ assertThat(definition.isDefined(), is(true));
+ assertThat(definition.getSealBase(), is(url));
+ verify(sealBaseLocator).findSealBase(classLoader, FOO + "." + BAR + "." + QUX);
+ verifyNoMoreInteractions(sealBaseLocator);
+ }
+
+ @Test
+ public void testSealBaseLocatorNonSealing() throws Exception {
+ assertThat(PackageDefinitionStrategy.ManifestReading.SealBaseLocator.NonSealing.INSTANCE.findSealBase(classLoader, FOO), nullValue(URL.class));
+ }
+
+ @Test
+ @IntegrationRule.Enforce
+ public void testSealBaseLocatorForFixedValue() throws Exception {
+ assertThat(new PackageDefinitionStrategy.ManifestReading.SealBaseLocator.ForFixedValue(url).findSealBase(classLoader, FOO), is(url));
+ }
+
+ @Test
+ @IntegrationRule.Enforce
+ public void testSealBaseLocatorForTypeResourceUrlUnknownUrl() throws Exception {
+ when(sealBaseLocator.findSealBase(classLoader, FOO + "." + BAR)).thenReturn(url);
+ assertThat(new PackageDefinitionStrategy.ManifestReading.SealBaseLocator.ForTypeResourceUrl(sealBaseLocator)
+ .findSealBase(classLoader, FOO + "." + BAR), is(url));
+ verify(sealBaseLocator).findSealBase(classLoader, FOO + "." + BAR);
+ verifyNoMoreInteractions(sealBaseLocator);
+ }
+
+ @Test
+ @IntegrationRule.Enforce
+ public void testSealBaseLocatorForTypeResourceUrlFileUrl() throws Exception {
+ URL url = new URL("file:/foo");
+ when(classLoader.getResource(FOO + "/" + BAR + ".class")).thenReturn(url);
+ assertThat(new PackageDefinitionStrategy.ManifestReading.SealBaseLocator.ForTypeResourceUrl(sealBaseLocator)
+ .findSealBase(classLoader, FOO + "." + BAR), is(url));
+ verifyZeroInteractions(sealBaseLocator);
+ }
+
+ @Test
+ @IntegrationRule.Enforce
+ public void testSealBaseLocatorForTypeResourceUrlJarUrl() throws Exception {
+ URL url = new URL("jar:file:/foo.jar!/bar");
+ when(classLoader.getResource(FOO + "/" + BAR + ".class")).thenReturn(url);
+ assertThat(new PackageDefinitionStrategy.ManifestReading.SealBaseLocator.ForTypeResourceUrl(sealBaseLocator)
+ .findSealBase(classLoader, FOO + "." + BAR), is(new URL("file:/foo.jar")));
+ verifyZeroInteractions(sealBaseLocator);
+ }
+
+ @Test
+ @IntegrationRule.Enforce
+ @JavaVersionRule.Enforce(9)
+ public void testSealBaseLocatorForTypeResourceUrlJavaRuntimeImageUrl() throws Exception {
+ URL url = new URL("jrt:/foo/bar");
+ when(classLoader.getResource(FOO + "/" + BAR + ".class")).thenReturn(url);
+ assertThat(new PackageDefinitionStrategy.ManifestReading.SealBaseLocator.ForTypeResourceUrl(sealBaseLocator)
+ .findSealBase(classLoader, FOO + "." + BAR), is(new URL("jrt:/foo")));
+ verifyZeroInteractions(sealBaseLocator);
+ }
+
+ @Test
+ @IntegrationRule.Enforce
+ @JavaVersionRule.Enforce(9)
+ public void testSealBaseLocatorForTypeResourceUrlJavaRuntimeImageUrlRawModule() throws Exception {
+ URL url = new URL("jrt:/foo");
+ when(classLoader.getResource(FOO + "/" + BAR + ".class")).thenReturn(url);
+ assertThat(new PackageDefinitionStrategy.ManifestReading.SealBaseLocator.ForTypeResourceUrl(sealBaseLocator)
+ .findSealBase(classLoader, FOO + "." + BAR), is(new URL("jrt:/foo")));
+ verifyZeroInteractions(sealBaseLocator);
+ }
+
+ @Test
+ @IntegrationRule.Enforce
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(PackageDefinitionStrategy.ManifestReading.class).apply();
+ ObjectPropertyAssertion.of(PackageDefinitionStrategy.ManifestReading.SealBaseLocator.NonSealing.class).apply();
+ final Iterator<URL> urls = Arrays.asList(new URL("file://foo"), new URL("file://bar")).iterator();
+ ObjectPropertyAssertion.of(PackageDefinitionStrategy.ManifestReading.SealBaseLocator.ForFixedValue.class)
+ .create(new ObjectPropertyAssertion.Creator<URL>() {
+ @Override
+ public URL create() {
+ return urls.next();
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(PackageDefinitionStrategy.ManifestReading.SealBaseLocator.ForTypeResourceUrl.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/PackageTypeStrategyNoOpTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/PackageTypeStrategyNoOpTest.java
new file mode 100644
index 0000000..a631491
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/loading/PackageTypeStrategyNoOpTest.java
@@ -0,0 +1,22 @@
+package net.bytebuddy.dynamic.loading;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class PackageTypeStrategyNoOpTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ @Test
+ public void testPackageNotDefined() throws Exception {
+ assertThat(PackageDefinitionStrategy.NoOp.INSTANCE.define(getClass().getClassLoader(), FOO, BAR).isDefined(), is(false));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(PackageDefinitionStrategy.NoOp.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/FieldLocatorForClassHierarchyTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/FieldLocatorForClassHierarchyTest.java
new file mode 100644
index 0000000..eab66e6
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/FieldLocatorForClassHierarchyTest.java
@@ -0,0 +1,103 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class FieldLocatorForClassHierarchyTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Test
+ public void testClassHierarchyTypeFound() throws Exception {
+ FieldLocator.Resolution resolution = new FieldLocator.ForClassHierarchy(new TypeDescription.ForLoadedType(Foo.class)).locate(FOO);
+ assertThat(resolution.isResolved(), is(true));
+ assertThat(resolution.getField(), is((FieldDescription) new FieldDescription.ForLoadedField(Foo.class.getDeclaredField(FOO))));
+ }
+
+ @Test
+ public void testClassHierarchyFoundWithType() throws Exception {
+ FieldLocator.Resolution resolution = new FieldLocator.ForClassHierarchy(new TypeDescription.ForLoadedType(Foo.class)).locate(FOO, new TypeDescription.ForLoadedType(Void.class));
+ assertThat(resolution.isResolved(), is(true));
+ assertThat(resolution.getField(), is((FieldDescription) new FieldDescription.ForLoadedField(Foo.class.getDeclaredField(FOO))));
+ }
+
+ @Test
+ public void testClassHierarchyFoundInherited() throws Exception {
+ FieldLocator.Resolution resolution = new FieldLocator.ForClassHierarchy(new TypeDescription.ForLoadedType(Qux.class)).locate(BAR);
+ assertThat(resolution.isResolved(), is(true));
+ assertThat(resolution.getField(), is((FieldDescription) new FieldDescription.ForLoadedField(Foo.class.getDeclaredField(BAR))));
+ }
+
+ @Test
+ public void testClassHierarchyFoundInheritedShadowed() throws Exception {
+ FieldLocator.Resolution resolution = new FieldLocator.ForClassHierarchy(new TypeDescription.ForLoadedType(Bar.class)).locate(BAR);
+ assertThat(resolution.isResolved(), is(true));
+ assertThat(resolution.getField(), is((FieldDescription) new FieldDescription.ForLoadedField(Bar.class.getDeclaredField(BAR))));
+ }
+
+ @Test
+ public void testClassHierarchyNotFoundInherited() throws Exception {
+ assertThat(new FieldLocator.ForClassHierarchy(new TypeDescription.ForLoadedType(Bar.class)).locate(FOO).isResolved(), is(false));
+ }
+
+ @Test
+ public void testClassHierarchyNotFoundNotExistent() throws Exception {
+ assertThat(new FieldLocator.ForClassHierarchy(new TypeDescription.ForLoadedType(Foo.class)).locate(QUX).isResolved(), is(false));
+ }
+
+ @Test
+ public void testClassHierarchyNotFoundInvisible() throws Exception {
+ assertThat(new FieldLocator.ForClassHierarchy(new TypeDescription.ForLoadedType(Foo.class), new TypeDescription.ForLoadedType(Object.class)).locate(FOO).isResolved(), is(false));
+ }
+
+ @Test
+ public void testClassHierarchyNotFoundWrongType() throws Exception {
+ assertThat(new FieldLocator.ForClassHierarchy(new TypeDescription.ForLoadedType(Foo.class)).locate(FOO, new TypeDescription.ForLoadedType(Object.class)).isResolved(), is(false));
+ }
+
+ @Test
+ public void testFactory() throws Exception {
+ assertThat(FieldLocator.ForClassHierarchy.Factory.INSTANCE.make(typeDescription), is((FieldLocator) new FieldLocator.ForClassHierarchy(typeDescription)));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(FieldLocator.ForClassHierarchy.class).apply();
+ ObjectPropertyAssertion.of(FieldLocator.ForClassHierarchy.Factory.class).apply();
+ }
+
+ @SuppressWarnings("unused")
+ private static class Foo {
+
+ private Void foo;
+
+ protected Void bar;
+ }
+
+ @SuppressWarnings("unused")
+ private static class Bar extends Foo {
+
+ protected Void bar;
+ }
+
+ @SuppressWarnings("unused")
+ private static class Qux extends Foo {
+
+ private Void baz;
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/FieldLocatorForExactTypeTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/FieldLocatorForExactTypeTest.java
new file mode 100644
index 0000000..75cab47
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/FieldLocatorForExactTypeTest.java
@@ -0,0 +1,83 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class FieldLocatorForExactTypeTest {
+
+ private static final String FOO = "foo", QUX = "qux";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription locatedType, typeDescription;
+
+ @Test
+ public void testExactTypeFound() throws Exception {
+ FieldLocator.Resolution resolution = new FieldLocator.ForExactType(new TypeDescription.ForLoadedType(Foo.class)).locate(FOO);
+ assertThat(resolution.isResolved(), is(true));
+ assertThat(resolution.getField(), is((FieldDescription) new FieldDescription.ForLoadedField(Foo.class.getDeclaredField(FOO))));
+ }
+
+ @Test
+ public void testExactTypeFoundWithType() throws Exception {
+ FieldLocator.Resolution resolution = new FieldLocator.ForExactType(new TypeDescription.ForLoadedType(Foo.class)).locate(FOO, new TypeDescription.ForLoadedType(Void.class));
+ assertThat(resolution.isResolved(), is(true));
+ assertThat(resolution.getField(), is((FieldDescription) new FieldDescription.ForLoadedField(Foo.class.getDeclaredField(FOO))));
+ }
+
+ @Test
+ public void testExactTypeNotFoundInherited() throws Exception {
+ assertThat(new FieldLocator.ForExactType(new TypeDescription.ForLoadedType(Bar.class)).locate(FOO).isResolved(), is(false));
+ }
+
+ @Test
+ public void testExactTypeNotFoundNotExistent() throws Exception {
+ assertThat(new FieldLocator.ForExactType(new TypeDescription.ForLoadedType(Foo.class)).locate(QUX).isResolved(), is(false));
+ }
+
+ @Test
+ public void testExactTypeNotFoundInvisible() throws Exception {
+ assertThat(new FieldLocator.ForExactType(new TypeDescription.ForLoadedType(Foo.class), new TypeDescription.ForLoadedType(Object.class)).locate(FOO).isResolved(), is(false));
+ }
+
+ @Test
+ public void testExactTypeNotFoundWrongType() throws Exception {
+ assertThat(new FieldLocator.ForExactType(new TypeDescription.ForLoadedType(Foo.class)).locate(FOO, new TypeDescription.ForLoadedType(Object.class)).isResolved(), is(false));
+ }
+
+ @Test
+ public void testFactory() throws Exception {
+ assertThat(new FieldLocator.ForExactType.Factory(locatedType).make(typeDescription), is((FieldLocator) new FieldLocator.ForExactType(locatedType, typeDescription)));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(FieldLocator.ForExactType.class).apply();
+ ObjectPropertyAssertion.of(FieldLocator.ForExactType.Factory.class).apply();
+ }
+
+ @SuppressWarnings("unused")
+ private static class Foo {
+
+ private Void foo;
+
+ protected Void bar;
+ }
+
+ @SuppressWarnings("unused")
+ private static class Bar extends Foo {
+
+ protected Void bar;
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/FieldLocatorForTopLevelTypeTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/FieldLocatorForTopLevelTypeTest.java
new file mode 100644
index 0000000..4192ac6
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/FieldLocatorForTopLevelTypeTest.java
@@ -0,0 +1,78 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class FieldLocatorForTopLevelTypeTest {
+
+ private static final String FOO = "foo", QUX = "qux";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Test
+ public void testExactTypeFound() throws Exception {
+ FieldLocator.Resolution resolution = new FieldLocator.ForTopLevelType(new TypeDescription.ForLoadedType(Foo.class)).locate(FOO);
+ assertThat(resolution.isResolved(), is(true));
+ assertThat(resolution.getField(), is((FieldDescription) new FieldDescription.ForLoadedField(Foo.class.getDeclaredField(FOO))));
+ }
+
+ @Test
+ public void testExactTypeFoundWithType() throws Exception {
+ FieldLocator.Resolution resolution = new FieldLocator.ForTopLevelType(new TypeDescription.ForLoadedType(Foo.class)).locate(FOO, new TypeDescription.ForLoadedType(Void.class));
+ assertThat(resolution.isResolved(), is(true));
+ assertThat(resolution.getField(), is((FieldDescription) new FieldDescription.ForLoadedField(Foo.class.getDeclaredField(FOO))));
+ }
+
+ @Test
+ public void testExactTypeNotFoundInherited() throws Exception {
+ assertThat(new FieldLocator.ForTopLevelType(new TypeDescription.ForLoadedType(Bar.class)).locate(FOO).isResolved(), is(false));
+ }
+
+ @Test
+ public void testExactTypeNotFoundNotExistent() throws Exception {
+ assertThat(new FieldLocator.ForTopLevelType(new TypeDescription.ForLoadedType(Foo.class)).locate(QUX).isResolved(), is(false));
+ }
+
+ @Test
+ public void testExactTypeNotFoundWrongType() throws Exception {
+ assertThat(new FieldLocator.ForTopLevelType(new TypeDescription.ForLoadedType(Foo.class)).locate(FOO, new TypeDescription.ForLoadedType(Object.class)).isResolved(), is(false));
+ }
+
+ @Test
+ public void testFactory() throws Exception {
+ assertThat(FieldLocator.ForTopLevelType.Factory.INSTANCE.make(typeDescription), is((FieldLocator) new FieldLocator.ForTopLevelType(typeDescription)));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(FieldLocator.ForTopLevelType.class).apply();
+ ObjectPropertyAssertion.of(FieldLocator.ForTopLevelType.Factory.class).apply();
+ }
+
+ @SuppressWarnings("unused")
+ private static class Foo {
+
+ private Void foo;
+
+ protected Void bar;
+ }
+
+ @SuppressWarnings("unused")
+ private static class Bar extends Foo {
+
+ protected Void bar;
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/FieldLocatorNoOpTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/FieldLocatorNoOpTest.java
new file mode 100644
index 0000000..5f099c8
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/FieldLocatorNoOpTest.java
@@ -0,0 +1,33 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class FieldLocatorNoOpTest {
+
+ private static final String FOO = "foo";
+
+ @Test
+ public void testCannotLocateWithoutType() throws Exception {
+ assertThat(FieldLocator.NoOp.INSTANCE.locate(FOO).isResolved(), is(false));
+ }
+
+ @Test
+ public void testCannotLocateWithType() throws Exception {
+ assertThat(FieldLocator.NoOp.INSTANCE.locate(FOO, TypeDescription.OBJECT).isResolved(), is(false));
+ }
+
+ @Test
+ public void testFactory() throws Exception {
+ assertThat(FieldLocator.NoOp.INSTANCE.make(TypeDescription.OBJECT), is((FieldLocator) FieldLocator.NoOp.INSTANCE));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(FieldLocator.NoOp.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/FieldLocatorResolutionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/FieldLocatorResolutionTest.java
new file mode 100644
index 0000000..c711b1b
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/FieldLocatorResolutionTest.java
@@ -0,0 +1,47 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class FieldLocatorResolutionTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private FieldDescription fieldDescription;
+
+ @Test
+ public void testSimpleResolutionResolved() throws Exception {
+ assertThat(new FieldLocator.Resolution.Simple(fieldDescription).isResolved(), is(true));
+ }
+
+ @Test
+ public void testSimpleResolutionFieldDescription() throws Exception {
+ assertThat(new FieldLocator.Resolution.Simple(fieldDescription).getField(), is(fieldDescription));
+ }
+
+ @Test
+ public void testIllegalResolutionUnresolved() throws Exception {
+ assertThat(FieldLocator.Resolution.Illegal.INSTANCE.isResolved(), is(false));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIllegalResolutionFieldDescription() throws Exception {
+ FieldLocator.Resolution.Illegal.INSTANCE.getField();
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(FieldLocator.Resolution.Simple.class).apply();
+ ObjectPropertyAssertion.of(FieldLocator.Resolution.Illegal.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/FieldRegistryCompiledNoOpTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/FieldRegistryCompiledNoOpTest.java
new file mode 100644
index 0000000..5bc89c7
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/FieldRegistryCompiledNoOpTest.java
@@ -0,0 +1,51 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.implementation.attribute.AnnotationValueFilter;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.FieldVisitor;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class FieldRegistryCompiledNoOpTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private FieldDescription fieldDescription;
+
+
+ @Test(expected = IllegalStateException.class)
+ public void testCannotResolveDefault() throws Exception {
+ FieldRegistry.Compiled.NoOp.INSTANCE.target(fieldDescription).resolveDefault(FieldDescription.NO_DEFAULT_VALUE);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testCannotResolveFieldAppender() throws Exception {
+ FieldRegistry.Compiled.NoOp.INSTANCE.target(fieldDescription).getFieldAppender();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testCannotApplyPartially() throws Exception {
+ FieldRegistry.Compiled.NoOp.INSTANCE.target(fieldDescription).apply(mock(FieldVisitor.class), mock(AnnotationValueFilter.Factory.class));
+ }
+
+ @Test
+ public void testReturnsFieldAttributeAppender() throws Exception {
+ TypeWriter.FieldPool.Record record = FieldRegistry.Compiled.NoOp.INSTANCE.target(fieldDescription);
+ assertThat(record.isImplicit(), is(true));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(FieldRegistry.Compiled.NoOp.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/FieldRegistryDefaultTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/FieldRegistryDefaultTest.java
new file mode 100644
index 0000000..28efd0c
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/FieldRegistryDefaultTest.java
@@ -0,0 +1,106 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.dynamic.Transformer;
+import net.bytebuddy.implementation.attribute.AnnotationValueFilter;
+import net.bytebuddy.implementation.attribute.FieldAttributeAppender;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.matcher.LatentMatcher;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.FieldVisitor;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class FieldRegistryDefaultTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private InstrumentedType instrumentedType;
+
+ @Mock
+ private FieldAttributeAppender.Factory distinctFactory;
+
+ @Mock
+ private FieldAttributeAppender distinct;
+
+ @Mock
+ private FieldDescription knownField, unknownField, instrumentedField;
+
+ @Mock
+ private LatentMatcher<FieldDescription> latentMatcher;
+
+ @Mock
+ private ElementMatcher<FieldDescription> matcher;
+
+ @Mock
+ private Object defaultValue, otherDefaultValue;
+
+ @Mock
+ private Transformer<FieldDescription> transformer;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ when(distinctFactory.make(instrumentedType)).thenReturn(distinct);
+ when(latentMatcher.resolve(instrumentedType)).thenReturn((ElementMatcher) matcher);
+ when(matcher.matches(knownField)).thenReturn(true);
+ when(transformer.transform(instrumentedType, knownField)).thenReturn(instrumentedField);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testImplicitFieldCannotResolveDefaultValue() throws Exception {
+ new FieldRegistry.Default()
+ .compile(instrumentedType)
+ .target(unknownField)
+ .resolveDefault(defaultValue);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testImplicitFieldCannotReceiveAppender() throws Exception {
+ new FieldRegistry.Default()
+ .compile(instrumentedType)
+ .target(unknownField)
+ .getFieldAppender();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testImplicitFieldCannotRApplyPartially() throws Exception {
+ new FieldRegistry.Default()
+ .compile(instrumentedType)
+ .target(unknownField)
+ .apply(mock(FieldVisitor.class), mock(AnnotationValueFilter.Factory.class));
+ }
+
+ @Test
+ public void testKnownFieldRegistered() throws Exception {
+ TypeWriter.FieldPool fieldPool = new FieldRegistry.Default()
+ .prepend(latentMatcher, distinctFactory, defaultValue, transformer)
+ .compile(instrumentedType);
+ assertThat(fieldPool.target(knownField).isImplicit(), is(false));
+ assertThat(fieldPool.target(knownField).getField(), is(instrumentedField));
+ assertThat(fieldPool.target(knownField).getFieldAppender(), is(distinct));
+ assertThat(fieldPool.target(knownField).resolveDefault(otherDefaultValue), is(defaultValue));
+ assertThat(fieldPool.target(unknownField).isImplicit(), is(true));
+ assertThat(fieldPool.target(unknownField).getField(), is(unknownField));
+
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(FieldRegistry.Default.class).apply();
+ ObjectPropertyAssertion.of(FieldRegistry.Default.Entry.class).apply();
+ ObjectPropertyAssertion.of(FieldRegistry.Default.Compiled.class).apply();
+ ObjectPropertyAssertion.of(FieldRegistry.Default.Compiled.Entry.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/InstrumentedTypeDefaultTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/InstrumentedTypeDefaultTest.java
new file mode 100644
index 0000000..b1a1ee3
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/InstrumentedTypeDefaultTest.java
@@ -0,0 +1,1301 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.description.ModifierReviewable;
+import net.bytebuddy.description.NamedElement;
+import net.bytebuddy.description.TypeVariableSource;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationSource;
+import net.bytebuddy.description.annotation.AnnotationValue;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.modifier.ModifierContributor;
+import net.bytebuddy.description.type.*;
+import net.bytebuddy.dynamic.TargetType;
+import net.bytebuddy.dynamic.Transformer;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.LoadedTypeInitializer;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.test.packaging.PackagePrivateType;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.io.Serializable;
+import java.lang.annotation.Target;
+import java.util.Arrays;
+import java.util.Collections;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class InstrumentedTypeDefaultTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux", BAZ = "baz", ILLEGAL_NAME = "<>";
+
+ private static final int ILLEGAL_MODIFIERS = -1, OTHER_MODIFIERS = 42;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @Mock
+ private AnnotationDescription annotationDescription;
+
+ protected static InstrumentedType.WithFlexibleName makePlainInstrumentedType() {
+ return new InstrumentedType.Default(FOO + "." + BAZ,
+ ModifierReviewable.EMPTY_MASK,
+ TypeDescription.Generic.OBJECT,
+ Collections.<TypeVariableToken>emptyList(),
+ Collections.<TypeDescription.Generic>emptyList(),
+ Collections.<FieldDescription.Token>emptyList(),
+ Collections.<MethodDescription.Token>emptyList(),
+ Collections.<AnnotationDescription>emptyList(),
+ TypeInitializer.None.INSTANCE,
+ LoadedTypeInitializer.NoOp.INSTANCE,
+ TypeDescription.UNDEFINED,
+ MethodDescription.UNDEFINED,
+ TypeDescription.UNDEFINED,
+ Collections.<TypeDescription>emptyList(),
+ false,
+ false,
+ false);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testWithTypeVariable() throws Exception {
+ TypeDescription.Generic boundType = mock(TypeDescription.Generic.class);
+ when(boundType.asGenericType()).thenReturn(boundType);
+ when(boundType.accept(Mockito.any(TypeDescription.Generic.Visitor.class))).thenReturn(boundType);
+ TypeDescription rawBoundType = mock(TypeDescription.class);
+ when(boundType.asErasure()).thenReturn(rawBoundType);
+ when(rawBoundType.getName()).thenReturn(FOO);
+ InstrumentedType instrumentedType = makePlainInstrumentedType();
+ assertThat(instrumentedType.getTypeVariables().size(), is(0));
+ instrumentedType = instrumentedType.withTypeVariable(new TypeVariableToken(BAR, Collections.singletonList(boundType)));
+ assertThat(instrumentedType.getTypeVariables().size(), is(1));
+ TypeDescription.Generic typeVariable = instrumentedType.getTypeVariables().get(0);
+ assertThat(typeVariable.getTypeName(), is(BAR));
+ assertThat(typeVariable.getTypeVariableSource(), sameInstance((TypeVariableSource) instrumentedType));
+ assertThat(typeVariable.getUpperBounds(), is(Collections.singletonList(boundType)));
+ }
+
+ @Test
+ public void testWithTypeVariableWithInstrumentedType() throws Exception {
+ InstrumentedType instrumentedType = makePlainInstrumentedType();
+ assertThat(instrumentedType.getTypeVariables().size(), is(0));
+ instrumentedType = instrumentedType.withTypeVariable(new TypeVariableToken(BAR, Collections.singletonList(TargetType.DESCRIPTION.asGenericType())));
+ assertThat(instrumentedType.getTypeVariables().size(), is(1));
+ TypeDescription.Generic typeVariable = instrumentedType.getTypeVariables().get(0);
+ assertThat(typeVariable.getTypeName(), is(BAR));
+ assertThat(typeVariable.getTypeVariableSource(), sameInstance((TypeVariableSource) instrumentedType));
+ assertThat(typeVariable.getUpperBounds(), is(Collections.singletonList(instrumentedType.asGenericType())));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testWithTypeVariableTransformed() throws Exception {
+ TypeDescription.Generic boundType = mock(TypeDescription.Generic.class);
+ when(boundType.asGenericType()).thenReturn(boundType);
+ when(boundType.accept(Mockito.any(TypeDescription.Generic.Visitor.class))).thenReturn(boundType);
+ TypeDescription rawBoundType = mock(TypeDescription.class);
+ when(boundType.asErasure()).thenReturn(rawBoundType);
+ when(rawBoundType.getName()).thenReturn(FOO);
+ InstrumentedType.WithFlexibleName instrumentedType = makePlainInstrumentedType();
+ assertThat(instrumentedType.getTypeVariables().size(), is(0));
+ instrumentedType = instrumentedType.withTypeVariable(new TypeVariableToken(BAR, Collections.singletonList(boundType)));
+ Transformer<TypeVariableToken> transformer = mock(Transformer.class);
+ TypeDescription.Generic otherBoundType = mock(TypeDescription.Generic.class);
+ when(otherBoundType.asGenericType()).thenReturn(otherBoundType);
+ when(otherBoundType.accept(Mockito.any(TypeDescription.Generic.Visitor.class))).thenReturn(otherBoundType);
+ TypeDescription rawOtherBoundType = mock(TypeDescription.class);
+ when(otherBoundType.asErasure()).thenReturn(rawOtherBoundType);
+ when(transformer.transform(instrumentedType, new TypeVariableToken(BAR, Collections.singletonList(boundType))))
+ .thenReturn(new TypeVariableToken(QUX, Collections.singletonList(otherBoundType)));
+ instrumentedType = instrumentedType.withTypeVariables(named(BAR), transformer);
+ assertThat(instrumentedType.getTypeVariables().size(), is(1));
+ TypeDescription.Generic typeVariable = instrumentedType.getTypeVariables().get(0);
+ assertThat(typeVariable.getTypeName(), is(QUX));
+ assertThat(typeVariable.getTypeVariableSource(), sameInstance((TypeVariableSource) instrumentedType));
+ assertThat(typeVariable.getUpperBounds(), is(Collections.singletonList(otherBoundType)));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testWithField() throws Exception {
+ TypeDescription.Generic fieldType = mock(TypeDescription.Generic.class);
+ when(fieldType.accept(Mockito.any(TypeDescription.Generic.Visitor.class))).thenReturn(fieldType);
+ TypeDescription rawFieldType = mock(TypeDescription.class);
+ when(fieldType.asErasure()).thenReturn(rawFieldType);
+ when(rawFieldType.getName()).thenReturn(FOO);
+ InstrumentedType instrumentedType = makePlainInstrumentedType();
+ assertThat(instrumentedType.getDeclaredFields().size(), is(0));
+ instrumentedType = instrumentedType.withField(new FieldDescription.Token(BAR, Opcodes.ACC_PUBLIC, fieldType));
+ assertThat(instrumentedType.getDeclaredFields().size(), is(1));
+ FieldDescription.InDefinedShape fieldDescription = instrumentedType.getDeclaredFields().get(0);
+ assertThat(fieldDescription.getType(), is(fieldType));
+ assertThat(fieldDescription.getModifiers(), is(Opcodes.ACC_PUBLIC));
+ assertThat(fieldDescription.getName(), is(BAR));
+ assertThat(fieldDescription.getDeclaringType(), sameInstance((TypeDescription) instrumentedType));
+ }
+
+ @Test
+ public void testWithFieldOfInstrumentedType() throws Exception {
+ InstrumentedType instrumentedType = makePlainInstrumentedType();
+ assertThat(instrumentedType.getDeclaredFields().size(), is(0));
+ instrumentedType = instrumentedType.withField(new FieldDescription.Token(BAR, Opcodes.ACC_PUBLIC, TargetType.DESCRIPTION.asGenericType()));
+ assertThat(instrumentedType.getDeclaredFields().size(), is(1));
+ FieldDescription.InDefinedShape fieldDescription = instrumentedType.getDeclaredFields().get(0);
+ assertThat(fieldDescription.getType().asErasure(), sameInstance((TypeDescription) instrumentedType));
+ assertThat(fieldDescription.getModifiers(), is(Opcodes.ACC_PUBLIC));
+ assertThat(fieldDescription.getName(), is(BAR));
+ assertThat(fieldDescription.getDeclaringType(), sameInstance((TypeDescription) instrumentedType));
+ }
+
+ @Test
+ public void testWithFieldOfInstrumentedTypeAsArray() throws Exception {
+ InstrumentedType instrumentedType = makePlainInstrumentedType();
+ assertThat(instrumentedType.getDeclaredFields().size(), is(0));
+ instrumentedType = instrumentedType.withField(new FieldDescription.Token(BAR, Opcodes.ACC_PUBLIC,
+ new TypeDescription.Generic.OfGenericArray.Latent(TargetType.DESCRIPTION.asGenericType(), new AnnotationSource.Explicit(annotationDescription))));
+ assertThat(instrumentedType.getDeclaredFields().size(), is(1));
+ FieldDescription.InDefinedShape fieldDescription = instrumentedType.getDeclaredFields().get(0);
+ assertThat(fieldDescription.getType().getSort(), is(TypeDefinition.Sort.NON_GENERIC));
+ assertThat(fieldDescription.getType().asErasure().isArray(), is(true));
+ assertThat(fieldDescription.getType().asErasure().getComponentType(), sameInstance((TypeDescription) instrumentedType));
+ assertThat(fieldDescription.getModifiers(), is(Opcodes.ACC_PUBLIC));
+ assertThat(fieldDescription.getName(), is(BAR));
+ assertThat(fieldDescription.getType().getDeclaredAnnotations().size(), is(1));
+ assertThat(fieldDescription.getType().getDeclaredAnnotations().getOnly(), is(annotationDescription));
+ assertThat(fieldDescription.getDeclaringType(), sameInstance((TypeDescription) instrumentedType));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testWithMethod() throws Exception {
+ TypeDescription.Generic returnType = mock(TypeDescription.Generic.class);
+ when(returnType.accept(Mockito.any(TypeDescription.Generic.Visitor.class))).thenReturn(returnType);
+ TypeDescription rawReturnType = mock(TypeDescription.class);
+ when(returnType.asErasure()).thenReturn(rawReturnType);
+ when(rawReturnType.getName()).thenReturn(FOO);
+ TypeDescription.Generic parameterType = mock(TypeDescription.Generic.class);
+ when(parameterType.accept(Mockito.any(TypeDescription.Generic.Visitor.class))).thenReturn(parameterType);
+ when(parameterType.asGenericType()).thenReturn(parameterType);
+ TypeDescription rawParameterType = mock(TypeDescription.class);
+ when(parameterType.asErasure()).thenReturn(rawParameterType);
+ when(rawParameterType.getName()).thenReturn(QUX);
+ when(rawParameterType.getStackSize()).thenReturn(StackSize.ZERO);
+ InstrumentedType instrumentedType = makePlainInstrumentedType();
+ assertThat(instrumentedType.getDeclaredFields().size(), is(0));
+ instrumentedType = instrumentedType.withMethod(new MethodDescription.Token(BAR,
+ Opcodes.ACC_PUBLIC,
+ returnType,
+ Collections.singletonList(parameterType)));
+ assertThat(instrumentedType.getDeclaredMethods().size(), is(1));
+ MethodDescription.InDefinedShape methodDescription = instrumentedType.getDeclaredMethods().get(0);
+ assertThat(methodDescription.getReturnType(), is(returnType));
+ assertThat(methodDescription.getParameters().size(), is(1));
+ assertThat(methodDescription.getParameters().asTypeList(), is(Collections.singletonList(parameterType)));
+ assertThat(methodDescription.getExceptionTypes().size(), is(0));
+ assertThat(methodDescription.getModifiers(), is(Opcodes.ACC_PUBLIC));
+ assertThat(methodDescription.getName(), is(BAR));
+ assertThat(methodDescription.getDeclaringType(), sameInstance((TypeDescription) instrumentedType));
+ }
+
+ @Test
+ public void testWithMethodOfInstrumentedType() throws Exception {
+ InstrumentedType instrumentedType = makePlainInstrumentedType();
+ assertThat(instrumentedType.getDeclaredFields().size(), is(0));
+ instrumentedType = instrumentedType.withMethod(new MethodDescription.Token(BAR,
+ Opcodes.ACC_PUBLIC,
+ TargetType.DESCRIPTION.asGenericType(),
+ Collections.singletonList(TargetType.DESCRIPTION.asGenericType())));
+ assertThat(instrumentedType.getDeclaredMethods().size(), is(1));
+ MethodDescription.InDefinedShape methodDescription = instrumentedType.getDeclaredMethods().get(0);
+ assertThat(methodDescription.getReturnType().asErasure(), sameInstance((TypeDescription) instrumentedType));
+ assertThat(methodDescription.getParameters().size(), is(1));
+ assertThat(methodDescription.getParameters().asTypeList().get(0).asErasure(), sameInstance((TypeDescription) instrumentedType));
+ assertThat(methodDescription.getExceptionTypes().size(), is(0));
+ assertThat(methodDescription.getModifiers(), is(Opcodes.ACC_PUBLIC));
+ assertThat(methodDescription.getName(), is(BAR));
+ assertThat(methodDescription.getDeclaringType(), sameInstance((TypeDescription) instrumentedType));
+ }
+
+ @Test
+ public void testWithMethodOfInstrumentedTypeAsArray() throws Exception {
+ InstrumentedType instrumentedType = makePlainInstrumentedType();
+ assertThat(instrumentedType.getDeclaredFields().size(), is(0));
+ instrumentedType = instrumentedType.withMethod(new MethodDescription.Token(BAR,
+ Opcodes.ACC_PUBLIC,
+ new TypeDescription.Generic.OfGenericArray.Latent(TargetType.DESCRIPTION.asGenericType(), new AnnotationSource.Explicit(annotationDescription)),
+ Collections.singletonList(new TypeDescription.Generic.OfGenericArray.Latent(TargetType.DESCRIPTION.asGenericType(), new AnnotationSource.Explicit(annotationDescription)))));
+ assertThat(instrumentedType.getDeclaredMethods().size(), is(1));
+ MethodDescription.InDefinedShape methodDescription = instrumentedType.getDeclaredMethods().get(0);
+ assertThat(methodDescription.getReturnType().asErasure().isArray(), is(true));
+ assertThat(methodDescription.getReturnType().getComponentType().asErasure(), sameInstance((TypeDescription) instrumentedType));
+ assertThat(methodDescription.getParameters().size(), is(1));
+ assertThat(methodDescription.getParameters().asTypeList().asErasures().get(0).isArray(), is(true));
+ assertThat(methodDescription.getParameters().asTypeList().get(0).getComponentType().asErasure(), sameInstance((TypeDescription) instrumentedType));
+ assertThat(methodDescription.getExceptionTypes().size(), is(0));
+ assertThat(methodDescription.getReturnType().getDeclaredAnnotations().size(), is(1));
+ assertThat(methodDescription.getReturnType().getDeclaredAnnotations().getOnly(), is(annotationDescription));
+ assertThat(methodDescription.getParameters().getOnly().getType().getDeclaredAnnotations().size(), is(1));
+ assertThat(methodDescription.getParameters().getOnly().getType().getDeclaredAnnotations().getOnly(), is(annotationDescription));
+ assertThat(methodDescription.getModifiers(), is(Opcodes.ACC_PUBLIC));
+ assertThat(methodDescription.getName(), is(BAR));
+ assertThat(methodDescription.getDeclaringType(), sameInstance((TypeDescription) instrumentedType));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testWithInterface() throws Exception {
+ TypeDescription.Generic interfaceType = mock(TypeDescription.Generic.class);
+ when(interfaceType.asGenericType()).thenReturn(interfaceType);
+ when(interfaceType.accept(Mockito.any(TypeDescription.Generic.Visitor.class))).thenReturn(interfaceType);
+ TypeDescription rawBoundType = mock(TypeDescription.class);
+ when(interfaceType.asErasure()).thenReturn(rawBoundType);
+ when(rawBoundType.getName()).thenReturn(FOO);
+ InstrumentedType instrumentedType = makePlainInstrumentedType();
+ assertThat(instrumentedType.getInterfaces().size(), is(0));
+ instrumentedType = instrumentedType.withInterfaces(new TypeList.Generic.Explicit(interfaceType));
+ assertThat(instrumentedType.getInterfaces().size(), is(1));
+ assertThat(instrumentedType.getInterfaces(), is(Collections.singletonList(interfaceType)));
+ }
+
+ @Test
+ public void testWithInterfaceOfInstrumentedType() throws Exception {
+ InstrumentedType instrumentedType = makePlainInstrumentedType();
+ assertThat(instrumentedType.getInterfaces().size(), is(0));
+ instrumentedType = instrumentedType.withInterfaces(new TypeList.Generic.Explicit(TargetType.DESCRIPTION));
+ assertThat(instrumentedType.getInterfaces().size(), is(1));
+ assertThat(instrumentedType.getInterfaces(), is(Collections.singletonList(instrumentedType.asGenericType())));
+ }
+
+ @Test
+ public void testWithAnnotation() throws Exception {
+ AnnotationDescription annotationDescription = mock(AnnotationDescription.class);
+ InstrumentedType instrumentedType = makePlainInstrumentedType();
+ assertThat(instrumentedType.getDeclaredAnnotations().size(), is(0));
+ instrumentedType = instrumentedType.withAnnotations(Collections.singletonList(annotationDescription));
+ assertThat(instrumentedType.getDeclaredAnnotations(), is(Collections.singletonList(annotationDescription)));
+ }
+
+ @Test
+ public void testWithName() throws Exception {
+ InstrumentedType.WithFlexibleName instrumentedType = makePlainInstrumentedType();
+ assertThat(instrumentedType.getName(), is(FOO + "." + BAZ));
+ instrumentedType = instrumentedType.withName(BAR);
+ assertThat(instrumentedType.getName(), is(BAR));
+ }
+
+ @Test
+ public void testModifiers() throws Exception {
+ InstrumentedType instrumentedType = makePlainInstrumentedType();
+ assertThat(instrumentedType.getModifiers(), is(ModifierContributor.EMPTY_MASK));
+ instrumentedType = instrumentedType.withModifiers(OTHER_MODIFIERS);
+ assertThat(instrumentedType.getModifiers(), is(OTHER_MODIFIERS));
+ }
+
+ @Test
+ public void testWithLoadedTypeInitializerInitial() throws Exception {
+ LoadedTypeInitializer loadedTypeInitializer = makePlainInstrumentedType().getLoadedTypeInitializer();
+ assertThat(loadedTypeInitializer.isAlive(), is(false));
+ }
+
+ @Test
+ public void testWithLoadedTypeInitializerSingle() throws Exception {
+ InstrumentedType instrumentedType = makePlainInstrumentedType();
+ assertThat(instrumentedType.getDeclaredFields().size(), is(0));
+ LoadedTypeInitializer loadedTypeInitializer = mock(LoadedTypeInitializer.class);
+ instrumentedType = instrumentedType.withInitializer(loadedTypeInitializer);
+ assertThat(instrumentedType.getLoadedTypeInitializer(),
+ is((LoadedTypeInitializer) new LoadedTypeInitializer.Compound(LoadedTypeInitializer.NoOp.INSTANCE, loadedTypeInitializer)));
+ }
+
+ @Test
+ public void testWithLoadedTypeInitializerDouble() throws Exception {
+ InstrumentedType instrumentedType = makePlainInstrumentedType();
+ assertThat(instrumentedType.getDeclaredFields().size(), is(0));
+ LoadedTypeInitializer first = mock(LoadedTypeInitializer.class), second = mock(LoadedTypeInitializer.class);
+ instrumentedType = instrumentedType.withInitializer(first).withInitializer(second);
+ assertThat(instrumentedType.getLoadedTypeInitializer(),
+ is((LoadedTypeInitializer) new LoadedTypeInitializer.Compound(new LoadedTypeInitializer
+ .Compound(LoadedTypeInitializer.NoOp.INSTANCE, first), second)));
+ }
+
+ @Test
+ public void testWithTypeInitializerInitial() throws Exception {
+ TypeInitializer typeInitializer = makePlainInstrumentedType().getTypeInitializer();
+ assertThat(typeInitializer.isDefined(), is(false));
+ }
+
+ @Test
+ public void testWithTypeInitializerSingle() throws Exception {
+ InstrumentedType instrumentedType = makePlainInstrumentedType();
+ assertThat(instrumentedType.getDeclaredFields().size(), is(0));
+ ByteCodeAppender byteCodeAppender = mock(ByteCodeAppender.class);
+ instrumentedType = instrumentedType.withInitializer(byteCodeAppender);
+ TypeInitializer typeInitializer = instrumentedType.getTypeInitializer();
+ assertThat(typeInitializer.isDefined(), is(true));
+ MethodDescription methodDescription = mock(MethodDescription.class);
+ typeInitializer.apply(methodVisitor, implementationContext, methodDescription);
+ verify(byteCodeAppender).apply(methodVisitor, implementationContext, methodDescription);
+ }
+
+ @Test
+ public void testWithTypeInitializerDouble() throws Exception {
+ InstrumentedType instrumentedType = makePlainInstrumentedType();
+ assertThat(instrumentedType.getDeclaredFields().size(), is(0));
+ ByteCodeAppender first = mock(ByteCodeAppender.class), second = mock(ByteCodeAppender.class);
+ MethodDescription methodDescription = mock(MethodDescription.class);
+ when(first.apply(methodVisitor, implementationContext, methodDescription)).thenReturn(new ByteCodeAppender.Size(0, 0));
+ when(second.apply(methodVisitor, implementationContext, methodDescription)).thenReturn(new ByteCodeAppender.Size(0, 0));
+ instrumentedType = instrumentedType.withInitializer(first).withInitializer(second);
+ TypeInitializer typeInitializer = instrumentedType.getTypeInitializer();
+ assertThat(typeInitializer.isDefined(), is(true));
+ typeInitializer.apply(methodVisitor, implementationContext, methodDescription);
+ verify(first).apply(methodVisitor, implementationContext, methodDescription);
+ verify(second).apply(methodVisitor, implementationContext, methodDescription);
+ }
+
+ @Test
+ public void testGetStackSize() throws Exception {
+ assertThat(makePlainInstrumentedType().getStackSize(), is(StackSize.SINGLE));
+ }
+
+ @Test
+ public void testHashCode() throws Exception {
+ InstrumentedType instrumentedType = makePlainInstrumentedType();
+ assertThat(instrumentedType.hashCode(), is(instrumentedType.getName().hashCode()));
+ }
+
+ @Test
+ public void testEquals() throws Exception {
+ InstrumentedType instrumentedType = makePlainInstrumentedType();
+ TypeDescription other = mock(TypeDescription.class);
+ when(other.getName()).thenReturn(instrumentedType.getName());
+ when(other.getSort()).thenReturn(TypeDefinition.Sort.NON_GENERIC);
+ when(other.asErasure()).thenReturn(other);
+ assertThat(instrumentedType, is(other));
+ verify(other, atLeast(1)).getName();
+ }
+
+ @Test
+ public void testIsAssignableFrom() {
+ assertThat(makePlainInstrumentedType().isAssignableFrom(Object.class), is(false));
+ assertThat(makePlainInstrumentedType().isAssignableFrom(Serializable.class), is(false));
+ assertThat(makePlainInstrumentedType().isAssignableFrom(Integer.class), is(false));
+ TypeDescription objectTypeDescription = TypeDescription.OBJECT;
+ assertThat(makePlainInstrumentedType().isAssignableFrom(objectTypeDescription), is(false));
+ TypeDescription serializableTypeDescription = new TypeDescription.ForLoadedType(Serializable.class);
+ assertThat(makePlainInstrumentedType().isAssignableFrom(serializableTypeDescription), is(false));
+ TypeDescription integerTypeDescription = new TypeDescription.ForLoadedType(Integer.class);
+ assertThat(makePlainInstrumentedType().isAssignableFrom(integerTypeDescription), is(false));
+ }
+
+ @Test
+ public void testIsAssignableTo() {
+ assertThat(makePlainInstrumentedType().isAssignableTo(Object.class), is(true));
+ assertThat(makePlainInstrumentedType().isAssignableTo(makePlainInstrumentedType()), is(true));
+ assertThat(makePlainInstrumentedType().isAssignableTo(Integer.class), is(false));
+ assertThat(makePlainInstrumentedType().isAssignableTo(TypeDescription.OBJECT), is(true));
+ }
+
+ @Test
+ public void testRepresents() {
+ assertThat(makePlainInstrumentedType().represents(Object.class), is(false));
+ assertThat(makePlainInstrumentedType().represents(Serializable.class), is(false));
+ assertThat(makePlainInstrumentedType().represents(Integer.class), is(false));
+ }
+
+ @Test
+ public void testSuperClass() {
+ assertThat(makePlainInstrumentedType().getSuperClass(), is(TypeDescription.Generic.OBJECT));
+ assertThat(makePlainInstrumentedType().getSuperClass(), not((TypeDescription.Generic) new TypeDescription.Generic.OfNonGenericType.ForLoadedType(Integer.class)));
+ assertThat(makePlainInstrumentedType().getSuperClass(), not((TypeDescription.Generic) new TypeDescription.Generic.OfNonGenericType.ForLoadedType(Serializable.class)));
+ }
+
+ @Test
+ public void testInterfaces() {
+ assertThat(makePlainInstrumentedType().getInterfaces().size(), is(0));
+ }
+
+ @Test
+ public void testPackage() {
+ assertThat(makePlainInstrumentedType().getPackage().getName(), is(FOO));
+ }
+
+ @Test
+ public void testSimpleName() {
+ assertThat(makePlainInstrumentedType().getSimpleName(), is(BAZ));
+ }
+
+ @Test
+ public void testEnclosingMethod() throws Exception {
+ assertThat(makePlainInstrumentedType().getEnclosingMethod(), nullValue());
+ }
+
+ @Test
+ public void testEnclosingType() throws Exception {
+ assertThat(makePlainInstrumentedType().getEnclosingType(), nullValue());
+ }
+
+ @Test
+ public void testDeclaringType() throws Exception {
+ assertThat(makePlainInstrumentedType().getDeclaringType(), nullValue());
+ }
+
+ @Test
+ public void testIsAnonymous() throws Exception {
+ assertThat(makePlainInstrumentedType().isAnonymousClass(), is(false));
+ }
+
+ @Test
+ public void testCanonicalName() throws Exception {
+ TypeDescription typeDescription = makePlainInstrumentedType();
+ assertThat(typeDescription.getCanonicalName(), is(typeDescription.getName()));
+ }
+
+ @Test
+ public void testIsMemberClass() throws Exception {
+ assertThat(makePlainInstrumentedType().isMemberClass(), is(false));
+ }
+
+ @Test
+ public void testDeclaredTypes() throws Exception {
+ assertThat(makePlainInstrumentedType().getDeclaredTypes().size(), is(0));
+ }
+
+ @Test
+ public void testFieldTokenIsVisited() throws Exception {
+ FieldDescription.Token token = mock(FieldDescription.Token.class);
+ InstrumentedType instrumentedType = makePlainInstrumentedType();
+ assertThat(instrumentedType.withField(token), is(instrumentedType));
+ verify(token).accept(TypeDescription.Generic.Visitor.Substitutor.ForDetachment.of(instrumentedType));
+ verifyNoMoreInteractions(token);
+ }
+
+ @Test
+ public void testMethodTokenIsVisited() throws Exception {
+ MethodDescription.Token token = mock(MethodDescription.Token.class);
+ InstrumentedType instrumentedType = makePlainInstrumentedType();
+ assertThat(instrumentedType.withMethod(token), is(instrumentedType));
+ verify(token).accept(TypeDescription.Generic.Visitor.Substitutor.ForDetachment.of(instrumentedType));
+ verifyNoMoreInteractions(token);
+ }
+
+ @Test
+ public void testTypeVariableIsVisited() throws Exception {
+ TypeVariableToken token = mock(TypeVariableToken.class);
+ InstrumentedType instrumentedType = makePlainInstrumentedType();
+ assertThat(instrumentedType.withTypeVariable(token), is(instrumentedType));
+ verify(token).accept(TypeDescription.Generic.Visitor.Substitutor.ForDetachment.of(instrumentedType));
+ verifyNoMoreInteractions(token);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testInterfaceTypesVisited() throws Exception {
+ TypeDescription.Generic typeDescription = mock(TypeDescription.Generic.class);
+ when(typeDescription.asGenericType()).thenReturn(typeDescription);
+ when(typeDescription.accept(Mockito.any(TypeDescription.Generic.Visitor.class))).thenReturn(typeDescription);
+ InstrumentedType instrumentedType = makePlainInstrumentedType();
+ assertThat(instrumentedType.withInterfaces(new TypeList.Generic.Explicit(typeDescription)), is(instrumentedType));
+ verify(typeDescription).accept(TypeDescription.Generic.Visitor.Substitutor.ForDetachment.of(instrumentedType));
+ verify(typeDescription, times(2)).asGenericType();
+ verifyNoMoreInteractions(typeDescription);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testTypeIllegalName() throws Exception {
+ makePlainInstrumentedType().withName(ILLEGAL_NAME).validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testTypeIllegalEndName() throws Exception {
+ makePlainInstrumentedType().withName(FOO + ILLEGAL_NAME).validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testTypeEmptyEndName() throws Exception {
+ makePlainInstrumentedType().withName(NamedElement.EMPTY_NAME).validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testTypeChainedEmptyEndName() throws Exception {
+ makePlainInstrumentedType().withName("." + FOO).validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testTypeIllegalKeywordName() throws Exception {
+ makePlainInstrumentedType().withName(void.class.getName()).validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testTypeIllegalSubType() throws Exception {
+ InstrumentedType.Factory.Default.MODIFIABLE.subclass(FOO, ModifierContributor.EMPTY_MASK, TypeDefinition.Sort.describe(Serializable.class)).validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testTypeInvisibleSubType() throws Exception {
+ InstrumentedType.Factory.Default.MODIFIABLE.subclass(FOO, ModifierContributor.EMPTY_MASK, TypeDefinition.Sort.describe(PackagePrivateType.TYPE)).validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testTypeIllegalModifiers() throws Exception {
+ InstrumentedType.Factory.Default.MODIFIABLE.subclass(FOO, ILLEGAL_MODIFIERS, TypeDefinition.Sort.describe(Object.class)).validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testPackageTypeIllegalModifiers() throws Exception {
+ InstrumentedType.Factory.Default.MODIFIABLE.subclass(FOO + "." + PackageDescription.PACKAGE_CLASS_NAME, ModifierContributor.EMPTY_MASK, TypeDefinition.Sort.describe(Object.class))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testTypeIllegalInterfaceType() throws Exception {
+ makePlainInstrumentedType().withInterfaces(new TypeList.Generic.Explicit(TypeDescription.Generic.OBJECT)).validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testInvisibleInterfaceType() throws Exception {
+ makePlainInstrumentedType().withInterfaces(new TypeList.Generic.Explicit(TypeDefinition.Sort.describe(PackagePrivateType.INTERFACE_TYPE))).validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testTypeDuplicateInterface() throws Exception {
+ makePlainInstrumentedType().withInterfaces(new TypeList.Generic.Explicit(
+ new TypeDescription.ForLoadedType(Serializable.class),
+ new TypeDescription.ForLoadedType(Serializable.class)
+ )).validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testTypeThrowableWithGenerics() throws Exception {
+ InstrumentedType.Factory.Default.MODIFIABLE.represent(new TypeDescription.ForLoadedType(Exception.class))
+ .withTypeVariable(new TypeVariableToken(FOO, Collections.singletonList(TypeDescription.Generic.OBJECT)))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testTypeDuplicateTypeVariableName() throws Exception {
+ makePlainInstrumentedType()
+ .withTypeVariable(new TypeVariableToken(FOO, Collections.singletonList(TypeDescription.Generic.OBJECT)))
+ .withTypeVariable(new TypeVariableToken(FOO, Collections.singletonList(TypeDescription.Generic.OBJECT)))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testTypeTypeVariableIllegalName() throws Exception {
+ makePlainInstrumentedType()
+ .withTypeVariable(new TypeVariableToken(ILLEGAL_NAME, Collections.singletonList(TypeDescription.Generic.OBJECT)))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testTypeTypeVariableMissingBound() throws Exception {
+ makePlainInstrumentedType()
+ .withTypeVariable(new TypeVariableToken(FOO, Collections.<TypeDescription.Generic>emptyList()))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testTypeTypeVariableDuplicateBound() throws Exception {
+ makePlainInstrumentedType()
+ .withTypeVariable(new TypeVariableToken(FOO, Arrays.asList(TypeDescription.Sort.describe(Serializable.class), TypeDefinition.Sort.describe(Serializable.class))))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testTypeTypeVariableIllegalBound() throws Exception {
+ makePlainInstrumentedType()
+ .withTypeVariable(new TypeVariableToken(FOO, Collections.singletonList(TypeDescription.Generic.VOID)))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testTypeTypeVariableDoubleClassBound() throws Exception {
+ makePlainInstrumentedType()
+ .withTypeVariable(new TypeVariableToken(FOO, Arrays.asList(TypeDescription.Generic.OBJECT, TypeDefinition.Sort.describe(String.class))))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testTypeDuplicateAnnotation() throws Exception {
+ makePlainInstrumentedType()
+ .withAnnotations(Arrays.asList(
+ AnnotationDescription.Builder.ofType(SampleAnnotation.class).build(),
+ AnnotationDescription.Builder.ofType(SampleAnnotation.class).build()
+ )).validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testTypeIncompatibleAnnotation() throws Exception {
+ makePlainInstrumentedType()
+ .withAnnotations(Collections.singletonList(AnnotationDescription.Builder.ofType(IncompatibleAnnotation.class).build()))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testPackageIncompatibleAnnotation() throws Exception {
+ makePlainInstrumentedType()
+ .withName(FOO + "." + PackageDescription.PACKAGE_CLASS_NAME)
+ .withAnnotations(Collections.singletonList(AnnotationDescription.Builder.ofType(IncompatibleAnnotation.class).build()))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAnnotationTypeIncompatibleAnnotation() throws Exception {
+ makePlainInstrumentedType()
+ .withModifiers(Opcodes.ACC_ANNOTATION | Opcodes.ACC_ABSTRACT)
+ .withAnnotations(Collections.singletonList(AnnotationDescription.Builder.ofType(IncompatibleAnnotation.class).build()))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAnnotationTypeIncompatibleSuperClassTypeAnnotation() throws Exception {
+ InstrumentedType.Factory.Default.MODIFIABLE.subclass(FOO, ModifierReviewable.EMPTY_MASK, TypeDescription.Generic.Builder.rawType(Object.class)
+ .build(AnnotationDescription.Builder.ofType(IncompatibleAnnotation.class).build()))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAnnotationTypeIncompatibleInterfaceTypeAnnotation() throws Exception {
+ makePlainInstrumentedType()
+ .withInterfaces(new TypeList.Generic.Explicit(TypeDescription.Generic.Builder.rawType(Runnable.class)
+ .build(AnnotationDescription.Builder.ofType(IncompatibleAnnotation.class).build())))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAnnotationTypeIncompatibleTypeVariableTypeAnnotation() throws Exception {
+ makePlainInstrumentedType()
+ .withTypeVariable(new TypeVariableToken(FOO,
+ Collections.singletonList(TypeDescription.Generic.OBJECT),
+ Collections.singletonList(AnnotationDescription.Builder.ofType(IncompatibleAnnotation.class).build())))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAnnotationTypeIncompatibleTypeVariableBoundTypeAnnotation() throws Exception {
+ makePlainInstrumentedType()
+ .withTypeVariable(new TypeVariableToken(FOO,
+ Collections.singletonList(TypeDescription.Generic.Builder.rawType(Object.class)
+ .build(AnnotationDescription.Builder.ofType(IncompatibleAnnotation.class).build()))))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testFieldDuplicateName() throws Exception {
+ makePlainInstrumentedType()
+ .withField(new FieldDescription.Token(FOO, ModifierContributor.EMPTY_MASK, TypeDescription.Generic.OBJECT))
+ .withField(new FieldDescription.Token(FOO, ModifierContributor.EMPTY_MASK, TypeDescription.Generic.OBJECT))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testFieldIllegalName() throws Exception {
+ makePlainInstrumentedType().withField(new FieldDescription.Token(ILLEGAL_NAME, ModifierContributor.EMPTY_MASK, TypeDescription.Generic.OBJECT)).validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testFieldIllegalModifiers() throws Exception {
+ makePlainInstrumentedType().withField(new FieldDescription.Token(FOO, ILLEGAL_MODIFIERS, TypeDescription.Generic.OBJECT)).validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testFieldIllegalType() throws Exception {
+ makePlainInstrumentedType().withField(new FieldDescription.Token(ILLEGAL_NAME, ModifierContributor.EMPTY_MASK, TypeDescription.Generic.VOID)).validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testFieldInvisibleType() throws Exception {
+ makePlainInstrumentedType()
+ .withField(new FieldDescription.Token(FOO, ModifierContributor.EMPTY_MASK, TypeDefinition.Sort.describe(PackagePrivateType.TYPE)))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void tesFieldDuplicateAnnotation() throws Exception {
+ makePlainInstrumentedType()
+ .withField(new FieldDescription.Token(FOO, ModifierContributor.EMPTY_MASK, TypeDescription.Generic.OBJECT, Arrays.asList(
+ AnnotationDescription.Builder.ofType(SampleAnnotation.class).build(),
+ AnnotationDescription.Builder.ofType(SampleAnnotation.class).build()
+ ))).validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void tesFieldIncompatibleAnnotation() throws Exception {
+ makePlainInstrumentedType()
+ .withField(new FieldDescription.Token(FOO, ModifierContributor.EMPTY_MASK, TypeDescription.Generic.OBJECT, Collections.singletonList(
+ AnnotationDescription.Builder.ofType(IncompatibleAnnotation.class).build()
+ ))).validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testFieldIncompatibleTypeAnnotation() throws Exception {
+ makePlainInstrumentedType()
+ .withField(new FieldDescription.Token(FOO,
+ ModifierContributor.EMPTY_MASK,
+ TypeDescription.Generic.Builder.rawType(Runnable.class)
+ .build(AnnotationDescription.Builder.ofType(IncompatibleAnnotation.class).build())))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodDuplicateErasure() throws Exception {
+ makePlainInstrumentedType()
+ .withMethod(new MethodDescription.Token(FOO, ModifierContributor.EMPTY_MASK, TypeDescription.Generic.OBJECT))
+ .withMethod(new MethodDescription.Token(FOO, ModifierContributor.EMPTY_MASK, TypeDescription.Generic.OBJECT))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodTypeInitializer() throws Exception {
+ makePlainInstrumentedType()
+ .withMethod(new MethodDescription.Token(MethodDescription.TYPE_INITIALIZER_INTERNAL_NAME, ModifierContributor.EMPTY_MASK, TypeDescription.Generic.VOID))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testConstructorNonVoidReturnType() throws Exception {
+ makePlainInstrumentedType()
+ .withMethod(new MethodDescription.Token(MethodDescription.CONSTRUCTOR_INTERNAL_NAME, ModifierContributor.EMPTY_MASK, TypeDescription.Generic.OBJECT))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodInvisibleReturnType() throws Exception {
+ makePlainInstrumentedType()
+ .withMethod(new MethodDescription.Token(FOO, ModifierContributor.EMPTY_MASK, TypeDefinition.Sort.describe(PackagePrivateType.TYPE)))
+ .validated();
+ }
+
+ @Test
+ public void testMethodInvisibleReturnTypeSynthetic() throws Exception {
+ assertThat(makePlainInstrumentedType()
+ .withMethod(new MethodDescription.Token(FOO, Opcodes.ACC_SYNTHETIC, TypeDefinition.Sort.describe(PackagePrivateType.TYPE)))
+ .validated(), instanceOf(TypeDescription.class));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodIllegalName() throws Exception {
+ makePlainInstrumentedType().withMethod(new MethodDescription.Token(ILLEGAL_NAME, ModifierContributor.EMPTY_MASK, TypeDescription.Generic.OBJECT)).validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodIllegalModifiers() throws Exception {
+ makePlainInstrumentedType().withMethod(new MethodDescription.Token(FOO, ILLEGAL_MODIFIERS, TypeDescription.Generic.OBJECT)).validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodDuplicateAnnotation() throws Exception {
+ makePlainInstrumentedType()
+ .withMethod(new MethodDescription.Token(FOO,
+ ModifierContributor.EMPTY_MASK,
+ Collections.<TypeVariableToken>emptyList(),
+ TypeDescription.Generic.OBJECT,
+ Collections.<ParameterDescription.Token>emptyList(),
+ Collections.<TypeDescription.Generic>emptyList(),
+ Arrays.asList(
+ AnnotationDescription.Builder.ofType(SampleAnnotation.class).build(),
+ AnnotationDescription.Builder.ofType(SampleAnnotation.class).build()
+ ), AnnotationValue.UNDEFINED,
+ TypeDescription.Generic.UNDEFINED))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodIncompatibleAnnotation() throws Exception {
+ makePlainInstrumentedType()
+ .withMethod(new MethodDescription.Token(FOO,
+ ModifierContributor.EMPTY_MASK,
+ Collections.<TypeVariableToken>emptyList(),
+ TypeDescription.Generic.OBJECT,
+ Collections.<ParameterDescription.Token>emptyList(),
+ Collections.<TypeDescription.Generic>emptyList(),
+ Collections.singletonList(AnnotationDescription.Builder.ofType(IncompatibleAnnotation.class).build()),
+ AnnotationValue.UNDEFINED,
+ TypeDescription.Generic.UNDEFINED))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodIncompatibleReturnTypeTypeAnnotation() throws Exception {
+ makePlainInstrumentedType()
+ .withMethod(new MethodDescription.Token(FOO,
+ ModifierContributor.EMPTY_MASK,
+ TypeDescription.Generic.Builder.rawType(Runnable.class)
+ .build(AnnotationDescription.Builder.ofType(IncompatibleAnnotation.class).build())))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodIllegalTypeVariableTypeAnnotation() throws Exception {
+ makePlainInstrumentedType()
+ .withMethod(new MethodDescription.Token(FOO,
+ ModifierContributor.EMPTY_MASK,
+ Collections.singletonList(new TypeVariableToken(FOO,
+ Collections.singletonList(TypeDescription.Generic.OBJECT),
+ Collections.singletonList(AnnotationDescription.Builder.ofType(IncompatibleAnnotation.class).build()))),
+ TypeDescription.Generic.OBJECT,
+ Collections.<ParameterDescription.Token>emptyList(),
+ Collections.<TypeDescription.Generic>emptyList(),
+ Collections.<AnnotationDescription>emptyList(),
+ AnnotationValue.UNDEFINED,
+ TypeDescription.Generic.UNDEFINED))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodIllegalTypeVariableBoundTypeAnnotation() throws Exception {
+ makePlainInstrumentedType()
+ .withMethod(new MethodDescription.Token(FOO,
+ ModifierContributor.EMPTY_MASK,
+ Collections.singletonList(new TypeVariableToken(FOO,
+ Collections.singletonList(TypeDescription.Generic.Builder.rawType(Object.class).build(
+ AnnotationDescription.Builder.ofType(IncompatibleAnnotation.class).build())))),
+ TypeDescription.Generic.OBJECT,
+ Collections.<ParameterDescription.Token>emptyList(),
+ Collections.<TypeDescription.Generic>emptyList(),
+ Collections.<AnnotationDescription>emptyList(),
+ AnnotationValue.UNDEFINED,
+ TypeDescription.Generic.UNDEFINED))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodIllegalTypeVariableName() throws Exception {
+ makePlainInstrumentedType()
+ .withMethod(new MethodDescription.Token(FOO,
+ ModifierContributor.EMPTY_MASK,
+ Collections.singletonList(new TypeVariableToken(ILLEGAL_NAME, Collections.singletonList(TypeDescription.Generic.OBJECT))),
+ TypeDescription.Generic.OBJECT,
+ Collections.<ParameterDescription.Token>emptyList(),
+ Collections.<TypeDescription.Generic>emptyList(),
+ Collections.<AnnotationDescription>emptyList(),
+ AnnotationValue.UNDEFINED,
+ TypeDescription.Generic.UNDEFINED))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodDuplicateTypeVariableName() throws Exception {
+ makePlainInstrumentedType()
+ .withMethod(new MethodDescription.Token(FOO,
+ ModifierContributor.EMPTY_MASK,
+ Arrays.asList(
+ new TypeVariableToken(FOO, Collections.singletonList(TypeDescription.Generic.OBJECT)),
+ new TypeVariableToken(FOO, Collections.singletonList(TypeDescription.Generic.OBJECT))
+ ),
+ TypeDescription.Generic.OBJECT,
+ Collections.<ParameterDescription.Token>emptyList(),
+ Collections.<TypeDescription.Generic>emptyList(),
+ Collections.<AnnotationDescription>emptyList(),
+ AnnotationValue.UNDEFINED,
+ TypeDescription.Generic.UNDEFINED))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodTypeVariableMissingBound() throws Exception {
+ makePlainInstrumentedType()
+ .withMethod(new MethodDescription.Token(FOO,
+ ModifierContributor.EMPTY_MASK,
+ Collections.singletonList(new TypeVariableToken(FOO, Collections.<TypeDescription.Generic>emptyList())),
+ TypeDescription.Generic.OBJECT,
+ Collections.<ParameterDescription.Token>emptyList(),
+ Collections.<TypeDescription.Generic>emptyList(),
+ Collections.<AnnotationDescription>emptyList(),
+ AnnotationValue.UNDEFINED,
+ TypeDescription.Generic.UNDEFINED))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodTypeVariableIllegalBound() throws Exception {
+ makePlainInstrumentedType()
+ .withMethod(new MethodDescription.Token(FOO,
+ ModifierContributor.EMPTY_MASK,
+ Collections.singletonList(new TypeVariableToken(FOO, Collections.singletonList(TypeDescription.Generic.VOID))),
+ TypeDescription.Generic.OBJECT,
+ Collections.<ParameterDescription.Token>emptyList(),
+ Collections.<TypeDescription.Generic>emptyList(),
+ Collections.<AnnotationDescription>emptyList(),
+ AnnotationValue.UNDEFINED,
+ TypeDescription.Generic.UNDEFINED))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodTypeVariableDuplicateBound() throws Exception {
+ makePlainInstrumentedType()
+ .withMethod(new MethodDescription.Token(FOO,
+ ModifierContributor.EMPTY_MASK,
+ Collections.singletonList(new TypeVariableToken(FOO, Arrays.asList(TypeDefinition.Sort.describe(Serializable.class),
+ TypeDefinition.Sort.describe(Serializable.class)))),
+ TypeDescription.Generic.OBJECT,
+ Collections.<ParameterDescription.Token>emptyList(),
+ Collections.<TypeDescription.Generic>emptyList(),
+ Collections.<AnnotationDescription>emptyList(),
+ AnnotationValue.UNDEFINED,
+ TypeDescription.Generic.UNDEFINED))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodTypeVariableDoubleClassBound() throws Exception {
+ makePlainInstrumentedType()
+ .withMethod(new MethodDescription.Token(FOO,
+ ModifierContributor.EMPTY_MASK,
+ Collections.singletonList(new TypeVariableToken(FOO, Arrays.asList(TypeDescription.Generic.OBJECT, TypeDefinition.Sort.describe(String.class)))),
+ TypeDescription.Generic.OBJECT,
+ Collections.<ParameterDescription.Token>emptyList(),
+ Collections.<TypeDescription.Generic>emptyList(),
+ Collections.<AnnotationDescription>emptyList(),
+ AnnotationValue.UNDEFINED,
+ TypeDescription.Generic.UNDEFINED))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodParameterIllegalName() throws Exception {
+ makePlainInstrumentedType()
+ .withMethod(new MethodDescription.Token(FOO,
+ ModifierContributor.EMPTY_MASK,
+ Collections.<TypeVariableToken>emptyList(),
+ TypeDescription.Generic.OBJECT,
+ Collections.singletonList(new ParameterDescription.Token(TypeDescription.Generic.OBJECT, ILLEGAL_NAME, 0)),
+ Collections.<TypeDescription.Generic>emptyList(),
+ Collections.<AnnotationDescription>emptyList(),
+ AnnotationValue.UNDEFINED,
+ TypeDescription.Generic.UNDEFINED))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodParameterIllegalType() throws Exception {
+ makePlainInstrumentedType()
+ .withMethod(new MethodDescription.Token(FOO,
+ ModifierContributor.EMPTY_MASK,
+ Collections.<TypeVariableToken>emptyList(),
+ TypeDescription.Generic.OBJECT,
+ Collections.singletonList(new ParameterDescription.Token(TypeDescription.Generic.VOID)),
+ Collections.<TypeDescription.Generic>emptyList(),
+ Collections.<AnnotationDescription>emptyList(),
+ AnnotationValue.UNDEFINED,
+ TypeDescription.Generic.UNDEFINED))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodParameterInvisibleType() throws Exception {
+ makePlainInstrumentedType()
+ .withMethod(new MethodDescription.Token(FOO,
+ ModifierContributor.EMPTY_MASK,
+ Collections.<TypeVariableToken>emptyList(),
+ TypeDescription.Generic.OBJECT,
+ Collections.singletonList(new ParameterDescription.Token(TypeDefinition.Sort.describe(PackagePrivateType.TYPE))),
+ Collections.<TypeDescription.Generic>emptyList(),
+ Collections.<AnnotationDescription>emptyList(),
+ AnnotationValue.UNDEFINED,
+ TypeDescription.Generic.UNDEFINED))
+ .validated();
+ }
+
+ @Test
+ public void testMethodParameterInvisibleTypeSynthetic() throws Exception {
+ assertThat(makePlainInstrumentedType()
+ .withMethod(new MethodDescription.Token(FOO,
+ Opcodes.ACC_SYNTHETIC,
+ Collections.<TypeVariableToken>emptyList(),
+ TypeDescription.Generic.OBJECT,
+ Collections.singletonList(new ParameterDescription.Token(TypeDefinition.Sort.describe(PackagePrivateType.TYPE))),
+ Collections.<TypeDescription.Generic>emptyList(),
+ Collections.<AnnotationDescription>emptyList(),
+ AnnotationValue.UNDEFINED,
+ TypeDescription.Generic.UNDEFINED))
+ .validated(), notNullValue(TypeDescription.class));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodParameterDuplicateName() throws Exception {
+ makePlainInstrumentedType()
+ .withMethod(new MethodDescription.Token(FOO,
+ ModifierContributor.EMPTY_MASK,
+ Collections.<TypeVariableToken>emptyList(),
+ TypeDescription.Generic.OBJECT,
+ Arrays.asList(
+ new ParameterDescription.Token(TypeDescription.Generic.OBJECT, FOO, 0),
+ new ParameterDescription.Token(TypeDescription.Generic.OBJECT, FOO, 0)
+ ), Collections.<TypeDescription.Generic>emptyList(),
+ Collections.<AnnotationDescription>emptyList(),
+ AnnotationValue.UNDEFINED,
+ TypeDescription.Generic.UNDEFINED))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodParameterIllegalModifiers() throws Exception {
+ makePlainInstrumentedType()
+ .withMethod(new MethodDescription.Token(FOO,
+ ModifierContributor.EMPTY_MASK,
+ Collections.<TypeVariableToken>emptyList(),
+ TypeDescription.Generic.OBJECT,
+ Collections.singletonList(new ParameterDescription.Token(TypeDescription.Generic.OBJECT, FOO, -1)),
+ Collections.<TypeDescription.Generic>emptyList(),
+ Collections.<AnnotationDescription>emptyList(),
+ AnnotationValue.UNDEFINED,
+ TypeDescription.Generic.UNDEFINED))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodParameterDuplicateAnnotation() throws Exception {
+ makePlainInstrumentedType()
+ .withMethod(new MethodDescription.Token(FOO,
+ ModifierContributor.EMPTY_MASK,
+ Collections.<TypeVariableToken>emptyList(),
+ TypeDescription.Generic.OBJECT,
+ Collections.singletonList(new ParameterDescription.Token(TypeDescription.Generic.OBJECT, Arrays.asList(
+ AnnotationDescription.Builder.ofType(SampleAnnotation.class).build(),
+ AnnotationDescription.Builder.ofType(SampleAnnotation.class).build()
+ ))), Collections.<TypeDescription.Generic>emptyList(),
+ Collections.<AnnotationDescription>emptyList(),
+ AnnotationValue.UNDEFINED,
+ TypeDescription.Generic.UNDEFINED))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodParameterIncompatibleAnnotation() throws Exception {
+ makePlainInstrumentedType()
+ .withMethod(new MethodDescription.Token(FOO,
+ ModifierContributor.EMPTY_MASK,
+ Collections.<TypeVariableToken>emptyList(),
+ TypeDescription.Generic.OBJECT,
+ Collections.singletonList(new ParameterDescription.Token(TypeDescription.Generic.OBJECT, Collections.singletonList(
+ AnnotationDescription.Builder.ofType(IncompatibleAnnotation.class).build()
+ ))), Collections.<TypeDescription.Generic>emptyList(),
+ Collections.<AnnotationDescription>emptyList(),
+ AnnotationValue.UNDEFINED,
+ TypeDescription.Generic.UNDEFINED))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodIllegalExceptionType() throws Exception {
+ makePlainInstrumentedType()
+ .withMethod(new MethodDescription.Token(FOO,
+ ModifierContributor.EMPTY_MASK,
+ Collections.<TypeVariableToken>emptyList(),
+ TypeDescription.Generic.OBJECT,
+ Collections.<ParameterDescription.Token>emptyList(),
+ Collections.singletonList(TypeDescription.Generic.OBJECT),
+ Collections.<AnnotationDescription>emptyList(),
+ AnnotationValue.UNDEFINED,
+ TypeDescription.Generic.UNDEFINED))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodIncompatibleExceptionTypeTypeAnnotation() throws Exception {
+ makePlainInstrumentedType()
+ .withMethod(new MethodDescription.Token(FOO,
+ ModifierContributor.EMPTY_MASK,
+ Collections.<TypeVariableToken>emptyList(),
+ TypeDescription.Generic.OBJECT,
+ Collections.<ParameterDescription.Token>emptyList(),
+ Collections.singletonList(TypeDescription.Generic.Builder.rawType(Exception.class)
+ .build(AnnotationDescription.Builder.ofType(IncompatibleAnnotation.class).build())),
+ Collections.<AnnotationDescription>emptyList(),
+ AnnotationValue.UNDEFINED,
+ TypeDescription.Generic.UNDEFINED))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodInvisibleExceptionType() throws Exception {
+ makePlainInstrumentedType()
+ .withMethod(new MethodDescription.Token(FOO,
+ ModifierContributor.EMPTY_MASK,
+ Collections.<TypeVariableToken>emptyList(),
+ TypeDescription.Generic.OBJECT,
+ Collections.<ParameterDescription.Token>emptyList(),
+ Collections.singletonList(TypeDefinition.Sort.describe(PackagePrivateType.EXCEPTION_TYPE)),
+ Collections.<AnnotationDescription>emptyList(),
+ AnnotationValue.UNDEFINED,
+ TypeDescription.Generic.UNDEFINED))
+ .validated();
+ }
+
+ @Test
+ public void testMethodInvisibleExceptionSynthetic() throws Exception {
+ assertThat(makePlainInstrumentedType()
+ .withMethod(new MethodDescription.Token(FOO,
+ Opcodes.ACC_SYNTHETIC,
+ Collections.<TypeVariableToken>emptyList(),
+ TypeDescription.Generic.OBJECT,
+ Collections.<ParameterDescription.Token>emptyList(),
+ Collections.singletonList(TypeDefinition.Sort.describe(PackagePrivateType.EXCEPTION_TYPE)),
+ Collections.<AnnotationDescription>emptyList(),
+ AnnotationValue.UNDEFINED,
+ TypeDescription.Generic.UNDEFINED))
+ .validated(), notNullValue(TypeDescription.class));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodDuplicateExceptionType() throws Exception {
+ makePlainInstrumentedType()
+ .withMethod(new MethodDescription.Token(FOO,
+ ModifierContributor.EMPTY_MASK,
+ Collections.<TypeVariableToken>emptyList(),
+ TypeDescription.Generic.OBJECT,
+ Collections.<ParameterDescription.Token>emptyList(),
+ Arrays.asList(TypeDefinition.Sort.describe(Exception.class), TypeDefinition.Sort.describe(Exception.class)),
+ Collections.<AnnotationDescription>emptyList(),
+ AnnotationValue.UNDEFINED,
+ TypeDescription.Generic.UNDEFINED))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodIllegalDefaultValue() throws Exception {
+ makePlainInstrumentedType()
+ .withMethod(new MethodDescription.Token(FOO,
+ ModifierContributor.EMPTY_MASK,
+ Collections.<TypeVariableToken>emptyList(),
+ TypeDescription.Generic.OBJECT,
+ Collections.<ParameterDescription.Token>emptyList(),
+ Collections.<TypeDescription.Generic>emptyList(),
+ Collections.<AnnotationDescription>emptyList(),
+ AnnotationValue.ForConstant.of(FOO),
+ TypeDescription.Generic.UNDEFINED))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonNullReceiverStaticMethod() throws Exception {
+ makePlainInstrumentedType()
+ .withMethod(new MethodDescription.Token(FOO,
+ Opcodes.ACC_STATIC,
+ Collections.<TypeVariableToken>emptyList(),
+ TypeDescription.Generic.OBJECT,
+ Collections.<ParameterDescription.Token>emptyList(),
+ Collections.<TypeDescription.Generic>emptyList(),
+ Collections.<AnnotationDescription>emptyList(),
+ AnnotationValue.UNDEFINED,
+ TypeDescription.Generic.OBJECT))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testInconsistentReceiverNonStaticMethod() throws Exception {
+ makePlainInstrumentedType()
+ .withMethod(new MethodDescription.Token(FOO,
+ ModifierContributor.EMPTY_MASK,
+ Collections.<TypeVariableToken>emptyList(),
+ TypeDescription.Generic.OBJECT,
+ Collections.<ParameterDescription.Token>emptyList(),
+ Collections.<TypeDescription.Generic>emptyList(),
+ Collections.<AnnotationDescription>emptyList(),
+ AnnotationValue.UNDEFINED,
+ TypeDescription.Generic.OBJECT))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testInconsistentReceiverConstructor() throws Exception {
+ makePlainInstrumentedType()
+ .withMethod(new MethodDescription.Token(MethodDescription.CONSTRUCTOR_INTERNAL_NAME,
+ ModifierContributor.EMPTY_MASK,
+ Collections.<TypeVariableToken>emptyList(),
+ TypeDescription.Generic.OBJECT,
+ Collections.<ParameterDescription.Token>emptyList(),
+ Collections.<TypeDescription.Generic>emptyList(),
+ Collections.<AnnotationDescription>emptyList(),
+ AnnotationValue.UNDEFINED,
+ TypeDescription.Generic.OBJECT))
+ .validated();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testInconsistentReceiverConstructorInnerClass() throws Exception {
+ InstrumentedType.Factory.Default.MODIFIABLE.represent(new TypeDescription.ForLoadedType(Foo.class))
+ .withMethod(new MethodDescription.Token(MethodDescription.CONSTRUCTOR_INTERNAL_NAME,
+ ModifierContributor.EMPTY_MASK,
+ Collections.<TypeVariableToken>emptyList(),
+ TypeDescription.Generic.OBJECT,
+ Collections.<ParameterDescription.Token>emptyList(),
+ Collections.<TypeDescription.Generic>emptyList(),
+ Collections.<AnnotationDescription>emptyList(),
+ AnnotationValue.UNDEFINED,
+ TypeDefinition.Sort.describe(Foo.class)))
+ .validated();
+ }
+
+ @Test
+ public void testTypeVariableOutOfScopeIsErased() throws Exception {
+ TypeDescription typeDescription = new InstrumentedType.Default("foo",
+ Opcodes.ACC_PUBLIC,
+ new TypeDescription.Generic.OfNonGenericType.ForLoadedType(AbstractOuter.ExtendedInner.class),
+ Collections.<TypeVariableToken>emptyList(),
+ Collections.<TypeDescription.Generic>emptyList(),
+ Collections.<FieldDescription.Token>emptyList(),
+ Collections.singletonList(new MethodDescription.Token("foo",
+ Opcodes.ACC_BRIDGE,
+ TypeDescription.Generic.VOID,
+ Collections.<TypeDescription.Generic>emptyList())),
+ Collections.<AnnotationDescription>emptyList(),
+ TypeInitializer.None.INSTANCE,
+ LoadedTypeInitializer.NoOp.INSTANCE,
+ TypeDescription.UNDEFINED,
+ MethodDescription.UNDEFINED,
+ TypeDescription.UNDEFINED,
+ Collections.<TypeDescription>emptyList(),
+ false,
+ false,
+ false);
+ MethodDescription methodDescription = typeDescription.getSuperClass().getSuperClass().getDeclaredMethods().filter(named(FOO)).getOnly();
+ assertThat(methodDescription.getReturnType(), is(TypeDescription.Generic.OBJECT));
+ }
+
+ public @interface SampleAnnotation {
+ /* empty */
+ }
+
+ @Target({})
+ public @interface IncompatibleAnnotation {
+ /* empty */
+ }
+
+ private class Foo {
+ /* empty */
+ }
+
+ public static abstract class AbstractOuter<T> {
+
+ public abstract class Inner {
+
+ public abstract T foo();
+ }
+
+ public abstract class ExtendedInner extends Inner {
+ /* empty */
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/InstrumentedTypeFactoryDefaultTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/InstrumentedTypeFactoryDefaultTest.java
new file mode 100644
index 0000000..abb1e34
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/InstrumentedTypeFactoryDefaultTest.java
@@ -0,0 +1,30 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.description.type.TypeDescription;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class InstrumentedTypeFactoryDefaultTest {
+
+ @Test
+ public void testSubclassModifiable() throws Exception {
+ assertThat(InstrumentedType.Factory.Default.MODIFIABLE.subclass("foo", 0, TypeDescription.Generic.OBJECT), instanceOf(InstrumentedType.Default.class));
+ }
+
+ @Test
+ public void testSubclassFrozen() throws Exception {
+ assertThat(InstrumentedType.Factory.Default.FROZEN.subclass("foo", 0, TypeDescription.Generic.OBJECT), instanceOf(InstrumentedType.Default.class));
+ }
+
+ @Test
+ public void testRepresentModifiable() throws Exception {
+ assertThat(InstrumentedType.Factory.Default.MODIFIABLE.represent(TypeDescription.OBJECT), instanceOf(InstrumentedType.Default.class));
+ }
+
+ @Test
+ public void testRepresentFrozen() throws Exception {
+ assertThat(InstrumentedType.Factory.Default.FROZEN.represent(TypeDescription.OBJECT), instanceOf(InstrumentedType.Frozen.class));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/InstrumentedTypeFrozenTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/InstrumentedTypeFrozenTest.java
new file mode 100644
index 0000000..15b7239
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/InstrumentedTypeFrozenTest.java
@@ -0,0 +1,86 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeVariableToken;
+import net.bytebuddy.dynamic.Transformer;
+import net.bytebuddy.implementation.LoadedTypeInitializer;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.matcher.ElementMatcher;
+import org.junit.Test;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class InstrumentedTypeFrozenTest {
+
+ @Test
+ public void testDelegation() throws Exception {
+ for (Method method : TypeDescription.class.getDeclaredMethods()) {
+ if (method.getParameterTypes().length == 0 && Modifier.isPublic(method.getModifiers()) && !method.isSynthetic()) {
+ assertThat(method.invoke(new InstrumentedType.Frozen(TypeDescription.STRING, LoadedTypeInitializer.NoOp.INSTANCE)),
+ is(method.invoke(TypeDescription.STRING)));
+ }
+ }
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testFieldToken() throws Exception {
+ new InstrumentedType.Frozen(TypeDescription.STRING, LoadedTypeInitializer.NoOp.INSTANCE).withField(mock(FieldDescription.Token.class));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodToken() throws Exception {
+ new InstrumentedType.Frozen(TypeDescription.STRING, LoadedTypeInitializer.NoOp.INSTANCE).withMethod(mock(MethodDescription.Token.class));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAnnotation() throws Exception {
+ new InstrumentedType.Frozen(TypeDescription.STRING, LoadedTypeInitializer.NoOp.INSTANCE).withAnnotations(Collections.<AnnotationDescription>emptyList());
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testInitializer() throws Exception {
+ new InstrumentedType.Frozen(TypeDescription.STRING, LoadedTypeInitializer.NoOp.INSTANCE).withInitializer(mock(ByteCodeAppender.class));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testWithTypeVariable() throws Exception {
+ new InstrumentedType.Frozen(TypeDescription.STRING, LoadedTypeInitializer.NoOp.INSTANCE).withTypeVariable(mock(TypeVariableToken.class));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ @SuppressWarnings("unchecked")
+ public void testWithTypeVariables() throws Exception {
+ new InstrumentedType.Frozen(TypeDescription.STRING, LoadedTypeInitializer.NoOp.INSTANCE).withTypeVariables(mock(ElementMatcher.class), mock(Transformer.class));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testWithName() throws Exception {
+ new InstrumentedType.Frozen(TypeDescription.STRING, LoadedTypeInitializer.NoOp.INSTANCE).withName("foo");
+ }
+
+ @Test
+ public void testValidation() throws Exception {
+ assertThat(new InstrumentedType.Frozen(TypeDescription.STRING, LoadedTypeInitializer.NoOp.INSTANCE).validated(), is(TypeDescription.STRING));
+ }
+
+ @Test
+ public void testTypeInitializer() throws Exception {
+ assertThat(new InstrumentedType.Frozen(TypeDescription.STRING, LoadedTypeInitializer.NoOp.INSTANCE).getTypeInitializer(),
+ is((TypeInitializer) TypeInitializer.None.INSTANCE));
+ }
+
+ @Test
+ public void testLoadedTypeInitializer() throws Exception {
+ LoadedTypeInitializer loadedTypeInitializer = mock(LoadedTypeInitializer.class);
+ assertThat(new InstrumentedType.Frozen(TypeDescription.STRING, loadedTypeInitializer).getLoadedTypeInitializer(), is(loadedTypeInitializer));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphCompilerDefaultHarmonizerForJVMMethodTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphCompilerDefaultHarmonizerForJVMMethodTest.java
new file mode 100644
index 0000000..fa2630f
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphCompilerDefaultHarmonizerForJVMMethodTest.java
@@ -0,0 +1,83 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class MethodGraphCompilerDefaultHarmonizerForJVMMethodTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription first, second;
+
+ private MethodGraph.Compiler.Default.Harmonizer<MethodGraph.Compiler.Default.Harmonizer.ForJVMMethod.Token> harmonizer;
+
+ @Before
+ public void setUp() throws Exception {
+ harmonizer = MethodGraph.Compiler.Default.Harmonizer.ForJVMMethod.INSTANCE;
+ }
+
+ @Test
+ public void testMethodEqualityHashCode() throws Exception {
+ assertThat(harmonizer.harmonize(new MethodDescription.TypeToken(first, Collections.singletonList(first))).hashCode(),
+ is(harmonizer.harmonize(new MethodDescription.TypeToken(first, Collections.singletonList(first))).hashCode()));
+ }
+
+ @Test
+ public void testMethodEquality() throws Exception {
+ assertThat(harmonizer.harmonize(new MethodDescription.TypeToken(first, Collections.singletonList(first))),
+ is(harmonizer.harmonize(new MethodDescription.TypeToken(first, Collections.singletonList(first)))));
+ }
+
+ @Test
+ public void testMethodReturnTypeInequalityHashCode() throws Exception {
+ assertThat(harmonizer.harmonize(new MethodDescription.TypeToken(first, Collections.singletonList(first))).hashCode(),
+ not(harmonizer.harmonize(new MethodDescription.TypeToken(second, Collections.singletonList(first))).hashCode()));
+ }
+
+ @Test
+ public void testMethodReturnTypeInequality() throws Exception {
+ assertThat(harmonizer.harmonize(new MethodDescription.TypeToken(first, Collections.singletonList(first))),
+ not(harmonizer.harmonize(new MethodDescription.TypeToken(second, Collections.singletonList(first)))));
+ }
+
+ @Test
+ public void testMethodParameterTypesHashCode() throws Exception {
+ assertThat(harmonizer.harmonize(new MethodDescription.TypeToken(first, Collections.singletonList(first))).hashCode(),
+ not(harmonizer.harmonize(new MethodDescription.TypeToken(first, Collections.singletonList(second))).hashCode()));
+ }
+
+ @Test
+ public void testMethodParameterTypesEquality() throws Exception {
+ assertThat(harmonizer.harmonize(new MethodDescription.TypeToken(first, Collections.singletonList(first))),
+ not(harmonizer.harmonize(new MethodDescription.TypeToken(first, Collections.singletonList(second)))));
+ }
+
+ @Test
+ public void testFactory() throws Exception {
+ assertThat(MethodGraph.Compiler.Default.forJVMHierarchy(), is((MethodGraph.Compiler) new MethodGraph.Compiler
+ .Default<MethodGraph.Compiler.Default.Harmonizer.ForJVMMethod.Token>(MethodGraph.Compiler.Default.Harmonizer.ForJVMMethod.INSTANCE,
+ MethodGraph.Compiler.Default.Merger.Directional.LEFT, TypeDescription.Generic.Visitor.Reifying.INITIATING)));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodGraph.Compiler.Default.Harmonizer.ForJVMMethod.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphCompilerDefaultHarmonizerForJavaMethodTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphCompilerDefaultHarmonizerForJavaMethodTest.java
new file mode 100644
index 0000000..f7863b7
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphCompilerDefaultHarmonizerForJavaMethodTest.java
@@ -0,0 +1,87 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class MethodGraphCompilerDefaultHarmonizerForJavaMethodTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ private static final int MODIFIERS = 42;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription first, second;
+
+ private MethodGraph.Compiler.Default.Harmonizer<MethodGraph.Compiler.Default.Harmonizer.ForJavaMethod.Token> harmonizer;
+
+ @Before
+ public void setUp() throws Exception {
+ harmonizer = MethodGraph.Compiler.Default.Harmonizer.ForJavaMethod.INSTANCE;
+ }
+
+ @Test
+ public void testMethodEqualityHashCode() throws Exception {
+ assertThat(harmonizer.harmonize(new MethodDescription.TypeToken(first, Collections.singletonList(first))).hashCode(),
+ is(harmonizer.harmonize(new MethodDescription.TypeToken(first, Collections.singletonList(first))).hashCode()));
+ }
+
+ @Test
+ public void testMethodEquality() throws Exception {
+ assertThat(harmonizer.harmonize(new MethodDescription.TypeToken(first, Collections.singletonList(first))),
+ is(harmonizer.harmonize(new MethodDescription.TypeToken(first, Collections.singletonList(first)))));
+ }
+
+ @Test
+ public void testMethodReturnTypeInequalityHashCode() throws Exception {
+ assertThat(harmonizer.harmonize(new MethodDescription.TypeToken(first, Collections.singletonList(first))).hashCode(),
+ is(harmonizer.harmonize(new MethodDescription.TypeToken(second, Collections.singletonList(first))).hashCode()));
+ }
+
+ @Test
+ public void testMethodReturnTypeInequality() throws Exception {
+ assertThat(harmonizer.harmonize(new MethodDescription.TypeToken(first, Collections.singletonList(first))),
+ is(harmonizer.harmonize(new MethodDescription.TypeToken(second, Collections.singletonList(first)))));
+ }
+
+ @Test
+ public void testMethodParameterTypesHashCode() throws Exception {
+ assertThat(harmonizer.harmonize(new MethodDescription.TypeToken(first, Collections.singletonList(first))).hashCode(),
+ not(harmonizer.harmonize(new MethodDescription.TypeToken(first, Collections.singletonList(second))).hashCode()));
+ }
+
+ @Test
+ public void testMethodParameterTypesEquality() throws Exception {
+ assertThat(harmonizer.harmonize(new MethodDescription.TypeToken(first, Collections.singletonList(first))),
+ not(harmonizer.harmonize(new MethodDescription.TypeToken(first, Collections.singletonList(second)))));
+ }
+
+ @Test
+ public void testFactory() throws Exception {
+ assertThat(MethodGraph.Compiler.Default.forJavaHierarchy(), is((MethodGraph.Compiler) new MethodGraph.Compiler
+ .Default<MethodGraph.Compiler.Default.Harmonizer.ForJavaMethod.Token>(MethodGraph.Compiler.Default.Harmonizer.ForJavaMethod.INSTANCE,
+ MethodGraph.Compiler.Default.Merger.Directional.LEFT, TypeDescription.Generic.Visitor.Reifying.INITIATING)));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodGraph.Compiler.Default.Harmonizer.ForJavaMethod.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphCompilerDefaultKeyTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphCompilerDefaultKeyTest.java
new file mode 100644
index 0000000..0c1a986
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphCompilerDefaultKeyTest.java
@@ -0,0 +1,130 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class MethodGraphCompilerDefaultKeyTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private SampleKey foo, bar, qux;
+
+ @Test
+ public void testEqualsSimilar() throws Exception {
+ assertThat(new PseudoKey(FOO, Collections.singleton(foo)).hashCode(),
+ is(new PseudoKey(FOO, Collections.singleton(foo)).hashCode()));
+ assertThat(new PseudoKey(FOO, Collections.singleton(foo)),
+ is(new PseudoKey(FOO, Collections.singleton(foo))));
+ }
+
+ @Test
+ public void testNotEqualsDifferentName() throws Exception {
+ assertThat(new PseudoKey(FOO, Collections.singleton(foo)).hashCode(),
+ not(new PseudoKey(BAR, Collections.singleton(foo)).hashCode()));
+ assertThat(new PseudoKey(FOO, Collections.singleton(foo)),
+ not(new PseudoKey(BAR, Collections.singleton(foo))));
+ }
+
+ @Test
+ public void testNotEqualDifferentToken() throws Exception {
+ assertThat(new PseudoKey(FOO, Collections.singleton(foo)).hashCode(),
+ is(new PseudoKey(FOO, Collections.singleton(bar)).hashCode()));
+ assertThat(new PseudoKey(FOO, Collections.singleton(foo)),
+ not(new PseudoKey(BAR, Collections.singleton(bar))));
+ }
+
+ @Test
+ public void testEqualsSuperSet() throws Exception {
+ assertThat(new PseudoKey(FOO, new HashSet<SampleKey>(Arrays.asList(foo, bar))).hashCode(),
+ is(new PseudoKey(FOO, Collections.singleton(bar)).hashCode()));
+ assertThat(new PseudoKey(FOO, new HashSet<SampleKey>(Arrays.asList(foo, bar))),
+ is(new PseudoKey(FOO, Collections.singleton(foo))));
+ }
+
+ @Test
+ public void testEqualsSubSet() throws Exception {
+ assertThat(new PseudoKey(FOO, Collections.singleton(foo)).hashCode(),
+ is(new PseudoKey(FOO, new HashSet<SampleKey>(Arrays.asList(foo, bar))).hashCode()));
+ assertThat(new PseudoKey(FOO, Collections.singleton(foo)),
+ is(new PseudoKey(FOO, new HashSet<SampleKey>(Arrays.asList(foo, bar)))));
+ }
+
+ @Test
+ public void testNotEqualsDistinctSet() throws Exception {
+ assertThat(new PseudoKey(FOO, new HashSet<SampleKey>(Arrays.asList(foo, bar))).hashCode(),
+ is(new PseudoKey(FOO, Collections.singleton(qux)).hashCode()));
+ assertThat(new PseudoKey(FOO, new HashSet<SampleKey>(Arrays.asList(foo, bar))),
+ not(new PseudoKey(FOO, Collections.singleton(qux))));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ @SuppressWarnings("unchecked")
+ public void testInitialEntryCannotInject() throws Exception {
+ new MethodGraph.Compiler.Default.Key.Store.Entry.Initial(new MethodGraph.Compiler.Default.Key.Harmonized(FOO, Collections.emptyMap()))
+ .inject(mock(MethodGraph.Compiler.Default.Key.Harmonized.class), Visibility.PUBLIC);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ @SuppressWarnings("unchecked")
+ public void testInitialEntryCannotBeTransformed() throws Exception {
+ new MethodGraph.Compiler.Default.Key.Store.Entry.Initial(new MethodGraph.Compiler.Default.Key.Harmonized(FOO, Collections.emptyMap()))
+ .asNode(mock(MethodGraph.Compiler.Default.Merger.class));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ @SuppressWarnings("unchecked")
+ public void testInitialEntryCannotExposeKey() throws Exception {
+ new MethodGraph.Compiler.Default.Key.Store.Entry.Initial(new MethodGraph.Compiler.Default.Key.Harmonized(FOO, Collections.emptyMap()))
+ .getKey();
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodGraph.Compiler.Default.Key.Store.class).apply();
+ ObjectPropertyAssertion.of(MethodGraph.Compiler.Default.Key.Store.class).apply();
+ ObjectPropertyAssertion.of(MethodGraph.Compiler.Default.Key.Store.Graph.class).apply();
+ ObjectPropertyAssertion.of(MethodGraph.Compiler.Default.Key.Store.Entry.Initial.class).apply();
+ ObjectPropertyAssertion.of(MethodGraph.Compiler.Default.Key.Store.Entry.Ambiguous.class).apply();
+ ObjectPropertyAssertion.of(MethodGraph.Compiler.Default.Key.Store.Entry.Ambiguous.Node.class).apply();
+ ObjectPropertyAssertion.of(MethodGraph.Compiler.Default.Key.Store.Entry.Resolved.class).apply();
+ ObjectPropertyAssertion.of(MethodGraph.Compiler.Default.Key.Store.Entry.Resolved.Node.class).apply();
+ }
+
+ protected static class PseudoKey extends MethodGraph.Compiler.Default.Key<SampleKey> {
+
+ private final Set<SampleKey> identifiers;
+
+ public PseudoKey(String internalName, Set<SampleKey> identifiers) {
+ super(internalName);
+ this.identifiers = identifiers;
+ }
+
+ @Override
+ protected Set<SampleKey> getIdentifiers() {
+ return identifiers;
+ }
+ }
+
+ public static class SampleKey {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphCompilerDefaultMergerDirectionalTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphCompilerDefaultMergerDirectionalTest.java
new file mode 100644
index 0000000..d32b0ee
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphCompilerDefaultMergerDirectionalTest.java
@@ -0,0 +1,36 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodGraphCompilerDefaultMergerDirectionalTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodDescription left, right;
+
+ @Test
+ public void testLeft() throws Exception {
+ assertThat(MethodGraph.Compiler.Default.Merger.Directional.LEFT.merge(left, right), is(left));
+ }
+
+ @Test
+ public void testRight() throws Exception {
+ assertThat(MethodGraph.Compiler.Default.Merger.Directional.RIGHT.merge(left, right), is(right));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodGraph.Compiler.Default.Merger.Directional.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphCompilerDefaultTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphCompilerDefaultTest.java
new file mode 100644
index 0000000..39b36c1
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphCompilerDefaultTest.java
@@ -0,0 +1,1665 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeVariableToken;
+import net.bytebuddy.implementation.LoadedTypeInitializer;
+import net.bytebuddy.matcher.ElementMatchers;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Collections;
+
+import static net.bytebuddy.matcher.ElementMatchers.*;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class MethodGraphCompilerDefaultTest {
+
+ private static final String TYPE_VARIABLE_INTERFACE_BRIDGE = "net.bytebuddy.test.precompiled.TypeVariableInterfaceBridge";
+
+ private static final String RETURN_TYPE_INTERFACE_BRIDGE = "net.bytebuddy.test.precompiled.ReturnTypeInterfaceBridge";
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ @Test
+ public void testTrivialJavaHierarchy() throws Exception {
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(TypeDescription.OBJECT);
+ assertThat(methodGraph.listNodes().size(), is(TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual()).size()));
+ assertThat(methodGraph.getSuperClassGraph().listNodes().size(), is(0));
+ assertThat(methodGraph.getInterfaceGraph(mock(TypeDescription.class)).listNodes().size(), is(0));
+ for (MethodDescription methodDescription : TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual())) {
+ MethodGraph.Node node = methodGraph.locate(methodDescription.asSignatureToken());
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(node.getMethodTypes().size(), is(1));
+ assertThat(node.getMethodTypes().contains(methodDescription.asTypeToken()), is(true));
+ assertThat(node.getRepresentative(), is(methodDescription));
+ assertThat(node.getVisibility(), is(methodDescription.getVisibility()));
+ assertThat(methodGraph.listNodes().contains(node), is(true));
+ }
+ }
+
+ @Test
+ public void testTrivialJVMHierarchy() throws Exception {
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJVMHierarchy().compile(TypeDescription.OBJECT);
+ assertThat(methodGraph.listNodes().size(), is(TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual()).size()));
+ assertThat(methodGraph.getSuperClassGraph().listNodes().size(), is(0));
+ assertThat(methodGraph.getInterfaceGraph(mock(TypeDescription.class)).listNodes().size(), is(0));
+ for (MethodDescription methodDescription : TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual())) {
+ MethodGraph.Node node = methodGraph.locate(methodDescription.asSignatureToken());
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(node.getMethodTypes().size(), is(1));
+ assertThat(node.getMethodTypes().contains(methodDescription.asTypeToken()), is(true));
+ assertThat(node.getRepresentative(), is(methodDescription));
+ assertThat(node.getVisibility(), is(methodDescription.getVisibility()));
+ assertThat(methodGraph.listNodes().contains(node), is(true));
+ }
+ }
+
+ @Test
+ public void testSimpleClass() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(SimpleClass.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual()).size()));
+ assertThat(methodGraph.getSuperClassGraph().listNodes().size(), is(TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual()).size()));
+ assertThat(methodGraph.getInterfaceGraph(mock(TypeDescription.class)).listNodes().size(), is(0));
+ for (MethodDescription methodDescription : TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual())) {
+ MethodGraph.Node node = methodGraph.locate(methodDescription.asSignatureToken());
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(node.getMethodTypes().size(), is(1));
+ assertThat(node.getMethodTypes().contains(methodDescription.asTypeToken()), is(true));
+ assertThat(node.getRepresentative(), is(methodDescription));
+ assertThat(node.getVisibility(), is(methodDescription.getVisibility()));
+ assertThat(methodGraph.listNodes().contains(node), is(true));
+ }
+ }
+
+ @Test
+ public void testSimpleInterface() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(SimpleInterface.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(0));
+ }
+
+ @Test
+ public void testClassInheritance() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(ClassBase.Inner.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual()).size() + 1));
+ MethodDescription method = typeDescription.getDeclaredMethods().filter(isMethod()).getOnly();
+ MethodGraph.Node node = methodGraph.locate(method.asSignatureToken());
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(node.getMethodTypes().size(), is(1));
+ assertThat(node.getMethodTypes().contains(method.asTypeToken()), is(true));
+ assertThat(node.getRepresentative(), is(method));
+ assertThat(node.getVisibility(), is(method.getVisibility()));
+ assertThat(methodGraph.listNodes().contains(node), is(true));
+ MethodGraph.Node baseNode = methodGraph.getSuperClassGraph().locate(method.asSignatureToken());
+ assertThat(node, not(baseNode));
+ assertThat(typeDescription.getSuperClass().getDeclaredMethods().filter(ElementMatchers.is(baseNode.getRepresentative())).getOnly(),
+ is(baseNode.getRepresentative()));
+ }
+
+ @Test
+ public void testInterfaceImplementation() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(InterfaceBase.InnerClass.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual()).size() + 1));
+ MethodDescription method = typeDescription.getInterfaces().getOnly().getDeclaredMethods().getOnly();
+ MethodGraph.Node node = methodGraph.locate(method.asSignatureToken());
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(node.getMethodTypes().size(), is(1));
+ assertThat(node.getMethodTypes().contains(method.asTypeToken()), is(true));
+ assertThat(node.getRepresentative(), is(method));
+ assertThat(node.getVisibility(), is(method.getVisibility()));
+ assertThat(methodGraph.listNodes().contains(node), is(true));
+ }
+
+ @Test
+ public void testInterfaceExtension() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(InterfaceBase.InnerInterface.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(1));
+ MethodDescription method = typeDescription.getInterfaces().getOnly().getDeclaredMethods().getOnly();
+ MethodGraph.Node node = methodGraph.locate(method.asSignatureToken());
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(node.getMethodTypes().size(), is(1));
+ assertThat(node.getMethodTypes().contains(method.asTypeToken()), is(true));
+ assertThat(node.getRepresentative(), is(method));
+ assertThat(node.getVisibility(), is(method.getVisibility()));
+ assertThat(methodGraph.listNodes().contains(node), is(true));
+ }
+
+ @Test
+ public void testInterfaceDuplicateInHierarchyImplementation() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(InterfaceBase.InterfaceDuplicate.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(1));
+ MethodDescription method = typeDescription.getInterfaces().filter(ElementMatchers.is(InterfaceBase.class)).getOnly().getDeclaredMethods().getOnly();
+ MethodGraph.Node node = methodGraph.locate(method.asSignatureToken());
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(node.getMethodTypes().size(), is(1));
+ assertThat(node.getMethodTypes().contains(method.asTypeToken()), is(true));
+ assertThat(node.getRepresentative(), is(method));
+ assertThat(node.getVisibility(), is(method.getVisibility()));
+ assertThat(methodGraph.listNodes().contains(node), is(true));
+ }
+
+ @Test
+ public void testClassAndInterfaceDominantInheritance() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(ClassAndInterfaceInheritance.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual()).size() + 1));
+ MethodDescription method = typeDescription.getSuperClass().getDeclaredMethods().filter(isMethod()).getOnly();
+ MethodGraph.Node node = methodGraph.locate(method.asSignatureToken());
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(node.getMethodTypes().size(), is(1));
+ assertThat(node.getMethodTypes().contains(method.asTypeToken()), is(true));
+ assertThat(node.getRepresentative(), is(method));
+ assertThat(node.getVisibility(), is(method.getVisibility()));
+ MethodGraph.Node baseNode = methodGraph.getInterfaceGraph(new TypeDescription.ForLoadedType(InterfaceBase.class)).locate(method.asSignatureToken());
+ assertThat(node, not(baseNode));
+ assertThat(baseNode.getRepresentative(),
+ is((MethodDescription) typeDescription.getInterfaces().getOnly().getDeclaredMethods().getOnly()));
+ }
+
+ @Test
+ public void testMultipleAmbiguousClassInheritance() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(AmbiguousInterfaceBase.ClassTarget.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual()).size() + 1));
+ MethodDescription first = typeDescription.getInterfaces().filter(erasure(InterfaceBase.class)).getOnly()
+ .getDeclaredMethods().filter(isMethod()).getOnly();
+ MethodDescription second = typeDescription.getInterfaces().filter(erasure(AmbiguousInterfaceBase.class)).getOnly()
+ .getDeclaredMethods().filter(isMethod()).getOnly();
+ MethodGraph.Node node = methodGraph.locate(first.asSignatureToken());
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.AMBIGUOUS));
+ assertThat(node.getMethodTypes().size(), is(1));
+ assertThat(node.getMethodTypes().contains(first.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(second.asTypeToken()), is(true));
+ assertThat(node.getRepresentative(), is(first));
+ assertThat(node.getRepresentative(), not(second));
+ assertThat(node.getVisibility(), is(first.getVisibility()));
+ assertThat(node, is(methodGraph.locate(second.asSignatureToken())));
+ MethodGraph.Node firstBaseNode = methodGraph.getInterfaceGraph(new TypeDescription.ForLoadedType(InterfaceBase.class)).locate(first.asSignatureToken());
+ assertThat(node, not(firstBaseNode));
+ assertThat(firstBaseNode.getRepresentative(), is(first));
+ MethodGraph.Node secondBaseNode = methodGraph.getInterfaceGraph(new TypeDescription.ForLoadedType(InterfaceBase.class)).locate(second.asSignatureToken());
+ assertThat(node, not(secondBaseNode));
+ assertThat(secondBaseNode.getRepresentative(), is(first));
+ }
+
+ @Test
+ public void testMultipleAmbiguousInterfaceInheritance() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(AmbiguousInterfaceBase.InterfaceTarget.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(1));
+ MethodDescription first = typeDescription.getInterfaces().filter(erasure(InterfaceBase.class)).getOnly()
+ .getDeclaredMethods().filter(isMethod()).getOnly();
+ MethodDescription second = typeDescription.getInterfaces().filter(erasure(AmbiguousInterfaceBase.class)).getOnly()
+ .getDeclaredMethods().filter(isMethod()).getOnly();
+ MethodGraph.Node node = methodGraph.locate(first.asSignatureToken());
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.AMBIGUOUS));
+ assertThat(node.getMethodTypes().size(), is(1));
+ assertThat(node.getMethodTypes().contains(first.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(second.asTypeToken()), is(true));
+ assertThat(node.getRepresentative(), is(first));
+ assertThat(node.getRepresentative(), not(second));
+ assertThat(node.getVisibility(), is(first.getVisibility()));
+ assertThat(node, is(methodGraph.locate(second.asSignatureToken())));
+ MethodGraph.Node firstBaseNode = methodGraph.getInterfaceGraph(new TypeDescription.ForLoadedType(InterfaceBase.class)).locate(first.asSignatureToken());
+ assertThat(node, not(firstBaseNode));
+ assertThat(firstBaseNode.getRepresentative(), is(first));
+ MethodGraph.Node secondBaseNode = methodGraph.getInterfaceGraph(new TypeDescription.ForLoadedType(InterfaceBase.class)).locate(second.asSignatureToken());
+ assertThat(node, not(secondBaseNode));
+ assertThat(secondBaseNode.getRepresentative(), is(first));
+ }
+
+ @Test
+ public void testDominantClassInheritance() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(AmbiguousInterfaceBase.DominantClassTarget.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual()).size() + 1));
+ MethodDescription methodDescription = new TypeDescription.ForLoadedType(AmbiguousInterfaceBase.DominantIntermediate.class)
+ .getDeclaredMethods().getOnly();
+ MethodGraph.Node node = methodGraph.locate(methodDescription.asSignatureToken());
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(node.getMethodTypes().size(), is(1));
+ assertThat(node.getMethodTypes().contains(methodDescription.asTypeToken()), is(true));
+ assertThat(node.getRepresentative(), is(methodDescription));
+ assertThat(node.getVisibility(), is(methodDescription.getVisibility()));
+ }
+
+ @Test
+ public void testDominantInterfaceInheritanceLeft() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(AmbiguousInterfaceBase.DominantInterfaceTargetLeft.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(1));
+ MethodDescription methodDescription = new TypeDescription.ForLoadedType(AmbiguousInterfaceBase.DominantIntermediate.class)
+ .getDeclaredMethods().getOnly();
+ MethodGraph.Node node = methodGraph.locate(methodDescription.asSignatureToken());
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(node.getMethodTypes().size(), is(1));
+ assertThat(node.getMethodTypes().contains(methodDescription.asTypeToken()), is(true));
+ assertThat(node.getRepresentative(), is(methodDescription));
+ assertThat(node.getVisibility(), is(methodDescription.getVisibility()));
+ }
+
+ @Test
+ public void testDominantInterfaceInheritanceRight() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(AmbiguousInterfaceBase.DominantInterfaceTargetRight.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(1));
+ MethodDescription methodDescription = new TypeDescription.ForLoadedType(AmbiguousInterfaceBase.DominantIntermediate.class)
+ .getDeclaredMethods().getOnly();
+ MethodGraph.Node node = methodGraph.locate(methodDescription.asSignatureToken());
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(node.getMethodTypes().size(), is(1));
+ assertThat(node.getMethodTypes().contains(methodDescription.asTypeToken()), is(true));
+ assertThat(node.getRepresentative(), is(methodDescription));
+ assertThat(node.getVisibility(), is(methodDescription.getVisibility()));
+ }
+
+ @Test
+ public void testNonDominantInterfaceInheritanceLeft() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(AmbiguousInterfaceBase.NonDominantTargetLeft.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(1));
+ MethodDescription methodDescription = new TypeDescription.ForLoadedType(AmbiguousInterfaceBase.DominantIntermediate.class)
+ .getDeclaredMethods().getOnly();
+ MethodGraph.Node node = methodGraph.locate(methodDescription.asSignatureToken());
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.AMBIGUOUS));
+ assertThat(node.getMethodTypes().size(), is(1));
+ assertThat(node.getMethodTypes().contains(methodDescription.asTypeToken()), is(true));
+ assertThat(node.getRepresentative(), is(methodDescription));
+ assertThat(node.getVisibility(), is(methodDescription.getVisibility()));
+ }
+
+ @Test
+ public void testNonDominantInterfaceInheritanceRight() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(AmbiguousInterfaceBase.NonDominantTargetRight.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(1));
+ MethodDescription methodDescription = new TypeDescription.ForLoadedType(AmbiguousInterfaceBase.DominantIntermediate.class)
+ .getDeclaredMethods().getOnly();
+ MethodGraph.Node node = methodGraph.locate(methodDescription.asSignatureToken());
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.AMBIGUOUS));
+ assertThat(node.getMethodTypes().size(), is(1));
+ assertThat(node.getMethodTypes().contains(methodDescription.asTypeToken()), is(true));
+ assertThat(node.getRepresentative(), is(methodDescription));
+ assertThat(node.getVisibility(), is(methodDescription.getVisibility()));
+ }
+
+ @Test
+ public void testGenericClassSingleEvolution() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(GenericClassBase.Inner.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual()).size() + 1));
+ MethodDescription.SignatureToken token = typeDescription.getDeclaredMethods().filter(isMethod().and(ElementMatchers.not(isBridge()))).getOnly().asSignatureToken();
+ MethodGraph.Node node = methodGraph.locate(token);
+ MethodDescription.SignatureToken bridgeToken = typeDescription.getSuperClass().getDeclaredMethods().filter(isMethod()).getOnly().asDefined().asSignatureToken();
+ assertThat(node, is(methodGraph.locate(bridgeToken)));
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(node.getMethodTypes().size(), is(2));
+ assertThat(node.getMethodTypes().contains(token.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(bridgeToken.asTypeToken()), is(true));
+ assertThat(node.getVisibility(), is(Visibility.PUBLIC));
+ }
+
+ @Test
+ public void testGenericClassMultipleEvolution() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(GenericClassBase.Intermediate.Inner.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual()).size() + 1));
+ MethodDescription.SignatureToken token = typeDescription.getDeclaredMethods().filter(isMethod().and(ElementMatchers.not(isBridge()))).getOnly().asSignatureToken();
+ MethodGraph.Node node = methodGraph.locate(token);
+ MethodDescription.SignatureToken firstBridgeToken = typeDescription.getSuperClass().getDeclaredMethods()
+ .filter(isMethod().and(ElementMatchers.not(isBridge()))).getOnly().asDefined().asSignatureToken();
+ MethodDescription.SignatureToken secondBridgeToken = typeDescription.getSuperClass().getSuperClass().getDeclaredMethods()
+ .filter(isMethod()).getOnly().asDefined().asSignatureToken();
+ assertThat(node, is(methodGraph.locate(firstBridgeToken)));
+ assertThat(node, is(methodGraph.locate(secondBridgeToken)));
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(node.getMethodTypes().size(), is(3));
+ assertThat(node.getMethodTypes().contains(token.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(firstBridgeToken.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(secondBridgeToken.asTypeToken()), is(true));
+ assertThat(node.getVisibility(), is(Visibility.PUBLIC));
+ }
+
+ @Test
+ public void testReturnTypeClassSingleEvolution() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(ReturnTypeClassBase.Inner.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual()).size() + 1));
+ MethodDescription.SignatureToken token = typeDescription.getDeclaredMethods().filter(isMethod().and(ElementMatchers.not(isBridge()))).getOnly().asSignatureToken();
+ MethodGraph.Node node = methodGraph.locate(token);
+ MethodDescription.SignatureToken bridgeToken = typeDescription.getSuperClass().getDeclaredMethods().filter(isMethod()).getOnly().asSignatureToken();
+ assertThat(node, is(methodGraph.locate(bridgeToken)));
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(node.getMethodTypes().size(), is(2));
+ assertThat(node.getMethodTypes().contains(token.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(bridgeToken.asTypeToken()), is(true));
+ assertThat(node.getVisibility(), is(Visibility.PUBLIC));
+ }
+
+ @Test
+ public void testReturnTypeClassMultipleEvolution() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(ReturnTypeClassBase.Intermediate.Inner.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual()).size() + 1));
+ MethodDescription.SignatureToken token = typeDescription.getDeclaredMethods().filter(isMethod().and(ElementMatchers.not(isBridge()))).getOnly().asSignatureToken();
+ MethodGraph.Node node = methodGraph.locate(token);
+ MethodDescription.SignatureToken firstBridgeToken = typeDescription.getSuperClass().getDeclaredMethods()
+ .filter(isMethod().and(ElementMatchers.not(isBridge()))).getOnly().asDefined().asSignatureToken();
+ MethodDescription.SignatureToken secondBridgeToken = typeDescription.getSuperClass().getSuperClass().getDeclaredMethods()
+ .filter(isMethod()).getOnly().asDefined().asSignatureToken();
+ assertThat(node, is(methodGraph.locate(firstBridgeToken)));
+ assertThat(node, is(methodGraph.locate(secondBridgeToken)));
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(node.getMethodTypes().size(), is(3));
+ assertThat(node.getMethodTypes().contains(token.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(firstBridgeToken.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(secondBridgeToken.asTypeToken()), is(true));
+ assertThat(node.getVisibility(), is(Visibility.PUBLIC));
+ }
+
+ @Test
+ public void testGenericReturnTypeClassSingleEvolution() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(GenericReturnClassBase.Inner.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual()).size() + 1));
+ MethodDescription.SignatureToken token = typeDescription.getDeclaredMethods().filter(isMethod().and(ElementMatchers.not(isBridge()))).getOnly().asSignatureToken();
+ MethodGraph.Node node = methodGraph.locate(token);
+ MethodDescription.SignatureToken bridgeToken = typeDescription.getSuperClass().getDeclaredMethods().filter(isMethod()).getOnly().asSignatureToken();
+ assertThat(node, is(methodGraph.locate(bridgeToken)));
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(node.getMethodTypes().size(), is(2));
+ assertThat(node.getMethodTypes().contains(token.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(bridgeToken.asTypeToken()), is(true));
+ assertThat(node.getVisibility(), is(Visibility.PUBLIC));
+ }
+
+ @Test
+ public void testGenericReturnTypeClassMultipleEvolution() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(GenericReturnClassBase.Intermediate.Inner.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual()).size() + 1));
+ MethodDescription.SignatureToken token = typeDescription.getDeclaredMethods().filter(isMethod().and(ElementMatchers.not(isBridge()))).getOnly().asSignatureToken();
+ MethodGraph.Node node = methodGraph.locate(token);
+ MethodDescription.SignatureToken firstBridgeToken = typeDescription.getSuperClass().getDeclaredMethods()
+ .filter(isMethod().and(ElementMatchers.not(isBridge()))).getOnly().asDefined().asSignatureToken();
+ MethodDescription.SignatureToken secondBridgeToken = typeDescription.getSuperClass().getSuperClass().getDeclaredMethods()
+ .filter(isMethod()).getOnly().asDefined().asSignatureToken();
+ assertThat(node, is(methodGraph.locate(firstBridgeToken)));
+ assertThat(node, is(methodGraph.locate(secondBridgeToken)));
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(node.getMethodTypes().size(), is(3));
+ assertThat(node.getMethodTypes().contains(token.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(firstBridgeToken.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(secondBridgeToken.asTypeToken()), is(true));
+ assertThat(node.getVisibility(), is(Visibility.PUBLIC));
+ }
+
+ @Test
+ public void testGenericInterfaceSingleEvolution() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(GenericInterfaceBase.Inner.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(1));
+ MethodDescription.SignatureToken token = typeDescription.getDeclaredMethods().filter(isMethod().and(ElementMatchers.not(isBridge()))).getOnly().asSignatureToken();
+ MethodGraph.Node node = methodGraph.locate(token);
+ MethodDescription.SignatureToken bridgeToken = typeDescription.getInterfaces().getOnly()
+ .getDeclaredMethods().filter(isMethod()).getOnly().asDefined().asSignatureToken();
+ assertThat(node, is(methodGraph.locate(bridgeToken)));
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(node.getMethodTypes().size(), is(2));
+ assertThat(node.getMethodTypes().contains(token.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(bridgeToken.asTypeToken()), is(true));
+ assertThat(node.getVisibility(), is(Visibility.PUBLIC));
+ }
+
+ @Test
+ public void testGenericInterfaceMultipleEvolution() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(GenericInterfaceBase.Intermediate.Inner.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(1));
+ MethodDescription.SignatureToken token = typeDescription.getDeclaredMethods().filter(ElementMatchers.not(isBridge())).getOnly().asSignatureToken();
+ MethodGraph.Node node = methodGraph.locate(token);
+ MethodDescription.SignatureToken firstBridgeToken = typeDescription.getInterfaces().getOnly()
+ .getDeclaredMethods().filter(ElementMatchers.not(isBridge())).getOnly().asDefined().asSignatureToken();
+ MethodDescription.SignatureToken secondBridgeToken = typeDescription.getInterfaces().getOnly().getInterfaces().getOnly()
+ .getDeclaredMethods().getOnly().asDefined().asSignatureToken();
+ assertThat(node, is(methodGraph.locate(firstBridgeToken)));
+ assertThat(node, is(methodGraph.locate(secondBridgeToken)));
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(node.getMethodTypes().size(), is(3));
+ assertThat(node.getMethodTypes().contains(token.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(firstBridgeToken.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(secondBridgeToken.asTypeToken()), is(true));
+ assertThat(node.getVisibility(), is(Visibility.PUBLIC));
+ }
+
+ @Test
+ public void testReturnTypeInterfaceSingleEvolution() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(ReturnTypeInterfaceBase.Inner.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(1));
+ MethodDescription.SignatureToken token = typeDescription.getDeclaredMethods().filter(ElementMatchers.not(isBridge())).getOnly().asSignatureToken();
+ MethodGraph.Node node = methodGraph.locate(token);
+ MethodDescription.SignatureToken bridgeToken = typeDescription.getInterfaces().getOnly().getDeclaredMethods().getOnly().asSignatureToken();
+ assertThat(node, is(methodGraph.locate(bridgeToken)));
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(node.getMethodTypes().size(), is(2));
+ assertThat(node.getMethodTypes().contains(token.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(bridgeToken.asTypeToken()), is(true));
+ assertThat(node.getVisibility(), is(Visibility.PUBLIC));
+ }
+
+ @Test
+ public void testReturnTypeInterfaceMultipleEvolution() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(ReturnTypeInterfaceBase.Intermediate.Inner.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(1));
+ MethodDescription.SignatureToken token = typeDescription.getDeclaredMethods().filter(ElementMatchers.not(isBridge())).getOnly().asSignatureToken();
+ MethodGraph.Node node = methodGraph.locate(token);
+ MethodDescription.SignatureToken firstBridgeToken = typeDescription.getInterfaces().getOnly()
+ .getDeclaredMethods().filter(ElementMatchers.not(isBridge())).getOnly().asSignatureToken();
+ MethodDescription.SignatureToken secondBridgeToken = typeDescription.getInterfaces().getOnly().getInterfaces().getOnly()
+ .getDeclaredMethods().getOnly().asSignatureToken();
+ assertThat(node, is(methodGraph.locate(firstBridgeToken)));
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(node.getMethodTypes().size(), is(3));
+ assertThat(node.getMethodTypes().contains(token.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(firstBridgeToken.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(secondBridgeToken.asTypeToken()), is(true));
+ assertThat(node.getVisibility(), is(Visibility.PUBLIC));
+ }
+
+ @Test
+ public void testGenericWithReturnTypeClassSingleEvolution() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(GenericWithReturnTypeClassBase.Inner.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual()).size() + 1));
+ MethodDescription.SignatureToken token = typeDescription.getDeclaredMethods().filter(isMethod().and(ElementMatchers.not(isBridge()))).getOnly().asSignatureToken();
+ MethodGraph.Node node = methodGraph.locate(token);
+ MethodDescription.SignatureToken bridgeToken = typeDescription.getSuperClass().getDeclaredMethods().filter(isMethod()).getOnly().asDefined().asSignatureToken();
+ assertThat(node, is(methodGraph.locate(bridgeToken)));
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(node.getMethodTypes().size(), is(2));
+ assertThat(node.getMethodTypes().contains(token.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(bridgeToken.asTypeToken()), is(true));
+ assertThat(node.getVisibility(), is(Visibility.PUBLIC));
+ }
+
+ @Test
+ public void testGenericWithReturnTypeClassMultipleEvolution() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(GenericWithReturnTypeClassBase.Intermediate.Inner.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual()).size() + 1));
+ MethodDescription.SignatureToken token = typeDescription.getDeclaredMethods().filter(isMethod().and(ElementMatchers.not(isBridge()))).getOnly().asSignatureToken();
+ MethodGraph.Node node = methodGraph.locate(token);
+ MethodDescription.SignatureToken firstBridgeToken = typeDescription.getSuperClass().getDeclaredMethods()
+ .filter(isMethod().and(ElementMatchers.not(isBridge()))).getOnly().asDefined().asSignatureToken();
+ MethodDescription.SignatureToken secondBridgeToken = typeDescription.getSuperClass().getSuperClass().getDeclaredMethods()
+ .filter(isMethod()).getOnly().asDefined().asSignatureToken();
+ assertThat(node, is(methodGraph.locate(firstBridgeToken)));
+ assertThat(node, is(methodGraph.locate(secondBridgeToken)));
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(node.getMethodTypes().size(), is(3));
+ assertThat(node.getMethodTypes().contains(token.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(firstBridgeToken.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(secondBridgeToken.asTypeToken()), is(true));
+ assertThat(node.getVisibility(), is(Visibility.PUBLIC));
+ }
+
+ @Test
+ public void testGenericWithReturnTypeInterfaceSingleEvolution() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(GenericWithReturnTypeInterfaceBase.Inner.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(1));
+ MethodDescription.SignatureToken token = typeDescription.getDeclaredMethods().filter(isMethod().and(ElementMatchers.not(isBridge()))).getOnly().asSignatureToken();
+ MethodGraph.Node node = methodGraph.locate(token);
+ MethodDescription.SignatureToken bridgeToken = typeDescription.getInterfaces().getOnly()
+ .getDeclaredMethods().filter(isMethod()).getOnly().asDefined().asSignatureToken();
+ assertThat(node, is(methodGraph.locate(bridgeToken)));
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(node.getMethodTypes().size(), is(2));
+ assertThat(node.getMethodTypes().contains(token.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(bridgeToken.asTypeToken()), is(true));
+ assertThat(node.getVisibility(), is(Visibility.PUBLIC));
+ }
+
+ @Test
+ public void testGenericWithReturnTypeInterfaceMultipleEvolution() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(GenericWithReturnTypeInterfaceBase.Intermediate.Inner.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(1));
+ MethodDescription.SignatureToken token = typeDescription.getDeclaredMethods().filter(ElementMatchers.not(isBridge())).getOnly().asSignatureToken();
+ MethodGraph.Node node = methodGraph.locate(token);
+ MethodDescription.SignatureToken firstBridgeToken = typeDescription.getInterfaces().getOnly()
+ .getDeclaredMethods().filter(ElementMatchers.not(isBridge())).getOnly().asDefined().asSignatureToken();
+ MethodDescription.SignatureToken secondBridgeToken = typeDescription.getInterfaces().getOnly().getInterfaces().getOnly()
+ .getDeclaredMethods().getOnly().asDefined().asSignatureToken();
+ assertThat(node, is(methodGraph.locate(firstBridgeToken)));
+ assertThat(node, is(methodGraph.locate(secondBridgeToken)));
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(node.getMethodTypes().size(), is(3));
+ assertThat(node.getMethodTypes().contains(token.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(firstBridgeToken.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(secondBridgeToken.asTypeToken()), is(true));
+ assertThat(node.getVisibility(), is(Visibility.PUBLIC));
+ }
+
+ @Test
+ public void testGenericNonOverriddenClassExtension() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(GenericNonOverriddenClassBase.Inner.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual()).size() + 1));
+ MethodDescription methodDescription = typeDescription.getSuperClass().getDeclaredMethods().filter(isMethod()).getOnly();
+ MethodGraph.Node node = methodGraph.locate(methodDescription.asSignatureToken());
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(node.getRepresentative(), is(methodDescription));
+ assertThat(node.getMethodTypes().size(), is(2));
+ assertThat(node.getMethodTypes().contains(methodDescription.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(methodDescription.asDefined().asTypeToken()), is(true));
+ assertThat(node, is(methodGraph.getSuperClassGraph().locate(methodDescription.asSignatureToken())));
+ assertThat(node.getVisibility(), is(Visibility.PUBLIC));
+ }
+
+ @Test
+ public void testGenericNonOverriddenInterfaceExtension() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(GenericNonOverriddenInterfaceBase.InnerClass.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual()).size() + 1));
+ MethodDescription methodDescription = typeDescription.getInterfaces().getOnly().getDeclaredMethods().filter(isMethod()).getOnly();
+ MethodGraph.Node node = methodGraph.locate(methodDescription.asSignatureToken());
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(node.getRepresentative(), is(methodDescription));
+ assertThat(node.getMethodTypes().size(), is(2));
+ assertThat(node.getMethodTypes().contains(methodDescription.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(methodDescription.asDefined().asTypeToken()), is(true));
+ assertThat(node, is(methodGraph.getInterfaceGraph(new TypeDescription.ForLoadedType(GenericNonOverriddenInterfaceBase.class))
+ .locate(methodDescription.asSignatureToken())));
+ assertThat(node.getVisibility(), is(Visibility.PUBLIC));
+ }
+
+ @Test
+ public void testGenericNonOverriddenInterfaceImplementation() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(GenericNonOverriddenInterfaceBase.InnerInterface.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(1));
+ MethodDescription methodDescription = typeDescription.getInterfaces().getOnly().getDeclaredMethods().filter(isMethod()).getOnly();
+ MethodGraph.Node node = methodGraph.locate(methodDescription.asSignatureToken());
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(node.getRepresentative(), is(methodDescription));
+ assertThat(node.getMethodTypes().size(), is(2));
+ assertThat(node.getMethodTypes().contains(methodDescription.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(methodDescription.asDefined().asTypeToken()), is(true));
+ assertThat(node, is(methodGraph.getInterfaceGraph(new TypeDescription.ForLoadedType(GenericNonOverriddenInterfaceBase.class))
+ .locate(methodDescription.asSignatureToken())));
+ assertThat(node.getVisibility(), is(Visibility.PUBLIC));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testTypeVariableInterfaceBridge() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(Class.forName(TYPE_VARIABLE_INTERFACE_BRIDGE));
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(1));
+ MethodDescription methodDescription = typeDescription.getDeclaredMethods().filter(takesArguments(String.class)).getOnly();
+ MethodGraph.Node node = methodGraph.locate(methodDescription.asSignatureToken());
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(node.getRepresentative(), is(methodDescription));
+ assertThat(node.getMethodTypes().size(), is(2));
+ assertThat(node.getMethodTypes().contains(methodDescription.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(typeDescription.getDeclaredMethods().filter(takesArguments(Object.class)).getOnly().asTypeToken()), is(true));
+ assertThat(node.getVisibility(), is(Visibility.PUBLIC));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testReturnTypeInterfaceBridge() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(Class.forName(RETURN_TYPE_INTERFACE_BRIDGE));
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(1));
+ MethodDescription methodDescription = typeDescription.getDeclaredMethods().filter(returns(String.class)).getOnly();
+ MethodGraph.Node node = methodGraph.locate(methodDescription.asSignatureToken());
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(node.getRepresentative(), is(methodDescription));
+ assertThat(node.getMethodTypes().size(), is(2));
+ assertThat(node.getMethodTypes().contains(methodDescription.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(typeDescription.getDeclaredMethods().filter(returns(Object.class)).getOnly().asTypeToken()), is(true));
+ assertThat(node.getVisibility(), is(Visibility.PUBLIC));
+ }
+
+ @Test
+ public void testDuplicateNameClass() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(DuplicateNameClass.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual()).size() + 2));
+ MethodDescription objectMethod = typeDescription.getDeclaredMethods().filter(takesArguments(Object.class)).getOnly();
+ MethodGraph.Node objectNode = methodGraph.locate(objectMethod.asSignatureToken());
+ assertThat(objectNode.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(objectNode.getRepresentative(), is(objectMethod));
+ assertThat(objectNode.getMethodTypes().size(), is(1));
+ assertThat(objectNode.getMethodTypes().contains(objectMethod.asTypeToken()), is(true));
+ assertThat(objectNode.getVisibility(), is(objectMethod.getVisibility()));
+ MethodDescription voidMethod = typeDescription.getDeclaredMethods().filter(takesArguments(Integer.class)).getOnly();
+ MethodGraph.Node voidNode = methodGraph.locate(voidMethod.asSignatureToken());
+ assertThat(voidNode.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(voidNode.getRepresentative(), is(voidMethod));
+ assertThat(voidNode.getMethodTypes().size(), is(1));
+ assertThat(voidNode.getMethodTypes().contains(voidMethod.asTypeToken()), is(true));
+ assertThat(voidNode.getVisibility(), is(voidMethod.getVisibility()));
+ }
+
+ @Test
+ public void testDuplicateNameClassExtension() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(DuplicateNameClass.Inner.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual()).size() + 3));
+ MethodDescription objectMethod = typeDescription.getSuperClass().getDeclaredMethods().filter(takesArguments(Object.class)).getOnly();
+ MethodGraph.Node objectNode = methodGraph.locate(objectMethod.asSignatureToken());
+ assertThat(objectNode.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(objectNode.getRepresentative(), is(objectMethod));
+ assertThat(objectNode.getMethodTypes().size(), is(1));
+ assertThat(objectNode.getMethodTypes().contains(objectMethod.asTypeToken()), is(true));
+ assertThat(objectNode.getVisibility(), is(objectMethod.getVisibility()));
+ MethodDescription integerMethod = typeDescription.getSuperClass().getDeclaredMethods().filter(takesArguments(Integer.class)).getOnly();
+ MethodGraph.Node integerNode = methodGraph.locate(integerMethod.asSignatureToken());
+ assertThat(integerNode.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(integerNode.getRepresentative(), is(integerMethod));
+ assertThat(integerNode.getMethodTypes().size(), is(1));
+ assertThat(integerNode.getMethodTypes().contains(integerMethod.asTypeToken()), is(true));
+ assertThat(integerNode.getVisibility(), is(integerMethod.getVisibility()));
+ MethodDescription voidMethod = typeDescription.getDeclaredMethods().filter(takesArguments(Void.class)).getOnly();
+ MethodGraph.Node voidNode = methodGraph.locate(voidMethod.asSignatureToken());
+ assertThat(voidNode.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(voidNode.getRepresentative(), is(voidMethod));
+ assertThat(voidNode.getMethodTypes().size(), is(1));
+ assertThat(voidNode.getMethodTypes().contains(voidMethod.asTypeToken()), is(true));
+ assertThat(voidNode.getVisibility(), is(voidMethod.getVisibility()));
+ }
+
+ @Test
+ public void testDuplicateNameInterface() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(DuplicateNameInterface.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(2));
+ MethodDescription objectMethod = typeDescription.getDeclaredMethods().filter(takesArguments(Object.class)).getOnly();
+ MethodGraph.Node objectNode = methodGraph.locate(objectMethod.asSignatureToken());
+ assertThat(objectNode.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(objectNode.getRepresentative(), is(objectMethod));
+ assertThat(objectNode.getMethodTypes().size(), is(1));
+ assertThat(objectNode.getMethodTypes().contains(objectMethod.asTypeToken()), is(true));
+ assertThat(objectNode.getVisibility(), is(objectMethod.getVisibility()));
+ MethodDescription voidMethod = typeDescription.getDeclaredMethods().filter(takesArguments(Integer.class)).getOnly();
+ MethodGraph.Node voidNode = methodGraph.locate(voidMethod.asSignatureToken());
+ assertThat(voidNode.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(voidNode.getRepresentative(), is(voidMethod));
+ assertThat(voidNode.getMethodTypes().size(), is(1));
+ assertThat(voidNode.getMethodTypes().contains(voidMethod.asTypeToken()), is(true));
+ assertThat(voidNode.getVisibility(), is(voidMethod.getVisibility()));
+ }
+
+ @Test
+ public void testDuplicateNameInterfaceImplementation() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(DuplicateNameInterface.InnerClass.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual()).size() + 3));
+ MethodDescription objectMethod = typeDescription.getInterfaces().getOnly().getDeclaredMethods().filter(takesArguments(Object.class)).getOnly();
+ MethodGraph.Node objectNode = methodGraph.locate(objectMethod.asSignatureToken());
+ assertThat(objectNode.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(objectNode.getRepresentative(), is(objectMethod));
+ assertThat(objectNode.getMethodTypes().size(), is(1));
+ assertThat(objectNode.getMethodTypes().contains(objectMethod.asTypeToken()), is(true));
+ assertThat(objectMethod.getVisibility(), is(objectMethod.getVisibility()));
+ MethodDescription integerMethod = typeDescription.getInterfaces().getOnly().getDeclaredMethods().filter(takesArguments(Integer.class)).getOnly();
+ MethodGraph.Node integerNode = methodGraph.locate(integerMethod.asSignatureToken());
+ assertThat(integerNode.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(integerNode.getRepresentative(), is(integerMethod));
+ assertThat(integerNode.getMethodTypes().size(), is(1));
+ assertThat(integerNode.getMethodTypes().contains(integerMethod.asTypeToken()), is(true));
+ assertThat(integerNode.getVisibility(), is(integerMethod.getVisibility()));
+ MethodDescription voidMethod = typeDescription.getDeclaredMethods().filter(takesArguments(Void.class)).getOnly();
+ MethodGraph.Node voidNode = methodGraph.locate(voidMethod.asSignatureToken());
+ assertThat(voidNode.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(voidNode.getRepresentative(), is(voidMethod));
+ assertThat(voidNode.getMethodTypes().size(), is(1));
+ assertThat(voidNode.getMethodTypes().contains(voidMethod.asTypeToken()), is(true));
+ assertThat(voidNode.getVisibility(), is(voidMethod.getVisibility()));
+ }
+
+ @Test
+ public void testDuplicateNameInterfaceExtension() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(DuplicateNameInterface.InnerInterface.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(3));
+ MethodDescription objectMethod = typeDescription.getInterfaces().getOnly().getDeclaredMethods().filter(takesArguments(Object.class)).getOnly();
+ MethodGraph.Node objectNode = methodGraph.locate(objectMethod.asSignatureToken());
+ assertThat(objectNode.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(objectNode.getRepresentative(), is(objectMethod));
+ assertThat(objectNode.getMethodTypes().size(), is(1));
+ assertThat(objectNode.getMethodTypes().contains(objectMethod.asTypeToken()), is(true));
+ assertThat(objectNode.getVisibility(), is(objectMethod.getVisibility()));
+ MethodDescription integerMethod = typeDescription.getInterfaces().getOnly().getDeclaredMethods().filter(takesArguments(Integer.class)).getOnly();
+ MethodGraph.Node integerNode = methodGraph.locate(integerMethod.asSignatureToken());
+ assertThat(integerNode.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(integerNode.getRepresentative(), is(integerMethod));
+ assertThat(integerNode.getMethodTypes().size(), is(1));
+ assertThat(integerNode.getMethodTypes().contains(integerMethod.asTypeToken()), is(true));
+ assertThat(integerMethod.getVisibility(), is(integerNode.getVisibility()));
+ MethodDescription voidMethod = typeDescription.getDeclaredMethods().filter(takesArguments(Void.class)).getOnly();
+ MethodGraph.Node voidNode = methodGraph.locate(voidMethod.asSignatureToken());
+ assertThat(voidNode.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(voidNode.getRepresentative(), is(voidMethod));
+ assertThat(voidNode.getMethodTypes().size(), is(1));
+ assertThat(voidNode.getMethodTypes().contains(voidMethod.asTypeToken()), is(true));
+ assertThat(voidNode.getVisibility(), is(voidMethod.getVisibility()));
+ }
+
+ @Test
+ public void testDuplicateNameGenericClass() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(DuplicateNameGenericClass.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual()).size() + 2));
+ MethodDescription objectMethod = typeDescription.getDeclaredMethods().filter(takesArguments(Object.class)).getOnly();
+ MethodGraph.Node objectNode = methodGraph.locate(objectMethod.asSignatureToken());
+ assertThat(objectNode.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(objectNode.getRepresentative(), is(objectMethod));
+ assertThat(objectNode.getMethodTypes().size(), is(1));
+ assertThat(objectNode.getMethodTypes().contains(objectMethod.asTypeToken()), is(true));
+ assertThat(objectNode.getVisibility(), is(objectMethod.getVisibility()));
+ MethodDescription voidMethod = typeDescription.getDeclaredMethods().filter(takesArguments(Integer.class)).getOnly();
+ MethodGraph.Node voidNode = methodGraph.locate(voidMethod.asSignatureToken());
+ assertThat(voidNode.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(voidNode.getRepresentative(), is(voidMethod));
+ assertThat(voidNode.getMethodTypes().size(), is(1));
+ assertThat(voidNode.getMethodTypes().contains(voidMethod.asTypeToken()), is(true));
+ assertThat(voidNode.getVisibility(), is(voidMethod.getVisibility()));
+ }
+
+ @Test
+ public void testDuplicateNameGenericClassExtension() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(DuplicateNameGenericClass.Inner.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual()).size() + 3));
+ MethodDescription objectMethod = typeDescription.getSuperClass().getDeclaredMethods().filter(takesArguments(String.class)).getOnly();
+ MethodGraph.Node objectNode = methodGraph.locate(objectMethod.asSignatureToken());
+ assertThat(objectNode.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(objectNode.getRepresentative(), is(objectMethod));
+ assertThat(objectNode.getMethodTypes().size(), is(2));
+ assertThat(objectNode.getMethodTypes().contains(objectMethod.asTypeToken()), is(true));
+ assertThat(objectNode.getMethodTypes().contains(objectMethod.asDefined().asTypeToken()), is(true));
+ assertThat(objectNode.getVisibility(), is(objectMethod.getVisibility()));
+ MethodDescription integerMethod = typeDescription.getSuperClass().getDeclaredMethods().filter(takesArguments(Integer.class)).getOnly();
+ MethodGraph.Node integerNode = methodGraph.locate(integerMethod.asSignatureToken());
+ assertThat(integerNode.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(integerNode.getRepresentative(), is(integerMethod));
+ assertThat(integerNode.getMethodTypes().size(), is(1));
+ assertThat(integerNode.getMethodTypes().contains(integerMethod.asTypeToken()), is(true));
+ MethodDescription voidMethod = typeDescription.getDeclaredMethods().filter(takesArguments(Void.class)).getOnly();
+ MethodGraph.Node voidNode = methodGraph.locate(voidMethod.asSignatureToken());
+ assertThat(voidNode.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(voidNode.getRepresentative(), is(voidMethod));
+ assertThat(voidNode.getMethodTypes().size(), is(1));
+ assertThat(voidNode.getMethodTypes().contains(voidMethod.asTypeToken()), is(true));
+ assertThat(voidNode.getVisibility(), is(voidMethod.getVisibility()));
+ }
+
+ @Test
+ public void testDuplicateNameGenericInterface() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(DuplicateNameGenericInterface.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(2));
+ MethodDescription objectMethod = typeDescription.getDeclaredMethods().filter(takesArguments(Object.class)).getOnly();
+ MethodGraph.Node objectNode = methodGraph.locate(objectMethod.asSignatureToken());
+ assertThat(objectNode.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(objectNode.getRepresentative(), is(objectMethod));
+ assertThat(objectNode.getMethodTypes().size(), is(1));
+ assertThat(objectNode.getMethodTypes().contains(objectMethod.asTypeToken()), is(true));
+ assertThat(objectNode.getVisibility(), is(objectMethod.getVisibility()));
+ MethodDescription voidMethod = typeDescription.getDeclaredMethods().filter(takesArguments(Integer.class)).getOnly();
+ MethodGraph.Node voidNode = methodGraph.locate(voidMethod.asSignatureToken());
+ assertThat(voidNode.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(voidNode.getRepresentative(), is(voidMethod));
+ assertThat(voidNode.getMethodTypes().size(), is(1));
+ assertThat(voidNode.getMethodTypes().contains(voidMethod.asTypeToken()), is(true));
+ assertThat(voidNode.getVisibility(), is(voidMethod.getVisibility()));
+ }
+
+ @Test
+ public void testDuplicateNameGenericInterfaceImplementation() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(DuplicateNameGenericInterface.InnerClass.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual()).size() + 3));
+ MethodDescription objectMethod = typeDescription.getInterfaces().getOnly().getDeclaredMethods().filter(takesArguments(String.class)).getOnly();
+ MethodGraph.Node objectNode = methodGraph.locate(objectMethod.asSignatureToken());
+ assertThat(objectNode.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(objectNode.getRepresentative(), is(objectMethod));
+ assertThat(objectNode.getMethodTypes().size(), is(2));
+ assertThat(objectNode.getMethodTypes().contains(objectMethod.asTypeToken()), is(true));
+ assertThat(objectNode.getMethodTypes().contains(objectMethod.asDefined().asTypeToken()), is(true));
+ assertThat(objectNode.getVisibility(), is(objectMethod.getVisibility()));
+ MethodDescription integerMethod = typeDescription.getInterfaces().getOnly().getDeclaredMethods().filter(takesArguments(Integer.class)).getOnly();
+ MethodGraph.Node integerNode = methodGraph.locate(integerMethod.asSignatureToken());
+ assertThat(integerNode.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(integerNode.getRepresentative(), is(integerMethod));
+ assertThat(integerNode.getMethodTypes().size(), is(1));
+ assertThat(integerNode.getMethodTypes().contains(integerMethod.asTypeToken()), is(true));
+ assertThat(integerNode.getMethodTypes().contains(integerMethod.asDefined().asTypeToken()), is(true));
+ assertThat(integerNode.getVisibility(), is(integerMethod.getVisibility()));
+ MethodDescription voidMethod = typeDescription.getDeclaredMethods().filter(takesArguments(Void.class)).getOnly();
+ MethodGraph.Node voidNode = methodGraph.locate(voidMethod.asSignatureToken());
+ assertThat(voidNode.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(voidNode.getRepresentative(), is(voidMethod));
+ assertThat(voidNode.getMethodTypes().size(), is(1));
+ assertThat(voidNode.getMethodTypes().contains(voidMethod.asTypeToken()), is(true));
+ assertThat(voidNode.getVisibility(), is(voidMethod.getVisibility()));
+ }
+
+ @Test
+ public void testDuplicateNameGenericInterfaceExtension() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(DuplicateNameGenericInterface.InnerInterface.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(3));
+ MethodDescription objectMethod = typeDescription.getInterfaces().getOnly().getDeclaredMethods().filter(takesArguments(String.class)).getOnly();
+ MethodGraph.Node objectNode = methodGraph.locate(objectMethod.asSignatureToken());
+ assertThat(objectNode.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(objectNode.getRepresentative(), is(objectMethod));
+ assertThat(objectNode.getMethodTypes().size(), is(2));
+ assertThat(objectNode.getMethodTypes().contains(objectMethod.asTypeToken()), is(true));
+ assertThat(objectNode.getMethodTypes().contains(objectMethod.asDefined().asTypeToken()), is(true));
+ assertThat(objectNode.getVisibility(), is(objectMethod.getVisibility()));
+ MethodDescription integerMethod = typeDescription.getInterfaces().getOnly().getDeclaredMethods().filter(takesArguments(Integer.class)).getOnly();
+ MethodGraph.Node integerNode = methodGraph.locate(integerMethod.asSignatureToken());
+ assertThat(integerNode.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(integerNode.getRepresentative(), is(integerMethod));
+ assertThat(integerNode.getMethodTypes().size(), is(1));
+ assertThat(integerNode.getMethodTypes().contains(integerMethod.asTypeToken()), is(true));
+ assertThat(integerNode.getMethodTypes().contains(integerMethod.asDefined().asTypeToken()), is(true));
+ assertThat(integerNode.getVisibility(), is(integerMethod.getVisibility()));
+ MethodDescription voidMethod = typeDescription.getDeclaredMethods().filter(takesArguments(Void.class)).getOnly();
+ MethodGraph.Node voidNode = methodGraph.locate(voidMethod.asSignatureToken());
+ assertThat(voidNode.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(voidNode.getRepresentative(), is(voidMethod));
+ assertThat(voidNode.getMethodTypes().size(), is(1));
+ assertThat(voidNode.getMethodTypes().contains(voidMethod.asTypeToken()), is(true));
+ assertThat(voidNode.getVisibility(), is(voidMethod.getVisibility()));
+ }
+
+ @Test
+ public void testVisibilityBridge() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(VisibilityBridgeTarget.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual()).size() + 1));
+ MethodDescription methodDescription = typeDescription.getSuperClass().getDeclaredMethods().filter(isMethod()).getOnly();
+ MethodGraph.Node node = methodGraph.locate(methodDescription.asSignatureToken());
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.VISIBLE));
+ assertThat(node.getMethodTypes().size(), is(1));
+ assertThat(node.getMethodTypes().contains(methodDescription.asTypeToken()), is(true));
+ assertThat(node.getRepresentative(), is(methodDescription));
+ assertThat(node.getVisibility(), is(methodDescription.getVisibility()));
+ }
+
+ @Test
+ public void testGenericVisibilityBridge() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(GenericVisibilityBridgeTarget.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual()).size() + 1));
+ MethodDescription methodDescription = typeDescription.getSuperClass()
+ .getDeclaredMethods().filter(isMethod().and(ElementMatchers.not(isBridge()))).getOnly();
+ MethodDescription.SignatureToken bridgeToken = typeDescription.getSuperClass().getSuperClass()
+ .getDeclaredMethods().filter(isMethod()).getOnly().asSignatureToken();
+ MethodGraph.Node node = methodGraph.locate(methodDescription.asSignatureToken());
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.VISIBLE));
+ assertThat(node, is(methodGraph.locate(bridgeToken)));
+ assertThat(node.getMethodTypes().size(), is(2));
+ assertThat(node.getMethodTypes().contains(methodDescription.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(methodDescription.asDefined().asTypeToken()), is(true));
+ assertThat(node.getRepresentative(), is(methodDescription));
+ assertThat(node.getVisibility(), is(methodDescription.getVisibility()));
+ }
+
+ @Test
+ public void testMethodClassConvergence() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(MethodClassConvergence.Inner.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual()).size() + 1));
+ MethodDescription methodDescription = typeDescription.getDeclaredMethods().filter(isMethod().and(ElementMatchers.not(isBridge()))).getOnly();
+ MethodDescription genericMethod = typeDescription.getSuperClass().getDeclaredMethods()
+ .filter(isMethod().and(definedMethod(takesArguments(Object.class)))).getOnly();
+ MethodDescription nonGenericMethod = typeDescription.getSuperClass().getDeclaredMethods()
+ .filter(isMethod().and(definedMethod(takesArguments(Void.class)))).getOnly();
+ MethodGraph.Node node = methodGraph.locate(methodDescription.asSignatureToken());
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(node, is(methodGraph.locate(genericMethod.asDefined().asSignatureToken())));
+ assertThat(node, is(methodGraph.locate(nonGenericMethod.asDefined().asSignatureToken())));
+ assertThat(node.getMethodTypes().size(), is(2));
+ assertThat(node.getMethodTypes().contains(methodDescription.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(methodDescription.asDefined().asTypeToken()), is(true));
+ assertThat(node.getRepresentative(), is(methodDescription));
+ assertThat(node.getVisibility(), is(methodDescription.getVisibility()));
+ MethodGraph superGraph = methodGraph.getSuperClassGraph();
+ MethodGraph.Node superNode = superGraph.locate(methodDescription.asSignatureToken());
+ assertThat(superNode.getSort(), is(MethodGraph.Node.Sort.AMBIGUOUS));
+ assertThat(superNode.getMethodTypes().size(), is(2));
+ assertThat(superNode.getMethodTypes().contains(methodDescription.asTypeToken()), is(true));
+ assertThat(superNode.getMethodTypes().contains(methodDescription.asDefined().asTypeToken()), is(true));
+ assertThat(superNode.getRepresentative(), is(nonGenericMethod));
+ assertThat(superNode.getRepresentative(), is(genericMethod));
+ assertThat(superNode.getVisibility(), is(methodDescription.getVisibility()));
+ }
+
+ @Test
+ public void testMethodInterfaceConvergence() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(MethodInterfaceConvergenceTarget.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(1));
+ MethodDescription genericMethod = typeDescription.getInterfaces().filter(erasure(MethodInterfaceConvergenceFirstBase.class)).getOnly()
+ .getDeclaredMethods().filter(isMethod()).getOnly();
+ MethodDescription nonGenericMethod = typeDescription.getInterfaces().filter(erasure(MethodInterfaceConvergenceSecondBase.class)).getOnly()
+ .getDeclaredMethods().filter(isMethod()).getOnly();
+ assertThat(methodGraph.getSuperClassGraph().locate(genericMethod.asSignatureToken()).getSort(), is(MethodGraph.Node.Sort.UNRESOLVED));
+ assertThat(methodGraph.getSuperClassGraph().locate(nonGenericMethod.asSignatureToken()).getSort(), is(MethodGraph.Node.Sort.UNRESOLVED));
+ MethodGraph.Node node = methodGraph.locate(genericMethod.asSignatureToken());
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.AMBIGUOUS));
+ assertThat(node, is(methodGraph.locate(genericMethod.asDefined().asSignatureToken())));
+ assertThat(node, is(methodGraph.locate(nonGenericMethod.asDefined().asSignatureToken())));
+ assertThat(node.getMethodTypes().size(), is(2));
+ assertThat(node.getMethodTypes().contains(genericMethod.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(genericMethod.asDefined().asTypeToken()), is(true));
+ assertThat(node.getRepresentative(), is(genericMethod));
+ assertThat(node.getRepresentative(), not(nonGenericMethod));
+ assertThat(node.getVisibility(), is(genericMethod.getVisibility()));
+ }
+
+ @Test
+ public void testMethodConvergenceVisibilityTarget() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(MethodConvergenceVisibilityBridgeTarget.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual()).size() + 1));
+ MethodDescription genericMethod = typeDescription.getSuperClass().getSuperClass()
+ .getDeclaredMethods().filter(isMethod().and(definedMethod(takesArguments(Object.class)))).getOnly();
+ MethodDescription nonGenericMethod = typeDescription.getSuperClass().getSuperClass()
+ .getDeclaredMethods().filter(isMethod().and(definedMethod(takesArguments(Void.class)))).getOnly();
+ MethodGraph.Node node = methodGraph.locate(genericMethod.asSignatureToken());
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.VISIBLE));
+ assertThat(node, is(methodGraph.locate(nonGenericMethod.asSignatureToken())));
+ assertThat(node.getMethodTypes().size(), is(2));
+ assertThat(node.getMethodTypes().contains(genericMethod.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(genericMethod.asDefined().asTypeToken()), is(true));
+ assertThat(node.getRepresentative(),
+ is((MethodDescription) typeDescription.getSuperClass().getDeclaredMethods().filter(isMethod().and(ElementMatchers.not(isBridge()))).getOnly()));
+ assertThat(node.getVisibility(), is(genericMethod.getVisibility()));
+ }
+
+ @Test
+ public void testDiamondInheritanceClass() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(GenericDiamondClassBase.Inner.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual()).size() + 1));
+ MethodDescription diamondOverride = typeDescription.getInterfaces().getOnly().getDeclaredMethods().getOnly();
+ MethodDescription explicitOverride = typeDescription.getSuperClass().getDeclaredMethods().filter(isVirtual()).getOnly();
+ MethodGraph.Node node = methodGraph.locate(diamondOverride.asSignatureToken());
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(methodGraph.locate(explicitOverride.asDefined().asSignatureToken()), is(node));
+ assertThat(node.getMethodTypes().size(), is(2));
+ assertThat(node.getMethodTypes().contains(diamondOverride.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(explicitOverride.asDefined().asTypeToken()), is(true));
+ assertThat(node.getVisibility(), is(explicitOverride.getVisibility()));
+ }
+
+ @Test
+ public void testDiamondInheritanceInterface() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(GenericDiamondInterfaceBase.Inner.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(1));
+ MethodDescription diamondOverride = typeDescription.getInterfaces().get(0).getDeclaredMethods().getOnly();
+ MethodDescription explicitOverride = typeDescription.getInterfaces().get(1).getDeclaredMethods().getOnly();
+ MethodGraph.Node node = methodGraph.locate(diamondOverride.asSignatureToken());
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.AMBIGUOUS));
+ assertThat(methodGraph.locate(explicitOverride.asDefined().asSignatureToken()), is(node));
+ assertThat(node.getMethodTypes().size(), is(2));
+ assertThat(node.getMethodTypes().contains(diamondOverride.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(explicitOverride.asDefined().asTypeToken()), is(true));
+ assertThat(node.getVisibility(), is(explicitOverride.getVisibility()));
+ }
+
+ @Test
+ public void testVisibilityExtension() throws Exception {
+ TypeDescription typeDescription = new InstrumentedType.Default("foo",
+ Opcodes.ACC_PUBLIC,
+ new TypeDescription.Generic.OfNonGenericType.ForLoadedType(VisibilityExtension.Base.class),
+ Collections.<TypeVariableToken>emptyList(),
+ Collections.<TypeDescription.Generic>singletonList(new TypeDescription.Generic.OfNonGenericType.ForLoadedType(VisibilityExtension.class)),
+ Collections.<FieldDescription.Token>emptyList(),
+ Collections.<MethodDescription.Token>emptyList(),
+ Collections.<AnnotationDescription>emptyList(),
+ TypeInitializer.None.INSTANCE,
+ LoadedTypeInitializer.NoOp.INSTANCE,
+ TypeDescription.UNDEFINED,
+ MethodDescription.UNDEFINED,
+ TypeDescription.UNDEFINED,
+ Collections.<TypeDescription>emptyList(),
+ false,
+ false,
+ false);
+ MethodDescription.SignatureToken signatureToken = new MethodDescription.SignatureToken("foo",
+ new TypeDescription.ForLoadedType(void.class),
+ Collections.<TypeDescription>emptyList());
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(1 + TypeDescription.OBJECT.getDeclaredMethods().filter(ElementMatchers.isVirtual()).size()));
+ MethodGraph.Node node = methodGraph.locate(signatureToken);
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(node.getRepresentative().asSignatureToken(), is(signatureToken));
+ assertThat(node.getMethodTypes().size(), is(1));
+ assertThat(node.getMethodTypes(), hasItem(signatureToken.asTypeToken()));
+ assertThat(node.getVisibility(), is(Visibility.PUBLIC));
+ }
+
+ @Test
+ public void testOrphanedBridge() throws Exception {
+ MethodDescription.SignatureToken bridgeMethod = new MethodDescription.SignatureToken("foo",
+ TypeDescription.VOID,
+ Collections.<TypeDescription>emptyList());
+ TypeDescription typeDescription = new InstrumentedType.Default("foo",
+ Opcodes.ACC_PUBLIC,
+ TypeDescription.Generic.OBJECT,
+ Collections.<TypeVariableToken>emptyList(),
+ Collections.<TypeDescription.Generic>emptyList(),
+ Collections.<FieldDescription.Token>emptyList(),
+ Collections.singletonList(new MethodDescription.Token("foo",
+ Opcodes.ACC_BRIDGE,
+ TypeDescription.Generic.VOID,
+ Collections.<TypeDescription.Generic>emptyList())),
+ Collections.<AnnotationDescription>emptyList(),
+ TypeInitializer.None.INSTANCE,
+ LoadedTypeInitializer.NoOp.INSTANCE,
+ TypeDescription.UNDEFINED,
+ MethodDescription.UNDEFINED,
+ TypeDescription.UNDEFINED,
+ Collections.<TypeDescription>emptyList(),
+ false,
+ false,
+ false);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(1 + TypeDescription.OBJECT.getDeclaredMethods().filter(ElementMatchers.isVirtual()).size()));
+ MethodGraph.Node node = methodGraph.locate(bridgeMethod);
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(node.getRepresentative().asSignatureToken(), is(bridgeMethod));
+ assertThat(node.getMethodTypes().size(), is(1));
+ assertThat(node.getMethodTypes(), hasItem(bridgeMethod.asTypeToken()));
+ assertThat(node.getVisibility(), is(Visibility.PACKAGE_PRIVATE));
+ }
+
+ @Test
+ public void testRawType() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(RawType.Raw.class);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile(typeDescription);
+ assertThat(methodGraph.getSuperClassGraph().listNodes().size(), is(TypeDescription.OBJECT.getDeclaredMethods().filter(isVirtual()).size() + 1));
+ MethodDescription method = typeDescription.getSuperClass().getDeclaredMethods().filter(isMethod().and(ElementMatchers.not(isBridge()))).getOnly();
+ MethodGraph.Node node = methodGraph.locate(method.asSignatureToken());
+ assertThat(node.getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ assertThat(methodGraph.locate(method.asDefined().asSignatureToken()), is(node));
+ assertThat(node.getMethodTypes().size(), is(2));
+ assertThat(node.getMethodTypes().contains(method.asTypeToken()), is(true));
+ assertThat(node.getMethodTypes().contains(method.asDefined().asTypeToken()), is(true));
+ assertThat(node.getVisibility(), is(method.getVisibility()));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodGraph.Compiler.Default.class).apply();
+ }
+
+ public interface SimpleInterface {
+ /* empty */
+ }
+
+ public interface InterfaceBase {
+
+ void foo();
+
+ interface InnerInterface extends InterfaceBase {
+ /* empty */
+ }
+
+ abstract class InnerClass implements InterfaceBase {
+ /* empty */
+ }
+
+ interface InterfaceDuplicate extends InnerInterface, InterfaceBase {
+ /* empty */
+ }
+ }
+
+ public interface AmbiguousInterfaceBase {
+
+ void foo();
+
+ interface InterfaceTarget extends InterfaceBase, AmbiguousInterfaceBase {
+ /* empty */
+ }
+
+ interface DominantIntermediate extends InterfaceBase, AmbiguousInterfaceBase {
+
+ @Override
+ void foo();
+ }
+
+ interface DominantInterfaceTargetLeft extends InterfaceBase, DominantIntermediate {
+ /* empty */
+ }
+
+ interface DominantInterfaceTargetRight extends DominantIntermediate, InterfaceBase {
+ /* empty */
+ }
+
+ interface NonDominantAmbiguous {
+
+ void foo();
+ }
+
+ interface NonDominantIntermediateLeft extends InterfaceBase, NonDominantAmbiguous {
+ /* empty */
+ }
+
+ interface NonDominantIntermediateRight extends NonDominantAmbiguous, InterfaceBase {
+ /* empty */
+ }
+
+ interface NonDominantTargetLeft extends DominantIntermediate, NonDominantIntermediateLeft {
+ /* empty */
+ }
+
+ interface NonDominantTargetRight extends DominantIntermediate, NonDominantIntermediateRight {
+ /* empty */
+ }
+
+ abstract class ClassTarget implements InterfaceBase, AmbiguousInterfaceBase {
+ /* empty */
+ }
+
+ abstract class DominantClassBase implements DominantIntermediate {
+ /* empty */
+ }
+
+ abstract class DominantClassTarget extends DominantClassBase implements InterfaceBase {
+ /* empty */
+ }
+ }
+
+ public interface GenericInterfaceBase<T> {
+
+ void foo(T t);
+
+ interface Inner extends GenericInterfaceBase<Void> {
+
+ @Override
+ void foo(Void t);
+ }
+
+ interface Intermediate<T extends Number> extends GenericInterfaceBase<T> {
+
+ @Override
+ void foo(T t);
+
+ interface Inner extends Intermediate<Integer> {
+
+ @Override
+ void foo(Integer t);
+ }
+ }
+ }
+
+ public interface ReturnTypeInterfaceBase {
+
+ Object foo();
+
+ interface Inner extends ReturnTypeInterfaceBase {
+
+ @Override
+ Void foo();
+ }
+
+ interface Intermediate extends ReturnTypeInterfaceBase {
+
+ @Override
+ Number foo();
+
+ interface Inner extends Intermediate {
+
+ @Override
+ Integer foo();
+ }
+ }
+ }
+
+ public interface GenericWithReturnTypeInterfaceBase<T> {
+
+ Object foo(T t);
+
+ interface Inner extends GenericWithReturnTypeInterfaceBase<Void> {
+
+ @Override
+ Void foo(Void t);
+ }
+
+ interface Intermediate<T extends Number> extends GenericWithReturnTypeInterfaceBase<T> {
+
+ @Override
+ Number foo(T t);
+
+ interface Inner extends Intermediate<Integer> {
+
+ @Override
+ Integer foo(Integer t);
+ }
+ }
+ }
+
+ public interface GenericNonOverriddenInterfaceBase<T> {
+
+ T foo(T t);
+
+ interface InnerInterface extends GenericNonOverriddenInterfaceBase<Void> {
+ /* empty */
+ }
+
+ abstract class InnerClass implements GenericNonOverriddenInterfaceBase<Void> {
+ /* empty */
+ }
+ }
+
+ public interface DuplicateNameInterface {
+
+ void foo(Object o);
+
+ void foo(Integer o);
+
+ interface InnerInterface extends DuplicateNameInterface {
+
+ void foo(Void o);
+ }
+
+ abstract class InnerClass implements DuplicateNameInterface {
+
+ public abstract void foo(Void o);
+ }
+ }
+
+ public interface DuplicateNameGenericInterface<T> {
+
+ void foo(T o);
+
+ void foo(Integer o);
+
+ interface InnerInterface extends DuplicateNameGenericInterface<String> {
+
+ void foo(Void o);
+ }
+
+ @SuppressWarnings("unused")
+ abstract class InnerClass implements DuplicateNameGenericInterface<String> {
+
+ public abstract void foo(Void o);
+ }
+ }
+
+ public interface MethodInterfaceConvergenceFirstBase<T> {
+
+ T foo();
+ }
+
+ public interface MethodInterfaceConvergenceSecondBase {
+
+ Void foo();
+ }
+
+ public interface MethodInterfaceConvergenceTarget extends MethodInterfaceConvergenceFirstBase<Void>, MethodInterfaceConvergenceSecondBase {
+ /* empty */
+ }
+
+ public static class SimpleClass {
+ /* empty */
+ }
+
+ public static class ClassBase {
+
+ public void foo() {
+ /* empty */
+ }
+
+ static class Inner extends ClassBase {
+
+ @Override
+ public void foo() {
+ /* empty */
+ }
+ }
+ }
+
+ public static class ClassAndInterfaceInheritance extends ClassBase implements InterfaceBase {
+ /* empty */
+ }
+
+ public static class GenericClassBase<T> {
+
+ public void foo(T t) {
+ /* empty */
+ }
+
+ public static class Inner extends GenericClassBase<Void> {
+
+ @Override
+ public void foo(Void t) {
+ /* empty */
+ }
+ }
+
+ public static class Intermediate<T extends Number> extends GenericClassBase<T> {
+
+ @Override
+ public void foo(T t) {
+ /* empty */
+ }
+
+ public static class Inner extends Intermediate<Integer> {
+
+ @Override
+ public void foo(Integer t) {
+ /* empty */
+ }
+ }
+ }
+ }
+
+ public static class GenericReturnClassBase<T> {
+
+ public T foo() {
+ return null;
+ }
+
+ public static class Inner extends GenericReturnClassBase<Void> {
+
+ @Override
+ public Void foo() {
+ return null;
+ }
+ }
+
+ public static class Intermediate<T extends Number> extends GenericReturnClassBase<T> {
+
+ @Override
+ public T foo() {
+ return null;
+ }
+
+ public static class Inner extends Intermediate<Integer> {
+
+ @Override
+ public Integer foo() {
+ return null;
+ }
+ }
+ }
+ }
+
+ public static class ReturnTypeClassBase {
+
+ public Object foo() {
+ return null;
+ }
+
+ public static class Inner extends ReturnTypeClassBase {
+
+ @Override
+ public Void foo() {
+ return null;
+ }
+ }
+
+ public static class Intermediate extends ReturnTypeClassBase {
+
+ @Override
+ public Number foo() {
+ return null;
+ }
+
+ public static class Inner extends Intermediate {
+
+ @Override
+ public Integer foo() {
+ return null;
+ }
+ }
+ }
+ }
+
+ public static class GenericWithReturnTypeClassBase<T> {
+
+ public Object foo(T t) {
+ return null;
+ }
+
+ public static class Inner extends GenericWithReturnTypeClassBase<Void> {
+
+ @Override
+ public Void foo(Void t) {
+ return null;
+ }
+ }
+
+ public static class Intermediate<T extends Number> extends GenericWithReturnTypeClassBase<T> {
+
+ @Override
+ public Number foo(T t) {
+ return null;
+ }
+
+ public static class Inner extends Intermediate<Integer> {
+
+ @Override
+ public Integer foo(Integer t) {
+ return null;
+ }
+ }
+ }
+ }
+
+ public static class GenericNonOverriddenClassBase<T> {
+
+ public T foo(T t) {
+ return null;
+ }
+
+ public class Inner extends GenericNonOverriddenClassBase<Void> {
+ /* empty */
+ }
+ }
+
+ public static class GenericDiamondClassBase<T> {
+
+ public T foo(T t) {
+ return null;
+ }
+
+ public interface DiamondInterface {
+
+ Void foo(Void t);
+ }
+
+ public class Inner extends GenericNonOverriddenClassBase<Void> implements DiamondInterface {
+ /* empty */
+ }
+ }
+
+ public interface GenericDiamondInterfaceBase<T> {
+
+ T foo(T t);
+
+ interface DiamondInterface {
+
+ Void foo(Void s);
+ }
+
+ interface Inner extends GenericDiamondInterfaceBase<Void>, DiamondInterface {
+ /* empty */
+ }
+ }
+
+ public interface VisibilityExtension {
+
+ void foo();
+
+ class Base {
+
+ protected void foo() {
+ /* do nothing */
+ }
+ }
+ }
+
+ public static class DuplicateNameClass {
+
+ public void foo(Object o) {
+ /* empty */
+ }
+
+ public void foo(Integer o) {
+ /* empty */
+ }
+
+ public static class Inner extends DuplicateNameClass {
+
+ public void foo(Void o) {
+ /* empty */
+ }
+ }
+ }
+
+ public static class DuplicateNameGenericClass<T> {
+
+ public void foo(T o) {
+ /* empty */
+ }
+
+ public void foo(Integer o) {
+ /* empty */
+ }
+
+ public static class Inner extends DuplicateNameGenericClass<String> {
+
+ public void foo(Void o) {
+ /* empty */
+ }
+ }
+ }
+
+ static class VisibilityBridgeBase {
+
+ public void foo() {
+ /* empty */
+ }
+ }
+
+ public static class VisibilityBridgeTarget extends VisibilityBridgeBase {
+ /* empty */
+ }
+
+ public static class GenericVisibilityBridgeBase<T> {
+
+ public void foo(T t) {
+ /* empty */
+ }
+ }
+
+ static class GenericVisibilityBridge extends GenericVisibilityBridgeBase<Void> {
+
+ @Override
+ public void foo(Void aVoid) {
+ /* empty */
+ }
+ }
+
+ public static class GenericVisibilityBridgeTarget extends GenericVisibilityBridge {
+ /* empty */
+ }
+
+ public static class MethodClassConvergence<T> {
+
+ public T foo(T arg) {
+ return null;
+ }
+
+ public Void foo(Void arg) {
+ return null;
+ }
+
+ public static class Inner extends MethodClassConvergence<Void> {
+
+ @Override
+ public Void foo(Void arg) {
+ return null;
+ }
+ }
+ }
+
+ static class MethodConvergenceVisibilityBridgeBase<T> {
+
+ public T foo(T arg) {
+ return null;
+ }
+
+ public Void foo(Void arg) {
+ return null;
+ }
+ }
+
+ static class MethodConvergenceVisibilityBridgeIntermediate extends MethodConvergenceVisibilityBridgeBase<Void> {
+
+ @Override
+ public Void foo(Void arg) {
+ return null;
+ }
+ }
+
+ public static class MethodConvergenceVisibilityBridgeTarget extends MethodConvergenceVisibilityBridgeIntermediate {
+ /* empty */
+ }
+
+ public static class RawType<T> {
+
+ public void foo(T t) {
+ /* empty */
+ }
+
+ public static class Intermediate<T extends Number> extends RawType<T> {
+
+ @Override
+ public void foo(T t) {
+ /* empty */
+ }
+ }
+
+ public static class Raw extends Intermediate {
+
+ @Override
+ public void foo(Number t) {
+ /* empty */
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphCompilerForDeclaredMethodsTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphCompilerForDeclaredMethodsTest.java
new file mode 100644
index 0000000..0f58f51
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphCompilerForDeclaredMethodsTest.java
@@ -0,0 +1,73 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+import org.objectweb.asm.Opcodes;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class MethodGraphCompilerForDeclaredMethodsTest {
+
+ @Test
+ public void testCompilationInvisible() throws Exception {
+ TypeDescription typeDescription = mock(TypeDescription.class);
+ MethodDescription.InDefinedShape methodDescription = mock(MethodDescription.InDefinedShape.class);
+ when(typeDescription.getDeclaredMethods()).thenReturn(new MethodList.Explicit<MethodDescription.InDefinedShape>(methodDescription));
+ when(methodDescription.isVirtual()).thenReturn(true);
+ when(methodDescription.isBridge()).thenReturn(false);
+ when(methodDescription.isVisibleTo(typeDescription)).thenReturn(false);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.ForDeclaredMethods.INSTANCE.compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(0));
+ }
+
+ @Test
+ public void testCompilationNonVirtual() throws Exception {
+ TypeDescription typeDescription = mock(TypeDescription.class);
+ MethodDescription.InDefinedShape methodDescription = mock(MethodDescription.InDefinedShape.class);
+ when(typeDescription.getDeclaredMethods()).thenReturn(new MethodList.Explicit<MethodDescription.InDefinedShape>(methodDescription));
+ when(methodDescription.isVirtual()).thenReturn(false);
+ when(methodDescription.isBridge()).thenReturn(false);
+ when(methodDescription.isVisibleTo(typeDescription)).thenReturn(true);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.ForDeclaredMethods.INSTANCE.compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(0));
+ }
+
+ @Test
+ public void testCompilationNonBridge() throws Exception {
+ TypeDescription typeDescription = mock(TypeDescription.class);
+ MethodDescription.InDefinedShape methodDescription = mock(MethodDescription.InDefinedShape.class);
+ when(typeDescription.getDeclaredMethods()).thenReturn(new MethodList.Explicit<MethodDescription.InDefinedShape>(methodDescription));
+ when(methodDescription.isVirtual()).thenReturn(true);
+ when(methodDescription.getModifiers()).thenReturn(Opcodes.ACC_BRIDGE);
+ when(methodDescription.isVisibleTo(typeDescription)).thenReturn(true);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.ForDeclaredMethods.INSTANCE.compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(0));
+ }
+
+ @Test
+ public void testCompilation() throws Exception {
+ TypeDescription typeDescription = mock(TypeDescription.class);
+ MethodDescription.InDefinedShape methodDescription = mock(MethodDescription.InDefinedShape.class);
+ MethodDescription.SignatureToken token = mock(MethodDescription.SignatureToken.class);
+ when(methodDescription.asSignatureToken()).thenReturn(token);
+ when(typeDescription.getDeclaredMethods()).thenReturn(new MethodList.Explicit<MethodDescription.InDefinedShape>(methodDescription));
+ when(methodDescription.isVirtual()).thenReturn(true);
+ when(methodDescription.isBridge()).thenReturn(false);
+ when(methodDescription.isVisibleTo(typeDescription)).thenReturn(true);
+ MethodGraph.Linked methodGraph = MethodGraph.Compiler.ForDeclaredMethods.INSTANCE.compile(typeDescription);
+ assertThat(methodGraph.listNodes().size(), is(1));
+ assertThat(methodGraph.listNodes().getOnly().getRepresentative(), is((MethodDescription) methodDescription));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodGraph.Compiler.ForDeclaredMethods.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphEmptyTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphEmptyTest.java
new file mode 100644
index 0000000..b320e83
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphEmptyTest.java
@@ -0,0 +1,38 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class MethodGraphEmptyTest {
+
+ @Test
+ public void testNode() throws Exception {
+ assertThat(MethodGraph.Empty.INSTANCE.locate(mock(MethodDescription.SignatureToken.class)).getSort(), is(MethodGraph.Node.Sort.UNRESOLVED));
+ }
+
+ @Test
+ public void testListNode() throws Exception {
+ assertThat(MethodGraph.Empty.INSTANCE.listNodes().size(), is(0));
+ }
+
+ @Test
+ public void testSuperGraph() throws Exception {
+ assertThat(MethodGraph.Empty.INSTANCE.getSuperClassGraph(), is((MethodGraph) MethodGraph.Empty.INSTANCE));
+ }
+
+ @Test
+ public void testInterfaceGraph() throws Exception {
+ assertThat(MethodGraph.Empty.INSTANCE.getInterfaceGraph(mock(TypeDescription.class)), is((MethodGraph) MethodGraph.Empty.INSTANCE));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodGraph.Empty.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphLinkedDelegationTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphLinkedDelegationTest.java
new file mode 100644
index 0000000..fc3baa3
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphLinkedDelegationTest.java
@@ -0,0 +1,78 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class MethodGraphLinkedDelegationTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Mock
+ private MethodGraph methodGraph, superGraph, interfaceGraph;
+
+ @Mock
+ private MethodDescription.SignatureToken token;
+
+ @Mock
+ private MethodGraph.Node node;
+
+ @Mock
+ private MethodGraph.NodeList nodeList;
+
+ private MethodGraph.Linked linkedMethodGraph;
+
+ @Before
+ public void setUp() throws Exception {
+ when(methodGraph.locate(token)).thenReturn(node);
+ when(methodGraph.listNodes()).thenReturn(nodeList);
+ linkedMethodGraph = new MethodGraph.Linked.Delegation(methodGraph, superGraph, Collections.singletonMap(typeDescription, interfaceGraph));
+ }
+
+ @Test
+ public void testLocateNode() throws Exception {
+ assertThat(linkedMethodGraph.locate(token), is(node));
+ }
+
+ @Test
+ public void testNodeList() throws Exception {
+ assertThat(linkedMethodGraph.listNodes(), is(nodeList));
+ }
+
+ @Test
+ public void testSuperGraph() throws Exception {
+ assertThat(linkedMethodGraph.getSuperClassGraph(), is(superGraph));
+ }
+
+ @Test
+ public void testKnownInterfaceGraph() throws Exception {
+ assertThat(linkedMethodGraph.getInterfaceGraph(typeDescription), is(interfaceGraph));
+ }
+
+ @Test
+ public void testUnknownInterfaceGraph() throws Exception {
+ assertThat(linkedMethodGraph.getInterfaceGraph(mock(TypeDescription.class)), is((MethodGraph) MethodGraph.Empty.INSTANCE));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodGraph.Linked.Delegation.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphNodeListTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphNodeListTest.java
new file mode 100644
index 0000000..6b82b21
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphNodeListTest.java
@@ -0,0 +1,52 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.matcher.AbstractFilterableListTest;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodGraphNodeListTest extends AbstractFilterableListTest<MethodGraph.Node, MethodGraph.NodeList, MethodGraph.Node> {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodDescription first, second;
+
+ @Override
+ protected MethodGraph.Node getFirst() throws Exception {
+ return new MethodGraph.Node.Simple(first);
+ }
+
+ @Override
+ protected MethodGraph.Node getSecond() throws Exception {
+ return new MethodGraph.Node.Simple(second);
+ }
+
+ @Override
+ protected MethodGraph.NodeList asList(List<MethodGraph.Node> elements) {
+ return new MethodGraph.NodeList(elements);
+ }
+
+ @Override
+ protected MethodGraph.Node asElement(MethodGraph.Node element) {
+ return element;
+ }
+
+ @Test
+ @SuppressWarnings("unused")
+ public void testAsMethodList() throws Exception {
+ assertThat(new MethodGraph.NodeList(Arrays.asList(new MethodGraph.Node.Simple(first), new MethodGraph.Node.Simple(second))).asMethodList(),
+ is((MethodList) new MethodList.Explicit<MethodDescription>(first, second)));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphNodeSimpleTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphNodeSimpleTest.java
new file mode 100644
index 0000000..b363490
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphNodeSimpleTest.java
@@ -0,0 +1,41 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodGraphNodeSimpleTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodDescription methodDescription;
+
+ @Test
+ public void testRepresentative() throws Exception {
+ assertThat(new MethodGraph.Node.Simple(methodDescription).getRepresentative(), is(methodDescription));
+ }
+
+ @Test
+ public void testBridgesEmpty() throws Exception {
+ assertThat(new MethodGraph.Node.Simple(methodDescription).getMethodTypes().size(), is(0));
+ }
+
+ @Test
+ public void testSort() throws Exception {
+ assertThat(new MethodGraph.Node.Simple(methodDescription).getSort(), is(MethodGraph.Node.Sort.RESOLVED));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodGraph.Node.Simple.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphNodeSortTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphNodeSortTest.java
new file mode 100644
index 0000000..a1e5772
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphNodeSortTest.java
@@ -0,0 +1,43 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodGraphNodeSortTest {
+
+ @Test
+ public void testSortVisible() throws Exception {
+ assertThat(MethodGraph.Node.Sort.VISIBLE.isResolved(), is(true));
+ assertThat(MethodGraph.Node.Sort.VISIBLE.isUnique(), is(true));
+ assertThat(MethodGraph.Node.Sort.VISIBLE.isMadeVisible(), is(true));
+ }
+
+ @Test
+ public void testSortResolved() throws Exception {
+ assertThat(MethodGraph.Node.Sort.RESOLVED.isResolved(), is(true));
+ assertThat(MethodGraph.Node.Sort.RESOLVED.isUnique(), is(true));
+ assertThat(MethodGraph.Node.Sort.RESOLVED.isMadeVisible(), is(false));
+ }
+
+ @Test
+ public void testAmbiguous() throws Exception {
+ assertThat(MethodGraph.Node.Sort.AMBIGUOUS.isResolved(), is(true));
+ assertThat(MethodGraph.Node.Sort.AMBIGUOUS.isUnique(), is(false));
+ assertThat(MethodGraph.Node.Sort.AMBIGUOUS.isMadeVisible(), is(false));
+ }
+
+ @Test
+ public void testUnresolved() throws Exception {
+ assertThat(MethodGraph.Node.Sort.UNRESOLVED.isResolved(), is(false));
+ assertThat(MethodGraph.Node.Sort.UNRESOLVED.isUnique(), is(false));
+ assertThat(MethodGraph.Node.Sort.UNRESOLVED.isMadeVisible(), is(false));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodGraph.Node.Sort.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphNodeUnresolvedTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphNodeUnresolvedTest.java
new file mode 100644
index 0000000..886145b
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphNodeUnresolvedTest.java
@@ -0,0 +1,30 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodGraphNodeUnresolvedTest {
+
+ @Test
+ public void testSort() throws Exception {
+ assertThat(MethodGraph.Node.Unresolved.INSTANCE.getSort(), is(MethodGraph.Node.Sort.UNRESOLVED));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testBridgesThrowsException() throws Exception {
+ MethodGraph.Node.Unresolved.INSTANCE.getMethodTypes();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testRepresentativeThrowsException() throws Exception {
+ MethodGraph.Node.Unresolved.INSTANCE.getRepresentative();
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodGraph.Node.Unresolved.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphSimpleTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphSimpleTest.java
new file mode 100644
index 0000000..3bf11e7
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodGraphSimpleTest.java
@@ -0,0 +1,50 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class MethodGraphSimpleTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodDescription methodDescription;
+
+ @Mock
+ private MethodDescription.SignatureToken token;
+
+ @Before
+ public void setUp() throws Exception {
+ when(methodDescription.asSignatureToken()).thenReturn(token);
+ }
+
+ @Test
+ public void testNodeList() throws Exception {
+ assertThat(MethodGraph.Simple.of(Collections.singletonList(methodDescription)).listNodes().getOnly(),
+ is((MethodGraph.Node) new MethodGraph.Node.Simple(methodDescription)));
+ }
+
+ @Test
+ public void testNodeLocation() throws Exception {
+ assertThat(MethodGraph.Simple.of(Collections.singletonList(methodDescription)).locate(token),
+ is((MethodGraph.Node) new MethodGraph.Node.Simple(methodDescription)));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodGraph.Simple.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodRegistryDefaultTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodRegistryDefaultTest.java
new file mode 100644
index 0000000..a8305fc
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodRegistryDefaultTest.java
@@ -0,0 +1,370 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.Transformer;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.LoadedTypeInitializer;
+import net.bytebuddy.implementation.attribute.MethodAttributeAppender;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.matcher.ElementMatchers;
+import net.bytebuddy.matcher.LatentMatcher;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class MethodRegistryDefaultTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private ClassFileVersion classFileVersion;
+
+ @Mock
+ private LatentMatcher<MethodDescription> firstMatcher, secondMatcher, methodFilter;
+
+ @Mock
+ private MethodRegistry.Handler firstHandler, secondHandler;
+
+ @Mock
+ private MethodRegistry.Handler.Compiled firstCompiledHandler, secondCompiledHandler;
+
+ @Mock
+ private TypeWriter.MethodPool.Record firstRecord, secondRecord;
+
+ @Mock
+ private MethodAttributeAppender.Factory firstFactory, secondFactory;
+
+ @Mock
+ private MethodAttributeAppender firstAppender, secondAppender;
+
+ @Mock
+ private InstrumentedType firstType, secondType, thirdType;
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Mock
+ private MethodDescription instrumentedMethod;
+
+ @Mock
+ private MethodGraph.Compiler methodGraphCompiler;
+
+ @Mock
+ private MethodGraph.Linked methodGraph;
+
+ @Mock
+ private TypeInitializer typeInitializer;
+
+ @Mock
+ private LoadedTypeInitializer loadedTypeInitializer;
+
+ @Mock
+ private ElementMatcher<? super MethodDescription> resolvedMethodFilter, firstFilter, secondFilter;
+
+ @Mock
+ private Implementation.Target.Factory implementationTargetFactory;
+
+ @Mock
+ private Implementation.Target implementationTarget;
+
+ @Mock
+ private Transformer<MethodDescription> transformer;
+
+ @Mock
+ private TypeDescription returnType, parameterType;
+
+ @Mock
+ private TypeDescription.Generic genericReturnType, genericParameterType;
+
+ @Mock
+ private ParameterDescription.InDefinedShape parameterDescription;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ when(firstHandler.prepare(firstType)).thenReturn(secondType);
+ when(secondHandler.prepare(secondType)).thenReturn(thirdType);
+ when(firstHandler.compile(implementationTarget)).thenReturn(firstCompiledHandler);
+ when(secondHandler.compile(implementationTarget)).thenReturn(secondCompiledHandler);
+ when(thirdType.getTypeInitializer()).thenReturn(typeInitializer);
+ when(thirdType.getLoadedTypeInitializer()).thenReturn(loadedTypeInitializer);
+ when(methodGraphCompiler.compile(thirdType)).thenReturn(methodGraph);
+ when(methodGraph.listNodes()).thenReturn(new MethodGraph.NodeList(Collections.singletonList(new MethodGraph.Node.Simple(instrumentedMethod))));
+ when(firstType.getDeclaredMethods()).thenReturn(new MethodList.Empty<MethodDescription.InDefinedShape>());
+ when(secondType.getDeclaredMethods()).thenReturn(new MethodList.Empty<MethodDescription.InDefinedShape>());
+ when(thirdType.getDeclaredMethods()).thenReturn(new MethodList.Empty<MethodDescription.InDefinedShape>());
+ when(methodFilter.resolve(thirdType)).thenReturn((ElementMatcher) resolvedMethodFilter);
+ when(firstMatcher.resolve(thirdType)).thenReturn((ElementMatcher) firstFilter);
+ when(secondMatcher.resolve(thirdType)).thenReturn((ElementMatcher) secondFilter);
+ when(firstFactory.make(typeDescription)).thenReturn(firstAppender);
+ when(secondFactory.make(typeDescription)).thenReturn(secondAppender);
+ when(implementationTargetFactory.make(typeDescription, methodGraph, classFileVersion)).thenReturn(implementationTarget);
+ when(firstCompiledHandler.assemble(instrumentedMethod, firstAppender, Visibility.PUBLIC)).thenReturn(firstRecord);
+ when(secondCompiledHandler.assemble(instrumentedMethod, secondAppender, Visibility.PUBLIC)).thenReturn(secondRecord);
+ when(transformer.transform(thirdType, instrumentedMethod)).thenReturn(instrumentedMethod);
+ when(thirdType.validated()).thenReturn(typeDescription);
+ when(implementationTarget.getInstrumentedType()).thenReturn(typeDescription);
+ when(genericReturnType.asErasure()).thenReturn(returnType);
+ when(genericReturnType.getSort()).thenReturn(TypeDefinition.Sort.NON_GENERIC);
+ when(returnType.isVisibleTo(thirdType)).thenReturn(true);
+ when(genericParameterType.asErasure()).thenReturn(parameterType);
+ when(genericParameterType.getSort()).thenReturn(TypeDefinition.Sort.NON_GENERIC);
+ when(parameterType.isVisibleTo(thirdType)).thenReturn(true);
+ when(instrumentedMethod.getReturnType()).thenReturn(genericReturnType);
+ when(instrumentedMethod.getParameters()).thenReturn((ParameterList) new ParameterList.Explicit<ParameterDescription>(parameterDescription));
+ when(parameterDescription.getType()).thenReturn(genericParameterType);
+ when(instrumentedMethod.getVisibility()).thenReturn(Visibility.PUBLIC);
+ }
+
+ @Test
+ public void testNonMatchedIsNotIncluded() throws Exception {
+ when(resolvedMethodFilter.matches(instrumentedMethod)).thenReturn(true);
+ MethodRegistry.Prepared methodRegistry = new MethodRegistry.Default()
+ .append(firstMatcher, firstHandler, firstFactory, transformer)
+ .append(secondMatcher, secondHandler, secondFactory, transformer)
+ .prepare(firstType, methodGraphCompiler, TypeValidation.ENABLED, methodFilter);
+ assertThat(methodRegistry.getInstrumentedType(), is(typeDescription));
+ assertThat(methodRegistry.getInstrumentedMethods().size(), is(0));
+ assertThat(methodRegistry.getTypeInitializer(), is(typeInitializer));
+ assertThat(methodRegistry.getLoadedTypeInitializer(), is(loadedTypeInitializer));
+ verify(firstHandler).prepare(firstType);
+ verify(secondHandler).prepare(secondType);
+ }
+
+ @Test
+ public void testIgnoredIsNotIncluded() throws Exception {
+ when(firstFilter.matches(instrumentedMethod)).thenReturn(true);
+ when(secondFilter.matches(instrumentedMethod)).thenReturn(true);
+ MethodRegistry.Prepared methodRegistry = new MethodRegistry.Default()
+ .append(firstMatcher, firstHandler, firstFactory, transformer)
+ .append(secondMatcher, secondHandler, secondFactory, transformer)
+ .prepare(firstType, methodGraphCompiler, TypeValidation.ENABLED, methodFilter);
+ assertThat(methodRegistry.getInstrumentedType(), is(typeDescription));
+ assertThat(methodRegistry.getInstrumentedMethods().size(), is(0));
+ assertThat(methodRegistry.getTypeInitializer(), is(typeInitializer));
+ assertThat(methodRegistry.getLoadedTypeInitializer(), is(loadedTypeInitializer));
+ verify(firstHandler).prepare(firstType);
+ verify(secondHandler).prepare(secondType);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMatchedFirst() throws Exception {
+ when(resolvedMethodFilter.matches(instrumentedMethod)).thenReturn(true);
+ when(firstFilter.matches(instrumentedMethod)).thenReturn(true);
+ MethodRegistry.Prepared methodRegistry = new MethodRegistry.Default()
+ .append(firstMatcher, firstHandler, firstFactory, transformer)
+ .append(secondMatcher, secondHandler, secondFactory, transformer)
+ .prepare(firstType, methodGraphCompiler, TypeValidation.ENABLED, methodFilter);
+ assertThat(methodRegistry.getInstrumentedType(), is(typeDescription));
+ assertThat(methodRegistry.getInstrumentedMethods(), is((MethodList) new MethodList.Explicit(instrumentedMethod)));
+ assertThat(methodRegistry.getTypeInitializer(), is(typeInitializer));
+ assertThat(methodRegistry.getLoadedTypeInitializer(), is(loadedTypeInitializer));
+ verify(firstHandler).prepare(firstType);
+ verify(secondHandler).prepare(secondType);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMatchedSecond() throws Exception {
+ when(resolvedMethodFilter.matches(instrumentedMethod)).thenReturn(true);
+ when(secondFilter.matches(instrumentedMethod)).thenReturn(true);
+ MethodRegistry.Prepared methodRegistry = new MethodRegistry.Default()
+ .append(firstMatcher, firstHandler, firstFactory, transformer)
+ .append(secondMatcher, secondHandler, secondFactory, transformer)
+ .prepare(firstType, methodGraphCompiler, TypeValidation.ENABLED, methodFilter);
+ assertThat(methodRegistry.getInstrumentedType(), is(typeDescription));
+ assertThat(methodRegistry.getInstrumentedMethods(), is((MethodList) new MethodList.Explicit(instrumentedMethod)));
+ assertThat(methodRegistry.getTypeInitializer(), is(typeInitializer));
+ assertThat(methodRegistry.getLoadedTypeInitializer(), is(loadedTypeInitializer));
+ verify(firstHandler).prepare(firstType);
+ verify(secondHandler).prepare(secondType);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMultipleRegistryDoesNotPrepareMultipleTimes() throws Exception {
+ when(resolvedMethodFilter.matches(instrumentedMethod)).thenReturn(true);
+ when(firstFilter.matches(instrumentedMethod)).thenReturn(true);
+ when(secondFilter.matches(instrumentedMethod)).thenReturn(true);
+ MethodRegistry.Prepared methodRegistry = new MethodRegistry.Default()
+ .append(firstMatcher, firstHandler, firstFactory, transformer)
+ .append(firstMatcher, firstHandler, firstFactory, transformer)
+ .append(secondMatcher, firstHandler, firstFactory, transformer)
+ .append(secondMatcher, firstHandler, secondFactory, transformer)
+ .append(secondMatcher, secondHandler, secondFactory, transformer)
+ .append(firstMatcher, secondHandler, secondFactory, transformer)
+ .append(firstMatcher, firstHandler, secondFactory, transformer)
+ .append(firstMatcher, secondHandler, firstFactory, transformer)
+ .prepare(firstType, methodGraphCompiler, TypeValidation.ENABLED, methodFilter);
+ assertThat(methodRegistry.getInstrumentedType(), is(typeDescription));
+ assertThat(methodRegistry.getInstrumentedMethods(), is((MethodList) new MethodList.Explicit(instrumentedMethod)));
+ assertThat(methodRegistry.getTypeInitializer(), is(typeInitializer));
+ assertThat(methodRegistry.getLoadedTypeInitializer(), is(loadedTypeInitializer));
+ verify(firstHandler).prepare(firstType);
+ verify(secondHandler).prepare(secondType);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testCompiledAppendingMatchesFirstAppended() throws Exception {
+ when(resolvedMethodFilter.matches(instrumentedMethod)).thenReturn(true);
+ when(firstFilter.matches(instrumentedMethod)).thenReturn(true);
+ when(secondFilter.matches(instrumentedMethod)).thenReturn(true);
+ when(resolvedMethodFilter.matches(instrumentedMethod)).thenReturn(true);
+ MethodRegistry.Compiled methodRegistry = new MethodRegistry.Default()
+ .append(firstMatcher, firstHandler, firstFactory, transformer)
+ .append(secondMatcher, secondHandler, secondFactory, transformer)
+ .prepare(firstType, methodGraphCompiler, TypeValidation.ENABLED, methodFilter)
+ .compile(implementationTargetFactory, classFileVersion);
+ assertThat(methodRegistry.getInstrumentedType(), is(typeDescription));
+ assertThat(methodRegistry.getInstrumentedMethods(), is((MethodList) new MethodList.Explicit(instrumentedMethod)));
+ assertThat(methodRegistry.getTypeInitializer(), is(typeInitializer));
+ assertThat(methodRegistry.getLoadedTypeInitializer(), is(loadedTypeInitializer));
+ verify(firstHandler).prepare(firstType);
+ verify(secondHandler).prepare(secondType);
+ verify(firstFactory).make(typeDescription);
+ verifyZeroInteractions(secondFactory);
+ assertThat(methodRegistry.target(instrumentedMethod), is(firstRecord));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testCompiledPrependingMatchesLastPrepended() throws Exception {
+ when(resolvedMethodFilter.matches(instrumentedMethod)).thenReturn(true);
+ when(firstFilter.matches(instrumentedMethod)).thenReturn(true);
+ when(secondFilter.matches(instrumentedMethod)).thenReturn(true);
+ when(resolvedMethodFilter.matches(instrumentedMethod)).thenReturn(true);
+ MethodRegistry.Compiled methodRegistry = new MethodRegistry.Default()
+ .append(secondMatcher, secondHandler, secondFactory, transformer)
+ .prepend(firstMatcher, firstHandler, firstFactory, transformer)
+ .prepare(firstType, methodGraphCompiler, TypeValidation.ENABLED, methodFilter)
+ .compile(implementationTargetFactory, classFileVersion);
+ assertThat(methodRegistry.getInstrumentedType(), is(typeDescription));
+ assertThat(methodRegistry.getInstrumentedMethods(), is((MethodList) new MethodList.Explicit(instrumentedMethod)));
+ assertThat(methodRegistry.getTypeInitializer(), is(typeInitializer));
+ assertThat(methodRegistry.getLoadedTypeInitializer(), is(loadedTypeInitializer));
+ verify(firstHandler).prepare(firstType);
+ verify(secondHandler).prepare(secondType);
+ verify(firstFactory).make(typeDescription);
+ verifyZeroInteractions(secondFactory);
+ assertThat(methodRegistry.target(instrumentedMethod), is(firstRecord));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testCompiledAppendingMatchesSecondAppendedIfFirstDoesNotMatch() throws Exception {
+ when(resolvedMethodFilter.matches(instrumentedMethod)).thenReturn(true);
+ when(firstFilter.matches(instrumentedMethod)).thenReturn(false);
+ when(secondFilter.matches(instrumentedMethod)).thenReturn(true);
+ when(resolvedMethodFilter.matches(instrumentedMethod)).thenReturn(true);
+ MethodRegistry.Compiled methodRegistry = new MethodRegistry.Default()
+ .append(firstMatcher, firstHandler, firstFactory, transformer)
+ .append(secondMatcher, secondHandler, secondFactory, transformer)
+ .prepare(firstType, methodGraphCompiler, TypeValidation.ENABLED, methodFilter)
+ .compile(implementationTargetFactory, classFileVersion);
+ assertThat(methodRegistry.getInstrumentedType(), is(typeDescription));
+ assertThat(methodRegistry.getInstrumentedMethods(), is((MethodList) new MethodList.Explicit(instrumentedMethod)));
+ assertThat(methodRegistry.getTypeInitializer(), is(typeInitializer));
+ assertThat(methodRegistry.getLoadedTypeInitializer(), is(loadedTypeInitializer));
+ verify(firstHandler).prepare(firstType);
+ verify(secondHandler).prepare(secondType);
+ verifyZeroInteractions(firstFactory);
+ verify(secondFactory).make(typeDescription);
+ assertThat(methodRegistry.target(instrumentedMethod), is(secondRecord));
+ }
+
+ @Test
+ public void testSkipEntryIfNotMatchedAndVisible() throws Exception {
+ when(resolvedMethodFilter.matches(instrumentedMethod)).thenReturn(true);
+ when(firstFilter.matches(instrumentedMethod)).thenReturn(false);
+ when(secondFilter.matches(instrumentedMethod)).thenReturn(false);
+ when(resolvedMethodFilter.matches(instrumentedMethod)).thenReturn(true);
+ TypeDescription declaringType = mock(TypeDescription.class);
+ when(declaringType.asErasure()).thenReturn(declaringType);
+ when(instrumentedMethod.getDeclaringType()).thenReturn(declaringType);
+ MethodRegistry.Compiled methodRegistry = new MethodRegistry.Default()
+ .append(firstMatcher, firstHandler, firstFactory, transformer)
+ .append(secondMatcher, secondHandler, secondFactory, transformer)
+ .prepare(firstType, methodGraphCompiler, TypeValidation.ENABLED, methodFilter)
+ .compile(implementationTargetFactory, classFileVersion);
+ assertThat(methodRegistry.getInstrumentedType(), is(typeDescription));
+ assertThat(methodRegistry.getInstrumentedMethods().size(), is(0));
+ assertThat(methodRegistry.getTypeInitializer(), is(typeInitializer));
+ assertThat(methodRegistry.getLoadedTypeInitializer(), is(loadedTypeInitializer));
+ verify(firstHandler).prepare(firstType);
+ verify(secondHandler).prepare(secondType);
+ verifyZeroInteractions(firstFactory);
+ verifyZeroInteractions(secondFactory);
+ assertThat(methodRegistry.target(instrumentedMethod), instanceOf(TypeWriter.MethodPool.Record.ForNonImplementedMethod.class));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testVisibilityBridgeIfNotMatchedAndVisible() throws Exception {
+ when(instrumentedMethod.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(parameterDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(resolvedMethodFilter.matches(instrumentedMethod)).thenReturn(true);
+ when(firstFilter.matches(instrumentedMethod)).thenReturn(false);
+ when(secondFilter.matches(instrumentedMethod)).thenReturn(false);
+ when(resolvedMethodFilter.matches(instrumentedMethod)).thenReturn(true);
+ TypeDescription declaringType = mock(TypeDescription.class);
+ when(declaringType.asErasure()).thenReturn(declaringType);
+ when(instrumentedMethod.getDeclaringType()).thenReturn(declaringType);
+ when(thirdType.isPublic()).thenReturn(true);
+ when(instrumentedMethod.isPublic()).thenReturn(true);
+ when(declaringType.isPackagePrivate()).thenReturn(true);
+ TypeDescription.Generic superClass = mock(TypeDescription.Generic.class);
+ TypeDescription rawSuperClass = mock(TypeDescription.class);
+ when(superClass.asErasure()).thenReturn(rawSuperClass);
+ when(typeDescription.getSuperClass()).thenReturn(superClass);
+ MethodDescription.Token methodToken = mock(MethodDescription.Token.class);
+ when(instrumentedMethod.asToken(ElementMatchers.is(typeDescription))).thenReturn(methodToken);
+ when(methodToken.accept(any(TypeDescription.Generic.Visitor.class))).thenReturn(methodToken);
+ when(classFileVersion.isAtLeast(ClassFileVersion.JAVA_V5)).thenReturn(true);
+ MethodRegistry.Compiled methodRegistry = new MethodRegistry.Default()
+ .append(firstMatcher, firstHandler, firstFactory, transformer)
+ .append(secondMatcher, secondHandler, secondFactory, transformer)
+ .prepare(firstType, methodGraphCompiler, TypeValidation.ENABLED, methodFilter)
+ .compile(implementationTargetFactory, classFileVersion);
+ assertThat(methodRegistry.getInstrumentedType(), is(typeDescription));
+ assertThat(methodRegistry.getInstrumentedMethods().size(), is(1));
+ assertThat(methodRegistry.getTypeInitializer(), is(typeInitializer));
+ assertThat(methodRegistry.getLoadedTypeInitializer(), is(loadedTypeInitializer));
+ verify(firstHandler).prepare(firstType);
+ verify(secondHandler).prepare(secondType);
+ verifyZeroInteractions(firstFactory);
+ verifyZeroInteractions(secondFactory);
+ assertThat(methodRegistry.target(instrumentedMethod), instanceOf(TypeWriter.MethodPool.Record.ForDefinedMethod.OfVisibilityBridge.class));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodRegistry.Default.class).apply();
+ ObjectPropertyAssertion.of(MethodRegistry.Default.Entry.class).apply();
+ ObjectPropertyAssertion.of(MethodRegistry.Default.Prepared.class).apply();
+ ObjectPropertyAssertion.of(MethodRegistry.Default.Prepared.Entry.class).apply();
+ ObjectPropertyAssertion.of(MethodRegistry.Default.Compiled.class).apply();
+ ObjectPropertyAssertion.of(MethodRegistry.Default.Compiled.Entry.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodRegistryHandlerTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodRegistryHandlerTest.java
new file mode 100644
index 0000000..df0e4ba
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/MethodRegistryHandlerTest.java
@@ -0,0 +1,100 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.description.annotation.AnnotationValue;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.attribute.MethodAttributeAppender;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.ClassVisitor;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class MethodRegistryHandlerTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private InstrumentedType instrumentedType, preparedInstrumentedType;
+
+ @Mock
+ private Implementation implementation;
+
+ @Mock
+ private AnnotationValue<?, ?> annotationValue;
+
+ @Mock
+ private Implementation.Target implementationTarget;
+
+ @Mock
+ private MethodAttributeAppender attributeAppender;
+
+ @Mock
+ private ClassVisitor classVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @Mock
+ private MethodDescription methodDescription;
+
+ @Before
+ public void setUp() throws Exception {
+ when(implementation.prepare(instrumentedType)).thenReturn(preparedInstrumentedType);
+ }
+
+ @Test
+ public void testHandlerForAbstractMethod() throws Exception {
+ MethodRegistry.Handler handler = MethodRegistry.Handler.ForAbstractMethod.INSTANCE;
+ assertThat(handler.prepare(instrumentedType), is(instrumentedType));
+ TypeWriter.MethodPool.Record record = handler.compile(implementationTarget).assemble(methodDescription,
+ attributeAppender,
+ Visibility.PUBLIC);
+ assertThat(record.getSort(), is(TypeWriter.MethodPool.Record.Sort.DEFINED));
+ }
+
+ @Test
+ public void testHandlerForImplementation() throws Exception {
+ MethodRegistry.Handler handler = new MethodRegistry.Handler.ForImplementation(implementation);
+ assertThat(handler.prepare(instrumentedType), is(preparedInstrumentedType));
+ TypeWriter.MethodPool.Record record = handler.compile(implementationTarget).assemble(methodDescription,
+ attributeAppender,
+ Visibility.PUBLIC);
+ assertThat(record.getSort(), is(TypeWriter.MethodPool.Record.Sort.IMPLEMENTED));
+ }
+
+ @Test
+ public void testHandlerForAnnotationValue() throws Exception {
+ MethodRegistry.Handler handler = new MethodRegistry.Handler.ForAnnotationValue(annotationValue);
+ assertThat(handler.prepare(instrumentedType), is(instrumentedType));
+ TypeWriter.MethodPool.Record record = handler.compile(implementationTarget).assemble(methodDescription,
+ attributeAppender,
+ Visibility.PUBLIC);
+ assertThat(record.getSort(), is(TypeWriter.MethodPool.Record.Sort.DEFINED));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testVisibilityBridgeHandlerPreparationThrowsException() throws Exception {
+ MethodRegistry.Handler.ForVisibilityBridge.INSTANCE.prepare(mock(InstrumentedType.class));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodRegistry.Handler.ForAbstractMethod.class).apply();
+ ObjectPropertyAssertion.of(MethodRegistry.Handler.ForImplementation.class).apply();
+ ObjectPropertyAssertion.of(MethodRegistry.Handler.ForImplementation.Compiled.class).apply();
+ ObjectPropertyAssertion.of(MethodRegistry.Handler.ForAnnotationValue.class).apply();
+ ObjectPropertyAssertion.of(MethodRegistry.Handler.ForVisibilityBridge.class).apply();
+ ObjectPropertyAssertion.of(MethodRegistry.Handler.ForVisibilityBridge.Compiled.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/TypeInitializerDrainDefaultTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/TypeInitializerDrainDefaultTest.java
new file mode 100644
index 0000000..1f96640
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/TypeInitializerDrainDefaultTest.java
@@ -0,0 +1,56 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.attribute.AnnotationValueFilter;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.ClassVisitor;
+
+import static org.mockito.Mockito.*;
+
+public class TypeInitializerDrainDefaultTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription instrumentedType;
+
+ @Mock
+ private TypeWriter.MethodPool methodPool;
+
+ @Mock
+ private AnnotationValueFilter.Factory annotationValueFilterFactory;
+
+ @Mock
+ private ClassVisitor classVisitor;
+
+ @Mock
+ private TypeInitializer typeInitializer;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @Mock
+ private TypeWriter.MethodPool.Record record, transformed;
+
+ @Test
+ public void testDrain() throws Exception {
+ when(methodPool.target(new MethodDescription.Latent.TypeInitializer(instrumentedType))).thenReturn(record);
+ when(typeInitializer.wrap(record)).thenReturn(transformed);
+ new TypeInitializer.Drain.Default(instrumentedType, methodPool, annotationValueFilterFactory).apply(classVisitor, typeInitializer, implementationContext);
+ verify(transformed).apply(classVisitor, implementationContext, annotationValueFilterFactory);
+ verifyNoMoreInteractions(transformed);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeInitializer.Drain.Default.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/TypeValidationTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/TypeValidationTest.java
new file mode 100644
index 0000000..54abbcc
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/TypeValidationTest.java
@@ -0,0 +1,42 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class TypeValidationTest {
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {TypeValidation.ENABLED, true},
+ {TypeValidation.DISABLED, false}
+ });
+ }
+
+ private final TypeValidation typeValidation;
+
+ private final boolean enabled;
+
+ public TypeValidationTest(TypeValidation typeValidation, boolean enabled) {
+ this.typeValidation = typeValidation;
+ this.enabled = enabled;
+ }
+
+ @Test
+ public void testIsEnabled() throws Exception {
+ assertThat(typeValidation.isEnabled(), is(enabled));
+ }
+
+ @Test
+ public void testReceival() throws Exception {
+ assertThat(TypeValidation.of(enabled), is(typeValidation));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/TypeWriterDefaultForInliningInitializationHandlerAppendingFrameWriterActiveTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/TypeWriterDefaultForInliningInitializationHandlerAppendingFrameWriterActiveTest.java
new file mode 100644
index 0000000..195a3c7
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/TypeWriterDefaultForInliningInitializationHandlerAppendingFrameWriterActiveTest.java
@@ -0,0 +1,80 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+
+public class TypeWriterDefaultForInliningInitializationHandlerAppendingFrameWriterActiveTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ private TypeWriter.Default.ForInlining.InitializationHandler.Appending.FrameWriter frameWriter = new TypeWriter.Default.ForInlining.InitializationHandler.Appending.FrameWriter.Active();
+
+ @Test
+ public void testNoFrame() throws Exception {
+ frameWriter.emitFrame(methodVisitor);
+ verify(methodVisitor).visitFrame(Opcodes.F_SAME, 0, new Object[0], 0, new Object[0]);
+ verifyZeroInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testSameFrame() throws Exception {
+ frameWriter.onFrame(Opcodes.F_SAME, 0);
+ frameWriter.emitFrame(methodVisitor);
+ verify(methodVisitor).visitFrame(Opcodes.F_SAME, 0, new Object[0], 0, new Object[0]);
+ verifyZeroInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testSameFrame1() throws Exception {
+ frameWriter.onFrame(Opcodes.F_SAME1, 0);
+ frameWriter.emitFrame(methodVisitor);
+ verify(methodVisitor).visitFrame(Opcodes.F_SAME, 0, new Object[0], 0, new Object[0]);
+ verifyZeroInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testAppendChop() throws Exception {
+ frameWriter.onFrame(Opcodes.F_APPEND, 2);
+ frameWriter.onFrame(Opcodes.F_CHOP, 1);
+ frameWriter.emitFrame(methodVisitor);
+ verify(methodVisitor).visitFrame(Opcodes.F_CHOP, 1, new Object[0], 0, new Object[0]);
+ verifyZeroInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testFull() throws Exception {
+ frameWriter.onFrame(Opcodes.F_FULL, 5);
+ frameWriter.onFrame(Opcodes.F_CHOP, 1);
+ frameWriter.emitFrame(methodVisitor);
+ verify(methodVisitor).visitFrame(Opcodes.F_FULL, 0, new Object[0], 0, new Object[0]);
+ verifyZeroInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testNew() throws Exception {
+ frameWriter.onFrame(Opcodes.F_NEW, 5);
+ frameWriter.onFrame(Opcodes.F_CHOP, 1);
+ frameWriter.emitFrame(methodVisitor);
+ verify(methodVisitor).visitFrame(Opcodes.F_FULL, 0, new Object[0], 0, new Object[0]);
+ verifyZeroInteractions(methodVisitor);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUnexpected() throws Exception {
+ frameWriter.onFrame(-2, 0);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/TypeWriterDefaultForInliningInitializationHandlerAppendingFrameWriterExpandingTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/TypeWriterDefaultForInliningInitializationHandlerAppendingFrameWriterExpandingTest.java
new file mode 100644
index 0000000..c345c03
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/TypeWriterDefaultForInliningInitializationHandlerAppendingFrameWriterExpandingTest.java
@@ -0,0 +1,25 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import static org.mockito.Mockito.*;
+
+public class TypeWriterDefaultForInliningInitializationHandlerAppendingFrameWriterExpandingTest {
+
+ @Test
+ public void testFrame() throws Exception {
+ TypeWriter.Default.ForInlining.InitializationHandler.Appending.FrameWriter.Expanding.INSTANCE.onFrame(0, 0);
+ MethodVisitor methodVisitor = mock(MethodVisitor.class);
+ TypeWriter.Default.ForInlining.InitializationHandler.Appending.FrameWriter.Expanding.INSTANCE.emitFrame(methodVisitor);
+ verify(methodVisitor).visitFrame(Opcodes.F_NEW, 0, new Object[0], 0, new Object[0]);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeWriter.Default.ForInlining.InitializationHandler.Appending.FrameWriter.Expanding.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/TypeWriterDefaultForInliningInitializationHandlerAppendingFrameWriterNoOpTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/TypeWriterDefaultForInliningInitializationHandlerAppendingFrameWriterNoOpTest.java
new file mode 100644
index 0000000..2914791
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/TypeWriterDefaultForInliningInitializationHandlerAppendingFrameWriterNoOpTest.java
@@ -0,0 +1,24 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+import org.objectweb.asm.MethodVisitor;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+public class TypeWriterDefaultForInliningInitializationHandlerAppendingFrameWriterNoOpTest {
+
+ @Test
+ public void testFrame() throws Exception {
+ TypeWriter.Default.ForInlining.InitializationHandler.Appending.FrameWriter.NoOp.INSTANCE.onFrame(0, 0);
+ MethodVisitor methodVisitor = mock(MethodVisitor.class);
+ TypeWriter.Default.ForInlining.InitializationHandler.Appending.FrameWriter.NoOp.INSTANCE.emitFrame(methodVisitor);
+ verifyZeroInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeWriter.Default.ForInlining.InitializationHandler.Appending.FrameWriter.NoOp.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/TypeWriterDefaultFrameComputingClassWriterTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/TypeWriterDefaultFrameComputingClassWriterTest.java
new file mode 100644
index 0000000..be72dae
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/TypeWriterDefaultFrameComputingClassWriterTest.java
@@ -0,0 +1,78 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.pool.TypePool;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.ClassReader;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class TypeWriterDefaultFrameComputingClassWriterTest {
+
+ private static final String FOO = "pkg/foo", BAR = "pkg/bar", QUX = "pkg/qux", BAZ = "pkg/baz", FOOBAR = "pkg/foobar";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypePool typePool;
+
+ @Mock
+ private TypeDescription leftType, rightType, superClass;
+
+ @Mock
+ private TypeDescription.Generic genericSuperClass;
+
+ private TypeWriter.Default.FrameComputingClassWriter frameComputingClassWriter;
+
+ @Before
+ public void setUp() throws Exception {
+ frameComputingClassWriter = new TypeWriter.Default.FrameComputingClassWriter(mock(ClassReader.class), 0, typePool);
+ when(typePool.describe(FOO.replace('/', '.'))).thenReturn(new TypePool.Resolution.Simple(leftType));
+ when(typePool.describe(BAR.replace('/', '.'))).thenReturn(new TypePool.Resolution.Simple(rightType));
+ when(leftType.getInternalName()).thenReturn(QUX);
+ when(rightType.getInternalName()).thenReturn(BAZ);
+ when(leftType.getSuperClass()).thenReturn(genericSuperClass);
+ when(genericSuperClass.asErasure()).thenReturn(superClass);
+ when(superClass.getInternalName()).thenReturn(FOOBAR);
+ }
+
+ @Test
+ public void testLeftIsAssignable() throws Exception {
+ when(leftType.isAssignableFrom(rightType)).thenReturn(true);
+ assertThat(frameComputingClassWriter.getCommonSuperClass(FOO, BAR), is(QUX));
+ }
+
+ @Test
+ public void testRightIsAssignable() throws Exception {
+ when(leftType.isAssignableTo(rightType)).thenReturn(true);
+ assertThat(frameComputingClassWriter.getCommonSuperClass(FOO, BAR), is(BAZ));
+ }
+
+ @Test
+ public void testLeftIsInterface() throws Exception {
+ when(leftType.isInterface()).thenReturn(true);
+ assertThat(frameComputingClassWriter.getCommonSuperClass(FOO, BAR), is(TypeDescription.OBJECT.getInternalName()));
+ }
+
+ @Test
+ public void testRightIsInterface() throws Exception {
+ when(rightType.isInterface()).thenReturn(true);
+ assertThat(frameComputingClassWriter.getCommonSuperClass(FOO, BAR), is(TypeDescription.OBJECT.getInternalName()));
+ }
+
+ @Test
+ public void testSuperClassIteration() throws Exception {
+ when(superClass.isAssignableFrom(rightType)).thenReturn(true);
+ assertThat(frameComputingClassWriter.getCommonSuperClass(FOO, BAR), is(FOOBAR));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/TypeWriterDefaultTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/TypeWriterDefaultTest.java
new file mode 100644
index 0000000..fd265ca
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/TypeWriterDefaultTest.java
@@ -0,0 +1,623 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.asm.AsmVisitorWrapper;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.modifier.*;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
+import net.bytebuddy.implementation.FixedValue;
+import net.bytebuddy.implementation.StubMethod;
+import net.bytebuddy.implementation.SuperMethodCall;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.utility.JavaConstant;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassWriter;
+
+import java.io.File;
+import java.io.Serializable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Field;
+import java.util.Collections;
+import java.util.List;
+
+import static net.bytebuddy.matcher.ElementMatchers.*;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class TypeWriterDefaultTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ private static final String LEGACY_INTERFACE = "net.bytebuddy.test.precompiled.LegacyInterface";
+
+ private static final String JAVA_8_INTERFACE = "net.bytebuddy.test.precompiled.SingleDefaultMethodInterface";
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ @Test(expected = IllegalStateException.class)
+ public void testConstructorOnInterfaceAssertion() throws Exception {
+ new ByteBuddy()
+ .makeInterface()
+ .defineConstructor(Visibility.PUBLIC)
+ .intercept(SuperMethodCall.INSTANCE)
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testConstructorOnAnnotationAssertion() throws Exception {
+ new ByteBuddy()
+ .makeAnnotation()
+ .defineConstructor(Visibility.PUBLIC)
+ .intercept(SuperMethodCall.INSTANCE)
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAbstractConstructorAssertion() throws Exception {
+ new ByteBuddy()
+ .subclass(Object.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
+ .defineConstructor(Visibility.PUBLIC)
+ .withoutCode()
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testStaticAbstractMethodAssertion() throws Exception {
+ new ByteBuddy()
+ .subclass(Object.class)
+ .defineMethod(FOO, void.class, Ownership.STATIC)
+ .withoutCode()
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testPrivateAbstractMethodAssertion() throws Exception {
+ new ByteBuddy()
+ .subclass(Object.class)
+ .defineMethod(FOO, void.class, Visibility.PRIVATE)
+ .withoutCode()
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAbstractMethodOnNonAbstractClassAssertion() throws Exception {
+ new ByteBuddy()
+ .subclass(Object.class)
+ .defineMethod(FOO, String.class)
+ .withoutCode()
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonPublicFieldOnInterfaceAssertion() throws Exception {
+ new ByteBuddy()
+ .makeInterface()
+ .defineField(FOO, String.class, Ownership.STATIC, FieldManifestation.FINAL)
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonPublicFieldOnAnnotationAssertion() throws Exception {
+ new ByteBuddy()
+ .makeAnnotation()
+ .defineField(FOO, String.class, Ownership.STATIC, FieldManifestation.FINAL)
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonStaticFieldOnInterfaceAssertion() throws Exception {
+ new ByteBuddy()
+ .makeInterface()
+ .defineField(FOO, String.class, Visibility.PUBLIC, FieldManifestation.FINAL)
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonStaticFieldOnAnnotationAssertion() throws Exception {
+ new ByteBuddy()
+ .makeAnnotation()
+ .defineField(FOO, String.class, Visibility.PUBLIC, FieldManifestation.FINAL)
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonFinalFieldOnInterfaceAssertion() throws Exception {
+ new ByteBuddy()
+ .makeInterface()
+ .defineField(FOO, String.class, Visibility.PUBLIC, Ownership.STATIC)
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonFinalFieldOnAnnotationAssertion() throws Exception {
+ new ByteBuddy()
+ .makeAnnotation()
+ .defineField(FOO, String.class, Visibility.PUBLIC, Ownership.STATIC)
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testStaticFieldWithIncompatibleConstantValue() throws Exception {
+ new ByteBuddy()
+ .subclass(Object.class)
+ .defineField(FOO, String.class, Ownership.STATIC)
+ .value(0)
+ .make();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testStaticFieldWithNullConstantValue() throws Exception {
+ new ByteBuddy()
+ .subclass(Object.class)
+ .defineField(FOO, String.class, Ownership.STATIC)
+ .value(null);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testStaticNumericFieldWithIncompatibleConstantValue() throws Exception {
+ new ByteBuddy()
+ .subclass(Object.class)
+ .defineField(FOO, boolean.class, Ownership.STATIC)
+ .value(Integer.MAX_VALUE)
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testStaticFieldWithNonDefinableConstantValue() throws Exception {
+ new ByteBuddy()
+ .subclass(Object.class)
+ .defineField(FOO, Object.class, Ownership.STATIC)
+ .value(FOO)
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonPublicMethodOnInterfaceAssertion() throws Exception {
+ new ByteBuddy(ClassFileVersion.JAVA_V6)
+ .makeInterface()
+ .defineMethod(FOO, void.class)
+ .withoutCode()
+ .make();
+ }
+
+ @Test
+ public void testNonPublicMethodOnInterfaceAssertionJava8() throws Exception {
+ new ByteBuddy(ClassFileVersion.JAVA_V8)
+ .makeInterface()
+ .defineMethod(FOO, void.class)
+ .withoutCode()
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonPublicMethodOnAnnotationAssertion() throws Exception {
+ new ByteBuddy()
+ .makeAnnotation()
+ .defineMethod(FOO, void.class)
+ .withoutCode()
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testStaticMethodOnInterfaceAssertion() throws Exception {
+ new ByteBuddy(ClassFileVersion.JAVA_V6)
+ .makeInterface()
+ .defineMethod(FOO, String.class, Visibility.PUBLIC, Ownership.STATIC)
+ .withoutCode()
+ .make();
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testStaticMethodOnAnnotationAssertionJava8() throws Exception {
+ new ByteBuddy()
+ .makeInterface()
+ .defineMethod(FOO, String.class, Visibility.PUBLIC, Ownership.STATIC)
+ .intercept(StubMethod.INSTANCE)
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testStaticMethodOnAnnotationAssertion() throws Exception {
+ new ByteBuddy(ClassFileVersion.JAVA_V6)
+ .makeAnnotation()
+ .defineMethod(FOO, String.class, Visibility.PUBLIC, Ownership.STATIC)
+ .intercept(StubMethod.INSTANCE)
+ .make();
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testStaticMethodOnInterfaceAssertionJava8() throws Exception {
+ new ByteBuddy()
+ .makeAnnotation()
+ .defineMethod(FOO, String.class, Visibility.PUBLIC, Ownership.STATIC)
+ .intercept(StubMethod.INSTANCE)
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAnnotationDefaultValueOnClassAssertion() throws Exception {
+ new ByteBuddy()
+ .subclass(Object.class)
+ .merge(TypeManifestation.ABSTRACT)
+ .defineMethod(FOO, String.class)
+ .defaultValue(BAR, String.class)
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAnnotationDefaultValueOnInterfaceClassAssertion() throws Exception {
+ new ByteBuddy()
+ .makeInterface()
+ .defineMethod(FOO, String.class)
+ .defaultValue(BAR, String.class)
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAnnotationPropertyWithVoidReturnAssertion() throws Exception {
+ new ByteBuddy()
+ .makeAnnotation()
+ .defineMethod(FOO, void.class, Visibility.PUBLIC)
+ .withoutCode()
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAnnotationPropertyWithParametersAssertion() throws Exception {
+ new ByteBuddy()
+ .makeAnnotation()
+ .defineMethod(FOO, String.class, Visibility.PUBLIC).withParameters(Void.class)
+ .withoutCode()
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testPackageDescriptionWithModifiers() throws Exception {
+ new ByteBuddy()
+ .makePackage(FOO)
+ .modifiers(Visibility.PRIVATE)
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testPackageDescriptionWithInterfaces() throws Exception {
+ new ByteBuddy()
+ .makePackage(FOO)
+ .implement(Serializable.class)
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testPackageDescriptionWithField() throws Exception {
+ new ByteBuddy()
+ .makePackage(FOO)
+ .defineField(FOO, Void.class)
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testPackageDescriptionWithMethod() throws Exception {
+ new ByteBuddy()
+ .makePackage(FOO)
+ .defineMethod(FOO, void.class)
+ .withoutCode()
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAnnotationPreJava5TypeAssertion() throws Exception {
+ new ByteBuddy(ClassFileVersion.JAVA_V4)
+ .makeAnnotation()
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAnnotationOnTypePreJava5TypeAssertion() throws Exception {
+ new ByteBuddy(ClassFileVersion.JAVA_V4)
+ .subclass(Object.class)
+ .annotateType(AnnotationDescription.Builder.ofType(Foo.class).build())
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAnnotationOnFieldPreJava5TypeAssertion() throws Exception {
+ new ByteBuddy(ClassFileVersion.JAVA_V4)
+ .subclass(Object.class)
+ .defineField(FOO, Void.class)
+ .annotateField(AnnotationDescription.Builder.ofType(Foo.class).build())
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAnnotationOnMethodPreJava5TypeAssertion() throws Exception {
+ new ByteBuddy(ClassFileVersion.JAVA_V4)
+ .subclass(Object.class)
+ .defineMethod(FOO, void.class)
+ .intercept(StubMethod.INSTANCE)
+ .annotateMethod(AnnotationDescription.Builder.ofType(Foo.class).build())
+ .make();
+ }
+
+ @Test
+ public void testTypeInitializerOnInterface() throws Exception {
+ assertThat(new ByteBuddy()
+ .makeInterface()
+ .invokable(isTypeInitializer())
+ .intercept(StubMethod.INSTANCE)
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded(), notNullValue(Class.class));
+ }
+
+ @Test
+ public void testTypeInitializerOnAnnotation() throws Exception {
+ assertThat(new ByteBuddy()
+ .makeAnnotation()
+ .invokable(isTypeInitializer())
+ .intercept(StubMethod.INSTANCE)
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded(), notNullValue(Class.class));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testTypeInitializerOnRebasedModernInterface() throws Exception {
+ assertThat(new ByteBuddy()
+ .rebase(Class.forName(JAVA_8_INTERFACE))
+ .invokable(isTypeInitializer())
+ .intercept(StubMethod.INSTANCE)
+ .make(), notNullValue(DynamicType.class));
+ }
+
+ @Test
+ public void testTypeInitializerOnRebasedLegacyInterface() throws Exception {
+ assertThat(new ByteBuddy()
+ .rebase(Class.forName(LEGACY_INTERFACE))
+ .invokable(isTypeInitializer())
+ .intercept(StubMethod.INSTANCE)
+ .make(), notNullValue(DynamicType.class));
+ }
+
+ @Test
+ public void testTypeInitializerOnRebasedInterfaceWithFrameComputation() throws Exception {
+ assertThat(new ByteBuddy()
+ .makeInterface()
+ .visit(new AsmVisitorWrapper.ForDeclaredMethods().writerFlags(ClassWriter.COMPUTE_FRAMES))
+ .invokable(isTypeInitializer())
+ .intercept(StubMethod.INSTANCE)
+ .make(), notNullValue(DynamicType.class));
+ }
+
+ @Test
+ public void testTypeInitializerOnRebasedInterfaceWithFrameExpansion() throws Exception {
+ assertThat(new ByteBuddy()
+ .makeInterface()
+ .visit(new AsmVisitorWrapper.ForDeclaredMethods().readerFlags(ClassReader.EXPAND_FRAMES))
+ .invokable(isTypeInitializer())
+ .intercept(StubMethod.INSTANCE)
+ .make(), notNullValue(DynamicType.class));
+ }
+
+ @Test
+ public void testTypeInitializerOnRebasedInterfaceWithInitializer() throws Exception {
+ assertThat(new ByteBuddy()
+ .makeInterface()
+ .initializer(new ByteCodeAppender.Simple())
+ .invokable(isTypeInitializer())
+ .intercept(StubMethod.INSTANCE)
+ .make(), notNullValue(DynamicType.class));
+ }
+
+ @Test
+ public void testTypeInLegacyConstantPoolRemapped() throws Exception {
+ Class<?> dynamicType = new ByteBuddy(ClassFileVersion.JAVA_V4)
+ .with(TypeValidation.DISABLED)
+ .subclass(Object.class)
+ .defineMethod(FOO, Object.class, Visibility.PUBLIC)
+ .intercept(FixedValue.value(Object.class))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(dynamicType.getDeclaredMethod(FOO).invoke(dynamicType.getDeclaredConstructor().newInstance()), is((Object) Object.class));
+ }
+
+ @Test
+ public void testArrayTypeInLegacyConstantPoolRemapped() throws Exception {
+ Class<?> dynamicType = new ByteBuddy(ClassFileVersion.JAVA_V4)
+ .with(TypeValidation.DISABLED)
+ .subclass(Object.class)
+ .defineMethod(FOO, Object.class, Visibility.PUBLIC)
+ .intercept(FixedValue.value(Object[].class))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(dynamicType.getDeclaredMethod(FOO).invoke(dynamicType.getDeclaredConstructor().newInstance()), is((Object) Object[].class));
+ }
+
+ @Test
+ public void testPrimitiveTypeInLegacyConstantPoolRemapped() throws Exception {
+ Class<?> dynamicType = new ByteBuddy(ClassFileVersion.JAVA_V4)
+ .with(TypeValidation.DISABLED)
+ .subclass(Object.class)
+ .defineMethod(FOO, Object.class, Visibility.PUBLIC)
+ .intercept(FixedValue.value(int.class))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(dynamicType.getDeclaredMethod(FOO).invoke(dynamicType.getDeclaredConstructor().newInstance()), is((Object) int.class));
+ }
+
+ @Test
+ public void testLegacyTypeRedefinitionIsDiscovered() throws Exception {
+ Class<?> dynamicType = new ByteBuddy()
+ .with(TypeValidation.DISABLED)
+ .redefine(Class.forName("net.bytebuddy.test.precompiled.TypeConstantSample"))
+ .method(named(BAR))
+ .intercept(FixedValue.value(int.class))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(dynamicType.getDeclaredMethod(BAR).invoke(null), is((Object) int.class));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodTypeInLegacyConstantPool() throws Exception {
+ new ByteBuddy(ClassFileVersion.JAVA_V4)
+ .subclass(Object.class)
+ .defineMethod(FOO, Object.class)
+ .intercept(FixedValue.value(JavaConstant.MethodType.of(Object.class, Object.class)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodHandleInLegacyConstantPool() throws Exception {
+ new ByteBuddy(ClassFileVersion.JAVA_V4)
+ .subclass(Object.class)
+ .defineMethod(FOO, Object.class)
+ .intercept(FixedValue.value(JavaConstant.MethodHandle.of(new MethodDescription.ForLoadedMethod(Object.class.getDeclaredMethod("toString")))))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ @JavaVersionRule.Enforce(8)
+ public void testDefaultMethodCallFromLegacyType() throws Exception {
+ new ByteBuddy(ClassFileVersion.JAVA_V7)
+ .subclass(Class.forName("net.bytebuddy.test.precompiled.SingleDefaultMethodInterface"))
+ .method(isDefaultMethod())
+ .intercept(SuperMethodCall.INSTANCE)
+ .make();
+ }
+
+ @Test
+ public void testBridgeNonLegacyType() throws Exception {
+ Class<?> base = new ByteBuddy(ClassFileVersion.JAVA_V5)
+ .subclass(Object.class)
+ .modifiers(Visibility.PACKAGE_PRIVATE)
+ .defineMethod("foo", void.class, Visibility.PUBLIC).intercept(StubMethod.INSTANCE)
+ .defineMethod("bar", Object.class).intercept(StubMethod.INSTANCE)
+ .defineMethod("bar", String.class).intercept(StubMethod.INSTANCE)
+ .make()
+ .load(null, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Class<?> subclass = new ByteBuddy(ClassFileVersion.JAVA_V5)
+ .subclass(base)
+ .modifiers(Visibility.PUBLIC)
+ .method(named("bar")).intercept(StubMethod.INSTANCE)
+ .make()
+ .load(base.getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded();
+ assertThat(subclass.getDeclaredMethods().length, is(3));
+ assertThat(subclass.getDeclaredMethod("foo").isBridge(), is(true));
+ assertThat(subclass.getDeclaredMethod("bar").isBridge(), is(false));
+ assertThat(subclass.getDeclaredMethod("bar").getReturnType(), is((Object) String.class));
+ }
+
+ @Test
+ public void testNoBridgeLegacyType() throws Exception {
+ Class<?> base = new ByteBuddy(ClassFileVersion.JAVA_V4)
+ .subclass(Object.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
+ .modifiers(Visibility.PACKAGE_PRIVATE)
+ .defineConstructor(Visibility.PUBLIC).intercept(SuperMethodCall.INSTANCE)
+ .defineMethod(FOO, void.class, Visibility.PUBLIC).intercept(StubMethod.INSTANCE)
+ .defineMethod(BAR, Object.class).intercept(StubMethod.INSTANCE)
+ .defineMethod(BAR, String.class).intercept(StubMethod.INSTANCE)
+ .make()
+ .load(null, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Class<?> subclass = new ByteBuddy(ClassFileVersion.JAVA_V4)
+ .subclass(base)
+ .modifiers(Visibility.PUBLIC)
+ .method(named(BAR)).intercept(StubMethod.INSTANCE)
+ .make()
+ .load(base.getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded();
+ assertThat(subclass.getDeclaredMethods().length, is(1));
+ assertThat(subclass.getDeclaredMethod(BAR).isBridge(), is(false));
+ assertThat(subclass.getDeclaredMethod(BAR).getReturnType(), is((Object) String.class));
+ }
+
+ @Test
+ public void testIncompatibleBridgeMethodIsFiltered() throws Exception {
+ Class<?> base = new ByteBuddy()
+ .subclass(Object.class)
+ .defineMethod(FOO, Object.class, Visibility.PUBLIC).intercept(StubMethod.INSTANCE)
+ .defineMethod(FOO, void.class, Visibility.PUBLIC, MethodManifestation.BRIDGE).intercept(StubMethod.INSTANCE)
+ .make()
+ .load(null, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Class<?> subclass = new ByteBuddy()
+ .subclass(base)
+ .method(named(FOO)).intercept(StubMethod.INSTANCE)
+ .make()
+ .load(base.getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded();
+ assertThat(subclass.getDeclaredMethods().length, is(1));
+ assertThat(subclass.getDeclaredMethod(FOO).isBridge(), is(false));
+ assertThat(subclass.getDeclaredMethod(FOO).getReturnType(), is((Object) Object.class));
+
+ }
+
+ @Test
+ public void testClassDump() throws Exception {
+ TypeDescription instrumentedType = mock(TypeDescription.class);
+ byte[] binaryRepresentation = new byte[]{1, 2, 3};
+ File file = File.createTempFile(FOO, BAR);
+ assertThat(file.delete(), is(true));
+ file = new File(file.getParentFile(), "temp" + System.currentTimeMillis());
+ assertThat(file.mkdir(), is(true));
+ when(instrumentedType.getName()).thenReturn(FOO + "." + BAR);
+ new TypeWriter.Default.ClassDumpAction(file.getAbsolutePath(), instrumentedType, binaryRepresentation).run();
+ File[] child = file.listFiles();
+ assertThat(child, notNullValue(File[].class));
+ assertThat(child.length, is(1));
+ assertThat(child[0].length(), is(3L));
+ assertThat(child[0].delete(), is(true));
+ assertThat(file.delete(), is(true));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeWriter.Default.UnresolvedType.class).apply();
+ ObjectPropertyAssertion.of(TypeWriter.Default.ClassDumpAction.class).apply();
+ ObjectPropertyAssertion.of(TypeWriter.Default.ForCreation.class).apply();
+ ObjectPropertyAssertion.of(TypeWriter.Default.ForInlining.class).apply();
+ ObjectPropertyAssertion.of(TypeWriter.Default.ForInlining.InitializationHandler.Creating.class).apply();
+ ObjectPropertyAssertion.of(TypeWriter.Default.ValidatingClassVisitor.Constraint.ForAnnotation.class).apply();
+ ObjectPropertyAssertion.of(TypeWriter.Default.ValidatingClassVisitor.Constraint.ForInterface.class).apply();
+ ObjectPropertyAssertion.of(TypeWriter.Default.ValidatingClassVisitor.Constraint.ForClass.class).apply();
+ ObjectPropertyAssertion.of(TypeWriter.Default.ValidatingClassVisitor.Constraint.ForPackageType.class).apply();
+ ObjectPropertyAssertion.of(TypeWriter.Default.ValidatingClassVisitor.Constraint.ForClassFileVersion.class).apply();
+ ObjectPropertyAssertion.of(TypeWriter.Default.ValidatingClassVisitor.Constraint.Compound.class).create(new ObjectPropertyAssertion.Creator<List<?>>() {
+ @Override
+ public List<?> create() {
+ return Collections.singletonList(mock(TypeWriter.Default.ValidatingClassVisitor.Constraint.class));
+ }
+ }).apply();
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface Foo {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/TypeWriterFieldPoolRecordTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/TypeWriterFieldPoolRecordTest.java
new file mode 100644
index 0000000..298839a
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/TypeWriterFieldPoolRecordTest.java
@@ -0,0 +1,149 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.attribute.AnnotationValueFilter;
+import net.bytebuddy.implementation.attribute.FieldAttributeAppender;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+
+import java.lang.annotation.RetentionPolicy;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class TypeWriterFieldPoolRecordTest {
+
+ private static final int MODIFIER = 42;
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux", BAZ = "baz";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private FieldAttributeAppender fieldAttributeAppender;
+
+ @Mock
+ private AnnotationValueFilter valueFilter;
+
+ @Mock
+ private AnnotationValueFilter.Factory annotationValueFilterFactory;
+
+ @Mock
+ private ClassVisitor classVisitor;
+
+ @Mock
+ private FieldVisitor fieldVisitor;
+
+ @Mock
+ private AnnotationVisitor annotationVisitor;
+
+ @Mock
+ private FieldDescription fieldDescription;
+
+ @Mock
+ private AnnotationDescription annotationDescription;
+
+ @Mock
+ private TypeDescription annotationType;
+
+ @Mock
+ private Object defaultValue;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ when(fieldDescription.getActualModifiers()).thenReturn(MODIFIER);
+ when(fieldDescription.getInternalName()).thenReturn(FOO);
+ when(fieldDescription.getDescriptor()).thenReturn(BAR);
+ when(fieldDescription.getGenericSignature()).thenReturn(QUX);
+ when(fieldDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.Explicit(annotationDescription));
+ when(fieldDescription.getType()).thenReturn(TypeDescription.Generic.OBJECT);
+ when(classVisitor.visitField(MODIFIER, FOO, BAR, QUX, defaultValue)).thenReturn(fieldVisitor);
+ when(classVisitor.visitField(MODIFIER, FOO, BAR, QUX, FieldDescription.NO_DEFAULT_VALUE)).thenReturn(fieldVisitor);
+ when(annotationValueFilterFactory.on(fieldDescription)).thenReturn(valueFilter);
+ when(fieldVisitor.visitAnnotation(any(String.class), anyBoolean())).thenReturn(annotationVisitor);
+ when(annotationDescription.getAnnotationType()).thenReturn(annotationType);
+ when(annotationType.getDescriptor()).thenReturn(BAZ);
+ when(annotationType.getDeclaredMethods()).thenReturn(new MethodList.Empty<MethodDescription.InDefinedShape>());
+ when(annotationDescription.getRetention()).thenReturn(RetentionPolicy.RUNTIME);
+ }
+
+ @Test
+ public void testExplicitFieldEntryProperties() throws Exception {
+ TypeWriter.FieldPool.Record record = new TypeWriter.FieldPool.Record.ForExplicitField(fieldAttributeAppender, defaultValue, fieldDescription);
+ assertThat(record.getFieldAppender(), is(fieldAttributeAppender));
+ assertThat(record.resolveDefault(FieldDescription.NO_DEFAULT_VALUE), is(defaultValue));
+ assertThat(record.isImplicit(), is(false));
+ }
+
+ @Test
+ public void testExplicitFieldEntryWritesField() throws Exception {
+ TypeWriter.FieldPool.Record record = new TypeWriter.FieldPool.Record.ForExplicitField(fieldAttributeAppender, defaultValue, fieldDescription);
+ record.apply(classVisitor, annotationValueFilterFactory);
+ verify(classVisitor).visitField(MODIFIER, FOO, BAR, QUX, defaultValue);
+ verifyNoMoreInteractions(classVisitor);
+ verify(fieldAttributeAppender).apply(fieldVisitor, fieldDescription, valueFilter);
+ verifyNoMoreInteractions(fieldAttributeAppender);
+ verify(fieldVisitor).visitEnd();
+ verifyNoMoreInteractions(fieldVisitor);
+ }
+
+ @Test
+ public void testExplicitFieldEntryWritesFieldPartialApplication() throws Exception {
+ TypeWriter.FieldPool.Record record = new TypeWriter.FieldPool.Record.ForExplicitField(fieldAttributeAppender, defaultValue, fieldDescription);
+ record.apply(fieldVisitor, annotationValueFilterFactory);
+ verify(fieldAttributeAppender).apply(fieldVisitor, fieldDescription, valueFilter);
+ verifyNoMoreInteractions(fieldAttributeAppender);
+ verifyZeroInteractions(fieldVisitor);
+ }
+
+ @Test
+ public void testImplicitFieldEntryProperties() throws Exception {
+ TypeWriter.FieldPool.Record record = new TypeWriter.FieldPool.Record.ForImplicitField(fieldDescription);
+ assertThat(record.isImplicit(), is(true));
+ }
+
+ @Test
+ public void testImplicitFieldEntryWritesField() throws Exception {
+ TypeWriter.FieldPool.Record record = new TypeWriter.FieldPool.Record.ForImplicitField(fieldDescription);
+ record.apply(classVisitor, annotationValueFilterFactory);
+ verify(classVisitor).visitField(MODIFIER, FOO, BAR, QUX, FieldDescription.NO_DEFAULT_VALUE);
+ verifyNoMoreInteractions(classVisitor);
+ verify(fieldVisitor).visitAnnotation(BAZ, true);
+ verify(fieldVisitor).visitEnd();
+ verifyNoMoreInteractions(fieldVisitor);
+ verify(annotationVisitor).visitEnd();
+ verifyNoMoreInteractions(annotationVisitor);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testImplicitFieldWritesFieldPartialApplication() throws Exception {
+ new TypeWriter.FieldPool.Record.ForImplicitField(fieldDescription).apply(fieldVisitor, annotationValueFilterFactory);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testImplicitFieldEntryAppliedToField() throws Exception {
+ new TypeWriter.FieldPool.Record.ForImplicitField(fieldDescription).apply(fieldVisitor, annotationValueFilterFactory);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeWriter.FieldPool.Record.ForImplicitField.class).apply();
+ ObjectPropertyAssertion.of(TypeWriter.FieldPool.Record.ForExplicitField.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/TypeWriterInitializerRemapperTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/TypeWriterInitializerRemapperTest.java
new file mode 100644
index 0000000..3b6aed5
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/TypeWriterInitializerRemapperTest.java
@@ -0,0 +1,113 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.StubMethod;
+import net.bytebuddy.test.utility.DebuggingWrapper;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Arrays;
+import java.util.Collection;
+
+import static net.bytebuddy.matcher.ElementMatchers.isTypeInitializer;
+
+ at RunWith(Parameterized.class)
+public class TypeWriterInitializerRemapperTest {
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {NoInitializer.class},
+ {BranchingInitializer.class},
+ });
+ }
+
+ private final Class<?> type;
+
+ public TypeWriterInitializerRemapperTest(Class<?> type) {
+ this.type = type;
+ }
+
+ @Test
+ public void testNoInitializerWithEnabledContext() throws Exception {
+ ClassLoader classLoader = new URLClassLoader(new URL[0], ClassLoadingStrategy.BOOTSTRAP_LOADER);
+ Class.forName(new ByteBuddy()
+ .redefine(type)
+ .make()
+ .load(classLoader)
+ .getLoaded().getName(), true, classLoader);
+ }
+
+ @Test
+ public void testNoInitializerWithDisabledContext() throws Exception {
+ ClassLoader classLoader = new URLClassLoader(new URL[0], ClassLoadingStrategy.BOOTSTRAP_LOADER);
+ Class.forName(new ByteBuddy()
+ .with(Implementation.Context.Disabled.Factory.INSTANCE)
+ .redefine(type)
+ .make()
+ .load(classLoader)
+ .getLoaded().getName(), true, classLoader);
+ }
+
+ @Test
+ public void testInitializerWithEnabledContext() throws Exception {
+ ClassLoader classLoader = new URLClassLoader(new URL[0], ClassLoadingStrategy.BOOTSTRAP_LOADER);
+ Class.forName(new ByteBuddy()
+ .redefine(type)
+ .invokable(isTypeInitializer()).intercept(StubMethod.INSTANCE)
+ .make()
+ .load(classLoader)
+ .getLoaded().getName(), true, classLoader);
+ }
+
+ @Test
+ public void testInitializerWithDisabledContext() throws Exception {
+ ClassLoader classLoader = new URLClassLoader(new URL[0], ClassLoadingStrategy.BOOTSTRAP_LOADER);
+ Class.forName(new ByteBuddy()
+ .with(Implementation.Context.Disabled.Factory.INSTANCE)
+ .redefine(type)
+ .invokable(isTypeInitializer()).intercept(StubMethod.INSTANCE)
+ .make()
+ .load(classLoader)
+ .getLoaded().getName(), true, classLoader);
+ }
+
+ private static class NoInitializer {
+ /* empty */
+ }
+
+ private static class BranchingInitializer {
+
+ static {
+ int ignored = 0;
+ {
+ long v1 = 1L, v2 = 2L, v3 = 3L;
+ if (ignored == 1) {
+ throw new AssertionError();
+ } else if (ignored == 2) {
+ if (v1 + v2 + v3 == 0L) {
+ throw new AssertionError();
+ }
+ }
+ }
+ long v4 = 4L, v5 = 5L, v6 = 6L, v7 = 7L;
+ if (ignored == 3) {
+ throw new AssertionError();
+ } else if (ignored == 4) {
+ if (v4 + v5 + v6 + v7 == 0L) {
+ throw new AssertionError();
+ }
+ }
+ try {
+ long v8 = 8L;
+ } catch (Exception exception) {
+ long v9 = 9L;
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/TypeWriterMethodPoolRecordTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/TypeWriterMethodPoolRecordTest.java
new file mode 100644
index 0000000..c24784b
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/TypeWriterMethodPoolRecordTest.java
@@ -0,0 +1,550 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.description.annotation.AnnotationValue;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.attribute.AnnotationValueFilter;
+import net.bytebuddy.implementation.attribute.MethodAttributeAppender;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.implementation.bytecode.constant.DefaultValue;
+import net.bytebuddy.implementation.bytecode.member.MethodReturn;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class TypeWriterMethodPoolRecordTest {
+
+ private static final int MODIFIERS = 42, ONE = 1, TWO = 2, MULTIPLIER = 4;
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux", BAZ = "baz";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodAttributeAppender methodAttributeAppender;
+
+ @Mock
+ private ClassVisitor classVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private AnnotationVisitor annotationVisitor;
+
+ @Mock
+ private MethodDescription methodDescription, bridgeTarget;
+
+ @Mock
+ private TypeDescription superClass;
+
+ @Mock
+ private ByteCodeAppender byteCodeAppender, otherAppender;
+
+ @Mock
+ private TypeList.Generic exceptionTypes;
+
+ @Mock
+ private TypeList rawExceptionTypes;
+
+ @Mock
+ private ParameterDescription parameterDescription;
+
+ @Mock
+ private TypeWriter.MethodPool.Record delegate;
+
+ @Mock
+ private TypeDescription instrumentedType;
+
+ @Mock
+ private MethodDescription.TypeToken typeToken;
+
+ @Mock
+ private AnnotationValueFilter annotationValueFilter;
+
+ @Mock
+ private AnnotationValueFilter.Factory annotationValueFilterFactory;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ when(methodDescription.getInternalName()).thenReturn(FOO);
+ when(methodDescription.getDescriptor()).thenReturn(BAR);
+ when(methodDescription.getGenericSignature()).thenReturn(QUX);
+ when(methodDescription.getExceptionTypes()).thenReturn(exceptionTypes);
+ when(methodDescription.getActualModifiers(anyBoolean(), any(Visibility.class))).thenReturn(MODIFIERS);
+ when(exceptionTypes.asErasures()).thenReturn(rawExceptionTypes);
+ when(rawExceptionTypes.toInternalNames()).thenReturn(new String[]{BAZ});
+ when(classVisitor.visitMethod(MODIFIERS, FOO, BAR, QUX, new String[]{BAZ})).thenReturn(methodVisitor);
+ when(methodDescription.getParameters()).thenReturn((ParameterList) new ParameterList.Explicit<ParameterDescription>(parameterDescription));
+ when(parameterDescription.getName()).thenReturn(FOO);
+ when(parameterDescription.getModifiers()).thenReturn(MODIFIERS);
+ when(methodVisitor.visitAnnotationDefault()).thenReturn(annotationVisitor);
+ when(byteCodeAppender.apply(methodVisitor, implementationContext, methodDescription))
+ .thenReturn(new ByteCodeAppender.Size(ONE, TWO));
+ when(otherAppender.apply(methodVisitor, implementationContext, methodDescription))
+ .thenReturn(new ByteCodeAppender.Size(ONE * MULTIPLIER, TWO * MULTIPLIER));
+ when(annotationValueFilterFactory.on(methodDescription)).thenReturn(annotationValueFilter);
+ when(methodDescription.getVisibility()).thenReturn(Visibility.PUBLIC);
+ }
+
+ @Test
+ public void testSkippedMethod() throws Exception {
+ TypeWriter.MethodPool.Record record = new TypeWriter.MethodPool.Record.ForNonImplementedMethod(methodDescription);
+ assertThat(record.getSort(), is(TypeWriter.MethodPool.Record.Sort.SKIPPED));
+ assertThat(record.getMethod(), is(methodDescription));
+ record.apply(classVisitor, implementationContext, annotationValueFilterFactory);
+ verifyZeroInteractions(classVisitor);
+ verifyZeroInteractions(implementationContext);
+ verifyZeroInteractions(methodAttributeAppender);
+ }
+
+ @Test
+ public void testSkippedMethodCannotBePrepended() throws Exception {
+ when(methodDescription.getReturnType()).thenReturn(TypeDescription.Generic.OBJECT);
+ assertThat(new TypeWriter.MethodPool.Record.ForNonImplementedMethod(methodDescription).prepend(byteCodeAppender), is((TypeWriter.MethodPool.Record)
+ new TypeWriter.MethodPool.Record.ForDefinedMethod.WithBody(methodDescription,
+ new ByteCodeAppender.Compound(byteCodeAppender, new ByteCodeAppender.Simple(DefaultValue.REFERENCE, MethodReturn.REFERENCE)))));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testSkippedMethodCannotApplyBody() throws Exception {
+ new TypeWriter.MethodPool.Record.ForNonImplementedMethod(methodDescription).applyBody(methodVisitor, implementationContext, annotationValueFilterFactory);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testSkippedMethodCannotApplyHead() throws Exception {
+ new TypeWriter.MethodPool.Record.ForNonImplementedMethod(methodDescription).applyHead(methodVisitor);
+ }
+
+ @Test
+ public void testDefinedMethod() throws Exception {
+ TypeWriter.MethodPool.Record record = new TypeWriter.MethodPool.Record.ForDefinedMethod.WithoutBody(methodDescription,
+ methodAttributeAppender,
+ Visibility.PUBLIC);
+ assertThat(record.getSort(), is(TypeWriter.MethodPool.Record.Sort.DEFINED));
+ record.apply(classVisitor, implementationContext, annotationValueFilterFactory);
+ verify(classVisitor).visitMethod(MODIFIERS, FOO, BAR, QUX, new String[]{BAZ});
+ verifyNoMoreInteractions(classVisitor);
+ verify(methodVisitor).visitEnd();
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ verify(methodAttributeAppender).apply(methodVisitor, methodDescription, annotationValueFilter);
+ verifyNoMoreInteractions(methodAttributeAppender);
+ }
+
+ @Test
+ public void testDefinedMethodHeadOnly() throws Exception {
+ when(parameterDescription.hasModifiers()).thenReturn(true);
+ when(parameterDescription.isNamed()).thenReturn(true);
+ TypeWriter.MethodPool.Record record = new TypeWriter.MethodPool.Record.ForDefinedMethod.WithoutBody(methodDescription,
+ methodAttributeAppender,
+ Visibility.PUBLIC);
+ record.applyHead(methodVisitor);
+ verifyZeroInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ verifyZeroInteractions(methodAttributeAppender);
+ }
+
+ @Test
+ public void testDefinedMethodBodyOnly() throws Exception {
+ when(parameterDescription.hasModifiers()).thenReturn(true);
+ when(parameterDescription.isNamed()).thenReturn(true);
+ TypeWriter.MethodPool.Record record = new TypeWriter.MethodPool.Record.ForDefinedMethod.WithoutBody(methodDescription,
+ methodAttributeAppender,
+ Visibility.PUBLIC);
+ record.applyBody(methodVisitor, implementationContext, annotationValueFilterFactory);
+ verifyZeroInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ verify(methodAttributeAppender).apply(methodVisitor, methodDescription, annotationValueFilter);
+ }
+
+ @Test
+ public void testDefinedMethodWithParameters() throws Exception {
+ when(parameterDescription.hasModifiers()).thenReturn(true);
+ when(parameterDescription.isNamed()).thenReturn(true);
+ TypeWriter.MethodPool.Record record = new TypeWriter.MethodPool.Record.ForDefinedMethod.WithoutBody(methodDescription,
+ methodAttributeAppender,
+ Visibility.PUBLIC);
+ assertThat(record.getSort(), is(TypeWriter.MethodPool.Record.Sort.DEFINED));
+ record.apply(classVisitor, implementationContext, annotationValueFilterFactory);
+ verify(classVisitor).visitMethod(MODIFIERS, FOO, BAR, QUX, new String[]{BAZ});
+ verifyNoMoreInteractions(classVisitor);
+ verify(methodVisitor).visitParameter(FOO, MODIFIERS);
+ verify(methodVisitor).visitEnd();
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ verify(methodAttributeAppender).apply(methodVisitor, methodDescription, annotationValueFilter);
+ verifyNoMoreInteractions(methodAttributeAppender);
+ }
+
+ @Test
+ public void testDefinedMethodApplyAttributes() throws Exception {
+ new TypeWriter.MethodPool.Record.ForDefinedMethod.WithoutBody(methodDescription,
+ methodAttributeAppender,
+ Visibility.PUBLIC).applyAttributes(methodVisitor, annotationValueFilterFactory);
+ verifyZeroInteractions(methodVisitor);
+ verify(methodAttributeAppender).apply(methodVisitor, methodDescription, annotationValueFilter);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testDefinedMethodApplyCode() throws Exception {
+ new TypeWriter.MethodPool.Record.ForDefinedMethod.WithoutBody(methodDescription,
+ methodAttributeAppender,
+ Visibility.PUBLIC).applyCode(methodVisitor, implementationContext);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testDefinedMethodPrepended() throws Exception {
+ new TypeWriter.MethodPool.Record.ForDefinedMethod.WithoutBody(methodDescription,
+ methodAttributeAppender,
+ Visibility.PUBLIC).prepend(otherAppender);
+ }
+
+ @Test
+ public void testDefaultValueMethod() throws Exception {
+ when(methodDescription.getReturnType()).thenReturn(new TypeDescription.Generic.OfNonGenericType.ForLoadedType(String.class));
+ when(methodDescription.isDefaultValue(AnnotationValue.ForConstant.of(FOO))).thenReturn(true);
+ TypeWriter.MethodPool.Record record = new TypeWriter.MethodPool.Record.ForDefinedMethod.WithAnnotationDefaultValue(methodDescription,
+ AnnotationValue.ForConstant.of(FOO),
+ methodAttributeAppender);
+ assertThat(record.getSort(), is(TypeWriter.MethodPool.Record.Sort.DEFINED));
+ record.apply(classVisitor, implementationContext, annotationValueFilterFactory);
+ verify(classVisitor).visitMethod(MODIFIERS, FOO, BAR, QUX, new String[]{BAZ});
+ verifyNoMoreInteractions(classVisitor);
+ verify(methodVisitor).visitAnnotationDefault();
+ verify(methodVisitor).visitEnd();
+ verifyNoMoreInteractions(methodVisitor);
+ verify(annotationVisitor).visit(null, FOO);
+ verify(annotationVisitor).visitEnd();
+ verifyNoMoreInteractions(annotationVisitor);
+ verifyZeroInteractions(implementationContext);
+ verify(methodAttributeAppender).apply(methodVisitor, methodDescription, annotationValueFilter);
+ verifyNoMoreInteractions(methodAttributeAppender);
+ }
+
+ @Test
+ public void testDefaultValueMethodHeadOnly() throws Exception {
+ when(methodDescription.getReturnType()).thenReturn(new TypeDescription.Generic.OfNonGenericType.ForLoadedType(String.class));
+ when(parameterDescription.hasModifiers()).thenReturn(true);
+ when(parameterDescription.isNamed()).thenReturn(true);
+ when(methodDescription.isDefaultValue(AnnotationValue.ForConstant.of(FOO))).thenReturn(true);
+ TypeWriter.MethodPool.Record record = new TypeWriter.MethodPool.Record.ForDefinedMethod.WithAnnotationDefaultValue(methodDescription,
+ AnnotationValue.ForConstant.of(FOO),
+ methodAttributeAppender);
+ record.applyHead(methodVisitor);
+ verify(methodVisitor).visitAnnotationDefault();
+ verifyNoMoreInteractions(methodVisitor);
+ verify(annotationVisitor).visit(null, FOO);
+ verify(annotationVisitor).visitEnd();
+ verifyNoMoreInteractions(annotationVisitor);
+ verifyZeroInteractions(implementationContext);
+ verifyZeroInteractions(methodAttributeAppender);
+ }
+
+ @Test
+ public void testDefaultValueMethodBodyOnly() throws Exception {
+ when(parameterDescription.hasModifiers()).thenReturn(true);
+ when(parameterDescription.isNamed()).thenReturn(true);
+ TypeWriter.MethodPool.Record record = new TypeWriter.MethodPool.Record.ForDefinedMethod.WithAnnotationDefaultValue(methodDescription,
+ AnnotationValue.ForConstant.of(FOO),
+ methodAttributeAppender);
+ record.applyBody(methodVisitor, implementationContext, annotationValueFilterFactory);
+ verifyZeroInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ verify(methodAttributeAppender).apply(methodVisitor, methodDescription, annotationValueFilter);
+ verifyNoMoreInteractions(methodAttributeAppender);
+ }
+
+ @Test
+ public void testDefaultValueMethodWithParameters() throws Exception {
+ when(parameterDescription.hasModifiers()).thenReturn(true);
+ when(parameterDescription.isNamed()).thenReturn(true);
+ when(methodDescription.getReturnType()).thenReturn(new TypeDescription.Generic.OfNonGenericType.ForLoadedType(String.class));
+ when(methodDescription.isDefaultValue(AnnotationValue.ForConstant.of(FOO))).thenReturn(true);
+ TypeWriter.MethodPool.Record record = new TypeWriter.MethodPool.Record.ForDefinedMethod.WithAnnotationDefaultValue(methodDescription,
+ AnnotationValue.ForConstant.of(FOO),
+ methodAttributeAppender);
+ assertThat(record.getSort(), is(TypeWriter.MethodPool.Record.Sort.DEFINED));
+ record.apply(classVisitor, implementationContext, annotationValueFilterFactory);
+ verify(classVisitor).visitMethod(MODIFIERS, FOO, BAR, QUX, new String[]{BAZ});
+ verifyNoMoreInteractions(classVisitor);
+ verify(methodVisitor).visitParameter(FOO, MODIFIERS);
+ verify(methodVisitor).visitAnnotationDefault();
+ verify(methodVisitor).visitEnd();
+ verifyNoMoreInteractions(methodVisitor);
+ verify(annotationVisitor).visit(null, FOO);
+ verify(annotationVisitor).visitEnd();
+ verifyNoMoreInteractions(annotationVisitor);
+ verifyZeroInteractions(implementationContext);
+ verify(methodAttributeAppender).apply(methodVisitor, methodDescription, annotationValueFilter);
+ verifyNoMoreInteractions(methodAttributeAppender);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testDefaultValueMethodApplyCode() throws Exception {
+ new TypeWriter.MethodPool.Record.ForDefinedMethod.WithAnnotationDefaultValue(methodDescription,
+ AnnotationValue.ForConstant.of(FOO),
+ methodAttributeAppender).applyCode(methodVisitor, implementationContext);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testDefaultValueMethodApplyAttributes() throws Exception {
+ new TypeWriter.MethodPool.Record.ForDefinedMethod.WithAnnotationDefaultValue(methodDescription,
+ AnnotationValue.ForConstant.of(FOO),
+ methodAttributeAppender).applyAttributes(methodVisitor, annotationValueFilterFactory);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testDefaultValueMethodPrepended() throws Exception {
+ new TypeWriter.MethodPool.Record.ForDefinedMethod.WithAnnotationDefaultValue(methodDescription,
+ AnnotationValue.ForConstant.of(FOO),
+ methodAttributeAppender).prepend(otherAppender);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNoDefaultValue() throws Exception {
+ when(methodDescription.isDefaultValue(AnnotationValue.ForConstant.of(FOO))).thenReturn(false);
+ new TypeWriter.MethodPool.Record.ForDefinedMethod.WithAnnotationDefaultValue(methodDescription,
+ AnnotationValue.ForConstant.of(FOO),
+ methodAttributeAppender).apply(classVisitor, implementationContext, annotationValueFilterFactory);
+ }
+
+ @Test
+ public void testImplementedMethod() throws Exception {
+ TypeWriter.MethodPool.Record record = new TypeWriter.MethodPool.Record.ForDefinedMethod.WithBody(methodDescription,
+ byteCodeAppender,
+ methodAttributeAppender,
+ Visibility.PUBLIC);
+ assertThat(record.getSort(), is(TypeWriter.MethodPool.Record.Sort.IMPLEMENTED));
+ record.apply(classVisitor, implementationContext, annotationValueFilterFactory);
+ verify(classVisitor).visitMethod(MODIFIERS, FOO, BAR, QUX, new String[]{BAZ});
+ verifyNoMoreInteractions(classVisitor);
+ verify(methodVisitor).visitCode();
+ verify(methodVisitor).visitMaxs(ONE, TWO);
+ verify(methodVisitor).visitEnd();
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ verify(methodAttributeAppender).apply(methodVisitor, methodDescription, annotationValueFilter);
+ verifyNoMoreInteractions(methodAttributeAppender);
+ verify(byteCodeAppender).apply(methodVisitor, implementationContext, methodDescription);
+ verifyNoMoreInteractions(byteCodeAppender);
+ }
+
+ @Test
+ public void testImplementedMethodHeadOnly() throws Exception {
+ when(parameterDescription.hasModifiers()).thenReturn(true);
+ when(parameterDescription.isNamed()).thenReturn(true);
+ TypeWriter.MethodPool.Record record = new TypeWriter.MethodPool.Record.ForDefinedMethod.WithBody(methodDescription,
+ byteCodeAppender,
+ methodAttributeAppender,
+ Visibility.PUBLIC);
+ record.applyHead(methodVisitor);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ verifyZeroInteractions(methodAttributeAppender);
+ verifyZeroInteractions(byteCodeAppender);
+ }
+
+ @Test
+ public void testImplementedMethodBodyOnly() throws Exception {
+ when(parameterDescription.hasModifiers()).thenReturn(true);
+ when(parameterDescription.isNamed()).thenReturn(true);
+ TypeWriter.MethodPool.Record record = new TypeWriter.MethodPool.Record.ForDefinedMethod.WithBody(methodDescription,
+ byteCodeAppender,
+ methodAttributeAppender,
+ Visibility.PUBLIC);
+ record.applyBody(methodVisitor, implementationContext, annotationValueFilterFactory);
+ verify(methodVisitor).visitCode();
+ verify(methodVisitor).visitMaxs(ONE, TWO);
+ verifyZeroInteractions(implementationContext);
+ verify(methodAttributeAppender).apply(methodVisitor, methodDescription, annotationValueFilter);
+ verifyNoMoreInteractions(methodAttributeAppender);
+ verify(byteCodeAppender).apply(methodVisitor, implementationContext, methodDescription);
+ verifyNoMoreInteractions(byteCodeAppender);
+ }
+
+ @Test
+ public void testImplementedMethodWithParameters() throws Exception {
+ when(parameterDescription.hasModifiers()).thenReturn(true);
+ when(parameterDescription.isNamed()).thenReturn(true);
+ TypeWriter.MethodPool.Record record = new TypeWriter.MethodPool.Record.ForDefinedMethod.WithBody(methodDescription,
+ byteCodeAppender,
+ methodAttributeAppender,
+ Visibility.PUBLIC);
+ assertThat(record.getSort(), is(TypeWriter.MethodPool.Record.Sort.IMPLEMENTED));
+ record.apply(classVisitor, implementationContext, annotationValueFilterFactory);
+ verify(classVisitor).visitMethod(MODIFIERS, FOO, BAR, QUX, new String[]{BAZ});
+ verifyNoMoreInteractions(classVisitor);
+ verify(methodVisitor).visitParameter(FOO, MODIFIERS);
+ verify(methodVisitor).visitCode();
+ verify(methodVisitor).visitMaxs(ONE, TWO);
+ verify(methodVisitor).visitEnd();
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ verify(methodAttributeAppender).apply(methodVisitor, methodDescription, annotationValueFilter);
+ verifyNoMoreInteractions(methodAttributeAppender);
+ verify(byteCodeAppender).apply(methodVisitor, implementationContext, methodDescription);
+ verifyNoMoreInteractions(byteCodeAppender);
+ }
+
+ @Test
+ public void testImplementedMethodPrepended() throws Exception {
+ TypeWriter.MethodPool.Record record = new TypeWriter.MethodPool.Record.ForDefinedMethod.WithBody(methodDescription,
+ byteCodeAppender,
+ methodAttributeAppender,
+ Visibility.PUBLIC)
+ .prepend(otherAppender);
+ assertThat(record.getSort(), is(TypeWriter.MethodPool.Record.Sort.IMPLEMENTED));
+ record.apply(classVisitor, implementationContext, annotationValueFilterFactory);
+ verify(classVisitor).visitMethod(MODIFIERS, FOO, BAR, QUX, new String[]{BAZ});
+ verifyNoMoreInteractions(classVisitor);
+ verify(methodVisitor).visitCode();
+ verify(methodVisitor).visitMaxs(ONE * MULTIPLIER, TWO * MULTIPLIER);
+ verify(methodVisitor).visitEnd();
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ verify(methodAttributeAppender).apply(methodVisitor, methodDescription, annotationValueFilter);
+ verifyNoMoreInteractions(methodAttributeAppender);
+ verify(byteCodeAppender).apply(methodVisitor, implementationContext, methodDescription);
+ verifyNoMoreInteractions(byteCodeAppender);
+ verify(otherAppender).apply(methodVisitor, implementationContext, methodDescription);
+ verifyNoMoreInteractions(otherAppender);
+ }
+
+ @Test
+ public void testVisibilityBridgeProperties() throws Exception {
+ TypeWriter.MethodPool.Record record = new TypeWriter.MethodPool.Record.ForDefinedMethod.OfVisibilityBridge(methodDescription,
+ bridgeTarget,
+ superClass,
+ methodAttributeAppender);
+ assertThat(record.getSort(), is(TypeWriter.MethodPool.Record.Sort.IMPLEMENTED));
+ }
+
+ @Test
+ public void testVisibilityBridgePrepending() throws Exception {
+ assertThat(new TypeWriter.MethodPool.Record.ForDefinedMethod.OfVisibilityBridge(methodDescription,
+ bridgeTarget,
+ superClass,
+ methodAttributeAppender).prepend(byteCodeAppender), instanceOf(TypeWriter.MethodPool.Record.ForDefinedMethod.WithBody.class));
+ }
+
+ @Test
+ public void testAccessorBridgeProperties() throws Exception {
+ when(delegate.getSort()).thenReturn(TypeWriter.MethodPool.Record.Sort.IMPLEMENTED);
+ TypeWriter.MethodPool.Record record = new TypeWriter.MethodPool.Record.AccessBridgeWrapper(delegate,
+ instrumentedType,
+ bridgeTarget,
+ Collections.singleton(typeToken),
+ methodAttributeAppender);
+ assertThat(record.getSort(), is(TypeWriter.MethodPool.Record.Sort.IMPLEMENTED));
+ }
+
+ @Test
+ public void testAccessorBridgeBodyApplication() throws Exception {
+ new TypeWriter.MethodPool.Record.AccessBridgeWrapper(delegate,
+ instrumentedType,
+ bridgeTarget,
+ Collections.singleton(typeToken),
+ methodAttributeAppender).applyBody(methodVisitor, implementationContext, annotationValueFilterFactory);
+ verify(delegate).applyBody(methodVisitor, implementationContext, annotationValueFilterFactory);
+ verifyNoMoreInteractions(delegate);
+ verifyZeroInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testAccessorBridgeHeadApplication() throws Exception {
+ new TypeWriter.MethodPool.Record.AccessBridgeWrapper(delegate,
+ instrumentedType,
+ bridgeTarget,
+ Collections.singleton(typeToken),
+ methodAttributeAppender).applyHead(methodVisitor);
+ verify(delegate).applyHead(methodVisitor);
+ verifyNoMoreInteractions(delegate);
+ verifyZeroInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testAccessorBridgeAttributeApplication() throws Exception {
+ new TypeWriter.MethodPool.Record.AccessBridgeWrapper(delegate,
+ instrumentedType,
+ bridgeTarget,
+ Collections.singleton(typeToken),
+ methodAttributeAppender).applyAttributes(methodVisitor, annotationValueFilterFactory);
+ verify(delegate).applyAttributes(methodVisitor, annotationValueFilterFactory);
+ verifyNoMoreInteractions(delegate);
+ verifyZeroInteractions(methodVisitor);
+ verifyZeroInteractions(annotationValueFilterFactory);
+ }
+
+ @Test
+ public void testAccessorBridgeCodeApplication() throws Exception {
+ new TypeWriter.MethodPool.Record.AccessBridgeWrapper(delegate,
+ instrumentedType,
+ bridgeTarget,
+ Collections.singleton(typeToken),
+ methodAttributeAppender).applyCode(methodVisitor, implementationContext);
+ verify(delegate).applyCode(methodVisitor, implementationContext);
+ verifyNoMoreInteractions(delegate);
+ verifyZeroInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testAccessorBridgePrepending() throws Exception {
+ assertThat(new TypeWriter.MethodPool.Record.AccessBridgeWrapper(delegate,
+ instrumentedType,
+ bridgeTarget,
+ Collections.singleton(typeToken),
+ methodAttributeAppender).prepend(byteCodeAppender), instanceOf(TypeWriter.MethodPool.Record.AccessBridgeWrapper.class));
+ }
+
+ @Test
+ public void testAccessorBridgePrependingTakesDelegateVisibility() throws Exception {
+ Visibility visibility = Visibility.PUBLIC;
+ when(delegate.getVisibility()).thenReturn(visibility);
+ assertThat(new TypeWriter.MethodPool.Record.AccessBridgeWrapper(delegate,
+ instrumentedType,
+ bridgeTarget,
+ Collections.singleton(typeToken),
+ methodAttributeAppender).getVisibility(), is(visibility));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeWriter.MethodPool.Record.ForDefinedMethod.WithBody.class).refine(new ObjectPropertyAssertion.Refinement<MethodDescription>() {
+ @Override
+ public void apply(MethodDescription mock) {
+ when(mock.getVisibility()).thenReturn(Visibility.PUBLIC);
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(TypeWriter.MethodPool.Record.ForDefinedMethod.OfVisibilityBridge.class).apply();
+ ObjectPropertyAssertion.of(TypeWriter.MethodPool.Record.ForDefinedMethod.WithoutBody.class).apply();
+ ObjectPropertyAssertion.of(TypeWriter.MethodPool.Record.ForDefinedMethod.WithAnnotationDefaultValue.class).apply();
+ ObjectPropertyAssertion.of(TypeWriter.MethodPool.Record.ForNonImplementedMethod.class).apply();
+ ObjectPropertyAssertion.of(TypeWriter.MethodPool.Record.AccessBridgeWrapper.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/TypeWriterModifierPreservationTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/TypeWriterModifierPreservationTest.java
new file mode 100644
index 0000000..7296a64
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/TypeWriterModifierPreservationTest.java
@@ -0,0 +1,145 @@
+package net.bytebuddy.dynamic.scaffold;
+
+import net.bytebuddy.ByteBuddy;
+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.dynamic.ClassFileLocator;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.pool.TypePool;
+import net.bytebuddy.test.scope.EnclosingType;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+ at RunWith(Parameterized.class)
+public class TypeWriterModifierPreservationTest {
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+// {Object.class},
+// {String.class},
+// {EnclosingType.class},
+// {new EnclosingType().localMethod},
+// {new EnclosingType().anonymousMethod},
+// {new EnclosingType().localConstructor},
+// {new EnclosingType().anonymousConstructor},
+// {EnclosingType.LOCAL_INITIALIZER},
+ {EnclosingType.ANONYMOUS_INITIALIZER},
+// {EnclosingType.LOCAL_METHOD},
+ {EnclosingType.ANONYMOUS_METHOD},
+// {EnclosingType.INNER},
+// {EnclosingType.NESTED},
+// {EnclosingType.PRIVATE_INNER},
+// {EnclosingType.PRIVATE_NESTED},
+// {EnclosingType.PROTECTED_INNER},
+// {EnclosingType.PROTECTED_NESTED},
+// {EnclosingType.PACKAGE_INNER},
+// {EnclosingType.PACKAGE_NESTED},
+// {EnclosingType.FINAL_NESTED},
+// {EnclosingType.FINAL_INNER},
+// {EnclosingType.DEPRECATED}
+ });
+ }
+
+ private final Class<?> type;
+
+ public TypeWriterModifierPreservationTest(Class<?> type) {
+ this.type = type;
+ }
+
+ @Test
+ public void testModifiers() throws Exception {
+ TypeModifierExtractor typeModifierExtractor = new TypeModifierExtractor();
+ new ClassReader(ClassFileLocator.ForClassLoader.read(type).resolve()).accept(typeModifierExtractor, 0);
+ new ByteBuddy()
+ .redefine(type)
+ .visit(new TypeValidator.Wrapper(typeModifierExtractor))
+ .make();
+ }
+
+ private static class TypeModifierExtractor extends ClassVisitor {
+
+ private String name;
+
+ public int modifiers, inner;
+
+ public TypeModifierExtractor() {
+ super(Opcodes.ASM5);
+ }
+
+ @Override
+ public void visit(int version, int modifiers, String name, String signature, String superName, String[] interfaceName) {
+ this.modifiers = modifiers;
+ this.name = name;
+ }
+
+ @Override
+ public void visitInnerClass(String name, String outerName, String innerName, int modifiers) {
+ if (name.equals(this.name)) {
+ inner = modifiers;
+ }
+ }
+ }
+
+ private static class TypeValidator extends ClassVisitor {
+
+ private String name;
+
+ public final int modifiers, inner;
+
+ public TypeValidator(ClassVisitor classVisitor, int modifiers, int inner) {
+ super(Opcodes.ASM5, classVisitor);
+ this.modifiers = modifiers;
+ this.inner = inner;
+ }
+
+ @Override
+ public void visit(int version, int modifiers, String name, String signature, String superName, String[] interfaceName) {
+ this.name = name;
+ if (modifiers != this.modifiers) {
+ throw new AssertionError("Unexpected modifiers: Observed " + modifiers + " instead of " + this.modifiers);
+ }
+ super.visit(version, modifiers, name, signature, superName, interfaceName);
+ }
+
+ @Override
+ public void visitInnerClass(String name, String outerName, String innerName, int modifiers) {
+ if (name.equals(this.name) && modifiers != inner) {
+ throw new AssertionError("Unexpected inner modifiers: Observed " + modifiers + " instead of " + inner);
+ }
+ super.visitInnerClass(name, outerName, innerName, modifiers);
+ }
+
+ private static class Wrapper extends AsmVisitorWrapper.AbstractBase {
+
+ public final int modifiers, inner;
+
+ public Wrapper(TypeModifierExtractor typeModifierExtractor) {
+ modifiers = typeModifierExtractor.modifiers;
+ inner = typeModifierExtractor.inner;
+ }
+
+ @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 TypeValidator(classVisitor, modifiers, inner);
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/AbstractDynamicTypeBuilderForInliningTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/AbstractDynamicTypeBuilderForInliningTest.java
new file mode 100644
index 0000000..f8c1b47
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/AbstractDynamicTypeBuilderForInliningTest.java
@@ -0,0 +1,613 @@
+package net.bytebuddy.dynamic.scaffold.inline;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.asm.AsmVisitorWrapper;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.modifier.MethodManifestation;
+import net.bytebuddy.description.modifier.TypeManifestation;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.AbstractDynamicTypeBuilderTest;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.Transformer;
+import net.bytebuddy.dynamic.loading.ByteArrayClassLoader;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.dynamic.loading.PackageDefinitionStrategy;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.MethodCall;
+import net.bytebuddy.implementation.StubMethod;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.implementation.bytecode.constant.TextConstant;
+import net.bytebuddy.implementation.bytecode.member.MethodReturn;
+import net.bytebuddy.matcher.ElementMatchers;
+import net.bytebuddy.pool.TypePool;
+import net.bytebuddy.test.scope.GenericType;
+import net.bytebuddy.test.utility.ClassFileExtraction;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.objectweb.asm.*;
+import org.objectweb.asm.commons.LocalVariablesSorter;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.*;
+import java.lang.reflect.Type;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.ProtectionDomain;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+import static junit.framework.TestCase.assertEquals;
+import static net.bytebuddy.matcher.ElementMatchers.isTypeInitializer;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+public abstract class AbstractDynamicTypeBuilderForInliningTest extends AbstractDynamicTypeBuilderTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ private static final int QUX = 42;
+
+ private static final String PARAMETER_NAME_CLASS = "net.bytebuddy.test.precompiled.ParameterNames";
+
+ private static final String SIMPLE_TYPE_ANNOTATED = "net.bytebuddy.test.precompiled.SimpleTypeAnnotatedType";
+
+ private static final String TYPE_VARIABLE_NAME = "net.bytebuddy.test.precompiled.TypeAnnotation", VALUE = "value";
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ private TypePool typePool;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ typePool = TypePool.Default.ofClassPath();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ typePool.clear();
+ }
+
+ protected abstract DynamicType.Builder<?> create(Class<?> type);
+
+ protected abstract DynamicType.Builder<?> create(TypeDescription typeDescription, ClassFileLocator classFileLocator);
+
+ protected abstract DynamicType.Builder<?> createDisabledContext();
+
+ protected abstract DynamicType.Builder<?> createDisabledRetention(Class<?> annotatedClass);
+
+ @Test
+ public void testTypeInitializerRetention() throws Exception {
+ Class<?> type = create(Qux.class)
+ .invokable(isTypeInitializer()).intercept(MethodCall.invoke(Qux.class.getDeclaredMethod("invoke")))
+ .make()
+ .load(new URLClassLoader(new URL[0], null), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredConstructor().newInstance(), notNullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).get(null), is((Object) FOO));
+ assertThat(type.getDeclaredField(BAR).get(null), is((Object) BAR));
+ }
+
+ @Test
+ public void testDefaultValue() throws Exception {
+ Class<?> dynamicType = create(Baz.class)
+ .method(named(FOO)).defaultValue(FOO, String.class)
+ .make()
+ .load(new URLClassLoader(new URL[0], null), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(dynamicType.getDeclaredMethods().length, is(1));
+ assertThat(dynamicType.getDeclaredMethod(FOO).getDefaultValue(), is((Object) FOO));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testParameterMetaDataRetention() throws Exception {
+ Class<?> dynamicType = create(Class.forName(PARAMETER_NAME_CLASS))
+ .method(named(FOO)).intercept(StubMethod.INSTANCE)
+ .make()
+ .load(new URLClassLoader(new URL[0], null), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Class<?> executable = Class.forName("java.lang.reflect.Executable");
+ Method getParameters = executable.getDeclaredMethod("getParameters");
+ Class<?> parameter = Class.forName("java.lang.reflect.Parameter");
+ Method getName = parameter.getDeclaredMethod("getName");
+ Method getModifiers = parameter.getDeclaredMethod("getModifiers");
+ Method first = dynamicType.getDeclaredMethod("foo", String.class, long.class, int.class);
+ Object[] methodParameter = (Object[]) getParameters.invoke(first);
+ assertThat(getName.invoke(methodParameter[0]), is((Object) "first"));
+ assertThat(getName.invoke(methodParameter[1]), is((Object) "second"));
+ assertThat(getName.invoke(methodParameter[2]), is((Object) "third"));
+ assertThat(getModifiers.invoke(methodParameter[0]), is((Object) Opcodes.ACC_FINAL));
+ assertThat(getModifiers.invoke(methodParameter[1]), is((Object) 0));
+ assertThat(getModifiers.invoke(methodParameter[2]), is((Object) 0));
+ }
+
+ @Test
+ public void testGenericType() throws Exception {
+ ClassLoader classLoader = new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassFileExtraction.of(GenericType.class));
+ Class<?> dynamicType = create(GenericType.Inner.class)
+ .method(named(FOO)).intercept(StubMethod.INSTANCE)
+ .make()
+ .load(classLoader, ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded();
+ assertThat(dynamicType.getTypeParameters().length, is(2));
+ assertThat(dynamicType.getTypeParameters()[0].getName(), is("T"));
+ assertThat(dynamicType.getTypeParameters()[0].getBounds().length, is(1));
+ assertThat(dynamicType.getTypeParameters()[0].getBounds()[0], instanceOf(Class.class));
+ assertThat(dynamicType.getTypeParameters()[0].getBounds()[0], is((Type) String.class));
+ assertThat(dynamicType.getTypeParameters()[1].getName(), is("S"));
+ assertThat(dynamicType.getTypeParameters()[1].getBounds().length, is(1));
+ assertThat(dynamicType.getTypeParameters()[1].getBounds()[0], is((Type) dynamicType.getTypeParameters()[0]));
+ assertThat(dynamicType.getGenericSuperclass(), instanceOf(ParameterizedType.class));
+ assertThat(((ParameterizedType) dynamicType.getGenericSuperclass()).getActualTypeArguments().length, is(1));
+ assertThat(((ParameterizedType) dynamicType.getGenericSuperclass()).getActualTypeArguments()[0], instanceOf(ParameterizedType.class));
+ ParameterizedType superClass = (ParameterizedType) ((ParameterizedType) dynamicType.getGenericSuperclass()).getActualTypeArguments()[0];
+ assertThat(superClass.getActualTypeArguments().length, is(2));
+ assertThat(superClass.getActualTypeArguments()[0], is((Type) dynamicType.getTypeParameters()[0]));
+ assertThat(superClass.getActualTypeArguments()[1], is((Type) dynamicType.getTypeParameters()[1]));
+ assertThat(superClass.getOwnerType(), instanceOf(ParameterizedType.class));
+ assertThat(((ParameterizedType) superClass.getOwnerType()).getRawType(), instanceOf(Class.class));
+ assertThat(((Class<?>) ((ParameterizedType) superClass.getOwnerType()).getRawType()).getName(), is(GenericType.class.getName()));
+ assertThat(((ParameterizedType) superClass.getOwnerType()).getActualTypeArguments().length, is(1));
+ assertThat(((ParameterizedType) superClass.getOwnerType()).getActualTypeArguments()[0],
+ is((Type) ((Class<?>) ((ParameterizedType) superClass.getOwnerType()).getRawType()).getTypeParameters()[0]));
+ assertThat(dynamicType.getGenericInterfaces().length, is(1));
+ assertThat(dynamicType.getGenericInterfaces()[0], instanceOf(ParameterizedType.class));
+ assertThat(((ParameterizedType) dynamicType.getGenericInterfaces()[0]).getActualTypeArguments()[0], instanceOf(ParameterizedType.class));
+ assertThat(((ParameterizedType) dynamicType.getGenericInterfaces()[0]).getRawType(), is((Type) Callable.class));
+ assertThat(((ParameterizedType) dynamicType.getGenericInterfaces()[0]).getOwnerType(), nullValue(Type.class));
+ assertThat(((ParameterizedType) ((ParameterizedType) dynamicType.getGenericInterfaces()[0]).getActualTypeArguments()[0])
+ .getActualTypeArguments().length, is(2));
+ ParameterizedType interfaceType = (ParameterizedType) ((ParameterizedType) dynamicType.getGenericInterfaces()[0]).getActualTypeArguments()[0];
+ assertThat(interfaceType.getRawType(), is((Type) Map.class));
+ assertThat(interfaceType.getActualTypeArguments().length, is(2));
+ assertThat(interfaceType.getActualTypeArguments()[0], instanceOf(WildcardType.class));
+ assertThat(((WildcardType) interfaceType.getActualTypeArguments()[0]).getUpperBounds().length, is(1));
+ assertThat(((WildcardType) interfaceType.getActualTypeArguments()[0]).getUpperBounds()[0], is((Type) Object.class));
+ assertThat(((WildcardType) interfaceType.getActualTypeArguments()[0]).getLowerBounds().length, is(1));
+ assertThat(((WildcardType) interfaceType.getActualTypeArguments()[0]).getLowerBounds()[0], is((Type) String.class));
+ assertThat(interfaceType.getActualTypeArguments()[1], instanceOf(WildcardType.class));
+ assertThat(((WildcardType) interfaceType.getActualTypeArguments()[1]).getUpperBounds().length, is(1));
+ assertThat(((WildcardType) interfaceType.getActualTypeArguments()[1]).getUpperBounds()[0], is((Type) String.class));
+ assertThat(((WildcardType) interfaceType.getActualTypeArguments()[1]).getLowerBounds().length, is(0));
+ Method foo = dynamicType.getDeclaredMethod(FOO, String.class);
+ assertThat(foo.getGenericReturnType(), instanceOf(ParameterizedType.class));
+ assertThat(((ParameterizedType) foo.getGenericReturnType()).getActualTypeArguments().length, is(1));
+ assertThat(((ParameterizedType) foo.getGenericReturnType()).getActualTypeArguments()[0], instanceOf(GenericArrayType.class));
+ assertThat(((GenericArrayType) ((ParameterizedType) foo.getGenericReturnType()).getActualTypeArguments()[0]).getGenericComponentType(),
+ is((Type) dynamicType.getTypeParameters()[0]));
+ assertThat(foo.getTypeParameters().length, is(2));
+ assertThat(foo.getTypeParameters()[0].getName(), is("V"));
+ assertThat(foo.getTypeParameters()[0].getBounds().length, is(1));
+ assertThat(foo.getTypeParameters()[0].getBounds()[0], is((Type) dynamicType.getTypeParameters()[0]));
+ assertThat(foo.getTypeParameters()[1].getName(), is("W"));
+ assertThat(foo.getTypeParameters()[1].getBounds().length, is(1));
+ assertThat(foo.getTypeParameters()[1].getBounds()[0], is((Type) Exception.class));
+ assertThat(foo.getGenericParameterTypes().length, is(1));
+ assertThat(foo.getGenericParameterTypes()[0], is((Type) foo.getTypeParameters()[0]));
+ assertThat(foo.getGenericExceptionTypes().length, is(1));
+ assertThat(foo.getGenericExceptionTypes()[0], is((Type) foo.getTypeParameters()[1]));
+ Method call = dynamicType.getDeclaredMethod("call");
+ assertThat(call.getGenericReturnType(), is((Type) interfaceType));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testBridgeMethodCreation() throws Exception {
+ Class<?> dynamicType = create(BridgeRetention.Inner.class)
+ .method(named(FOO)).intercept(new Implementation.Simple(new TextConstant(FOO), MethodReturn.REFERENCE))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded();
+ assertEquals(String.class, dynamicType.getDeclaredMethod(FOO).getReturnType());
+ assertThat(dynamicType.getDeclaredMethod(FOO).getGenericReturnType(), is((Type) String.class));
+ BridgeRetention<String> bridgeRetention = (BridgeRetention<String>) dynamicType.getDeclaredConstructor().newInstance();
+ assertThat(bridgeRetention.foo(), is(FOO));
+ bridgeRetention.assertZeroCalls();
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testBridgeMethodCreationForExistingBridgeMethod() throws Exception {
+ Class<?> dynamicType = create(CallSuperMethod.Inner.class)
+ .method(named(FOO)).intercept(net.bytebuddy.implementation.SuperMethodCall.INSTANCE)
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded();
+ assertThat(dynamicType.getDeclaredMethods().length, is(2));
+ assertEquals(String.class, dynamicType.getDeclaredMethod(FOO, String.class).getReturnType());
+ assertThat(dynamicType.getDeclaredMethod(FOO, String.class).getGenericReturnType(), is((Type) String.class));
+ assertThat(dynamicType.getDeclaredMethod(FOO, String.class).isBridge(), is(false));
+ assertEquals(Object.class, dynamicType.getDeclaredMethod(FOO, Object.class).getReturnType());
+ assertThat(dynamicType.getDeclaredMethod(FOO, Object.class).getGenericReturnType(), is((Type) Object.class));
+ assertThat(dynamicType.getDeclaredMethod(FOO, Object.class).isBridge(), is(true));
+ CallSuperMethod<String> callSuperMethod = (CallSuperMethod<String>) dynamicType.getDeclaredConstructor().newInstance();
+ assertThat(callSuperMethod.foo(FOO), is(FOO));
+ callSuperMethod.assertOnlyCall(FOO);
+ }
+
+ @Test
+ public void testBridgeMethodForAbstractMethod() throws Exception {
+ Class<?> dynamicType = create(AbstractGenericType.Inner.class)
+ .modifiers(Opcodes.ACC_ABSTRACT | Opcodes.ACC_PUBLIC)
+ .method(named(FOO)).withoutCode()
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded();
+ assertThat(dynamicType.getDeclaredMethods().length, is(2));
+ assertEquals(Void.class, dynamicType.getDeclaredMethod(FOO, Void.class).getReturnType());
+ assertThat(dynamicType.getDeclaredMethod(FOO, Void.class).getGenericReturnType(), is((Type) Void.class));
+ assertThat(dynamicType.getDeclaredMethod(FOO, Void.class).isBridge(), is(false));
+ assertThat(Modifier.isAbstract(dynamicType.getDeclaredMethod(FOO, Void.class).getModifiers()), is(true));
+ assertEquals(Object.class, dynamicType.getDeclaredMethod(FOO, Object.class).getReturnType());
+ assertThat(dynamicType.getDeclaredMethod(FOO, Object.class).getGenericReturnType(), is((Type) Object.class));
+ assertThat(dynamicType.getDeclaredMethod(FOO, Object.class).isBridge(), is(true));
+ assertThat(Modifier.isAbstract(dynamicType.getDeclaredMethod(FOO, Object.class).getModifiers()), is(false));
+ }
+
+ @Test
+ public void testVisibilityBridge() throws Exception {
+ ClassLoader classLoader = new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER,
+ ClassFileExtraction.of(PackagePrivateVisibilityBridgeExtension.class, VisibilityBridge.class, FooBar.class));
+ Class<?> type = create(PackagePrivateVisibilityBridgeExtension.class)
+ .modifiers(Opcodes.ACC_PUBLIC)
+ .make()
+ .load(classLoader, ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded();
+ assertThat(type.getDeclaredConstructors().length, is(1));
+ Constructor<?> constructor = type.getDeclaredConstructor();
+ constructor.setAccessible(true);
+ assertThat(type.getDeclaredMethods().length, is(2));
+ Method foo = type.getDeclaredMethod(FOO, String.class);
+ foo.setAccessible(true);
+ assertThat(foo.isBridge(), is(true));
+ assertThat(foo.getDeclaredAnnotations().length, is(1));
+ assertThat(foo.getDeclaredAnnotations()[0].annotationType().getName(), is(FooBar.class.getName()));
+ assertThat(foo.invoke(constructor.newInstance(), BAR), is((Object) (FOO + BAR)));
+ assertThat(foo.getParameterAnnotations()[0].length, is(1));
+ assertThat(foo.getParameterAnnotations()[0][0].annotationType().getName(), is(FooBar.class.getName()));
+ assertThat(foo.invoke(constructor.newInstance(), BAR), is((Object) (FOO + BAR)));
+ Method bar = type.getDeclaredMethod(BAR, List.class);
+ bar.setAccessible(true);
+ assertThat(bar.isBridge(), is(true));
+ assertThat(bar.getDeclaredAnnotations().length, is(0));
+ List<?> list = new ArrayList<Object>();
+ assertThat(bar.invoke(constructor.newInstance(), list), sameInstance((Object) list));
+ assertThat(bar.getGenericReturnType(), instanceOf(Class.class));
+ assertThat(bar.getGenericParameterTypes()[0], instanceOf(Class.class));
+ assertThat(bar.getGenericExceptionTypes()[0], instanceOf(Class.class));
+ }
+
+ @Test
+ public void testNoVisibilityBridgeForNonPublicType() throws Exception {
+ ClassLoader classLoader = new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER,
+ ClassFileExtraction.of(PackagePrivateVisibilityBridgeExtension.class, VisibilityBridge.class, FooBar.class));
+ Class<?> type = create(PackagePrivateVisibilityBridgeExtension.class)
+ .modifiers(0)
+ .make()
+ .load(classLoader, ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded();
+ assertThat(type.getDeclaredConstructors().length, is(1));
+ assertThat(type.getDeclaredMethods().length, is(0));
+ }
+
+ @Test
+ public void testNoVisibilityBridgeForInheritedType() throws Exception {
+ ClassLoader classLoader = new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER,
+ ClassFileExtraction.of(PublicVisibilityBridgeExtension.class, VisibilityBridge.class, FooBar.class));
+ Class<?> type = new ByteBuddy().subclass(PublicVisibilityBridgeExtension.class)
+ .modifiers(Opcodes.ACC_PUBLIC)
+ .make()
+ .load(classLoader, ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded();
+ assertThat(type.getDeclaredConstructors().length, is(1));
+ assertThat(type.getDeclaredMethods().length, is(0));
+ }
+
+ @Test
+ public void testNoVisibilityBridgeForAbstractMethod() throws Exception {
+ ClassLoader classLoader = new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER,
+ ClassFileExtraction.of(PackagePrivateVisibilityBridgeExtensionAbstractMethod.class, VisibilityBridgeAbstractMethod.class));
+ Class<?> type = create(PackagePrivateVisibilityBridgeExtensionAbstractMethod.class)
+ .modifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_ABSTRACT)
+ .make()
+ .load(classLoader, ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded();
+ assertThat(type.getDeclaredConstructors().length, is(1));
+ assertThat(type.getDeclaredMethods().length, is(0));
+ }
+
+ @Test
+ public void testMethodTransformationExistingMethod() throws Exception {
+ Class<?> type = create(Transform.class)
+ .method(named(FOO))
+ .intercept(new Implementation.Simple(new TextConstant(FOO), MethodReturn.REFERENCE))
+ .transform(Transformer.ForMethod.withModifiers(MethodManifestation.FINAL))
+ .make()
+ .load(new URLClassLoader(new URL[0], null), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Method foo = type.getDeclaredMethod(FOO);
+ assertThat(foo.invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ assertThat(foo.getModifiers(), is(Opcodes.ACC_FINAL | Opcodes.ACC_PUBLIC));
+ }
+
+ @Test
+ public void testFieldTransformationExistingField() throws Exception {
+ Class<?> type = create(Transform.class)
+ .field(named(FOO))
+ .transform(Transformer.ForField.withModifiers(Visibility.PUBLIC))
+ .make()
+ .load(new URLClassLoader(new URL[0], null), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredField(FOO).getModifiers(), is(Opcodes.ACC_PUBLIC));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testReaderHint() throws Exception {
+ AsmVisitorWrapper asmVisitorWrapper = mock(AsmVisitorWrapper.class);
+ when(asmVisitorWrapper.wrap(any(TypeDescription.class),
+ any(ClassVisitor.class),
+ any(Implementation.Context.class),
+ any(TypePool.class),
+ any(FieldList.class),
+ any(MethodList.class),
+ anyInt(),
+ anyInt())).then(new Answer<ClassVisitor>() {
+ @Override
+ public ClassVisitor answer(InvocationOnMock invocationOnMock) throws Throwable {
+ return new ClassVisitor(Opcodes.ASM5, (ClassVisitor) invocationOnMock.getArguments()[1]) {
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+ return new LocalVariablesSorter(access, desc, super.visitMethod(access, name, desc, signature, exceptions));
+ }
+ };
+ }
+ });
+ when(asmVisitorWrapper.mergeWriter(0)).thenReturn(ClassWriter.COMPUTE_MAXS);
+ when(asmVisitorWrapper.mergeReader(0)).thenReturn(ClassReader.EXPAND_FRAMES);
+ Class<?> type = create(StackMapFrames.class)
+ .visit(asmVisitorWrapper)
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(FOO).invoke(type.getDeclaredConstructor().newInstance()), is((Object) BAR));
+ verify(asmVisitorWrapper).mergeWriter(0);
+ verify(asmVisitorWrapper).mergeReader(0);
+ verify(asmVisitorWrapper).wrap(any(TypeDescription.class),
+ any(ClassVisitor.class),
+ any(Implementation.Context.class),
+ any(TypePool.class),
+ any(FieldList.class),
+ any(MethodList.class),
+ anyInt(),
+ anyInt());
+ verifyNoMoreInteractions(asmVisitorWrapper);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testForbidTypeInitilizerInterception() throws Exception {
+ createDisabledContext()
+ .initializer(new ByteCodeAppender.Simple())
+ .make();
+ }
+
+ @Test
+ public void testDisabledAnnotationRetention() throws Exception {
+ Class<?> type = createDisabledRetention(Annotated.class)
+ .field(ElementMatchers.any()).annotateField(new Annotation[0])
+ .method(ElementMatchers.any()).intercept(StubMethod.INSTANCE)
+ .make()
+ .load(new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER,
+ ClassFileExtraction.of(SampleAnnotation.class)), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ @SuppressWarnings("unchecked")
+ Class<? extends Annotation> sampleAnnotation = (Class<? extends Annotation>) type.getClassLoader().loadClass(SampleAnnotation.class.getName());
+ assertThat(type.isAnnotationPresent(sampleAnnotation), is(true));
+ assertThat(type.getDeclaredField(FOO).isAnnotationPresent(sampleAnnotation), is(false));
+ assertThat(type.getDeclaredMethod(FOO, Void.class).isAnnotationPresent(sampleAnnotation), is(false));
+ assertThat(type.getDeclaredMethod(FOO, Void.class).getParameterAnnotations()[0].length, is(0));
+ }
+
+ @Test
+ public void testEnabledAnnotationRetention() throws Exception {
+ Class<?> type = create(Annotated.class)
+ .field(ElementMatchers.any()).annotateField(new Annotation[0])
+ .method(ElementMatchers.any()).intercept(StubMethod.INSTANCE)
+ .make()
+ .load(new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER,
+ ClassFileExtraction.of(SampleAnnotation.class)), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ @SuppressWarnings("unchecked")
+ Class<? extends Annotation> sampleAnnotation = (Class<? extends Annotation>) type.getClassLoader().loadClass(SampleAnnotation.class.getName());
+ assertThat(type.isAnnotationPresent(sampleAnnotation), is(true));
+ assertThat(type.getDeclaredField(FOO).isAnnotationPresent(sampleAnnotation), is(true));
+ assertThat(type.getDeclaredMethod(FOO, Void.class).isAnnotationPresent(sampleAnnotation), is(true));
+ assertThat(type.getDeclaredMethod(FOO, Void.class).getParameterAnnotations()[0].length, is(1));
+ assertThat(type.getDeclaredMethod(FOO, Void.class).getParameterAnnotations()[0][0].annotationType(), is((Object) sampleAnnotation));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testAnnotationTypeOnInterfaceType() throws Exception {
+ Class<? extends Annotation> typeAnnotationType = (Class<? extends Annotation>) Class.forName(TYPE_VARIABLE_NAME);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotationType).getDeclaredMethods().filter(named(VALUE)).getOnly();
+ Class<?> type = create(Class.forName(SIMPLE_TYPE_ANNOTATED))
+ .merge(TypeManifestation.ABSTRACT)
+ .implement(TypeDescription.Generic.Builder.rawType(Callable.class)
+ .build(AnnotationDescription.Builder.ofType(typeAnnotationType).define(VALUE, QUX * 3).build()))
+ .make()
+ .load(typeAnnotationType.getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded();
+ assertThat(type.getInterfaces().length, is(2));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveInterfaceType(type, 0).asList().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveInterfaceType(type, 0).asList().ofType(typeAnnotationType)
+ .getValue(value).resolve(Integer.class), is(QUX * 2));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveInterfaceType(type, 1).asList().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveInterfaceType(type, 1).asList().ofType(typeAnnotationType)
+ .getValue(value).resolve(Integer.class), is(QUX * 3));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testAnnotationTypeOnTypeVariableType() throws Exception {
+ Class<? extends Annotation> typeAnnotationType = (Class<? extends Annotation>) Class.forName(TYPE_VARIABLE_NAME);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotationType).getDeclaredMethods().filter(named(VALUE)).getOnly();
+ Class<?> type = create(Class.forName(SIMPLE_TYPE_ANNOTATED))
+ .merge(TypeManifestation.ABSTRACT)
+ .typeVariable(BAR, TypeDescription.Generic.Builder.rawType(Callable.class)
+ .build(AnnotationDescription.Builder.ofType(typeAnnotationType).define(VALUE, QUX * 4).build()))
+ .annotateTypeVariable(AnnotationDescription.Builder.ofType(typeAnnotationType).define(VALUE, QUX * 3).build())
+ .make()
+ .load(typeAnnotationType.getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded();
+ assertThat(type.getTypeParameters().length, is(2));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(type.getTypeParameters()[0]).asList().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(type.getTypeParameters()[0]).asList().ofType(typeAnnotationType)
+ .getValue(value).resolve(Integer.class), is(QUX));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(type.getTypeParameters()[1]).asList().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(type.getTypeParameters()[1]).asList().ofType(typeAnnotationType)
+ .getValue(value).resolve(Integer.class), is(QUX * 3));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(type.getTypeParameters()[1]).ofTypeVariableBoundType(0)
+ .asList().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveTypeVariable(type.getTypeParameters()[1]).ofTypeVariableBoundType(0)
+ .asList().ofType(typeAnnotationType).getValue(value).resolve(Integer.class), is(QUX * 4));
+ }
+
+ public @interface Baz {
+
+ String foo();
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface FooBar {
+ /* empty */
+ }
+
+ public static class Qux {
+
+ public static final String foo;
+
+ public static String bar;
+
+ static {
+ foo = FOO;
+ }
+
+ public static void invoke() {
+ bar = BAR;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ static class VisibilityBridge {
+
+ @FooBar
+ public String foo(@FooBar String value) {
+ return FOO + value;
+ }
+
+ public <T extends Exception> List<String> bar(List<String> value) throws T {
+ return value;
+ }
+
+ void qux() {
+ /* empty */
+ }
+
+ protected void baz() {
+ /* empty */
+ }
+
+ public final void foobar() {
+ /* empty */
+ }
+ }
+
+ static class PackagePrivateVisibilityBridgeExtension extends VisibilityBridge {
+ /* empty */
+ }
+
+ public static class PublicVisibilityBridgeExtension extends VisibilityBridge {
+ /* empty */
+ }
+
+ abstract static class VisibilityBridgeAbstractMethod {
+
+ public abstract void foo();
+ }
+
+ abstract static class PackagePrivateVisibilityBridgeExtensionAbstractMethod extends VisibilityBridgeAbstractMethod {
+ /* empty */
+ }
+
+ @SuppressWarnings("unused")
+ public static class Transform {
+
+ Void foo;
+
+ public String foo() {
+ return null;
+ }
+ }
+
+ public abstract static class AbstractGenericType<T> {
+
+ public abstract T foo(T t);
+
+ public abstract static class Inner extends AbstractGenericType<Void> {
+ /* empty */
+ }
+ }
+
+ public static class StackMapFrames {
+
+ public boolean foo;
+
+ public String foo() {
+ return foo
+ ? FOO
+ : BAR;
+ }
+ }
+
+ @SampleAnnotation
+ @SuppressWarnings("unused")
+ public static class Annotated {
+
+ @SampleAnnotation
+ Void foo;
+
+ @SampleAnnotation
+ void foo(@SampleAnnotation Void v) {
+ /* empty */
+ }
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface SampleAnnotation {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/InlineImplementationMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/InlineImplementationMatcherTest.java
new file mode 100644
index 0000000..7301da1
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/InlineImplementationMatcherTest.java
@@ -0,0 +1,122 @@
+package net.bytebuddy.dynamic.scaffold.inline;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.matcher.LatentMatcher;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.objectweb.asm.Opcodes;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class InlineImplementationMatcherTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodDescription methodDescription;
+
+ @Mock
+ private TypeDescription rawTypeDescription, rawOtherType;
+
+ @Mock
+ private TypeDescription.Generic typeDescription, otherType;
+
+ @Mock
+ private LatentMatcher<? super MethodDescription> latentIgnoredMethods;
+
+ @Mock
+ private ElementMatcher<? super MethodDescription> predefinedMethods, ignoredMethods;
+
+ private LatentMatcher<MethodDescription> matcher;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ matcher = new InliningImplementationMatcher(latentIgnoredMethods, predefinedMethods);
+ when(rawTypeDescription.getSort()).thenReturn(TypeDefinition.Sort.NON_GENERIC);
+ when(rawTypeDescription.asGenericType()).thenReturn(typeDescription);
+ when(typeDescription.asErasure()).thenReturn(rawTypeDescription);
+ when(typeDescription.getSort()).thenReturn(TypeDefinition.Sort.NON_GENERIC);
+ when(otherType.asErasure()).thenReturn(rawOtherType);
+ when(otherType.getSort()).thenReturn(TypeDefinition.Sort.NON_GENERIC);
+ when(rawOtherType.asGenericType()).thenReturn(otherType);
+ when(latentIgnoredMethods.resolve(Mockito.any(TypeDescription.class))).thenReturn((ElementMatcher) ignoredMethods);
+ }
+
+ @Test
+ public void testMatchesVirtual() throws Exception {
+ when(methodDescription.isVirtual()).thenReturn(true);
+ when(methodDescription.getModifiers()).thenReturn(0);
+ when(ignoredMethods.matches(methodDescription)).thenReturn(false);
+ when(predefinedMethods.matches(methodDescription)).thenReturn(false);
+ when(methodDescription.getDeclaringType()).thenReturn(rawOtherType);
+ assertThat(matcher.resolve(rawTypeDescription).matches(methodDescription), is(true));
+ }
+
+ @Test
+ public void testNotMatchesVirtualAndFinal() throws Exception {
+ when(methodDescription.isVirtual()).thenReturn(true);
+ when(methodDescription.getModifiers()).thenReturn(Opcodes.ACC_FINAL);
+ when(ignoredMethods.matches(methodDescription)).thenReturn(false);
+ when(predefinedMethods.matches(methodDescription)).thenReturn(false);
+ when(methodDescription.getDeclaringType()).thenReturn(rawOtherType);
+ assertThat(matcher.resolve(rawTypeDescription).matches(methodDescription), is(false));
+ }
+
+ @Test
+ public void testMatchesDeclaredNotTargetType() throws Exception {
+ when(methodDescription.isVirtual()).thenReturn(false);
+ when(methodDescription.getModifiers()).thenReturn(Opcodes.ACC_FINAL);
+ when(ignoredMethods.matches(methodDescription)).thenReturn(false);
+ when(predefinedMethods.matches(methodDescription)).thenReturn(false);
+ when(methodDescription.getDeclaringType()).thenReturn(rawTypeDescription);
+ assertThat(matcher.resolve(rawTypeDescription).matches(methodDescription), is(true));
+ }
+
+ @Test
+ public void testMatchesDeclaredButIgnoredNotPredefined() throws Exception {
+ when(methodDescription.isVirtual()).thenReturn(false);
+ when(methodDescription.getModifiers()).thenReturn(Opcodes.ACC_FINAL);
+ when(ignoredMethods.matches(methodDescription)).thenReturn(true);
+ when(predefinedMethods.matches(methodDescription)).thenReturn(false);
+ when(methodDescription.getDeclaringType()).thenReturn(rawTypeDescription);
+ assertThat(matcher.resolve(rawTypeDescription).matches(methodDescription), is(true));
+ }
+
+ @Test
+ public void testMatchesDeclaredButIgnoredPredefined() throws Exception {
+ when(methodDescription.isVirtual()).thenReturn(false);
+ when(methodDescription.getModifiers()).thenReturn(Opcodes.ACC_FINAL);
+ when(ignoredMethods.matches(methodDescription)).thenReturn(true);
+ when(predefinedMethods.matches(methodDescription)).thenReturn(true);
+ when(methodDescription.getDeclaringType()).thenReturn(rawTypeDescription);
+ assertThat(matcher.resolve(rawTypeDescription).matches(methodDescription), is(false));
+ }
+
+ @Test
+ public void testNotMatchesOverridableIgnored() throws Exception {
+ when(methodDescription.isVirtual()).thenReturn(true);
+ when(methodDescription.getModifiers()).thenReturn(0);
+ when(ignoredMethods.matches(methodDescription)).thenReturn(true);
+ when(predefinedMethods.matches(methodDescription)).thenReturn(false);
+ when(methodDescription.getDeclaringType()).thenReturn(rawOtherType);
+ assertThat(matcher.resolve(rawTypeDescription).matches(methodDescription), is(false));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(InliningImplementationMatcher.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/MethodNameTransformerPrefixingTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/MethodNameTransformerPrefixingTest.java
new file mode 100644
index 0000000..1918398
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/MethodNameTransformerPrefixingTest.java
@@ -0,0 +1,38 @@
+package net.bytebuddy.dynamic.scaffold.inline;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.endsWith;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class MethodNameTransformerPrefixingTest {
+
+ private static final String FOO = "foo";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodDescription methodDescription;
+
+ @Test
+ public void testTransformation() throws Exception {
+ when(methodDescription.getInternalName()).thenReturn(FOO);
+ String transformed = new MethodNameTransformer.Prefixing().transform(methodDescription);
+ assertThat(transformed, not(FOO));
+ assertThat(transformed, endsWith(FOO));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodNameTransformer.Prefixing.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/MethodNameTransformerSuffixingTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/MethodNameTransformerSuffixingTest.java
new file mode 100644
index 0000000..2d0e97a
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/MethodNameTransformerSuffixingTest.java
@@ -0,0 +1,36 @@
+package net.bytebuddy.dynamic.scaffold.inline;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class MethodNameTransformerSuffixingTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodDescription methodDescription;
+
+ @Test
+ public void testTransformation() throws Exception {
+ when(methodDescription.getInternalName()).thenReturn(FOO);
+ String transformed = new MethodNameTransformer.Suffixing(BAR).transform(methodDescription);
+ assertThat(transformed, is(FOO + "$" + BAR));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodNameTransformer.Suffixing.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/MethodRebaseResolverDefaultTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/MethodRebaseResolverDefaultTest.java
new file mode 100644
index 0000000..94d3dbe
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/MethodRebaseResolverDefaultTest.java
@@ -0,0 +1,136 @@
+package net.bytebuddy.dynamic.scaffold.inline;
+
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
+import net.bytebuddy.matcher.ElementMatchers;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class MethodRebaseResolverDefaultTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodDescription.InDefinedShape methodDescription, otherMethod;
+
+ @Mock
+ private MethodDescription.Token token, otherToken;
+
+ @Mock
+ private MethodDescription.SignatureToken signatureToken;
+
+ @Mock
+ private MethodRebaseResolver.Resolution resolution;
+
+ @Mock
+ private DynamicType dynamicType;
+
+ @Mock
+ private TypeDescription instrumentedType;
+
+ @Mock
+ private ClassFileVersion classFileVersion;
+
+ @Mock
+ private AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy;
+
+ @Mock
+ private MethodNameTransformer methodNameTransformer;
+
+ @Before
+ public void setUp() throws Exception {
+ when(methodDescription.asDefined()).thenReturn(methodDescription);
+ when(methodDescription.getParameters()).thenReturn(new ParameterList.Empty<ParameterDescription.InDefinedShape>());
+ when(methodDescription.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(methodDescription.getInternalName()).thenReturn(FOO);
+ when(methodDescription.asToken(ElementMatchers.is(instrumentedType))).thenReturn(token);
+ when(methodDescription.asSignatureToken()).thenReturn(signatureToken);
+ when(instrumentedType.getDeclaredMethods()).thenReturn(new MethodList.Explicit<MethodDescription.InDefinedShape>(methodDescription));
+ when(otherMethod.asToken(ElementMatchers.is(instrumentedType))).thenReturn(otherToken);
+ when(methodNameTransformer.transform(methodDescription)).thenReturn(BAR);
+ when(auxiliaryTypeNamingStrategy.name(instrumentedType)).thenReturn(QUX);
+ when(classFileVersion.getMinorMajorVersion()).thenReturn(Opcodes.V1_6);
+ }
+
+ @Test
+ public void testResolutionLookup() throws Exception {
+ MethodRebaseResolver methodRebaseResolver = new MethodRebaseResolver.Default(Collections.singletonMap(methodDescription, resolution),
+ Collections.singletonList(dynamicType));
+ assertThat(methodRebaseResolver.resolve(methodDescription), is(resolution));
+ assertThat(methodRebaseResolver.resolve(otherMethod).isRebased(), is(false));
+ assertThat(methodRebaseResolver.resolve(otherMethod).getResolvedMethod(), is(otherMethod));
+ }
+
+ @Test
+ public void testAuxiliaryTypes() throws Exception {
+ MethodRebaseResolver methodRebaseResolver = new MethodRebaseResolver.Default(Collections.singletonMap(methodDescription, resolution),
+ Collections.singletonList(dynamicType));
+ assertThat(methodRebaseResolver.getAuxiliaryTypes().size(), is(1));
+ assertThat(methodRebaseResolver.getAuxiliaryTypes().contains(dynamicType), is(true));
+ }
+
+ @Test
+ public void testTokenMap() throws Exception {
+ MethodRebaseResolver methodRebaseResolver = new MethodRebaseResolver.Default(Collections.singletonMap(methodDescription, resolution),
+ Collections.singletonList(dynamicType));
+ assertThat(methodRebaseResolver.asTokenMap().size(), is(1));
+ assertThat(methodRebaseResolver.asTokenMap().get(signatureToken), is(resolution));
+ }
+
+ @Test
+ public void testCreationWithoutConstructor() throws Exception {
+ MethodRebaseResolver methodRebaseResolver = MethodRebaseResolver.Default.make(instrumentedType,
+ Collections.singleton(token),
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ methodNameTransformer);
+ assertThat(methodRebaseResolver.getAuxiliaryTypes().size(), is(0));
+ MethodRebaseResolver.Resolution resolution = methodRebaseResolver.resolve(methodDescription);
+ assertThat(resolution.isRebased(), is(true));
+ assertThat(resolution.getResolvedMethod(), not(methodDescription));
+ assertThat(resolution.getResolvedMethod().isConstructor(), is(false));
+ }
+
+ @Test
+ public void testCreationWithConstructor() throws Exception {
+ when(methodDescription.isConstructor()).thenReturn(true);
+ MethodRebaseResolver methodRebaseResolver = MethodRebaseResolver.Default.make(instrumentedType,
+ Collections.singleton(token),
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ methodNameTransformer);
+ assertThat(methodRebaseResolver.getAuxiliaryTypes().size(), is(1));
+ assertThat(methodRebaseResolver.getAuxiliaryTypes().get(0).getTypeDescription().getName(), is(QUX));
+ MethodRebaseResolver.Resolution resolution = methodRebaseResolver.resolve(methodDescription);
+ assertThat(resolution.isRebased(), is(true));
+ assertThat(resolution.getResolvedMethod(), not(methodDescription));
+ assertThat(resolution.getResolvedMethod().isConstructor(), is(true));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodRebaseResolver.Default.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/MethodRebaseResolverDisabledTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/MethodRebaseResolverDisabledTest.java
new file mode 100644
index 0000000..3dbd769
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/MethodRebaseResolverDisabledTest.java
@@ -0,0 +1,43 @@
+package net.bytebuddy.dynamic.scaffold.inline;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodRebaseResolverDisabledTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodDescription.InDefinedShape methodDescription;
+
+ @Test
+ public void testResolutionPreservesMethod() throws Exception {
+ MethodRebaseResolver.Resolution resolution = MethodRebaseResolver.Disabled.INSTANCE.resolve(methodDescription);
+ assertThat(resolution.isRebased(), is(false));
+ assertThat(resolution.getResolvedMethod(), is(methodDescription));
+ }
+
+ @Test
+ public void testNoAuxiliaryTypes() throws Exception {
+ assertThat(MethodRebaseResolver.Disabled.INSTANCE.getAuxiliaryTypes().size(), is(0));
+ }
+
+ @Test
+ public void testNoRebaseableMethods() throws Exception {
+ assertThat(MethodRebaseResolver.Disabled.INSTANCE.asTokenMap().size(), is(0));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodRebaseResolver.Disabled.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/MethodRebaseResolverResolutionForRebasedConstructorTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/MethodRebaseResolverResolutionForRebasedConstructorTest.java
new file mode 100644
index 0000000..f51d284
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/MethodRebaseResolverResolutionForRebasedConstructorTest.java
@@ -0,0 +1,118 @@
+package net.bytebuddy.dynamic.scaffold.inline;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class MethodRebaseResolverResolutionForRebasedConstructorTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux", BAZ = "baz";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodDescription.InDefinedShape methodDescription;
+
+ @Mock
+ private StackManipulation stackManipulation;
+
+ @Mock
+ private TypeDescription.Generic typeDescription, parameterType, placeholderType, returnType;
+
+ @Mock
+ private TypeDescription rawTypeDescription, rawParameterType, rawReturnType, otherPlaceHolderType, rawPlaceholderType;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ when(typeDescription.asErasure()).thenReturn(rawTypeDescription);
+ when(methodDescription.isConstructor()).thenReturn(true);
+ when(methodDescription.getDeclaringType()).thenReturn(rawTypeDescription);
+ when(methodDescription.getReturnType()).thenReturn(returnType);
+ when(methodDescription.getParameters()).thenReturn(new ParameterList.Explicit.ForTypes(methodDescription, parameterType));
+ when(placeholderType.getStackSize()).thenReturn(StackSize.ZERO);
+ when(placeholderType.asErasure()).thenReturn(rawPlaceholderType);
+ when(placeholderType.asGenericType()).thenReturn(placeholderType);
+ when(rawPlaceholderType.asGenericType()).thenReturn(placeholderType);
+ when(parameterType.asGenericType()).thenReturn(parameterType);
+ when(parameterType.getStackSize()).thenReturn(StackSize.ZERO);
+ when(rawParameterType.getStackSize()).thenReturn(StackSize.ZERO);
+ when(parameterType.asErasure()).thenReturn(rawParameterType);
+ when(parameterType.accept(any(TypeDescription.Generic.Visitor.class))).thenReturn(parameterType);
+ when(rawParameterType.asGenericType()).thenReturn(parameterType);
+ when(methodDescription.getInternalName()).thenReturn(FOO);
+ when(methodDescription.getDescriptor()).thenReturn(QUX);
+ when(rawTypeDescription.getInternalName()).thenReturn(BAR);
+ when(rawPlaceholderType.getDescriptor()).thenReturn(BAZ);
+ when(otherPlaceHolderType.getDescriptor()).thenReturn(FOO);
+ when(returnType.asErasure()).thenReturn(rawReturnType);
+ }
+
+ @Test
+ public void testPreservation() throws Exception {
+ MethodRebaseResolver.Resolution resolution = MethodRebaseResolver.Resolution.ForRebasedConstructor.of(methodDescription, rawPlaceholderType);
+ assertThat(resolution.isRebased(), is(true));
+ assertThat(resolution.getResolvedMethod().getDeclaringType(), is(rawTypeDescription));
+ assertThat(resolution.getResolvedMethod().getInternalName(), is(MethodDescription.CONSTRUCTOR_INTERNAL_NAME));
+ assertThat(resolution.getResolvedMethod().getModifiers(), is(Opcodes.ACC_SYNTHETIC | Opcodes.ACC_PRIVATE));
+ assertThat(resolution.getResolvedMethod().getReturnType(), is(TypeDescription.Generic.VOID));
+ assertThat(resolution.getResolvedMethod().getParameters(), is((ParameterList<ParameterDescription.InDefinedShape>) new ParameterList.Explicit
+ .ForTypes(resolution.getResolvedMethod(), parameterType, placeholderType)));
+ StackManipulation.Size size = resolution.getAdditionalArguments().apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(1));
+ assertThat(size.getMaximalSize(), is(1));
+ verify(methodVisitor).visitInsn(Opcodes.ACONST_NULL);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodRebaseResolver.Resolution.ForRebasedConstructor.class).refine(new ObjectPropertyAssertion.Refinement<MethodDescription>() {
+ @Override
+ public void apply(MethodDescription mock) {
+ when(mock.getParameters()).thenReturn((ParameterList) new ParameterList.Empty<ParameterDescription>());
+ when(mock.getExceptionTypes()).thenReturn(new TypeList.Generic.Empty());
+ when(mock.getDeclaringType()).thenReturn(mock(TypeDescription.class));
+ TypeDescription.Generic returnType = mock(TypeDescription.Generic.class);
+ TypeDescription rawReturnType = mock(TypeDescription.class);
+ when(returnType.asErasure()).thenReturn(rawReturnType);
+ when(mock.getReturnType()).thenReturn(returnType);
+ when(mock.getInternalName()).thenReturn(FOO + System.identityHashCode(mock));
+ }
+ }).refine(new ObjectPropertyAssertion.Refinement<TypeDescription>() {
+ @Override
+ public void apply(TypeDescription mock) {
+ when(mock.getDescriptor()).thenReturn(FOO + System.identityHashCode(mock));
+ when(mock.asErasure()).thenReturn(mock);
+ when(mock.getStackSize()).thenReturn(StackSize.ZERO);
+ }
+ }).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/MethodRebaseResolverResolutionForRebasedMethodTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/MethodRebaseResolverResolutionForRebasedMethodTest.java
new file mode 100644
index 0000000..5782c43
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/MethodRebaseResolverResolutionForRebasedMethodTest.java
@@ -0,0 +1,137 @@
+package net.bytebuddy.dynamic.scaffold.inline;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+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.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class MethodRebaseResolverResolutionForRebasedMethodTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux", BAZ = "baz";
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] {
+ {false, Opcodes.ACC_SYNTHETIC | Opcodes.ACC_PRIVATE},
+ {true, Opcodes.ACC_SYNTHETIC | Opcodes.ACC_PUBLIC}
+ });
+ }
+
+ private final boolean interfaceType;
+
+ private final int rebasedMethodModifiers;
+
+ public MethodRebaseResolverResolutionForRebasedMethodTest(boolean interfaceType, int rebasedMethodModifiers) {
+ this.interfaceType = interfaceType;
+ this.rebasedMethodModifiers = rebasedMethodModifiers;
+ }
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodDescription.InDefinedShape methodDescription;
+
+ @Mock
+ private MethodNameTransformer methodNameTransformer, otherMethodNameTransformer;
+
+ @Mock
+ private TypeDescription instrumentedType, typeDescription, returnType, parameterType;
+
+ @Mock
+ private TypeDescription.Generic genericReturnType, genericParameterType;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ when(methodDescription.getDeclaringType()).thenReturn(typeDescription);
+ when(methodDescription.getReturnType()).thenReturn(genericReturnType);
+ when(methodDescription.getInternalName()).thenReturn(FOO);
+ when(methodDescription.getDescriptor()).thenReturn(BAZ);
+ when(typeDescription.getInternalName()).thenReturn(BAR);
+ when(typeDescription.getDescriptor()).thenReturn(BAR);
+ when(instrumentedType.isInterface()).thenReturn(interfaceType);
+ when(methodNameTransformer.transform(methodDescription)).thenReturn(QUX);
+ when(otherMethodNameTransformer.transform(methodDescription)).thenReturn(FOO + BAR);
+ when(parameterType.getStackSize()).thenReturn(StackSize.ZERO);
+ when(methodDescription.getParameters()).thenReturn(new ParameterList.Explicit.ForTypes(methodDescription, genericParameterType));
+ when(genericReturnType.asErasure()).thenReturn(returnType);
+ when(genericReturnType.asRawType()).thenReturn(genericReturnType);
+ when(genericReturnType.accept(any(TypeDescription.Generic.Visitor.class))).thenReturn(genericReturnType);
+ when(genericParameterType.asErasure()).thenReturn(parameterType);
+ when(genericParameterType.asGenericType()).thenReturn(genericParameterType);
+ when(parameterType.asGenericType()).thenReturn(genericParameterType);
+ when(genericParameterType.asRawType()).thenReturn(genericParameterType);
+ when(genericParameterType.accept(any(TypeDescription.Generic.Visitor.class))).thenReturn(genericParameterType);
+ }
+
+ @Test
+ public void testPreservation() throws Exception {
+ MethodRebaseResolver.Resolution resolution = MethodRebaseResolver.Resolution.ForRebasedMethod.of(instrumentedType,
+ methodDescription,
+ methodNameTransformer);
+ assertThat(resolution.isRebased(), is(true));
+ assertThat(resolution.getResolvedMethod().getDeclaringType(), is(typeDescription));
+ assertThat(resolution.getResolvedMethod().getInternalName(), is(QUX));
+ assertThat(resolution.getResolvedMethod().getModifiers(), is(rebasedMethodModifiers));
+ assertThat(resolution.getResolvedMethod().getReturnType(), is(genericReturnType));
+ assertThat(resolution.getResolvedMethod().getParameters(), is((ParameterList<ParameterDescription.InDefinedShape>) new ParameterList.Explicit
+ .ForTypes(resolution.getResolvedMethod(), parameterType)));
+ StackManipulation.Size size = resolution.getAdditionalArguments().apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(0));
+ assertThat(size.getMaximalSize(), is(0));
+ verifyZeroInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodRebaseResolver.Resolution.ForRebasedMethod.class).refine(new ObjectPropertyAssertion.Refinement<MethodDescription>() {
+ @Override
+ public void apply(MethodDescription mock) {
+ when(mock.getParameters()).thenReturn((ParameterList) new ParameterList.Empty<ParameterDescription>());
+ when(mock.getExceptionTypes()).thenReturn(new TypeList.Generic.Empty());
+ when(mock.getDeclaringType()).thenReturn(mock(TypeDescription.class));
+ TypeDescription.Generic returnType = mock(TypeDescription.Generic.class);
+ TypeDescription rawReturnType = mock(TypeDescription.class);
+ when(returnType.asErasure()).thenReturn(rawReturnType);
+ when(mock.getReturnType()).thenReturn(returnType);
+ }
+ }).refine(new ObjectPropertyAssertion.Refinement<MethodNameTransformer>() {
+ @Override
+ public void apply(MethodNameTransformer mock) {
+ when(mock.transform(any(MethodDescription.class))).thenReturn(FOO + System.identityHashCode(mock));
+ }
+ }).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/MethodRebaseResolverResolutionPreservedTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/MethodRebaseResolverResolutionPreservedTest.java
new file mode 100644
index 0000000..613a238
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/MethodRebaseResolverResolutionPreservedTest.java
@@ -0,0 +1,38 @@
+package net.bytebuddy.dynamic.scaffold.inline;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodRebaseResolverResolutionPreservedTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodDescription.InDefinedShape methodDescription;
+
+ @Test
+ public void testPreservation() throws Exception {
+ MethodRebaseResolver.Resolution resolution = new MethodRebaseResolver.Resolution.Preserved(methodDescription);
+ assertThat(resolution.isRebased(), is(false));
+ assertThat(resolution.getResolvedMethod(), is(methodDescription));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testPreservationCannotAppendArguments() throws Exception {
+ new MethodRebaseResolver.Resolution.Preserved(methodDescription).getAdditionalArguments();
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodRebaseResolver.Resolution.Preserved.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/RebaseDynamicTypeBuilderRebaseableMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/RebaseDynamicTypeBuilderRebaseableMatcherTest.java
new file mode 100644
index 0000000..9adf249
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/RebaseDynamicTypeBuilderRebaseableMatcherTest.java
@@ -0,0 +1,38 @@
+package net.bytebuddy.dynamic.scaffold.inline;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class RebaseDynamicTypeBuilderRebaseableMatcherTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodDescription.Token targetToken, otherToken;
+
+ @Test
+ public void testMatchToken() throws Exception {
+ assertThat(new RebaseDynamicTypeBuilder.RebaseableMatcher(Collections.singleton(targetToken)).matches(targetToken), is(true));
+ }
+
+ @Test
+ public void testNoMatchToken() throws Exception {
+ assertThat(new RebaseDynamicTypeBuilder.RebaseableMatcher(Collections.singleton(otherToken)).matches(targetToken), is(false));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(RebaseDynamicTypeBuilder.RebaseableMatcher.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/RebaseDynamicTypeBuilderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/RebaseDynamicTypeBuilderTest.java
new file mode 100644
index 0000000..d4a2654
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/RebaseDynamicTypeBuilderTest.java
@@ -0,0 +1,245 @@
+package net.bytebuddy.dynamic.scaffold.inline;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.type.PackageDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.dynamic.scaffold.TypeValidation;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.MethodDelegation;
+import net.bytebuddy.implementation.StubMethod;
+import net.bytebuddy.implementation.SuperMethodCall;
+import net.bytebuddy.implementation.attribute.AnnotationRetention;
+import net.bytebuddy.matcher.ElementMatchers;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.test.visibility.PackageAnnotation;
+import net.bytebuddy.test.visibility.Sample;
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Field;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Collections;
+import java.util.List;
+
+import static net.bytebuddy.matcher.ElementMatchers.any;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class RebaseDynamicTypeBuilderTest extends AbstractDynamicTypeBuilderForInliningTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ private static final String DEFAULT_METHOD_INTERFACE = "net.bytebuddy.test.precompiled.SingleDefaultMethodInterface";
+
+ @Override
+ protected DynamicType.Builder<?> createPlain() {
+ return create(Foo.class);
+ }
+
+ @Override
+ protected DynamicType.Builder<?> createDisabledContext() {
+ return new ByteBuddy().with(Implementation.Context.Disabled.Factory.INSTANCE).rebase(Foo.class);
+ }
+
+ @Override
+ protected DynamicType.Builder createDisabledRetention(Class<?> annotatedClass) {
+ return new ByteBuddy().with(AnnotationRetention.DISABLED).rebase(annotatedClass);
+ }
+
+ @Override
+ protected DynamicType.Builder<?> create(Class<?> type) {
+ return new ByteBuddy().rebase(type);
+ }
+
+ @Override
+ protected DynamicType.Builder<?> create(TypeDescription typeDescription, ClassFileLocator classFileLocator) {
+ return new ByteBuddy().rebase(typeDescription, classFileLocator);
+ }
+
+ @Override
+ protected DynamicType.Builder<?> createPlainWithoutValidation() {
+ return new ByteBuddy().with(TypeValidation.DISABLED).redefine(Foo.class);
+ }
+
+ @Test
+ public void testConstructorRetentionNoAuxiliaryType() throws Exception {
+ DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
+ .rebase(Bar.class)
+ .make();
+ assertThat(dynamicType.getAuxiliaryTypes().size(), is(0));
+ Class<?> type = dynamicType.load(new URLClassLoader(new URL[0], null), ClassLoadingStrategy.Default.WRAPPER).getLoaded();
+ assertThat(type.getDeclaredConstructors().length, is(1));
+ assertThat(type.getDeclaredMethods().length, is(0));
+ Field field = type.getDeclaredField(BAR);
+ assertThat(field.get(type.getDeclaredConstructor(String.class).newInstance(FOO)), is((Object) FOO));
+ }
+
+ @Test
+ public void testConstructorRebaseSingleAuxiliaryType() throws Exception {
+ DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
+ .rebase(Bar.class)
+ .constructor(any()).intercept(SuperMethodCall.INSTANCE)
+ .make();
+ assertThat(dynamicType.getAuxiliaryTypes().size(), is(1));
+ Class<?> type = dynamicType.load(new URLClassLoader(new URL[0], null), ClassLoadingStrategy.Default.WRAPPER).getLoaded();
+ assertThat(type.getDeclaredConstructors().length, is(2));
+ assertThat(type.getDeclaredMethods().length, is(0));
+ Field field = type.getDeclaredField(BAR);
+ assertThat(field.get(type.getDeclaredConstructor(String.class).newInstance(FOO)), is((Object) FOO));
+ }
+
+ @Test
+ public void testMethodRebase() throws Exception {
+ DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
+ .rebase(Qux.class)
+ .method(named(BAR)).intercept(StubMethod.INSTANCE)
+ .make();
+ assertThat(dynamicType.getAuxiliaryTypes().size(), is(0));
+ Class<?> type = dynamicType.load(new URLClassLoader(new URL[0], null), ClassLoadingStrategy.Default.WRAPPER).getLoaded();
+ assertThat(type.getDeclaredConstructors().length, is(1));
+ assertThat(type.getDeclaredMethods().length, is(3));
+ assertThat(type.getDeclaredMethod(FOO).invoke(null), nullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).get(null), is((Object) FOO));
+ assertThat(type.getDeclaredMethod(BAR).invoke(null), nullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).get(null), is((Object) FOO));
+ }
+
+ @Test
+ public void testPackageRebasement() throws Exception {
+ Class<?> packageType = new ByteBuddy()
+ .rebase(Sample.class.getPackage(), ClassFileLocator.ForClassLoader.of(getClass().getClassLoader()))
+ .annotateType(AnnotationDescription.Builder.ofType(Baz.class).build())
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded();
+ assertThat(packageType.getSimpleName(), CoreMatchers.is(PackageDescription.PACKAGE_CLASS_NAME));
+ assertThat(packageType.getName(), CoreMatchers.is(Sample.class.getPackage().getName() + "." + PackageDescription.PACKAGE_CLASS_NAME));
+ assertThat(packageType.getModifiers(), CoreMatchers.is(PackageDescription.PACKAGE_MODIFIERS));
+ assertThat(packageType.getDeclaredFields().length, CoreMatchers.is(0));
+ assertThat(packageType.getDeclaredMethods().length, CoreMatchers.is(0));
+ assertThat(packageType.getDeclaredAnnotations().length, CoreMatchers.is(2));
+ assertThat(packageType.getAnnotation(PackageAnnotation.class), notNullValue(PackageAnnotation.class));
+ assertThat(packageType.getAnnotation(Baz.class), notNullValue(Baz.class));
+ }
+
+ @Test
+ public void testRebaseOfRenamedType() throws Exception {
+ Class<?> rebased = new ByteBuddy()
+ .rebase(Sample.class)
+ .name(Sample.class.getName() + FOO)
+ .constructor(ElementMatchers.any())
+ .intercept(SuperMethodCall.INSTANCE)
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(rebased.getName(), is(Sample.class.getName() + FOO));
+ assertThat(rebased.getDeclaredConstructors().length, is(2));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testCannotRebaseDefinedMethod() throws Exception {
+ new ByteBuddy()
+ .rebase(Foo.class)
+ .defineMethod(FOO, void.class).intercept(SuperMethodCall.INSTANCE)
+ .make();
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testDefaultInterfaceSubInterface() throws Exception {
+ Class<?> interfaceType = Class.forName(DEFAULT_METHOD_INTERFACE);
+ Class<?> dynamicInterfaceType = new ByteBuddy()
+ .rebase(interfaceType)
+ .method(named(FOO)).intercept(MethodDelegation.to(InterfaceOverrideInterceptor.class))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded();
+ Class<?> dynamicClassType = new ByteBuddy()
+ .subclass(dynamicInterfaceType)
+ .make()
+ .load(dynamicInterfaceType.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(dynamicClassType.getMethod(FOO).invoke(dynamicClassType.getDeclaredConstructor().newInstance()), is((Object) (FOO + BAR)));
+ assertThat(dynamicInterfaceType.getDeclaredMethods().length, is(3));
+ assertThat(dynamicClassType.getDeclaredMethods().length, is(0));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(RebaseDynamicTypeBuilder.class).create(new ObjectPropertyAssertion.Creator<List<?>>() {
+ @Override
+ public List<?> create() {
+ TypeDescription typeDescription = mock(TypeDescription.class);
+ when(typeDescription.asErasure()).thenReturn(typeDescription);
+ return Collections.singletonList(typeDescription);
+ }
+ }).create(new ObjectPropertyAssertion.Creator<TypeDescription>() {
+ @Override
+ public TypeDescription create() {
+ TypeDescription rawTypeDescription = mock(TypeDescription.class);
+ when(rawTypeDescription.asErasure()).thenReturn(rawTypeDescription);
+ when(rawTypeDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(rawTypeDescription.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ TypeDescription.Generic typeDescription = mock(TypeDescription.Generic.class);
+ when(typeDescription.asGenericType()).thenReturn(typeDescription);
+ when(typeDescription.asErasure()).thenReturn(rawTypeDescription);
+ when(rawTypeDescription.getInterfaces()).thenReturn(new TypeList.Generic.Explicit(typeDescription));
+ when(rawTypeDescription.getDeclaredFields()).thenReturn(new FieldList.Empty<FieldDescription.InDefinedShape>());
+ when(rawTypeDescription.getDeclaredMethods()).thenReturn(new MethodList.Empty<MethodDescription.InDefinedShape>());
+ return rawTypeDescription;
+ }
+ }).apply();
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface Baz {
+ /* empty */
+ }
+
+ public static class Bar {
+
+ public final String bar;
+
+ public Bar(String bar) {
+ this.bar = bar;
+ }
+ }
+
+ public static class Qux {
+
+ public static String foo;
+
+ public static String foo() {
+ try {
+ return foo;
+ } finally {
+ foo = FOO;
+ }
+ }
+
+ public static String bar() {
+ try {
+ return foo;
+ } finally {
+ foo = FOO;
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/RebaseImplementationTargetFactoryTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/RebaseImplementationTargetFactoryTest.java
new file mode 100644
index 0000000..cfd13b2
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/RebaseImplementationTargetFactoryTest.java
@@ -0,0 +1,60 @@
+package net.bytebuddy.dynamic.scaffold.inline;
+
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.scaffold.MethodGraph;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class RebaseImplementationTargetFactoryTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodRebaseResolver methodRebaseResolver;
+
+ @Mock
+ private MethodGraph.Linked methodGraph;
+
+ @Mock
+ private ClassFileVersion classFileVersion;
+
+ @Mock
+ private TypeDescription instrumentedType;
+
+ @Mock
+ private TypeDescription.Generic superClass;
+
+ private Implementation.Target.Factory factory;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ when(instrumentedType.getSuperClass()).thenReturn(superClass);
+ when(superClass.getDeclaredMethods()).thenReturn(new MethodList.Empty<MethodDescription.InGenericShape>());
+ factory = new RebaseImplementationTarget.Factory(methodRebaseResolver);
+ }
+
+ @Test
+ public void testReturnsRebaseImplementationTarget() throws Exception {
+ assertThat(factory.make(instrumentedType, methodGraph, classFileVersion) instanceof RebaseImplementationTarget, is(true));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(RebaseImplementationTarget.Factory.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/RebaseImplementationTargetSpecialMethodInvocationTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/RebaseImplementationTargetSpecialMethodInvocationTest.java
new file mode 100644
index 0000000..7b631e5
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/RebaseImplementationTargetSpecialMethodInvocationTest.java
@@ -0,0 +1,20 @@
+package net.bytebuddy.dynamic.scaffold.inline;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.AbstractSpecialMethodInvocationTest;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class RebaseImplementationTargetSpecialMethodInvocationTest extends AbstractSpecialMethodInvocationTest {
+
+ @Override
+ protected Implementation.SpecialMethodInvocation make(MethodDescription methodDescription, TypeDescription typeDescription) {
+ return new RebaseImplementationTarget.RebasedMethodInvocation(methodDescription, typeDescription, mock(StackManipulation.class));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/RebaseImplementationTargetTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/RebaseImplementationTargetTest.java
new file mode 100644
index 0000000..04015a2
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/RebaseImplementationTargetTest.java
@@ -0,0 +1,184 @@
+package net.bytebuddy.dynamic.scaffold.inline;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.scaffold.MethodGraph;
+import net.bytebuddy.implementation.AbstractImplementationTargetTest;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.constant.NullConstant;
+import net.bytebuddy.matcher.ElementMatchers;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class RebaseImplementationTargetTest extends AbstractImplementationTargetTest {
+
+ private static final String BAR = "bar";
+
+ @Mock
+ private MethodDescription.InDefinedShape rebasedMethod;
+
+ @Mock
+ private MethodDescription.Token rebasedToken;
+
+ @Mock
+ private MethodDescription.SignatureToken rebasedSignatureToken;
+
+ @Mock
+ private MethodRebaseResolver.Resolution resolution;
+
+ @Mock
+ private TypeDescription rawSuperClass;
+
+ @Mock
+ private TypeDescription.Generic superClass;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ when(methodGraph.locate(Mockito.any(MethodDescription.SignatureToken.class))).thenReturn(MethodGraph.Node.Unresolved.INSTANCE);
+ when(instrumentedType.getSuperClass()).thenReturn(superClass);
+ when(superClass.asErasure()).thenReturn(rawSuperClass);
+ when(rawSuperClass.getInternalName()).thenReturn(BAR);
+ when(rebasedMethod.getInternalName()).thenReturn(QUX);
+ when(rebasedMethod.asToken(ElementMatchers.is(instrumentedType))).thenReturn(rebasedToken);
+ when(rebasedMethod.getDescriptor()).thenReturn(FOO);
+ when(rebasedMethod.asDefined()).thenReturn(rebasedMethod);
+ when(rebasedMethod.getReturnType()).thenReturn(genericReturnType);
+ when(rebasedMethod.getParameters()).thenReturn(new ParameterList.Empty<ParameterDescription.InDefinedShape>());
+ when(rebasedMethod.getDeclaringType()).thenReturn(instrumentedType);
+ when(rebasedMethod.asSignatureToken()).thenReturn(rebasedSignatureToken);
+ super.setUp();
+ }
+
+ @Override
+ protected Implementation.Target makeImplementationTarget() {
+ return new RebaseImplementationTarget(instrumentedType, methodGraph, defaultMethodInvocation, Collections.singletonMap(rebasedSignatureToken, resolution));
+ }
+
+ @Test
+ public void testNonRebasedMethodIsInvokable() throws Exception {
+ when(invokableMethod.getDeclaringType()).thenReturn(instrumentedType);
+ when(invokableMethod.isSpecializableFor(instrumentedType)).thenReturn(true);
+ when(resolution.isRebased()).thenReturn(false);
+ when(resolution.getResolvedMethod()).thenReturn(invokableMethod);
+ Implementation.SpecialMethodInvocation specialMethodInvocation = makeImplementationTarget().invokeSuper(rebasedSignatureToken);
+ assertThat(specialMethodInvocation.isValid(), is(true));
+ assertThat(specialMethodInvocation.getMethodDescription(), is((MethodDescription) invokableMethod));
+ assertThat(specialMethodInvocation.getTypeDescription(), is(instrumentedType));
+ MethodVisitor methodVisitor = mock(MethodVisitor.class);
+ Implementation.Context implementationContext = mock(Implementation.Context.class);
+ StackManipulation.Size size = specialMethodInvocation.apply(methodVisitor, implementationContext);
+ verify(methodVisitor).visitMethodInsn(Opcodes.INVOKESPECIAL, BAZ, FOO, QUX, false);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ assertThat(size.getSizeImpact(), is(0));
+ assertThat(size.getMaximalSize(), is(0));
+ }
+
+ @Test
+ public void testRebasedMethodIsInvokable() throws Exception {
+ when(invokableMethod.getDeclaringType()).thenReturn(instrumentedType);
+ when(resolution.isRebased()).thenReturn(true);
+ when(resolution.getResolvedMethod()).thenReturn(rebasedMethod);
+ when(resolution.getAdditionalArguments()).thenReturn(StackManipulation.Trivial.INSTANCE);
+ when(rebasedMethod.isSpecializableFor(instrumentedType)).thenReturn(true);
+ Implementation.SpecialMethodInvocation specialMethodInvocation = makeImplementationTarget().invokeSuper(rebasedSignatureToken);
+ assertThat(specialMethodInvocation.isValid(), is(true));
+ assertThat(specialMethodInvocation.getMethodDescription(), is((MethodDescription) rebasedMethod));
+ assertThat(specialMethodInvocation.getTypeDescription(), is(instrumentedType));
+ MethodVisitor methodVisitor = mock(MethodVisitor.class);
+ Implementation.Context implementationContext = mock(Implementation.Context.class);
+ StackManipulation.Size size = specialMethodInvocation.apply(methodVisitor, implementationContext);
+ verify(methodVisitor).visitMethodInsn(Opcodes.INVOKESPECIAL, BAZ, QUX, FOO, false);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ assertThat(size.getSizeImpact(), is(0));
+ assertThat(size.getMaximalSize(), is(0));
+ }
+
+ @Test
+ public void testRebasedConstructorIsInvokable() throws Exception {
+ when(rebasedMethod.isConstructor()).thenReturn(true);
+ when(invokableMethod.getDeclaringType()).thenReturn(instrumentedType);
+ when(resolution.isRebased()).thenReturn(true);
+ when(resolution.getResolvedMethod()).thenReturn(rebasedMethod);
+ when(resolution.getAdditionalArguments()).thenReturn(NullConstant.INSTANCE);
+ when(rebasedMethod.isSpecializableFor(instrumentedType)).thenReturn(true);
+ Implementation.SpecialMethodInvocation specialMethodInvocation = makeImplementationTarget().invokeSuper(rebasedSignatureToken);
+ assertThat(specialMethodInvocation.isValid(), is(true));
+ assertThat(specialMethodInvocation.getMethodDescription(), is((MethodDescription) rebasedMethod));
+ assertThat(specialMethodInvocation.getTypeDescription(), is(instrumentedType));
+ MethodVisitor methodVisitor = mock(MethodVisitor.class);
+ Implementation.Context implementationContext = mock(Implementation.Context.class);
+ StackManipulation.Size size = specialMethodInvocation.apply(methodVisitor, implementationContext);
+ verify(methodVisitor).visitInsn(Opcodes.ACONST_NULL);
+ verify(methodVisitor).visitMethodInsn(Opcodes.INVOKESPECIAL, BAZ, QUX, FOO, false);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ assertThat(size.getSizeImpact(), is(1));
+ assertThat(size.getMaximalSize(), is(1));
+ }
+
+ @Test
+ public void testNonSpecializableRebaseMethodIsNotInvokable() throws Exception {
+ when(invokableMethod.getDeclaringType()).thenReturn(instrumentedType);
+ when(resolution.isRebased()).thenReturn(true);
+ when(resolution.getResolvedMethod()).thenReturn(rebasedMethod);
+ when(resolution.getAdditionalArguments()).thenReturn(StackManipulation.Trivial.INSTANCE);
+ when(rebasedMethod.isSpecializableFor(instrumentedType)).thenReturn(false);
+ Implementation.SpecialMethodInvocation specialMethodInvocation = makeImplementationTarget().invokeSuper(rebasedSignatureToken);
+ assertThat(specialMethodInvocation.isValid(), is(false));
+ }
+
+ @Test
+ public void testSuperTypeMethodIsInvokable() throws Exception {
+ when(invokableMethod.isSpecializableFor(rawSuperClass)).thenReturn(true);
+ Implementation.SpecialMethodInvocation specialMethodInvocation = makeImplementationTarget().invokeSuper(invokableToken);
+ assertThat(specialMethodInvocation.isValid(), is(true));
+ assertThat(specialMethodInvocation.getMethodDescription(), is((MethodDescription) invokableMethod));
+ assertThat(specialMethodInvocation.getTypeDescription(), is(rawSuperClass));
+ MethodVisitor methodVisitor = mock(MethodVisitor.class);
+ Implementation.Context implementationContext = mock(Implementation.Context.class);
+ StackManipulation.Size size = specialMethodInvocation.apply(methodVisitor, implementationContext);
+ verify(methodVisitor).visitMethodInsn(Opcodes.INVOKESPECIAL, BAR, FOO, QUX, false);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ assertThat(size.getSizeImpact(), is(0));
+ assertThat(size.getMaximalSize(), is(0));
+ }
+
+ @Test
+ public void testNonSpecializableSuperClassMethodIsNotInvokable() throws Exception {
+ when(invokableMethod.isSpecializableFor(rawSuperClass)).thenReturn(false);
+ when(resolution.isRebased()).thenReturn(false);
+ when(resolution.getResolvedMethod()).thenReturn(invokableMethod);
+ Implementation.SpecialMethodInvocation specialMethodInvocation = makeImplementationTarget().invokeSuper(invokableToken);
+ assertThat(specialMethodInvocation.isValid(), is(false));
+ }
+
+ @Test
+ public void testOriginType() throws Exception {
+ assertThat(makeImplementationTarget().getOriginType(), is((TypeDefinition) instrumentedType));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(RebaseImplementationTarget.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/RedefinitionDynamicTypeBuilderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/RedefinitionDynamicTypeBuilderTest.java
new file mode 100644
index 0000000..e46b5fe
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/inline/RedefinitionDynamicTypeBuilderTest.java
@@ -0,0 +1,194 @@
+package net.bytebuddy.dynamic.scaffold.inline;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.dynamic.scaffold.TypeValidation;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.MethodCall;
+import net.bytebuddy.implementation.StubMethod;
+import net.bytebuddy.implementation.attribute.AnnotationRetention;
+import net.bytebuddy.implementation.bytecode.constant.TextConstant;
+import net.bytebuddy.implementation.bytecode.member.MethodReturn;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Collections;
+import java.util.List;
+
+import static net.bytebuddy.matcher.ElementMatchers.any;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class RedefinitionDynamicTypeBuilderTest extends AbstractDynamicTypeBuilderForInliningTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ private static final String DEFAULT_METHOD_INTERFACE = "net.bytebuddy.test.precompiled.SingleDefaultMethodInterface";
+
+ @Override
+ protected DynamicType.Builder<?> create(Class<?> type) {
+ return new ByteBuddy().redefine(type);
+ }
+
+ @Override
+ protected DynamicType.Builder<?> createDisabledContext() {
+ return new ByteBuddy().with(Implementation.Context.Disabled.Factory.INSTANCE).redefine(Foo.class);
+ }
+
+ @Override
+ protected DynamicType.Builder createDisabledRetention(Class<?> annotatedClass) {
+ return new ByteBuddy().with(AnnotationRetention.DISABLED).redefine(annotatedClass);
+ }
+
+ @Override
+ protected DynamicType.Builder<?> createPlain() {
+ return new ByteBuddy().redefine(Foo.class);
+ }
+
+ @Override
+ protected DynamicType.Builder<?> create(TypeDescription typeDescription, ClassFileLocator classFileLocator) {
+ return new ByteBuddy().redefine(typeDescription, classFileLocator);
+ }
+
+ @Override
+ protected DynamicType.Builder<?> createPlainWithoutValidation() {
+ return new ByteBuddy().with(TypeValidation.DISABLED).redefine(Foo.class);
+ }
+
+ @Test
+ public void testConstructorRetentionNoAuxiliaryType() throws Exception {
+ DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
+ .redefine(Bar.class)
+ .make();
+ assertThat(dynamicType.getAuxiliaryTypes().size(), is(0));
+ Class<?> type = dynamicType.load(new URLClassLoader(new URL[0], null), ClassLoadingStrategy.Default.WRAPPER).getLoaded();
+ assertThat(type.getDeclaredConstructors().length, is(1));
+ assertThat(type.getDeclaredMethods().length, is(0));
+ Field field = type.getDeclaredField(BAR);
+ assertThat(field.get(type.getDeclaredConstructor(String.class).newInstance(FOO)), is((Object) FOO));
+ }
+
+ @Test
+ public void testConstructorRebaseSingleAuxiliaryType() throws Exception {
+ DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
+ .redefine(Bar.class)
+ .constructor(any()).intercept(MethodCall.invoke(Object.class.getDeclaredConstructor()))
+ .make();
+ assertThat(dynamicType.getAuxiliaryTypes().size(), is(0));
+ Class<?> type = dynamicType.load(new URLClassLoader(new URL[0], null), ClassLoadingStrategy.Default.WRAPPER).getLoaded();
+ assertThat(type.getDeclaredConstructors().length, is(1));
+ assertThat(type.getDeclaredMethods().length, is(0));
+ Field field = type.getDeclaredField(BAR);
+ assertThat(field.get(type.getDeclaredConstructor(String.class).newInstance(FOO)), nullValue(Object.class));
+ }
+
+ @Test
+ public void testMethodRebase() throws Exception {
+ DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
+ .redefine(Qux.class)
+ .method(named(BAR)).intercept(StubMethod.INSTANCE)
+ .make();
+ assertThat(dynamicType.getAuxiliaryTypes().size(), is(0));
+ Class<?> type = dynamicType.load(new URLClassLoader(new URL[0], null), ClassLoadingStrategy.Default.WRAPPER).getLoaded();
+ assertThat(type.getDeclaredConstructors().length, is(1));
+ assertThat(type.getDeclaredMethods().length, is(2));
+ assertThat(type.getDeclaredMethod(FOO).invoke(null), nullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).get(null), is((Object) FOO));
+ assertThat(type.getDeclaredMethod(BAR).invoke(null), nullValue(Object.class));
+ assertThat(type.getDeclaredField(FOO).get(null), is((Object) FOO));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testDefaultInterfaceSubInterface() throws Exception {
+ Class<?> interfaceType = Class.forName(DEFAULT_METHOD_INTERFACE);
+ Class<?> dynamicInterfaceType = new ByteBuddy()
+ .redefine(interfaceType)
+ .method(named(FOO)).intercept(new Implementation.Simple(new TextConstant(BAR), MethodReturn.REFERENCE))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded();
+ Class<?> dynamicClassType = new ByteBuddy()
+ .subclass(dynamicInterfaceType)
+ .make()
+ .load(dynamicInterfaceType.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(dynamicClassType.getMethod(FOO).invoke(dynamicClassType.getDeclaredConstructor().newInstance()), is((Object) BAR));
+ assertThat(dynamicInterfaceType.getDeclaredMethods().length, is(1));
+ assertThat(dynamicClassType.getDeclaredMethods().length, is(0));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(RedefinitionDynamicTypeBuilder.class).create(new ObjectPropertyAssertion.Creator<List<?>>() {
+ @Override
+ public List<?> create() {
+ TypeDescription typeDescription = mock(TypeDescription.class);
+ when(typeDescription.asErasure()).thenReturn(typeDescription);
+ return Collections.singletonList(typeDescription);
+ }
+ }).create(new ObjectPropertyAssertion.Creator<TypeDescription>() {
+ @Override
+ public TypeDescription create() {
+ TypeDescription rawTypeDescription = mock(TypeDescription.class);
+ when(rawTypeDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(rawTypeDescription.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ TypeDescription.Generic typeDescription = mock(TypeDescription.Generic.class);
+ when(typeDescription.asErasure()).thenReturn(rawTypeDescription);
+ when(typeDescription.asGenericType()).thenReturn(typeDescription);
+ when(rawTypeDescription.getInterfaces()).thenReturn(new TypeList.Generic.Explicit(typeDescription));
+ when(rawTypeDescription.getDeclaredFields()).thenReturn(new FieldList.Empty<FieldDescription.InDefinedShape>());
+ when(rawTypeDescription.getDeclaredMethods()).thenReturn(new MethodList.Empty<MethodDescription.InDefinedShape>());
+ return rawTypeDescription;
+ }
+ }).apply();
+ }
+
+ public static class Bar {
+
+ public final String bar;
+
+ public Bar(String bar) {
+ this.bar = bar;
+ }
+ }
+
+ public static class Qux {
+
+ public static String foo;
+
+ public static String foo() {
+ try {
+ return foo;
+ } finally {
+ foo = FOO;
+ }
+ }
+
+ public static String bar() {
+ try {
+ return foo;
+ } finally {
+ foo = FOO;
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/subclass/ConstructorStrategyDefaultTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/subclass/ConstructorStrategyDefaultTest.java
new file mode 100644
index 0000000..d2614e9
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/subclass/ConstructorStrategyDefaultTest.java
@@ -0,0 +1,318 @@
+package net.bytebuddy.dynamic.scaffold.subclass;
+
+import net.bytebuddy.description.ByteCodeElement;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.annotation.AnnotationValue;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.description.type.TypeVariableToken;
+import net.bytebuddy.dynamic.Transformer;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.dynamic.scaffold.MethodRegistry;
+import net.bytebuddy.implementation.attribute.MethodAttributeAppender;
+import net.bytebuddy.matcher.ElementMatchers;
+import net.bytebuddy.matcher.LatentMatcher;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class ConstructorStrategyDefaultTest {
+
+ private static final String FOO = "foo";
+
+ private static final int MODIFIERS = 42;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodRegistry methodRegistry;
+
+ @Mock
+ private InstrumentedType instrumentedType;
+
+ @Mock
+ private TypeDescription.Generic superClass, typeDescription;
+
+ @Mock
+ private MethodDescription.InGenericShape methodDescription;
+
+ @Mock
+ private MethodDescription.Token token;
+
+ @Mock
+ private AnnotationValue<?, ?> defaultValue;
+
+ private MethodDescription.Token stripped;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ when(methodRegistry.append(any(LatentMatcher.class),
+ any(MethodRegistry.Handler.class),
+ any(MethodAttributeAppender.Factory.class),
+ any(Transformer.class))).thenReturn(methodRegistry);
+ when(instrumentedType.getSuperClass()).thenReturn(superClass);
+ when(superClass.getDeclaredMethods()).thenReturn(new MethodList.Explicit<MethodDescription.InGenericShape>(methodDescription));
+ when(methodDescription.isConstructor()).thenReturn(true);
+ when(methodDescription.isVisibleTo(instrumentedType)).thenReturn(true);
+ when(methodDescription.asToken(ElementMatchers.is(instrumentedType))).thenReturn(token);
+ when(token.getName()).thenReturn(FOO);
+ when(token.getModifiers()).thenReturn(MODIFIERS);
+ when(token.getTypeVariableTokens()).thenReturn(new ByteCodeElement.Token.TokenList<TypeVariableToken>());
+ when(token.getReturnType()).thenReturn(typeDescription);
+ when(token.getParameterTokens()).thenReturn(new ByteCodeElement.Token.TokenList<ParameterDescription.Token>());
+ when(token.getExceptionTypes()).thenReturn(new TypeList.Generic.Empty());
+ when(token.getAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(token.getDefaultValue()).thenReturn((AnnotationValue) defaultValue);
+ when(token.getReceiverType()).thenReturn(typeDescription);
+ stripped = new MethodDescription.Token(FOO,
+ MODIFIERS,
+ Collections.<TypeVariableToken>emptyList(),
+ typeDescription,
+ Collections.<ParameterDescription.Token>emptyList(),
+ Collections.<TypeDescription.Generic>emptyList(),
+ Collections.<AnnotationDescription>emptyList(),
+ defaultValue,
+ TypeDescription.Generic.UNDEFINED);
+ }
+
+ @Test
+ public void testNoConstructorsStrategy() throws Exception {
+ assertThat(ConstructorStrategy.Default.NO_CONSTRUCTORS.extractConstructors(instrumentedType).size(), is(0));
+ assertThat(ConstructorStrategy.Default.NO_CONSTRUCTORS.inject(methodRegistry), is(methodRegistry));
+ verifyZeroInteractions(methodRegistry);
+ verifyZeroInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testNoConstructorsStrategyWithAttributeAppender() throws Exception {
+ MethodAttributeAppender.Factory methodAttributeAppenderFactory = mock(MethodAttributeAppender.Factory.class);
+ ConstructorStrategy constructorStrategy = ConstructorStrategy.Default.NO_CONSTRUCTORS.with(methodAttributeAppenderFactory);
+ assertThat(constructorStrategy.extractConstructors(instrumentedType).size(), is(0));
+ assertThat(constructorStrategy.inject(methodRegistry), is(methodRegistry));
+ verifyZeroInteractions(methodRegistry);
+ verifyZeroInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testNoConstructorsStrategyWithInheritedAnnotations() throws Exception {
+ ConstructorStrategy constructorStrategy = ConstructorStrategy.Default.NO_CONSTRUCTORS.withInheritedAnnotations();
+ assertThat(constructorStrategy.extractConstructors(instrumentedType).size(), is(0));
+ assertThat(constructorStrategy.inject(methodRegistry), is(methodRegistry));
+ verifyZeroInteractions(methodRegistry);
+ verifyZeroInteractions(instrumentedType);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testImitateSuperClassStrategy() throws Exception {
+ assertThat(ConstructorStrategy.Default.IMITATE_SUPER_CLASS.extractConstructors(instrumentedType), is(Collections.singletonList(stripped)));
+ assertThat(ConstructorStrategy.Default.IMITATE_SUPER_CLASS.inject(methodRegistry), is(methodRegistry));
+ verify(methodRegistry).append(any(LatentMatcher.class),
+ any(MethodRegistry.Handler.class),
+ eq(MethodAttributeAppender.NoOp.INSTANCE),
+ eq(Transformer.NoOp.<MethodDescription>make()));
+ verifyNoMoreInteractions(methodRegistry);
+ verify(instrumentedType, atLeastOnce()).getSuperClass();
+ verifyNoMoreInteractions(instrumentedType);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testImitateSuperClassStrategyWithAttributeAppender() throws Exception {
+ when(methodDescription.getModifiers()).thenReturn(Opcodes.ACC_PUBLIC);
+ MethodAttributeAppender.Factory methodAttributeAppenderFactory = mock(MethodAttributeAppender.Factory.class);
+ ConstructorStrategy constructorStrategy = ConstructorStrategy.Default.IMITATE_SUPER_CLASS.with(methodAttributeAppenderFactory);
+ assertThat(constructorStrategy.extractConstructors(instrumentedType), is(Collections.singletonList(stripped)));
+ assertThat(constructorStrategy.inject(methodRegistry), is(methodRegistry));
+ verify(methodRegistry).append(any(LatentMatcher.class),
+ any(MethodRegistry.Handler.class),
+ eq(methodAttributeAppenderFactory),
+ eq(Transformer.NoOp.<MethodDescription>make()));
+ verifyNoMoreInteractions(methodRegistry);
+ verify(instrumentedType, atLeastOnce()).getSuperClass();
+ verifyNoMoreInteractions(instrumentedType);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testImitateSuperClassStrategyWithInheritedAnnotations() throws Exception {
+ when(methodDescription.getModifiers()).thenReturn(Opcodes.ACC_PUBLIC);
+ ConstructorStrategy constructorStrategy = ConstructorStrategy.Default.IMITATE_SUPER_CLASS.withInheritedAnnotations();
+ assertThat(constructorStrategy.extractConstructors(instrumentedType), is(Collections.singletonList(stripped)));
+ assertThat(constructorStrategy.inject(methodRegistry), is(methodRegistry));
+ verify(methodRegistry).append(any(LatentMatcher.class),
+ any(MethodRegistry.Handler.class),
+ eq(MethodAttributeAppender.ForInstrumentedMethod.EXCLUDING_RECEIVER),
+ eq(Transformer.NoOp.<MethodDescription>make()));
+ verifyNoMoreInteractions(methodRegistry);
+ verify(instrumentedType, atLeastOnce()).getSuperClass();
+ verifyNoMoreInteractions(instrumentedType);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testImitateSuperClassPublicStrategy() throws Exception {
+ when(methodDescription.getModifiers()).thenReturn(Opcodes.ACC_PUBLIC);
+ assertThat(ConstructorStrategy.Default.IMITATE_SUPER_CLASS_PUBLIC.extractConstructors(instrumentedType), is(Collections.singletonList(stripped)));
+ assertThat(ConstructorStrategy.Default.IMITATE_SUPER_CLASS_PUBLIC.inject(methodRegistry), is(methodRegistry));
+ verify(methodRegistry).append(any(LatentMatcher.class),
+ any(MethodRegistry.Handler.class),
+ eq(MethodAttributeAppender.NoOp.INSTANCE),
+ eq(Transformer.NoOp.<MethodDescription>make()));
+ verifyNoMoreInteractions(methodRegistry);
+ verify(instrumentedType, atLeastOnce()).getSuperClass();
+ verifyNoMoreInteractions(instrumentedType);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testImitateSuperClassPublicStrategyWithAttributeAppender() throws Exception {
+ when(methodDescription.getModifiers()).thenReturn(Opcodes.ACC_PUBLIC);
+ MethodAttributeAppender.Factory methodAttributeAppenderFactory = mock(MethodAttributeAppender.Factory.class);
+ ConstructorStrategy constructorStrategy = ConstructorStrategy.Default.IMITATE_SUPER_CLASS_PUBLIC.with(methodAttributeAppenderFactory);
+ assertThat(constructorStrategy.extractConstructors(instrumentedType), is(Collections.singletonList(stripped)));
+ assertThat(constructorStrategy.inject(methodRegistry), is(methodRegistry));
+ verify(methodRegistry).append(any(LatentMatcher.class),
+ any(MethodRegistry.Handler.class),
+ eq(methodAttributeAppenderFactory),
+ eq(Transformer.NoOp.<MethodDescription>make()));
+ verifyNoMoreInteractions(methodRegistry);
+ verify(instrumentedType, atLeastOnce()).getSuperClass();
+ verifyNoMoreInteractions(instrumentedType);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testImitateSuperClassPublicStrategyWithInheritedAnnotations() throws Exception {
+ when(methodDescription.getModifiers()).thenReturn(Opcodes.ACC_PUBLIC);
+ ConstructorStrategy constructorStrategy = ConstructorStrategy.Default.IMITATE_SUPER_CLASS_PUBLIC.withInheritedAnnotations();
+ assertThat(constructorStrategy.extractConstructors(instrumentedType), is(Collections.singletonList(stripped)));
+ assertThat(constructorStrategy.inject(methodRegistry), is(methodRegistry));
+ verify(methodRegistry).append(any(LatentMatcher.class),
+ any(MethodRegistry.Handler.class),
+ eq(MethodAttributeAppender.ForInstrumentedMethod.EXCLUDING_RECEIVER),
+ eq(Transformer.NoOp.<MethodDescription>make()));
+ verifyNoMoreInteractions(methodRegistry);
+ verify(instrumentedType, atLeastOnce()).getSuperClass();
+ verifyNoMoreInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testImitateSuperClassPublicStrategyDoesNotSeeNonPublic() throws Exception {
+ when(methodDescription.getModifiers()).thenReturn(0);
+ assertThat(ConstructorStrategy.Default.IMITATE_SUPER_CLASS_PUBLIC.extractConstructors(instrumentedType).size(), is(0));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testDefaultConstructorStrategy() throws Exception {
+ when(methodDescription.getParameters()).thenReturn(new ParameterList.Empty<ParameterDescription.InGenericShape>());
+ assertThat(ConstructorStrategy.Default.DEFAULT_CONSTRUCTOR.extractConstructors(instrumentedType),
+ is(Collections.singletonList(new MethodDescription.Token(Opcodes.ACC_PUBLIC))));
+ assertThat(ConstructorStrategy.Default.DEFAULT_CONSTRUCTOR.inject(methodRegistry), is(methodRegistry));
+ verify(methodRegistry).append(any(LatentMatcher.class),
+ any(MethodRegistry.Handler.class),
+ eq(MethodAttributeAppender.NoOp.INSTANCE),
+ eq(Transformer.NoOp.<MethodDescription>make()));
+ verifyNoMoreInteractions(methodRegistry);
+ verify(instrumentedType).getSuperClass();
+ verifyNoMoreInteractions(instrumentedType);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testDefaultConstructorStrategyWithAttributeAppender() throws Exception {
+ when(methodDescription.getParameters()).thenReturn(new ParameterList.Empty<ParameterDescription.InGenericShape>());
+ MethodAttributeAppender.Factory methodAttributeAppenderFactory = mock(MethodAttributeAppender.Factory.class);
+ ConstructorStrategy constructorStrategy = ConstructorStrategy.Default.DEFAULT_CONSTRUCTOR.with(methodAttributeAppenderFactory);
+ assertThat(constructorStrategy.extractConstructors(instrumentedType),
+ is(Collections.singletonList(new MethodDescription.Token(Opcodes.ACC_PUBLIC))));
+ assertThat(constructorStrategy.inject(methodRegistry), is(methodRegistry));
+ verify(methodRegistry).append(any(LatentMatcher.class),
+ any(MethodRegistry.Handler.class),
+ eq(methodAttributeAppenderFactory),
+ eq(Transformer.NoOp.<MethodDescription>make()));
+ verifyNoMoreInteractions(methodRegistry);
+ verify(instrumentedType).getSuperClass();
+ verifyNoMoreInteractions(instrumentedType);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testDefaultConstructorStrategyWithInheritedAnnotations() throws Exception {
+ when(methodDescription.getParameters()).thenReturn(new ParameterList.Empty<ParameterDescription.InGenericShape>());
+ ConstructorStrategy constructorStrategy = ConstructorStrategy.Default.DEFAULT_CONSTRUCTOR.withInheritedAnnotations();
+ assertThat(constructorStrategy.extractConstructors(instrumentedType),
+ is(Collections.singletonList(new MethodDescription.Token(Opcodes.ACC_PUBLIC))));
+ assertThat(constructorStrategy.inject(methodRegistry), is(methodRegistry));
+ verify(methodRegistry).append(any(LatentMatcher.class),
+ any(MethodRegistry.Handler.class),
+ eq(MethodAttributeAppender.ForInstrumentedMethod.EXCLUDING_RECEIVER),
+ eq(Transformer.NoOp.<MethodDescription>make()));
+ verifyNoMoreInteractions(methodRegistry);
+ verify(instrumentedType).getSuperClass();
+ verifyNoMoreInteractions(instrumentedType);
+ }
+
+
+ @Test(expected = IllegalArgumentException.class)
+ @SuppressWarnings("unchecked")
+ public void testDefaultConstructorStrategyNoDefault() throws Exception {
+ when(methodDescription.getParameters())
+ .thenReturn(new ParameterList.Explicit<ParameterDescription.InGenericShape>(mock(ParameterDescription.InGenericShape.class)));
+ ConstructorStrategy.Default.DEFAULT_CONSTRUCTOR.extractConstructors(instrumentedType);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testImitateSuperClassOpeningStrategyNonVisible() throws Exception {
+ when(methodDescription.isVisibleTo(instrumentedType)).thenReturn(false);
+ assertThat(ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING.extractConstructors(instrumentedType).isEmpty(), is(true));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testImitateSuperClassOpeningStrategy() throws Exception {
+ assertThat(ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING.extractConstructors(instrumentedType), is(Collections.singletonList(new MethodDescription.Token(FOO,
+ Opcodes.ACC_PUBLIC,
+ Collections.<TypeVariableToken>emptyList(),
+ typeDescription,
+ Collections.<ParameterDescription.Token>emptyList(),
+ Collections.<TypeDescription.Generic>emptyList(),
+ Collections.<AnnotationDescription>emptyList(),
+ defaultValue,
+ TypeDescription.Generic.UNDEFINED))));
+ assertThat(ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING.inject(methodRegistry), is(methodRegistry));
+ verify(methodRegistry).append(any(LatentMatcher.class),
+ any(MethodRegistry.Handler.class),
+ eq(MethodAttributeAppender.NoOp.INSTANCE),
+ eq(Transformer.NoOp.<MethodDescription>make()));
+ verifyNoMoreInteractions(methodRegistry);
+ verify(instrumentedType, atLeastOnce()).getSuperClass();
+ verifyNoMoreInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ConstructorStrategy.Default.class).apply();
+ ObjectPropertyAssertion.of(ConstructorStrategy.Default.WithMethodAttributeAppenderFactory.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/subclass/SubclassDynamicTypeBuilderInstrumentableMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/subclass/SubclassDynamicTypeBuilderInstrumentableMatcherTest.java
new file mode 100644
index 0000000..e8ccf44
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/subclass/SubclassDynamicTypeBuilderInstrumentableMatcherTest.java
@@ -0,0 +1,150 @@
+package net.bytebuddy.dynamic.scaffold.subclass;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.matcher.LatentMatcher;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.Opcodes;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class SubclassDynamicTypeBuilderInstrumentableMatcherTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodDescription methodDescription;
+
+ @Mock
+ private TypeDescription rawTypeDescription, rawOtherType;
+
+ @Mock
+ private TypeDescription.Generic typeDescription, otherType;
+
+ @Mock
+ private LatentMatcher<? super MethodDescription> latentIgnoredMethods;
+
+ @Mock
+ private ElementMatcher<? super MethodDescription> ignoredMethods;
+
+ private LatentMatcher<MethodDescription> matcher;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ when(rawTypeDescription.asGenericType()).thenReturn(typeDescription);
+ when(rawTypeDescription.asErasure()).thenReturn(rawTypeDescription);
+ when(typeDescription.asErasure()).thenReturn(rawTypeDescription);
+ when(typeDescription.asGenericType()).thenReturn(typeDescription);
+ when(typeDescription.asErasure()).thenReturn(rawTypeDescription);
+ when(typeDescription.getSort()).thenReturn(TypeDefinition.Sort.NON_GENERIC);
+ when(rawOtherType.asGenericType()).thenReturn(otherType);
+ when(rawOtherType.asErasure()).thenReturn(rawOtherType);
+ when(otherType.asErasure()).thenReturn(rawOtherType);
+ when(otherType.asGenericType()).thenReturn(otherType);
+ when(otherType.asErasure()).thenReturn(rawOtherType);
+ when(otherType.getSort()).thenReturn(TypeDefinition.Sort.NON_GENERIC);
+ when(latentIgnoredMethods.resolve(any(TypeDescription.class))).thenReturn((ElementMatcher) ignoredMethods);
+ matcher = new SubclassDynamicTypeBuilder.InstrumentableMatcher(latentIgnoredMethods);
+ }
+
+ @Test
+ public void testMatchesVirtual() throws Exception {
+ when(methodDescription.isVirtual()).thenReturn(true);
+ when(methodDescription.getModifiers()).thenReturn(0);
+ when(ignoredMethods.matches(methodDescription)).thenReturn(false);
+ when(methodDescription.getDeclaringType()).thenReturn(rawOtherType);
+ when(methodDescription.isVisibleTo(rawTypeDescription)).thenReturn(true);
+ assertThat(matcher.resolve(rawTypeDescription).matches(methodDescription), is(true));
+ }
+
+ @Test
+ public void testNotMatchesVirtualIfNotVisible() throws Exception {
+ when(methodDescription.isVirtual()).thenReturn(true);
+ when(methodDescription.getModifiers()).thenReturn(0);
+ when(ignoredMethods.matches(methodDescription)).thenReturn(false);
+ when(methodDescription.getDeclaringType()).thenReturn(rawOtherType);
+ when(methodDescription.isVisibleTo(rawTypeDescription)).thenReturn(false);
+ assertThat(matcher.resolve(rawTypeDescription).matches(methodDescription), is(false));
+ }
+
+ @Test
+ public void testNotMatchesVirtualIfFinal() throws Exception {
+ when(methodDescription.isVirtual()).thenReturn(true);
+ when(methodDescription.getModifiers()).thenReturn(Opcodes.ACC_FINAL);
+ when(ignoredMethods.matches(methodDescription)).thenReturn(false);
+ when(methodDescription.getDeclaringType()).thenReturn(rawOtherType);
+ when(methodDescription.isVisibleTo(rawTypeDescription)).thenReturn(false);
+ assertThat(matcher.resolve(rawTypeDescription).matches(methodDescription), is(false));
+ }
+
+ @Test
+ public void testNotMatchesNonVirtualIfNotDeclared() throws Exception {
+ when(methodDescription.isVirtual()).thenReturn(false);
+ when(methodDescription.getModifiers()).thenReturn(0);
+ when(ignoredMethods.matches(methodDescription)).thenReturn(false);
+ when(methodDescription.getDeclaringType()).thenReturn(rawOtherType);
+ assertThat(matcher.resolve(rawTypeDescription).matches(methodDescription), is(false));
+ }
+
+ @Test
+ public void testNotMatchesIgnoredMethodIfNotDeclared() throws Exception {
+ when(methodDescription.isVirtual()).thenReturn(true);
+ when(methodDescription.getModifiers()).thenReturn(0);
+ when(ignoredMethods.matches(methodDescription)).thenReturn(true);
+ when(methodDescription.getDeclaringType()).thenReturn(rawOtherType);
+ assertThat(matcher.resolve(rawTypeDescription).matches(methodDescription), is(false));
+ }
+
+ @Test
+ public void testMatchesDeclaredMethod() throws Exception {
+ when(methodDescription.isVirtual()).thenReturn(true);
+ when(methodDescription.getModifiers()).thenReturn(0);
+ when(ignoredMethods.matches(methodDescription)).thenReturn(false);
+ when(methodDescription.getDeclaringType()).thenReturn(rawTypeDescription);
+ assertThat(matcher.resolve(rawTypeDescription).matches(methodDescription), is(true));
+ }
+
+ @Test
+ public void testMatchesDeclaredMethodIfIgnored() throws Exception {
+ when(methodDescription.isVirtual()).thenReturn(true);
+ when(methodDescription.getModifiers()).thenReturn(0);
+ when(ignoredMethods.matches(methodDescription)).thenReturn(true);
+ when(methodDescription.getDeclaringType()).thenReturn(rawTypeDescription);
+ assertThat(matcher.resolve(rawTypeDescription).matches(methodDescription), is(true));
+ }
+
+ @Test
+ public void testMatchesDeclaredMethodIfNotVirtual() throws Exception {
+ when(methodDescription.isVirtual()).thenReturn(false);
+ when(methodDescription.getModifiers()).thenReturn(0);
+ when(ignoredMethods.matches(methodDescription)).thenReturn(false);
+ when(methodDescription.getDeclaringType()).thenReturn(rawTypeDescription);
+ assertThat(matcher.resolve(rawTypeDescription).matches(methodDescription), is(true));
+ }
+
+ @Test
+ public void testMatchesDeclaredMethodIfFinal() throws Exception {
+ when(methodDescription.isVirtual()).thenReturn(true);
+ when(methodDescription.getModifiers()).thenReturn(Opcodes.ACC_FINAL);
+ when(ignoredMethods.matches(methodDescription)).thenReturn(false);
+ when(methodDescription.getDeclaringType()).thenReturn(rawTypeDescription);
+ assertThat(matcher.resolve(rawTypeDescription).matches(methodDescription), is(true));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(SubclassDynamicTypeBuilder.InstrumentableMatcher.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/subclass/SubclassDynamicTypeBuilderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/subclass/SubclassDynamicTypeBuilderTest.java
new file mode 100644
index 0000000..72476bb
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/subclass/SubclassDynamicTypeBuilderTest.java
@@ -0,0 +1,668 @@
+package net.bytebuddy.dynamic.scaffold.subclass;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.description.type.PackageDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.AbstractDynamicTypeBuilderTest;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.TargetType;
+import net.bytebuddy.dynamic.loading.ByteArrayClassLoader;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.dynamic.loading.PackageDefinitionStrategy;
+import net.bytebuddy.dynamic.scaffold.TypeValidation;
+import net.bytebuddy.implementation.FixedValue;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.MethodDelegation;
+import net.bytebuddy.implementation.StubMethod;
+import net.bytebuddy.implementation.bytecode.constant.TextConstant;
+import net.bytebuddy.implementation.bytecode.member.MethodReturn;
+import net.bytebuddy.test.scope.GenericType;
+import net.bytebuddy.test.utility.ClassFileExtraction;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.hamcrest.CoreMatchers;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+import org.objectweb.asm.Opcodes;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Type;
+import java.security.ProtectionDomain;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static junit.framework.TestCase.assertEquals;
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class SubclassDynamicTypeBuilderTest extends AbstractDynamicTypeBuilderTest {
+
+ private static final String TYPE_VARIABLE_NAME = "net.bytebuddy.test.precompiled.TypeAnnotation", VALUE = "value";
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux";
+
+ private static final int BAZ = 42;
+
+ private static final String DEFAULT_METHOD_INTERFACE = "net.bytebuddy.test.precompiled.SingleDefaultMethodInterface";
+
+ private static final String PARAMETER_NAME_CLASS = "net.bytebuddy.test.precompiled.ParameterNames";
+
+ private static final Object STATIC_FIELD = null;
+
+ private static final String INTERFACE_STATIC_FIELD_NAME = "FOO";
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ @Override
+ protected DynamicType.Builder<?> createPlain() {
+ return new ByteBuddy().subclass(Object.class);
+ }
+
+ @Override
+ protected DynamicType.Builder<?> createPlainWithoutValidation() {
+ return new ByteBuddy().with(TypeValidation.DISABLED).subclass(Object.class);
+ }
+
+ @Test
+ public void testSimpleSubclass() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .subclass(Object.class)
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethods().length, is(0));
+ assertThat(type.getDeclaredConstructors().length, is(1));
+ assertThat(type.getDeclaredConstructor(), notNullValue(Constructor.class));
+ assertThat(Object.class.isAssignableFrom(type), is(true));
+ assertThat(type, not(CoreMatchers.<Class<?>>is(Object.class)));
+ assertThat(type.getDeclaredConstructor().newInstance(), notNullValue(Object.class));
+ assertThat(type.isInterface(), is(false));
+ assertThat(type.isAnnotation(), is(false));
+ }
+
+ @Test
+ public void testSimpleSubclassWithoutConstructor() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .subclass(Object.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethods().length, is(0));
+ assertThat(type.getDeclaredConstructors().length, is(0));
+ assertThat(Object.class.isAssignableFrom(type), is(true));
+ assertThat(type, not(CoreMatchers.<Class<?>>is(Object.class)));
+ assertThat(type.isInterface(), is(false));
+ assertThat(type.isAnnotation(), is(false));
+ }
+
+ @Test
+ public void testSimpleSubclassWithDefaultConstructor() throws Exception {
+ Class<? extends DefaultConstructor> type = new ByteBuddy()
+ .subclass(DefaultConstructor.class, ConstructorStrategy.Default.DEFAULT_CONSTRUCTOR)
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethods().length, is(0));
+ assertThat(type.getDeclaredConstructors().length, is(1));
+ assertThat(type.getDeclaredConstructor(), notNullValue(Constructor.class));
+ assertThat(DefaultConstructor.class.isAssignableFrom(type), is(true));
+ assertThat(type, not(CoreMatchers.<Class<?>>is(DefaultConstructor.class)));
+ assertThat(type.getDeclaredConstructor().newInstance(), notNullValue(DefaultConstructor.class));
+ assertThat(type.isInterface(), is(false));
+ assertThat(type.isAnnotation(), is(false));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNonExtendableIsIllegal() throws Exception {
+ new ByteBuddy().subclass(String.class);
+ }
+
+ @Test
+ public void testInterfaceDefinition() throws Exception {
+ Class<? extends SimpleInterface> type = new ByteBuddy()
+ .makeInterface(SimpleInterface.class)
+ .defineMethod(FOO, void.class, Visibility.PUBLIC).withParameters(Void.class)
+ .withoutCode()
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethods().length, is(1));
+ assertThat(type.getDeclaredMethod(FOO, Void.class), notNullValue(Method.class));
+ assertThat(type.getDeclaredConstructors().length, is(0));
+ assertThat(SimpleInterface.class.isAssignableFrom(type), is(true));
+ assertThat(type, not(CoreMatchers.<Class<?>>is(SimpleInterface.class)));
+ assertThat(type.isInterface(), is(true));
+ assertThat(type.isAnnotation(), is(false));
+ }
+
+ @Test
+ public void testAnnotationDefinition() throws Exception {
+ Class<? extends Annotation> type = new ByteBuddy()
+ .makeAnnotation()
+ .defineMethod(FOO, int.class, Visibility.PUBLIC)
+ .withoutCode()
+ .defineMethod(BAR, String.class, Visibility.PUBLIC)
+ .defaultValue(FOO, String.class)
+ .defineMethod(QUX, SimpleEnum.class, Visibility.PUBLIC)
+ .defaultValue(SimpleEnum.FIRST, SimpleEnum.class)
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethods().length, is(3));
+ assertThat(type.getDeclaredMethod(FOO), notNullValue(Method.class));
+ assertThat(type.getDeclaredMethod(BAR).getDefaultValue(), is((Object) FOO));
+ assertThat(type.getDeclaredMethod(QUX).getDefaultValue(), is((Object) SimpleEnum.FIRST));
+ assertThat(type.getDeclaredConstructors().length, is(0));
+ assertThat(Annotation.class.isAssignableFrom(type), is(true));
+ assertThat(type, not(CoreMatchers.<Class<?>>is(Annotation.class)));
+ assertThat(type.isInterface(), is(true));
+ assertThat(type.isAnnotation(), is(true));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testEnumerationDefinition() throws Exception {
+ Class<? extends Enum<?>> type = new ByteBuddy()
+ .makeEnumeration(FOO, BAR)
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethods().length, is(2));
+ assertThat(type.getDeclaredConstructors().length, is(1));
+ assertThat(type.getDeclaredFields().length, is(3));
+ assertThat(Enum.class.isAssignableFrom(type), is(true));
+ assertThat(type, not(CoreMatchers.<Class<?>>is(Enum.class)));
+ assertThat(type.isInterface(), is(false));
+ assertThat(type.isAnnotation(), is(false));
+ assertThat(type.isEnum(), is(true));
+ Enum foo = Enum.valueOf((Class) type, FOO);
+ assertThat(foo.name(), is(FOO));
+ assertThat(foo.ordinal(), is(0));
+ Enum bar = Enum.valueOf((Class) type, BAR);
+ assertThat(bar.name(), is(BAR));
+ assertThat(bar.ordinal(), is(1));
+ }
+
+ @Test
+ public void testPackageDefinition() throws Exception {
+ Class<?> packageType = new ByteBuddy()
+ .makePackage(FOO)
+ .annotateType(AnnotationDescription.Builder.ofType(Foo.class).build())
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded();
+ assertThat(packageType.getSimpleName(), is(PackageDescription.PACKAGE_CLASS_NAME));
+ assertThat(packageType.getName(), is(FOO + "." + PackageDescription.PACKAGE_CLASS_NAME));
+ assertThat(packageType.getModifiers(), is(PackageDescription.PACKAGE_MODIFIERS));
+ assertThat(packageType.getDeclaredFields().length, is(0));
+ assertThat(packageType.getDeclaredMethods().length, is(0));
+ assertThat(packageType.getDeclaredAnnotations().length, is(1));
+ assertThat(packageType.getAnnotation(Foo.class), notNullValue(Foo.class));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testDefaultMethodNonOverridden() throws Exception {
+ Class<?> interfaceType = Class.forName(DEFAULT_METHOD_INTERFACE);
+ Object interfaceMarker = interfaceType.getDeclaredField(INTERFACE_STATIC_FIELD_NAME).get(STATIC_FIELD);
+ Method interfaceMethod = interfaceType.getDeclaredMethod(FOO);
+ Class<?> dynamicType = new ByteBuddy()
+ .subclass(interfaceType)
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(dynamicType.getDeclaredFields().length, is(0));
+ assertThat(dynamicType.getDeclaredMethods().length, is(0));
+ assertThat(interfaceMethod.invoke(dynamicType.getDeclaredConstructor().newInstance()), is(interfaceMarker));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testDefaultMethodOverridden() throws Exception {
+ Class<?> interfaceType = Class.forName(DEFAULT_METHOD_INTERFACE);
+ Method interfaceMethod = interfaceType.getDeclaredMethod(FOO);
+ Class<?> dynamicType = new ByteBuddy()
+ .subclass(interfaceType)
+ .method(isDeclaredBy(interfaceType)).intercept(FixedValue.value(BAR))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(dynamicType.getDeclaredFields().length, is(0));
+ assertThat(dynamicType.getDeclaredMethods().length, is(1));
+ assertThat(interfaceMethod.invoke(dynamicType.getDeclaredConstructor().newInstance()), is((Object) BAR));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testParameterMetaDataSubclassForLoaded() throws Exception {
+ Class<?> dynamicType = new ByteBuddy()
+ .subclass(Class.forName(PARAMETER_NAME_CLASS))
+ .method(named(FOO)).intercept(StubMethod.INSTANCE)
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(dynamicType.getDeclaredMethods().length, is(1));
+ Class<?> executable = Class.forName("java.lang.reflect.Executable");
+ Method getParameters = executable.getDeclaredMethod("getParameters");
+ Class<?> parameter = Class.forName("java.lang.reflect.Parameter");
+ Method getName = parameter.getDeclaredMethod("getName");
+ Method getModifiers = parameter.getDeclaredMethod("getModifiers");
+ Method first = dynamicType.getDeclaredMethod("foo", String.class, long.class, int.class);
+ Object[] methodParameter = (Object[]) getParameters.invoke(first);
+ assertThat(getName.invoke(methodParameter[0]), is((Object) "first"));
+ assertThat(getName.invoke(methodParameter[1]), is((Object) "second"));
+ assertThat(getName.invoke(methodParameter[2]), is((Object) "third"));
+ assertThat(getModifiers.invoke(methodParameter[0]), is((Object) Opcodes.ACC_FINAL));
+ assertThat(getModifiers.invoke(methodParameter[1]), is((Object) 0));
+ assertThat(getModifiers.invoke(methodParameter[2]), is((Object) 0));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testDefaultInterfaceSubInterface() throws Exception {
+ Class<?> interfaceType = Class.forName(DEFAULT_METHOD_INTERFACE);
+ Class<?> dynamicInterfaceType = new ByteBuddy()
+ .subclass(interfaceType)
+ .modifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT)
+ .method(named(FOO)).intercept(MethodDelegation.to(InterfaceOverrideInterceptor.class))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ Class<?> dynamicClassType = new ByteBuddy()
+ .subclass(dynamicInterfaceType)
+ .make()
+ .load(dynamicInterfaceType.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(dynamicClassType.getMethod(FOO).invoke(dynamicClassType.getDeclaredConstructor().newInstance()), is((Object) (FOO + BAR)));
+ assertThat(dynamicInterfaceType.getDeclaredMethods().length, is(2));
+ assertThat(dynamicClassType.getDeclaredMethods().length, is(0));
+ }
+
+ @Test
+ public void testDoesNotOverrideMethodWithPackagePrivateReturnType() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .subclass(PackagePrivateReturnType.class)
+ .name("net.bytebuddy.test.generated." + FOO)
+ .method(isDeclaredBy(PackagePrivateReturnType.class))
+ .intercept(StubMethod.INSTANCE)
+ .make()
+ .load(new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER,
+ ClassFileExtraction.of(PackagePrivateReturnType.class, PackagePrivateReturnType.Argument.class)), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethods().length, is(0));
+ }
+
+ @Test
+ public void testDoesNotOverrideMethodWithPackagePrivateArgumentType() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .subclass(PackagePrivateArgumentType.class)
+ .name("net.bytebuddy.test.generated." + FOO)
+ .method(isDeclaredBy(PackagePrivateArgumentType.class))
+ .intercept(StubMethod.INSTANCE)
+ .make()
+ .load(new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER,
+ ClassFileExtraction.of(PackagePrivateArgumentType.class, PackagePrivateArgumentType.Argument.class)), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethods().length, is(0));
+ }
+
+ @Test
+ public void testDoesNotOverridePrivateMethod() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .subclass(PrivateMethod.class)
+ .method(isDeclaredBy(PrivateMethod.class))
+ .intercept(StubMethod.INSTANCE)
+ .make()
+ .load(new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER,
+ ClassFileExtraction.of(PrivateMethod.class)), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(type.getDeclaredMethods().length, is(0));
+ }
+
+ @Test
+ public void testGenericTypeRawExtension() throws Exception {
+ Class<?> dynamicType = new ByteBuddy()
+ .subclass(GenericType.Inner.class)
+ .method(named(FOO).or(named("call"))).intercept(StubMethod.INSTANCE)
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded();
+ assertThat(dynamicType.getTypeParameters().length, is(0));
+ assertThat(dynamicType.getGenericSuperclass(), instanceOf(Class.class));
+ assertThat(dynamicType.getGenericSuperclass(), is((Type) GenericType.Inner.class));
+ assertThat(dynamicType.getGenericInterfaces().length, is(0));
+ Method foo = dynamicType.getDeclaredMethod(FOO, String.class);
+ assertThat(foo.getTypeParameters().length, is(0));
+ assertThat(foo.getGenericReturnType(), is((Object) List.class));
+ Method call = dynamicType.getDeclaredMethod("call");
+ assertThat(call.getGenericReturnType(), is((Object) Map.class));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testBridgeMethodCreation() throws Exception {
+ Class<?> dynamicType = new ByteBuddy()
+ .subclass(BridgeRetention.Inner.class)
+ .method(named(FOO)).intercept(new Implementation.Simple(new TextConstant(FOO), MethodReturn.REFERENCE))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded();
+ assertEquals(String.class, dynamicType.getDeclaredMethod(FOO).getReturnType());
+ assertThat(dynamicType.getDeclaredMethod(FOO).getGenericReturnType(), is((Type) String.class));
+ BridgeRetention<String> bridgeRetention = (BridgeRetention<String>) dynamicType.getDeclaredConstructor().newInstance();
+ assertThat(bridgeRetention.foo(), is(FOO));
+ bridgeRetention.assertZeroCalls();
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testBridgeMethodCreationForExistingBridgeMethod() throws Exception {
+ Class<?> dynamicType = new ByteBuddy()
+ .subclass(CallSuperMethod.Inner.class)
+ .method(named(FOO)).intercept(net.bytebuddy.implementation.SuperMethodCall.INSTANCE)
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded();
+ assertThat(dynamicType.getDeclaredMethods().length, is(2));
+ assertEquals(String.class, dynamicType.getDeclaredMethod(FOO, String.class).getReturnType());
+ assertThat(dynamicType.getDeclaredMethod(FOO, String.class).getGenericReturnType(), is((Type) String.class));
+ assertThat(dynamicType.getDeclaredMethod(FOO, String.class).isBridge(), is(false));
+ assertEquals(Object.class, dynamicType.getDeclaredMethod(FOO, Object.class).getReturnType());
+ assertThat(dynamicType.getDeclaredMethod(FOO, Object.class).getGenericReturnType(), is((Type) Object.class));
+ assertThat(dynamicType.getDeclaredMethod(FOO, Object.class).isBridge(), is(true));
+ CallSuperMethod<String> callSuperMethod = (CallSuperMethod<String>) dynamicType.getDeclaredConstructor().newInstance();
+ assertThat(callSuperMethod.foo(FOO), is(FOO));
+ callSuperMethod.assertOnlyCall(FOO);
+ }
+
+ @Test
+ public void testBridgeMethodForAbstractMethod() throws Exception {
+ Class<?> dynamicType = new ByteBuddy()
+ .subclass(AbstractGenericType.Inner.class)
+ .modifiers(Opcodes.ACC_ABSTRACT | Opcodes.ACC_PUBLIC)
+ .method(named(FOO)).withoutCode()
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded();
+ assertThat(dynamicType.getDeclaredMethods().length, is(2));
+ assertEquals(Void.class, dynamicType.getDeclaredMethod(FOO, Void.class).getReturnType());
+ assertThat(dynamicType.getDeclaredMethod(FOO, Void.class).getGenericReturnType(), is((Type) Void.class));
+ assertThat(dynamicType.getDeclaredMethod(FOO, Void.class).isBridge(), is(false));
+ assertThat(Modifier.isAbstract(dynamicType.getDeclaredMethod(FOO, Void.class).getModifiers()), is(true));
+ assertEquals(Object.class, dynamicType.getDeclaredMethod(FOO, Object.class).getReturnType());
+ assertThat(dynamicType.getDeclaredMethod(FOO, Object.class).getGenericReturnType(), is((Type) Object.class));
+ assertThat(dynamicType.getDeclaredMethod(FOO, Object.class).isBridge(), is(true));
+ assertThat(Modifier.isAbstract(dynamicType.getDeclaredMethod(FOO, Object.class).getModifiers()), is(false));
+ }
+
+ @Test
+ public void testVisibilityBridge() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .subclass(VisibilityBridge.class)
+ .modifiers(Visibility.PUBLIC)
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded();
+ assertThat(type.getDeclaredConstructors().length, is(1));
+ assertThat(type.getDeclaredMethods().length, is(2));
+ Method foo = type.getDeclaredMethod(FOO, String.class);
+ assertThat(foo.isBridge(), is(true));
+ assertThat(foo.getDeclaredAnnotations().length, is(1));
+ assertThat(foo.getAnnotation(Foo.class), notNullValue(Foo.class));
+ assertThat(foo.invoke(type.getDeclaredConstructor().newInstance(), BAR), is((Object) (FOO + BAR)));
+ Method bar = type.getDeclaredMethod(BAR, List.class);
+ assertThat(bar.isBridge(), is(true));
+ assertThat(bar.getDeclaredAnnotations().length, is(0));
+ List<?> list = new ArrayList<Object>();
+ assertThat(bar.invoke(type.getDeclaredConstructor().newInstance(), list), sameInstance((Object) list));
+ assertThat(bar.getGenericReturnType(), instanceOf(Class.class));
+ assertThat(bar.getGenericParameterTypes()[0], instanceOf(Class.class));
+ assertThat(bar.getGenericExceptionTypes()[0], instanceOf(Class.class));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testVisibilityBridgeForDefaultMethod() throws Exception {
+ Class<?> defaultInterface = new ByteBuddy()
+ .makeInterface()
+ .merge(Visibility.PACKAGE_PRIVATE)
+ .defineMethod(FOO, String.class, Visibility.PUBLIC)
+ .intercept(FixedValue.value(BAR))
+ .make()
+ .load(ClassLoadingStrategy.BOOTSTRAP_LOADER)
+ .getLoaded();
+ Class<?> type = new ByteBuddy()
+ .subclass(defaultInterface)
+ .modifiers(Visibility.PUBLIC)
+ .make()
+ .load(defaultInterface.getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded();
+ assertThat(type.getDeclaredConstructors().length, is(1));
+ assertThat(type.getDeclaredMethods().length, is(1));
+ Method foo = type.getDeclaredMethod(FOO);
+ assertThat(foo.isBridge(), is(true));
+ assertThat(foo.invoke(type.getDeclaredConstructor().newInstance()), is((Object) (BAR)));
+ }
+
+ @Test
+ public void testNoVisibilityBridgeForNonPublicType() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .subclass(VisibilityBridge.class)
+ .modifiers(0)
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded();
+ assertThat(type.getDeclaredConstructors().length, is(1));
+ assertThat(type.getDeclaredMethods().length, is(0));
+ }
+
+ @Test
+ public void testNoVisibilityBridgeForInheritedType() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .subclass(VisibilityBridgeExtension.class)
+ .modifiers(Opcodes.ACC_PUBLIC)
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded();
+ assertThat(type.getDeclaredConstructors().length, is(1));
+ assertThat(type.getDeclaredMethods().length, is(0));
+ }
+
+ @Test
+ public void testNoVisibilityBridgeForAbstractMethod() throws Exception {
+ Class<?> type = new ByteBuddy()
+ .subclass(VisibilityBridgeAbstractMethod.class)
+ .modifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_ABSTRACT)
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded();
+ assertThat(type.getDeclaredConstructors().length, is(1));
+ assertThat(type.getDeclaredMethods().length, is(0));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testAnnotationTypeOnSuperClass() throws Exception {
+ Class<? extends Annotation> typeAnnotationType = (Class<? extends Annotation>) Class.forName(TYPE_VARIABLE_NAME);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotationType).getDeclaredMethods().filter(named(VALUE)).getOnly();
+ Class<?> type = new ByteBuddy()
+ .subclass(TypeDescription.Generic.Builder.rawType(Object.class)
+ .build(AnnotationDescription.Builder.ofType(typeAnnotationType).define(VALUE, BAZ).build()))
+ .make()
+ .load(typeAnnotationType.getClassLoader(), ClassLoadingStrategy.Default.CHILD_FIRST)
+ .getLoaded();
+ assertThat(type.getSuperclass(), is((Object) Object.class));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveSuperClassType(type).asList().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveSuperClassType(type).asList().ofType(typeAnnotationType)
+ .getValue(value).resolve(Integer.class), is(BAZ));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testReceiverTypeDefinition() throws Exception {
+ Class<? extends Annotation> typeAnnotationType = (Class<? extends Annotation>) Class.forName(TYPE_VARIABLE_NAME);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotationType).getDeclaredMethods().filter(named(VALUE)).getOnly();
+ Method method = createPlain()
+ .defineMethod(FOO, void.class)
+ .intercept(StubMethod.INSTANCE)
+ .receiverType(TypeDescription.Generic.Builder.rawType(TargetType.class)
+ .annotate(AnnotationDescription.Builder.ofType(typeAnnotationType).define(VALUE, BAZ).build())
+ .build())
+ .make()
+ .load(typeAnnotationType.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded()
+ .getDeclaredMethod(FOO);
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveReceiverType(method).getDeclaredAnnotations().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveReceiverType(method).getDeclaredAnnotations()
+ .ofType(typeAnnotationType).getValue(value).resolve(Integer.class), is(BAZ));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ @SuppressWarnings("unchecked")
+ public void testReceiverTypeInterception() throws Exception {
+ Class<? extends Annotation> typeAnnotationType = (Class<? extends Annotation>) Class.forName(TYPE_VARIABLE_NAME);
+ MethodDescription.InDefinedShape value = new TypeDescription.ForLoadedType(typeAnnotationType).getDeclaredMethods().filter(named(VALUE)).getOnly();
+ Method method = createPlain()
+ .method(named("toString"))
+ .intercept(StubMethod.INSTANCE)
+ .receiverType(TypeDescription.Generic.Builder.rawType(TargetType.class)
+ .annotate(AnnotationDescription.Builder.ofType(typeAnnotationType).define(VALUE, BAZ).build())
+ .build())
+ .make()
+ .load(typeAnnotationType.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded()
+ .getDeclaredMethod("toString");
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveReceiverType(method).getDeclaredAnnotations().size(), is(1));
+ assertThat(TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveReceiverType(method).getDeclaredAnnotations()
+ .ofType(typeAnnotationType).getValue(value).resolve(Integer.class), is(BAZ));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(SubclassDynamicTypeBuilder.class).create(new ObjectPropertyAssertion.Creator<List<?>>() {
+ @Override
+ public List<?> create() {
+ return Collections.singletonList(new Object());
+ }
+ }).apply();
+ }
+
+ @SuppressWarnings("unused")
+ public enum SimpleEnum {
+ FIRST,
+ SECOND
+ }
+
+ public interface SimpleInterface {
+
+ void bar(Void arg);
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface Foo {
+ /* empty */
+ }
+
+ @SuppressWarnings("unused")
+ public static class DefaultConstructor {
+
+ public DefaultConstructor() {
+ /* empty */
+ }
+
+ public DefaultConstructor(Void arg) {
+ /* empty */
+ }
+ }
+
+ public static class PackagePrivateReturnType {
+
+ public Argument foo() {
+ return null;
+ }
+
+ static class Argument {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class PackagePrivateArgumentType {
+
+ public void foo(Argument argument) {
+ /* empty */
+ }
+
+ static class Argument {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class PrivateMethod {
+
+ private void foo() {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ static class VisibilityBridge {
+
+ @Foo
+ public String foo(@Foo String value) {
+ return FOO + value;
+ }
+
+ public <T extends Exception> List<String> bar(List<String> value) throws T {
+ return value;
+ }
+
+ void qux() {
+ /* empty */
+ }
+
+ protected void baz() {
+ /* empty */
+ }
+
+ public final void foobar() {
+ /* empty */
+ }
+ }
+
+ abstract static class VisibilityBridgeAbstractMethod {
+
+ public abstract void foo();
+ }
+
+ public static class VisibilityBridgeExtension extends VisibilityBridge {
+ /* empty */
+ }
+
+ public abstract static class AbstractGenericType<T> {
+
+ public abstract T foo(T t);
+
+ public abstract static class Inner extends AbstractGenericType<Void> {
+ /* empty */
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/subclass/SubclassImplementationTargetFactoryTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/subclass/SubclassImplementationTargetFactoryTest.java
new file mode 100644
index 0000000..25dd6fa
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/subclass/SubclassImplementationTargetFactoryTest.java
@@ -0,0 +1,66 @@
+package net.bytebuddy.dynamic.scaffold.subclass;
+
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.scaffold.MethodGraph;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class SubclassImplementationTargetFactoryTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodGraph.Linked methodGraph;
+
+ @Mock
+ private TypeDescription instrumentedType, rawSuperClass;
+
+ @Mock
+ private ClassFileVersion classFileVersion;
+
+ @Mock
+ private TypeDescription.Generic superClass;
+
+ @Before
+ public void setUp() throws Exception {
+ when(instrumentedType.getSuperClass()).thenReturn(superClass);
+ when(superClass.asErasure()).thenReturn(rawSuperClass);
+ when(superClass.getDeclaredMethods()).thenReturn(new MethodList.Empty<MethodDescription.InGenericShape>());
+ }
+
+ @Test
+ public void testReturnsSubclassImplementationTarget() throws Exception {
+ assertThat(SubclassImplementationTarget.Factory.SUPER_CLASS.make(instrumentedType, methodGraph, classFileVersion), instanceOf(SubclassImplementationTarget.class));
+ assertThat(SubclassImplementationTarget.Factory.LEVEL_TYPE.make(instrumentedType, methodGraph, classFileVersion), instanceOf(SubclassImplementationTarget.class));
+ }
+
+ @Test
+ public void testOriginTypeSuperClass() throws Exception {
+ assertThat(SubclassImplementationTarget.Factory.SUPER_CLASS.make(instrumentedType, methodGraph, classFileVersion).getOriginType(), is((TypeDefinition) superClass));
+ }
+
+ @Test
+ public void testOriginTypeLevelType() throws Exception {
+ assertThat(SubclassImplementationTarget.Factory.LEVEL_TYPE.make(instrumentedType, methodGraph, classFileVersion).getOriginType(), is((TypeDefinition) instrumentedType));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(SubclassImplementationTarget.Factory.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/subclass/SubclassImplementationTargetTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/subclass/SubclassImplementationTargetTest.java
new file mode 100644
index 0000000..836c292
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/dynamic/scaffold/subclass/SubclassImplementationTargetTest.java
@@ -0,0 +1,125 @@
+package net.bytebuddy.dynamic.scaffold.subclass;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.scaffold.MethodGraph;
+import net.bytebuddy.implementation.AbstractImplementationTargetTest;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class SubclassImplementationTargetTest extends AbstractImplementationTargetTest {
+
+ private static final String BAR = "bar", BAZ = "baz";
+
+ @Mock
+ private TypeDescription.Generic superClass;
+
+ @Mock
+ private TypeDescription rawSuperClass;
+
+ @Mock
+ private MethodDescription.InGenericShape superClassConstructor;
+
+ @Mock
+ private MethodDescription.InDefinedShape definedSuperClassConstructor;
+
+ @Mock
+ private MethodDescription.SignatureToken superConstructorToken;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ when(superGraph.locate(Mockito.any(MethodDescription.SignatureToken.class))).thenReturn(MethodGraph.Node.Unresolved.INSTANCE);
+ when(superGraph.locate(invokableToken)).thenReturn(new MethodGraph.Node.Simple(invokableMethod));
+ when(instrumentedType.getSuperClass()).thenReturn(superClass);
+ when(superClass.asErasure()).thenReturn(rawSuperClass);
+ when(superClass.asGenericType()).thenReturn(superClass);
+ when(rawSuperClass.asGenericType()).thenReturn(superClass);
+ when(rawSuperClass.asErasure()).thenReturn(rawSuperClass);
+ when(rawSuperClass.getInternalName()).thenReturn(BAR);
+ when(superClass.getDeclaredMethods())
+ .thenReturn(new MethodList.Explicit<MethodDescription.InGenericShape>(superClassConstructor));
+ when(superClassConstructor.asDefined()).thenReturn(definedSuperClassConstructor);
+ when(definedSuperClassConstructor.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(definedSuperClassConstructor.getDeclaringType()).thenReturn(rawSuperClass);
+ when(definedSuperClassConstructor.isConstructor()).thenReturn(true);
+ when(superClassConstructor.isVisibleTo(instrumentedType)).thenReturn(true);
+ when(superClassConstructor.asSignatureToken()).thenReturn(superConstructorToken);
+ when(definedSuperClassConstructor.getInternalName()).thenReturn(QUX);
+ when(definedSuperClassConstructor.getDescriptor()).thenReturn(BAZ);
+ when(superClassConstructor.isConstructor()).thenReturn(true);
+ when(superClassConstructor.getDeclaringType()).thenReturn(superClass);
+ when(superClassConstructor.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(superClassConstructor.getParameters()).thenReturn(new ParameterList.Empty<ParameterDescription.InGenericShape>());
+ when(invokableToken.getName()).thenReturn(FOO);
+ when(superConstructorToken.getName()).thenReturn(MethodDescription.CONSTRUCTOR_INTERNAL_NAME);
+ super.setUp();
+ }
+
+ @Override
+ protected Implementation.Target makeImplementationTarget() {
+ return new SubclassImplementationTarget(instrumentedType, methodGraph, defaultMethodInvocation, SubclassImplementationTarget.OriginTypeResolver.SUPER_CLASS);
+ }
+
+ @Test
+ public void testSuperTypeMethodIsInvokable() throws Exception {
+ when(invokableMethod.isSpecializableFor(rawSuperClass)).thenReturn(true);
+ Implementation.SpecialMethodInvocation specialMethodInvocation = makeImplementationTarget().invokeSuper(invokableToken);
+ assertThat(specialMethodInvocation.isValid(), is(true));
+ assertThat(specialMethodInvocation.getMethodDescription(), is((MethodDescription) invokableMethod));
+ assertThat(specialMethodInvocation.getTypeDescription(), is(rawSuperClass));
+ MethodVisitor methodVisitor = mock(MethodVisitor.class);
+ Implementation.Context implementationContext = mock(Implementation.Context.class);
+ StackManipulation.Size size = specialMethodInvocation.apply(methodVisitor, implementationContext);
+ verify(methodVisitor).visitMethodInsn(Opcodes.INVOKESPECIAL, BAR, FOO, QUX, false);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ assertThat(size.getSizeImpact(), is(0));
+ assertThat(size.getMaximalSize(), is(0));
+ }
+
+ @Test
+ public void testNonSpecializableSuperClassMethodIsNotInvokable() throws Exception {
+ when(invokableMethod.isSpecializableFor(rawSuperClass)).thenReturn(false);
+ Implementation.SpecialMethodInvocation specialMethodInvocation = makeImplementationTarget().invokeSuper(invokableToken);
+ assertThat(specialMethodInvocation.isValid(), is(false));
+ }
+
+ @Test
+ public void testSuperConstructorIsInvokable() throws Exception {
+ when(invokableMethod.isConstructor()).thenReturn(true);
+ when(definedSuperClassConstructor.isSpecializableFor(rawSuperClass)).thenReturn(true);
+ Implementation.SpecialMethodInvocation specialMethodInvocation = makeImplementationTarget().invokeSuper(superConstructorToken);
+ assertThat(specialMethodInvocation.isValid(), is(true));
+ assertThat(specialMethodInvocation.getMethodDescription(), is((MethodDescription) superClassConstructor));
+ assertThat(specialMethodInvocation.getTypeDescription(), is(rawSuperClass));
+ MethodVisitor methodVisitor = mock(MethodVisitor.class);
+ Implementation.Context implementationContext = mock(Implementation.Context.class);
+ StackManipulation.Size size = specialMethodInvocation.apply(methodVisitor, implementationContext);
+ verify(methodVisitor).visitMethodInsn(Opcodes.INVOKESPECIAL, BAR, QUX, BAZ, false);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ assertThat(size.getSizeImpact(), is(0));
+ assertThat(size.getMaximalSize(), is(0));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(SubclassImplementationTarget.class).apply();
+ ObjectPropertyAssertion.of(SubclassImplementationTarget.OriginTypeResolver.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/AbstractImplementationTargetTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/AbstractImplementationTargetTest.java
new file mode 100644
index 0000000..ebb52d5
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/AbstractImplementationTargetTest.java
@@ -0,0 +1,128 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.scaffold.MethodGraph;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public abstract class AbstractImplementationTargetTest {
+
+ protected static final String FOO = "foo", QUX = "qux", BAZ = "baz", QUXBAZ = "quxbaz", FOOBAZ = "foobaz", BAZBAR = "bazbar";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ protected MethodGraph.Linked methodGraph, superGraph, defaultGraph;
+
+ @Mock
+ protected TypeDescription instrumentedType, methodDeclaringType, returnType, defaultMethodDeclaringType;
+
+ @Mock
+ protected TypeDescription.Generic genericInstrumentedType, genericReturnType;
+
+ @Mock
+ protected MethodDescription.InDefinedShape invokableMethod, defaultMethod;
+
+ @Mock
+ protected MethodDescription.SignatureToken invokableToken, defaultToken;
+
+ protected Implementation.Target.AbstractBase.DefaultMethodInvocation defaultMethodInvocation;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ when(instrumentedType.asErasure()).thenReturn(instrumentedType);
+ when(instrumentedType.getInternalName()).thenReturn(BAZ);
+ when(methodGraph.getSuperClassGraph()).thenReturn(superGraph);
+ when(superGraph.locate(any(MethodDescription.SignatureToken.class))).thenReturn(MethodGraph.Node.Unresolved.INSTANCE);
+ when(superGraph.locate(invokableToken)).thenReturn(new MethodGraph.Node.Simple(invokableMethod));
+ when(methodGraph.getInterfaceGraph(defaultMethodDeclaringType)).thenReturn(defaultGraph);
+ when(defaultGraph.locate(any(MethodDescription.SignatureToken.class))).thenReturn(MethodGraph.Node.Unresolved.INSTANCE);
+ when(defaultGraph.locate(defaultToken)).thenReturn(new MethodGraph.Node.Simple(defaultMethod));
+ when(methodDeclaringType.asErasure()).thenReturn(methodDeclaringType);
+ when(invokableMethod.getDeclaringType()).thenReturn(methodDeclaringType);
+ when(invokableMethod.getReturnType()).thenReturn(genericReturnType);
+ when(returnType.getStackSize()).thenReturn(StackSize.ZERO);
+ when(genericReturnType.getStackSize()).thenReturn(StackSize.ZERO);
+ when(returnType.asErasure()).thenReturn(returnType);
+ when(invokableMethod.getInternalName()).thenReturn(FOO);
+ when(invokableMethod.getDescriptor()).thenReturn(QUX);
+ when(invokableMethod.asSignatureToken()).thenReturn(invokableToken);
+ when(invokableMethod.asDefined()).thenReturn(invokableMethod);
+ when(defaultMethod.getInternalName()).thenReturn(QUXBAZ);
+ when(defaultMethod.getDescriptor()).thenReturn(FOOBAZ);
+ when(defaultMethod.getDeclaringType()).thenReturn(defaultMethodDeclaringType);
+ when(defaultMethod.getReturnType()).thenReturn(genericReturnType);
+ when(defaultMethod.asSignatureToken()).thenReturn(defaultToken);
+ when(defaultMethod.asDefined()).thenReturn(defaultMethod);
+ when(defaultMethod.isSpecializableFor(defaultMethodDeclaringType)).thenReturn(true);
+ when(defaultMethodDeclaringType.isInterface()).thenReturn(true);
+ when(defaultMethodDeclaringType.asErasure()).thenReturn(defaultMethodDeclaringType);
+ when(defaultMethodDeclaringType.getInternalName()).thenReturn(BAZBAR);
+ when(genericReturnType.asErasure()).thenReturn(returnType);
+ when(genericReturnType.asGenericType()).thenReturn(genericReturnType);
+ when(returnType.asGenericType()).thenReturn(genericReturnType);
+ when(genericInstrumentedType.asErasure()).thenReturn(instrumentedType);
+ when(genericInstrumentedType.asGenericType()).thenReturn(genericInstrumentedType);
+ when(instrumentedType.asGenericType()).thenReturn(genericInstrumentedType);
+ defaultMethodInvocation = Implementation.Target.AbstractBase.DefaultMethodInvocation.ENABLED;
+ }
+
+ protected abstract Implementation.Target makeImplementationTarget();
+
+ @Test
+ public void testDefaultMethodInvocation() throws Exception {
+ Implementation.SpecialMethodInvocation specialMethodInvocation = makeImplementationTarget().invokeDefault(defaultToken, defaultMethodDeclaringType);
+ assertThat(specialMethodInvocation.isValid(), is(true));
+ assertThat(specialMethodInvocation.getMethodDescription(), is((MethodDescription) defaultMethod));
+ assertThat(specialMethodInvocation.getTypeDescription(), is(defaultMethodDeclaringType));
+ MethodVisitor methodVisitor = mock(MethodVisitor.class);
+ Implementation.Context implementationContext = mock(Implementation.Context.class);
+ StackManipulation.Size size = specialMethodInvocation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(0));
+ assertThat(size.getMaximalSize(), is(0));
+ verify(methodVisitor).visitMethodInsn(Opcodes.INVOKESPECIAL, BAZBAR, QUXBAZ, FOOBAZ, true);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testDefaultMethodInvocationNotSupported() throws Exception {
+ defaultMethodInvocation = Implementation.Target.AbstractBase.DefaultMethodInvocation.DISABLED;
+ Implementation.SpecialMethodInvocation specialMethodInvocation = makeImplementationTarget().invokeDefault(defaultToken, defaultMethodDeclaringType);
+ assertThat(specialMethodInvocation.isValid(), is(false));
+ }
+
+ @Test
+ public void testIllegalDefaultMethod() throws Exception {
+ assertThat(makeImplementationTarget().invokeDefault(mock(MethodDescription.SignatureToken.class), defaultMethodDeclaringType).isValid(), is(false));
+ }
+
+ @Test
+ public void testIllegalSuperMethod() throws Exception {
+ MethodDescription.SignatureToken token = mock(MethodDescription.SignatureToken.class);
+ when(token.getName()).thenReturn(FOO);
+ assertThat(makeImplementationTarget().invokeSuper(token).isValid(), is(false));
+ }
+
+ @Test
+ public void testIllegalSuperConstructor() throws Exception {
+ MethodDescription.SignatureToken token = mock(MethodDescription.SignatureToken.class);
+ when(token.getName()).thenReturn(MethodDescription.CONSTRUCTOR_INTERNAL_NAME);
+ assertThat(makeImplementationTarget().invokeSuper(token).isValid(), is(false));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/AbstractSpecialMethodInvocationTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/AbstractSpecialMethodInvocationTest.java
new file mode 100644
index 0000000..87daa3e
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/AbstractSpecialMethodInvocationTest.java
@@ -0,0 +1,77 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+public abstract class AbstractSpecialMethodInvocationTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodDescription methodDescription, otherMethod;
+
+ @Mock
+ private MethodDescription.SignatureToken token, otherToken;
+
+ @Mock
+ private TypeDescription typeDescription, otherType;
+
+ @Mock
+ private StackManipulation stackManipulation;
+
+ @Before
+ public void setUp() throws Exception {
+ when(methodDescription.asSignatureToken()).thenReturn(token);
+ when(otherMethod.asSignatureToken()).thenReturn(otherToken);
+ }
+
+ protected abstract Implementation.SpecialMethodInvocation make(MethodDescription methodDescription, TypeDescription typeDescription);
+
+ @Test
+ public void testEquality() throws Exception {
+ assertThat(make(methodDescription, typeDescription).hashCode(),
+ is(new Implementation.SpecialMethodInvocation.Simple(methodDescription, typeDescription, stackManipulation).hashCode()));
+ assertThat(make(methodDescription, typeDescription),
+ is((Implementation.SpecialMethodInvocation) new Implementation.SpecialMethodInvocation.Simple(methodDescription,
+ typeDescription,
+ stackManipulation)));
+ }
+
+ @Test
+ public void testTypeInequality() throws Exception {
+ assertThat(make(methodDescription, typeDescription).hashCode(),
+ not(new Implementation.SpecialMethodInvocation.Simple(methodDescription, otherType, stackManipulation).hashCode()));
+ assertThat(make(methodDescription, typeDescription),
+ not((Implementation.SpecialMethodInvocation) new Implementation.SpecialMethodInvocation.Simple(methodDescription,
+ otherType,
+ stackManipulation)));
+ }
+
+ @Test
+ public void testTokenInequality() throws Exception {
+ assertThat(make(methodDescription, typeDescription).hashCode(),
+ not(new Implementation.SpecialMethodInvocation.Simple(otherMethod, typeDescription, stackManipulation).hashCode()));
+ assertThat(make(methodDescription, typeDescription),
+ not((Implementation.SpecialMethodInvocation) new Implementation.SpecialMethodInvocation.Simple(otherMethod,
+ typeDescription,
+ stackManipulation)));
+ }
+
+ @Test
+ public void testValidity() throws Exception {
+ assertThat(make(methodDescription, typeDescription).isValid(), is(true));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/DefaultMethodCallTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/DefaultMethodCallTest.java
new file mode 100644
index 0000000..bdc2e19
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/DefaultMethodCallTest.java
@@ -0,0 +1,204 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static net.bytebuddy.matcher.ElementMatchers.not;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class DefaultMethodCallTest {
+
+ private static final String FOO = "foo", QUX = "qux";
+
+ private static final String SINGLE_DEFAULT_METHOD = "net.bytebuddy.test.precompiled.SingleDefaultMethodInterface";
+
+ private static final String SINGLE_DEFAULT_METHOD_CLASS = "net.bytebuddy.test.precompiled.SingleDefaultMethodClass";
+
+ private static final String CONFLICTING_INTERFACE = "net.bytebuddy.test.precompiled.SingleDefaultMethodConflictingInterface";
+
+ private static final String NON_OVERRIDING_INTERFACE = "net.bytebuddy.test.precompiled.SingleDefaultMethodNonOverridingInterface";
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testUnambiguousDefaultMethod() throws Exception {
+ DynamicType.Loaded<?> loaded = new ByteBuddy()
+ .subclass(Object.class)
+ .implement(Class.forName(SINGLE_DEFAULT_METHOD))
+ .intercept(DefaultMethodCall.unambiguousOnly())
+ .make()
+ .load(Class.forName(SINGLE_DEFAULT_METHOD).getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ Method method = loaded.getLoaded().getDeclaredMethod(FOO);
+ Object instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(method.invoke(instance), is((Object) FOO));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ @JavaVersionRule.Enforce(8)
+ public void testAmbiguousDefaultMethodThrowsException() throws Exception {
+ new ByteBuddy()
+ .subclass(Object.class)
+ .implement(Class.forName(SINGLE_DEFAULT_METHOD), Class.forName(CONFLICTING_INTERFACE))
+ .intercept(DefaultMethodCall.unambiguousOnly())
+ .make();
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testAmbiguousDefaultMethodWithExplicitPreference() throws Exception {
+ Class<?> singleMethodInterface = Class.forName(SINGLE_DEFAULT_METHOD);
+ Class<?> conflictingInterface = Class.forName(CONFLICTING_INTERFACE);
+ assertConflictChoice(singleMethodInterface, conflictingInterface, FOO, singleMethodInterface);
+ assertConflictChoice(singleMethodInterface, conflictingInterface, QUX, conflictingInterface);
+ assertConflictChoice(singleMethodInterface, conflictingInterface, FOO, singleMethodInterface, conflictingInterface);
+ assertConflictChoice(singleMethodInterface, conflictingInterface, QUX, conflictingInterface, singleMethodInterface);
+ assertConflictChoice(singleMethodInterface, conflictingInterface, FOO, singleMethodInterface, Class.forName(NON_OVERRIDING_INTERFACE));
+ assertConflictChoice(singleMethodInterface, conflictingInterface, FOO, Class.forName(NON_OVERRIDING_INTERFACE), singleMethodInterface);
+ }
+
+ private void assertConflictChoice(Class<?> preferredInterface,
+ Class<?> secondInterface,
+ Object expectedResult,
+ Class<?>... preferredInterfaces) throws Exception {
+ DynamicType.Loaded<?> loaded = new ByteBuddy()
+ .subclass(Object.class)
+ .implement(preferredInterface, secondInterface)
+ .intercept(DefaultMethodCall.prioritize(preferredInterfaces))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ Method method = loaded.getLoaded().getDeclaredMethod(FOO);
+ Object instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(method.invoke(instance), is(expectedResult));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ @JavaVersionRule.Enforce(8)
+ public void testUnrelatedPreferredDefaultMethodThrowsException() throws Exception {
+ new ByteBuddy()
+ .subclass(Object.class)
+ .implement(Class.forName(SINGLE_DEFAULT_METHOD), Class.forName(CONFLICTING_INTERFACE))
+ .intercept(DefaultMethodCall.prioritize(Class.forName(NON_OVERRIDING_INTERFACE)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ @JavaVersionRule.Enforce(8)
+ public void testNonDeclaredDefaultMethodThrowsException() throws Exception {
+ new ByteBuddy()
+ .subclass(Class.forName(SINGLE_DEFAULT_METHOD_CLASS))
+ .method(not(isDeclaredBy(Object.class)))
+ .intercept(DefaultMethodCall.unambiguousOnly())
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ @JavaVersionRule.Enforce(8)
+ public void testNonDeclaredPreferredDefaultMethodThrowsException() throws Exception {
+ new ByteBuddy()
+ .subclass(Class.forName(SINGLE_DEFAULT_METHOD_CLASS))
+ .method(not(isDeclaredBy(Object.class)))
+ .intercept(DefaultMethodCall.prioritize(Class.forName(SINGLE_DEFAULT_METHOD)))
+ .make();
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testDeclaredAndImplementedMethod() throws Exception {
+ DynamicType.Loaded<?> loaded = new ByteBuddy()
+ .subclass(Class.forName(SINGLE_DEFAULT_METHOD_CLASS))
+ .implement(Class.forName(SINGLE_DEFAULT_METHOD))
+ .method(not(isDeclaredBy(Object.class)))
+ .intercept(DefaultMethodCall.unambiguousOnly())
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ Method method = loaded.getLoaded().getDeclaredMethod(FOO);
+ Object instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(method.invoke(instance), is((Object) FOO));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ @JavaVersionRule.Enforce(8)
+ public void testDeclaredAndImplementedAmbiguousMethodThrowsException() throws Exception {
+ new ByteBuddy()
+ .subclass(Class.forName(SINGLE_DEFAULT_METHOD_CLASS))
+ .implement(Class.forName(SINGLE_DEFAULT_METHOD), Class.forName(CONFLICTING_INTERFACE))
+ .method(not(isDeclaredBy(Object.class)))
+ .intercept(DefaultMethodCall.unambiguousOnly())
+ .make();
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testDeclaredAndImplementedAmbiguousMethodWithPreference() throws Exception {
+ DynamicType.Loaded<?> loaded = new ByteBuddy()
+ .subclass(Class.forName(SINGLE_DEFAULT_METHOD_CLASS))
+ .implement(Class.forName(SINGLE_DEFAULT_METHOD), Class.forName(CONFLICTING_INTERFACE))
+ .method(not(isDeclaredBy(Object.class)))
+ .intercept(DefaultMethodCall.prioritize(Class.forName(SINGLE_DEFAULT_METHOD)))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ Method method = loaded.getLoaded().getDeclaredMethod(FOO);
+ Object instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(method.invoke(instance), is((Object) FOO));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(DefaultMethodCall.class).create(new ObjectPropertyAssertion.Creator<List<?>>() {
+ @Override
+ public List<?> create() {
+ TypeDescription typeDescription = mock(TypeDescription.class);
+ when(typeDescription.isInterface()).thenReturn(true);
+ when(typeDescription.asErasure()).thenReturn(typeDescription);
+ when(typeDescription.getSort()).thenReturn(TypeDefinition.Sort.NON_GENERIC);
+ return Collections.singletonList(typeDescription);
+ }
+ }).apply();
+ final TypeDescription removalType = mock(TypeDescription.class);
+ final TypeDescription.Generic genericRemovalType = mock(TypeDescription.Generic.class);
+ when(genericRemovalType.asGenericType()).thenReturn(genericRemovalType);
+ when(genericRemovalType.asErasure()).thenReturn(removalType);
+ ObjectPropertyAssertion.of(DefaultMethodCall.Appender.class).refine(new ObjectPropertyAssertion.Refinement<Implementation.Target>() {
+ @Override
+ public void apply(Implementation.Target mock) {
+ TypeDescription typeDescription = mock(TypeDescription.class), otherType = mock(TypeDescription.class);
+ TypeDescription.Generic otherGenericType = mock(TypeDescription.Generic.class);
+ when(otherGenericType.asErasure()).thenReturn(otherType);
+ when(otherGenericType.asGenericType()).thenReturn(otherGenericType);
+ when(typeDescription.getInterfaces()).thenReturn(new TypeList.Generic.Explicit(genericRemovalType, otherGenericType));
+ when(mock.getInstrumentedType()).thenReturn(typeDescription);
+ }
+ }).create(new ObjectPropertyAssertion.Creator<List<?>>() {
+ @Override
+ public List<?> create() {
+ TypeDescription typeDescription = mock(TypeDescription.class);
+ when(typeDescription.asErasure()).thenReturn(typeDescription);
+ return Arrays.asList(removalType, typeDescription);
+ }
+ }).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/ExceptionMethodTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/ExceptionMethodTest.java
new file mode 100644
index 0000000..5175a18
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/ExceptionMethodTest.java
@@ -0,0 +1,118 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.test.utility.CallTraceable;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.when;
+
+public class ExceptionMethodTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ @Test
+ public void testWithoutMessage() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(ExceptionMethod.throwing(RuntimeException.class))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(Foo.class)));
+ assertThat(instance, instanceOf(Foo.class));
+ try {
+ instance.foo();
+ fail();
+ } catch (RuntimeException exception) {
+ assertThat(exception.getClass(), CoreMatchers.<Class<?>>is(RuntimeException.class));
+ assertThat(exception.getMessage(), nullValue());
+ }
+ instance.assertZeroCalls();
+ }
+
+ @Test
+ public void testWithMessage() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(ExceptionMethod.throwing(RuntimeException.class, BAR))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(Foo.class)));
+ assertThat(instance, instanceOf(Foo.class));
+ try {
+ instance.foo();
+ fail();
+ } catch (RuntimeException exception) {
+ assertThat(exception.getClass(), CoreMatchers.<Class<?>>is(RuntimeException.class));
+ assertThat(exception.getMessage(), is(BAR));
+ }
+ instance.assertZeroCalls();
+ }
+
+ @Test
+ public void testWithNonDeclaredCheckedException() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(ExceptionMethod.throwing(Exception.class))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(Foo.class)));
+ assertThat(instance, instanceOf(Foo.class));
+ try {
+ instance.foo();
+ fail();
+ } catch (Exception exception) {
+ assertThat(exception.getClass(), CoreMatchers.<Class<?>>is(Exception.class));
+ assertThat(exception.getMessage(), nullValue());
+ }
+ instance.assertZeroCalls();
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ExceptionMethod.class).apply();
+ ObjectPropertyAssertion.of(ExceptionMethod.ConstructionDelegate.ForDefaultConstructor.class).refine(new ObjectPropertyAssertion.Refinement<TypeDescription>() {
+ @Override
+ public void apply(TypeDescription mock) {
+ when(mock.getDeclaredMethods()).thenReturn(new MethodList.ForLoadedMethods(Object.class));
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(ExceptionMethod.ConstructionDelegate.ForStringConstructor.class).refine(new ObjectPropertyAssertion.Refinement<TypeDescription>() {
+ @Override
+ public void apply(TypeDescription mock) {
+ when(mock.getDeclaredMethods()).thenReturn(new MethodList.ForLoadedMethods(String.class));
+ }
+ }).apply();
+ }
+
+ public static class Foo extends CallTraceable {
+
+ public void foo() {
+ register(FOO);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/FieldAccessorFieldNameExtractorForBeanPropertyTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/FieldAccessorFieldNameExtractorForBeanPropertyTest.java
new file mode 100644
index 0000000..11b045a
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/FieldAccessorFieldNameExtractorForBeanPropertyTest.java
@@ -0,0 +1,71 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class FieldAccessorFieldNameExtractorForBeanPropertyTest {
+
+ private static final String FOO = "foo", FOO_CAPITAL = "Foo";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodDescription methodDescription;
+
+ @Test
+ public void testGetterMethod() throws Exception {
+ when(methodDescription.getInternalName()).thenReturn("get" + FOO_CAPITAL);
+ assertThat(FieldAccessor.FieldNameExtractor.ForBeanProperty.INSTANCE.resolve(methodDescription), is(FOO));
+ }
+
+ @Test
+ public void testSetterMethod() throws Exception {
+ when(methodDescription.getInternalName()).thenReturn("set" + FOO_CAPITAL);
+ assertThat(FieldAccessor.FieldNameExtractor.ForBeanProperty.INSTANCE.resolve(methodDescription), is(FOO));
+ }
+
+ @Test
+ public void testGetterMethodBooleanPrefix() throws Exception {
+ when(methodDescription.getInternalName()).thenReturn("is" + FOO_CAPITAL);
+ assertThat(FieldAccessor.FieldNameExtractor.ForBeanProperty.INSTANCE.resolve(methodDescription), is(FOO));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testEmptyGetter() throws Exception {
+ when(methodDescription.getInternalName()).thenReturn("get");
+ FieldAccessor.FieldNameExtractor.ForBeanProperty.INSTANCE.resolve(methodDescription);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testEmptySetter() throws Exception {
+ when(methodDescription.getInternalName()).thenReturn("set");
+ FieldAccessor.FieldNameExtractor.ForBeanProperty.INSTANCE.resolve(methodDescription);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testEmptyGetterBooleanPrefix() throws Exception {
+ when(methodDescription.getInternalName()).thenReturn("is");
+ FieldAccessor.FieldNameExtractor.ForBeanProperty.INSTANCE.resolve(methodDescription);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testIllegalName() throws Exception {
+ when(methodDescription.getInternalName()).thenReturn(FOO);
+ FieldAccessor.FieldNameExtractor.ForBeanProperty.INSTANCE.resolve(methodDescription);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(FieldAccessor.FieldNameExtractor.ForBeanProperty.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/FieldAccessorFieldNameExtractorForFixedValueTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/FieldAccessorFieldNameExtractorForFixedValueTest.java
new file mode 100644
index 0000000..519b9f1
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/FieldAccessorFieldNameExtractorForFixedValueTest.java
@@ -0,0 +1,33 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class FieldAccessorFieldNameExtractorForFixedValueTest {
+
+ private static final String FOO = "foo", FOO_CAPITAL = "Foo";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodDescription methodDescription;
+
+ @Test
+ public void testGetterMethod() throws Exception {
+ assertThat(new FieldAccessor.FieldNameExtractor.ForFixedValue(FOO).resolve(methodDescription), is(FOO));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(FieldAccessor.FieldNameExtractor.ForFixedValue.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/FieldAccessorOtherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/FieldAccessorOtherTest.java
new file mode 100644
index 0000000..20fa797
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/FieldAccessorOtherTest.java
@@ -0,0 +1,264 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.modifier.FieldManifestation;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
+import net.bytebuddy.test.utility.CallTraceable;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class FieldAccessorOtherTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux", BAZ = "baz";
+
+ @Test
+ public void testArgumentSetter() throws Exception {
+ Class<? extends SampleArgumentSetter> loaded = new ByteBuddy()
+ .subclass(SampleArgumentSetter.class)
+ .method(named(FOO))
+ .intercept(FieldAccessor.ofField(FOO).setsArgumentAt(0))
+ .make()
+ .load(SampleArgumentSetter.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ SampleArgumentSetter sampleArgumentSetter = loaded.getDeclaredConstructor().newInstance();
+ sampleArgumentSetter.foo(FOO);
+ assertThat(sampleArgumentSetter.foo, is((Object) FOO));
+ }
+
+ @Test
+ public void testArgumentSetterChained() throws Exception {
+ Class<? extends SampleArgumentSetter> loaded = new ByteBuddy()
+ .subclass(SampleArgumentSetter.class)
+ .method(named(BAR))
+ .intercept(FieldAccessor.ofField(FOO).setsArgumentAt(0).andThen(FixedValue.value(BAR)))
+ .make()
+ .load(SampleArgumentSetter.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ SampleArgumentSetter sampleArgumentSetter = loaded.getDeclaredConstructor().newInstance();
+ assertThat(sampleArgumentSetter.bar(FOO), is((Object) BAR));
+ assertThat(sampleArgumentSetter.foo, is((Object) FOO));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testArgumentSetterNonVoid() throws Exception {
+ new ByteBuddy()
+ .subclass(SampleArgumentSetter.class)
+ .method(named(BAR))
+ .intercept(FieldAccessor.ofField(FOO).setsArgumentAt(0))
+ .make();
+ }
+
+ @Test
+ public void testArgumentSetterConstructor() throws Exception {
+ Class<?> loaded = new ByteBuddy()
+ .subclass(Object.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
+ .defineField(FOO, String.class, Visibility.PUBLIC, FieldManifestation.FINAL)
+ .defineConstructor(Visibility.PUBLIC)
+ .withParameters(String.class)
+ .intercept(MethodCall.invoke(Object.class.getDeclaredConstructor()).andThen(FieldAccessor.ofField(FOO).setsArgumentAt(0)))
+ .make()
+ .load(null, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(loaded.getDeclaredField(FOO).get(loaded.getDeclaredConstructor(String.class).newInstance(FOO)), is((Object) FOO));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testArgumentCannotBeNegative() throws Exception {
+ FieldAccessor.ofField(FOO).setsArgumentAt(-1);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testArgumentSetterNoParameterAtIndex() throws Exception {
+ new ByteBuddy()
+ .subclass(SampleArgumentSetter.class)
+ .method(named(FOO))
+ .intercept(FieldAccessor.ofField(FOO).setsArgumentAt(1).andThen(FixedValue.value(BAR)))
+ .make();
+ }
+
+ @Test
+ public void testExplicitNameSetter() throws Exception {
+ DynamicType.Loaded<SampleSetter> loaded = new ByteBuddy()
+ .subclass(SampleSetter.class)
+ .method(isDeclaredBy(SampleSetter.class))
+ .intercept(FieldAccessor.ofField(FOO))
+ .make()
+ .load(SampleSetter.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ SampleSetter sampleSetter = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ Field field = SampleSetter.class.getDeclaredField(FOO);
+ field.setAccessible(true);
+ assertThat(field.get(sampleSetter), is((Object) QUX));
+ sampleSetter.bar(BAZ);
+ assertThat(field.get(sampleSetter), is((Object) BAZ));
+ sampleSetter.assertZeroCalls();
+ }
+
+ @Test
+ public void testExplicitNameGetter() throws Exception {
+ DynamicType.Loaded<SampleGetter> loaded = new ByteBuddy()
+ .subclass(SampleGetter.class)
+ .method(isDeclaredBy(SampleGetter.class))
+ .intercept(FieldAccessor.ofField(FOO))
+ .make()
+ .load(SampleSetter.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ SampleGetter sampleGetter = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ Field field = SampleGetter.class.getDeclaredField(FOO);
+ field.setAccessible(true);
+ assertThat(field.get(sampleGetter), is((Object) BAZ));
+ assertThat(sampleGetter.bar(), is((Object) BAZ));
+ assertThat(field.get(sampleGetter), is((Object) BAZ));
+ sampleGetter.assertZeroCalls();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNotAssignable() throws Exception {
+ new ByteBuddy()
+ .subclass(Baz.class)
+ .method(isDeclaredBy(Baz.class))
+ .intercept(FieldAccessor.ofField(FOO))
+ .make();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testFinalFieldSetter() throws Exception {
+ new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(FieldAccessor.ofBeanProperty())
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testFieldNoVisibleField() throws Exception {
+ new ByteBuddy()
+ .subclass(Bar.class)
+ .method(isDeclaredBy(Bar.class))
+ .intercept(FieldAccessor.ofBeanProperty())
+ .make();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testFieldNoBeanMethodName() throws Exception {
+ new ByteBuddy()
+ .subclass(Qux.class)
+ .method(isDeclaredBy(Qux.class))
+ .intercept(FieldAccessor.ofBeanProperty())
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIncompatibleExplicitField() throws Exception {
+ new ByteBuddy()
+ .subclass(Qux.class)
+ .method(isDeclaredBy(Qux.class))
+ .intercept(FieldAccessor.of(Bar.class.getDeclaredField(BAR)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testInaccessibleExplicitField() throws Exception {
+ new ByteBuddy()
+ .subclass(Bar.class)
+ .method(isDeclaredBy(Bar.class))
+ .intercept(FieldAccessor.of(Bar.class.getDeclaredField(BAR)))
+ .make();
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(FieldAccessor.ForImplicitProperty.class).apply();
+ ObjectPropertyAssertion.of(FieldAccessor.ForImplicitProperty.Appender.class).apply();
+ ObjectPropertyAssertion.of(FieldAccessor.ForParameterSetter.class).apply();
+ ObjectPropertyAssertion.of(FieldAccessor.ForParameterSetter.TerminationHandler.class).apply();
+ ObjectPropertyAssertion.of(FieldAccessor.ForParameterSetter.Appender.class).apply();
+ ObjectPropertyAssertion.of(FieldAccessor.FieldLocation.Absolute.class).apply();
+ ObjectPropertyAssertion.of(FieldAccessor.FieldLocation.Relative.class).apply();
+ ObjectPropertyAssertion.of(FieldAccessor.FieldLocation.Relative.Prepared.class).apply();
+ }
+
+ @SuppressWarnings("unused")
+ public static class Foo {
+
+ protected final Object foo = null;
+
+ public void setFoo(Object o) {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class Bar {
+
+ private Object bar;
+
+ public void setBar(Object o) {
+ /* empty */
+ }
+ }
+
+ public static class BarSub extends Bar {
+ /* empty */
+ }
+
+ @SuppressWarnings("unused")
+ public static class Qux {
+
+ private Object qux;
+
+ public void qux(Object o) {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class Baz {
+
+ public String foo;
+
+ public void qux(Object o) {
+ /* empty */
+ }
+ }
+
+ public static class SampleArgumentSetter {
+
+ public Object foo;
+
+ public void foo(String value) {
+ throw new AssertionError();
+ }
+
+ public Object bar(String value) {
+ throw new AssertionError();
+ }
+ }
+
+ public static class SampleGetter extends CallTraceable {
+
+ protected Object foo = BAZ;
+
+ public Object bar() {
+ register(FOO);
+ return QUX;
+ }
+ }
+
+ public static class SampleSetter extends CallTraceable {
+
+ protected Object foo = QUX;
+
+ public void bar(Object foo) {
+ register(FOO, foo);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/FieldAccessorTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/FieldAccessorTest.java
new file mode 100644
index 0000000..5623aac
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/FieldAccessorTest.java
@@ -0,0 +1,590 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.test.utility.CallTraceable;
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Collection;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class FieldAccessorTest<T extends CallTraceable,
+ S extends CallTraceable,
+ U extends CallTraceable,
+ V extends CallTraceable,
+ X extends CallTraceable,
+ Y extends CallTraceable> {
+
+ private static final String FOO = "foo";
+
+ private static final String GET = "get", SET = "set";
+
+ private static final Object STATIC_FIELD = null;
+
+ private static final String STRING_VALUE = "qux";
+
+ private static final boolean BOOLEAN_VALUE = true;
+
+ private static final byte BYTE_VALUE = 42;
+
+ private static final short SHORT_VALUE = 42;
+
+ private static final char CHAR_VALUE = '@';
+
+ private static final int INT_VALUE = 42;
+
+ private static final long LONG_VALUE = 42L;
+
+ private static final float FLOAT_VALUE = 42f;
+
+ private static final double DOUBLE_VALUE = 42d;
+
+ private static final String STRING_DEFAULT_VALUE = "baz";
+
+ private static final boolean BOOLEAN_DEFAULT_VALUE = false;
+
+ private static final byte BYTE_DEFAULT_VALUE = 0;
+
+ private static final short SHORT_DEFAULT_VALUE = 0;
+
+ private static final char CHAR_DEFAULT_VALUE = 0;
+
+ private static final int INT_DEFAULT_VALUE = 0;
+
+ private static final long LONG_DEFAULT_VALUE = 0L;
+
+ private static final float FLOAT_DEFAULT_VALUE = 0f;
+
+ private static final double DOUBLE_DEFAULT_VALUE = 0d;
+
+ private final Object value;
+
+ private final Class<T> instanceGetter;
+
+ private final Class<S> instanceSetter;
+
+ private final Class<U> staticGetter;
+
+ private final Class<V> staticSetter;
+
+ private final Class<?> propertyType;
+
+ public FieldAccessorTest(Object value,
+ Class<T> instanceGetter,
+ Class<S> instanceSetter,
+ Class<U> staticGetter,
+ Class<V> staticSetter,
+ Class<?> propertyType) {
+ this.value = value;
+ this.instanceGetter = instanceGetter;
+ this.instanceSetter = instanceSetter;
+ this.staticGetter = staticGetter;
+ this.staticSetter = staticSetter;
+ this.propertyType = propertyType;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {BOOLEAN_VALUE,
+ BooleanInstanceGetter.class, BooleanInstanceSetter.class,
+ BooleanClassGetter.class, BooleanClassSetter.class,
+ boolean.class},
+ {BYTE_VALUE,
+ ByteInstanceGetter.class, ByteInstanceSetter.class,
+ ByteClassGetter.class, ByteClassSetter.class,
+ byte.class},
+ {SHORT_VALUE,
+ ShortInstanceGetter.class, ShortInstanceSetter.class,
+ ShortClassGetter.class, ShortClassSetter.class,
+ short.class},
+ {CHAR_VALUE,
+ CharacterInstanceGetter.class, CharacterInstanceSetter.class,
+ CharacterClassGetter.class, CharacterClassSetter.class,
+ char.class},
+ {INT_VALUE,
+ IntegerInstanceGetter.class, IntegerInstanceSetter.class,
+ IntegerClassGetter.class, IntegerClassSetter.class,
+ int.class},
+ {LONG_VALUE,
+ LongInstanceGetter.class, LongInstanceSetter.class,
+ LongClassGetter.class, LongClassSetter.class,
+ long.class},
+ {FLOAT_VALUE,
+ FloatInstanceGetter.class, FloatInstanceSetter.class,
+ FloatClassGetter.class, FloatClassSetter.class,
+ float.class},
+ {DOUBLE_VALUE,
+ DoubleInstanceGetter.class, DoubleInstanceSetter.class,
+ DoubleClassGetter.class, DoubleClassSetter.class,
+ double.class},
+ {STRING_VALUE,
+ ObjectInstanceGetter.class, ObjectInstanceSetter.class,
+ ObjectClassGetter.class, ObjectClassSetter.class,
+ Object.class}
+ });
+ }
+
+ @Test
+ public void testInstanceGetterBeanProperty() throws Exception {
+ testGetter(instanceGetter, FieldAccessor.ofBeanProperty());
+ }
+
+ @Test
+ public void testStaticGetterBeanProperty() throws Exception {
+ testGetter(staticGetter, FieldAccessor.ofBeanProperty());
+ }
+
+ @Test
+ public void testInstanceGetterExplicit() throws Exception {
+ testGetter(instanceGetter, FieldAccessor.ofField(FOO));
+ }
+
+ @Test
+ public void testStaticGetterExplicit() throws Exception {
+ testGetter(staticGetter, FieldAccessor.ofField(FOO));
+ }
+
+ @Test
+ public void testInstanceGetterField() throws Exception {
+ testGetter(instanceGetter, FieldAccessor.of(instanceGetter.getDeclaredField(FOO)));
+ }
+
+ @Test
+ public void testStaticGetterField() throws Exception {
+ testGetter(staticGetter, FieldAccessor.of(staticGetter.getDeclaredField(FOO)));
+ }
+
+ @Test
+ public void testInstanceSetterBeanProperty() throws Exception {
+ testSetter(instanceSetter, FieldAccessor.ofBeanProperty());
+ }
+
+ @Test
+ public void testStaticSetterBeanProperty() throws Exception {
+ testSetter(staticSetter, FieldAccessor.ofBeanProperty());
+ }
+
+ @Test
+ public void testInstanceSetterExplicit() throws Exception {
+ testSetter(instanceSetter, FieldAccessor.ofField(FOO));
+ }
+
+ @Test
+ public void testStaticSetterExplicit() throws Exception {
+ testSetter(staticSetter, FieldAccessor.ofField(FOO));
+ }
+
+ @Test
+ public void testStaticSetterField() throws Exception {
+ testSetter(staticSetter, FieldAccessor.of(staticSetter.getDeclaredField(FOO)));
+ }
+
+ @Test
+ public void testInstanceSetterField() throws Exception {
+ testSetter(instanceSetter, FieldAccessor.of(instanceSetter.getDeclaredField(FOO)));
+ }
+
+ @SuppressWarnings("unchecked")
+ private <Z extends CallTraceable> void testGetter(Class<Z> target, Implementation implementation) throws Exception {
+ DynamicType.Loaded<Z> loaded = new ByteBuddy()
+ .subclass(target)
+ .method(isDeclaredBy(target))
+ .intercept(implementation)
+ .make()
+ .load(target.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ Z instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(target)));
+ assertThat(instance, instanceOf(target));
+ Method getter = loaded.getLoaded()
+ .getDeclaredMethod(GET + Character.toUpperCase(FOO.charAt(0)) + FOO.substring(1));
+ assertThat(getter.invoke(instance), is(value));
+ instance.assertZeroCalls();
+ assertFieldValue(target, instance);
+ }
+
+ @SuppressWarnings("unchecked")
+ private <Z extends CallTraceable> void testSetter(Class<Z> target, Implementation implementation) throws Exception {
+ DynamicType.Loaded<Z> loaded = new ByteBuddy()
+ .subclass(target)
+ .method(isDeclaredBy(target))
+ .intercept(implementation)
+ .make()
+ .load(target.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ Z instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(target)));
+ assertThat(instance, instanceOf(target));
+ Method setter = loaded.getLoaded()
+ .getDeclaredMethod(SET + Character.toUpperCase(FOO.charAt(0)) + FOO.substring(1), propertyType);
+ assertThat(setter.invoke(instance, value), nullValue());
+ instance.assertZeroCalls();
+ assertFieldValue(target, instance);
+ }
+
+ private void assertFieldValue(Class<?> fieldHolder, Object instance) throws Exception {
+ Field field = fieldHolder.getDeclaredField(FOO);
+ boolean isStatic = (Modifier.STATIC & field.getModifiers()) != 0;
+ Object fieldValue = isStatic ? field.get(STATIC_FIELD) : field.get(instance);
+ assertThat(fieldValue, is(value));
+ }
+
+ public static class BooleanInstanceGetter extends CallTraceable {
+
+ protected boolean foo = BOOLEAN_VALUE;
+
+ public boolean getFoo() {
+ register(FOO);
+ return BOOLEAN_DEFAULT_VALUE;
+ }
+ }
+
+ public static class BooleanInstanceSetter extends CallTraceable {
+
+ protected boolean foo = BOOLEAN_DEFAULT_VALUE;
+
+ public void setFoo(boolean foo) {
+ register(FOO, foo);
+ }
+ }
+
+ public static class BooleanClassGetter extends CallTraceable {
+
+ protected static boolean foo = BOOLEAN_VALUE;
+
+ public boolean getFoo() {
+ register(FOO);
+ return BOOLEAN_DEFAULT_VALUE;
+ }
+ }
+
+ public static class BooleanClassSetter extends CallTraceable {
+
+ protected static boolean foo = BOOLEAN_DEFAULT_VALUE;
+
+ public void setFoo(boolean foo) {
+ register(FOO, foo);
+ }
+ }
+
+ public static class ByteInstanceGetter extends CallTraceable {
+
+ protected byte foo = BYTE_VALUE;
+
+ public byte getFoo() {
+ register(FOO);
+ return BYTE_DEFAULT_VALUE;
+ }
+ }
+
+ public static class ByteInstanceSetter extends CallTraceable {
+
+ protected byte foo = BYTE_DEFAULT_VALUE;
+
+ public void setFoo(byte foo) {
+ register(FOO, foo);
+ }
+ }
+
+ public static class ByteClassGetter extends CallTraceable {
+
+ protected static byte foo = BYTE_VALUE;
+
+ public byte getFoo() {
+ register(FOO);
+ return BYTE_DEFAULT_VALUE;
+ }
+ }
+
+ public static class ByteClassSetter extends CallTraceable {
+
+ protected static byte foo = BYTE_DEFAULT_VALUE;
+
+ public void setFoo(byte foo) {
+ register(FOO, foo);
+ }
+ }
+
+ public static class ShortInstanceGetter extends CallTraceable {
+
+ protected short foo = SHORT_VALUE;
+
+ public short getFoo() {
+ register(FOO);
+ return SHORT_DEFAULT_VALUE;
+ }
+ }
+
+ public static class ShortInstanceSetter extends CallTraceable {
+
+ protected short foo = SHORT_DEFAULT_VALUE;
+
+ public void setFoo(short foo) {
+ register(FOO, foo);
+ }
+ }
+
+ public static class ShortClassGetter extends CallTraceable {
+
+ protected static short foo = SHORT_VALUE;
+
+ public short getFoo() {
+ register(FOO);
+ return SHORT_DEFAULT_VALUE;
+ }
+ }
+
+ public static class ShortClassSetter extends CallTraceable {
+
+ protected static short foo = SHORT_DEFAULT_VALUE;
+
+ public void setFoo(short foo) {
+ register(FOO, foo);
+ }
+ }
+
+ public static class IntegerInstanceGetter extends CallTraceable {
+
+ protected int foo = INT_VALUE;
+
+ public int getFoo() {
+ register(FOO);
+ return INT_DEFAULT_VALUE;
+ }
+ }
+
+ public static class IntegerInstanceSetter extends CallTraceable {
+
+ protected int foo = INT_DEFAULT_VALUE;
+
+ public void setFoo(int foo) {
+ register(FOO, foo);
+ }
+ }
+
+ public static class IntegerClassGetter extends CallTraceable {
+
+ protected static int foo = INT_VALUE;
+
+ public int getFoo() {
+ register(FOO);
+ return INT_DEFAULT_VALUE;
+ }
+ }
+
+ public static class IntegerClassSetter extends CallTraceable {
+
+ protected static int foo = INT_DEFAULT_VALUE;
+
+ public void setFoo(int foo) {
+ register(FOO, foo);
+ }
+ }
+
+ public static class CharacterInstanceGetter extends CallTraceable {
+
+ protected char foo = CHAR_VALUE;
+
+ public char getFoo() {
+ register(FOO);
+ return CHAR_DEFAULT_VALUE;
+ }
+ }
+
+ public static class CharacterInstanceSetter extends CallTraceable {
+
+ protected char foo = CHAR_DEFAULT_VALUE;
+
+ public void setFoo(char foo) {
+ register(FOO, foo);
+ }
+ }
+
+ public static class CharacterClassGetter extends CallTraceable {
+
+ protected static char foo = CHAR_VALUE;
+
+ public char getFoo() {
+ register(FOO);
+ return CHAR_DEFAULT_VALUE;
+ }
+ }
+
+ public static class CharacterClassSetter extends CallTraceable {
+
+ protected static char foo = CHAR_DEFAULT_VALUE;
+
+ public void setFoo(char foo) {
+ register(FOO, foo);
+ }
+ }
+
+ public static class LongInstanceGetter extends CallTraceable {
+
+ protected long foo = LONG_VALUE;
+
+ public long getFoo() {
+ register(FOO);
+ return LONG_DEFAULT_VALUE;
+ }
+ }
+
+ public static class LongInstanceSetter extends CallTraceable {
+
+ protected long foo = LONG_DEFAULT_VALUE;
+
+ public void setFoo(long foo) {
+ register(FOO, foo);
+ }
+ }
+
+ public static class LongClassGetter extends CallTraceable {
+
+ protected static long foo = LONG_VALUE;
+
+ public long getFoo() {
+ register(FOO);
+ return LONG_DEFAULT_VALUE;
+ }
+ }
+
+ public static class LongClassSetter extends CallTraceable {
+
+ protected static long foo = LONG_DEFAULT_VALUE;
+
+ public void setFoo(long foo) {
+ register(FOO, foo);
+ }
+ }
+
+ public static class FloatInstanceGetter extends CallTraceable {
+
+ protected float foo = FLOAT_VALUE;
+
+ public float getFoo() {
+ register(FOO);
+ return FLOAT_DEFAULT_VALUE;
+ }
+ }
+
+ public static class FloatInstanceSetter extends CallTraceable {
+
+ protected float foo = FLOAT_DEFAULT_VALUE;
+
+ public void setFoo(float foo) {
+ register(FOO, foo);
+ }
+ }
+
+ public static class FloatClassGetter extends CallTraceable {
+
+ protected static float foo = FLOAT_VALUE;
+
+ public float getFoo() {
+ register(FOO);
+ return FLOAT_DEFAULT_VALUE;
+ }
+ }
+
+ public static class FloatClassSetter extends CallTraceable {
+
+ protected static float foo = FLOAT_DEFAULT_VALUE;
+
+ public void setFoo(float foo) {
+ register(FOO, foo);
+ }
+ }
+
+ public static class DoubleInstanceGetter extends CallTraceable {
+
+ protected double foo = DOUBLE_VALUE;
+
+ public double getFoo() {
+ register(FOO);
+ return DOUBLE_DEFAULT_VALUE;
+ }
+ }
+
+ public static class DoubleInstanceSetter extends CallTraceable {
+
+ protected double foo = DOUBLE_DEFAULT_VALUE;
+
+ public void setFoo(double foo) {
+ register(FOO, foo);
+ }
+ }
+
+ public static class DoubleClassGetter extends CallTraceable {
+
+ protected static double foo = DOUBLE_VALUE;
+
+ public double getFoo() {
+ register(FOO);
+ return DOUBLE_DEFAULT_VALUE;
+ }
+ }
+
+ public static class DoubleClassSetter extends CallTraceable {
+
+ protected static double foo = DOUBLE_DEFAULT_VALUE;
+
+ public void setFoo(double foo) {
+ register(FOO, foo);
+ }
+ }
+
+ public static class ObjectInstanceGetter extends CallTraceable {
+
+ protected Object foo = STRING_VALUE;
+
+ public Object getFoo() {
+ register(FOO);
+ return STRING_DEFAULT_VALUE;
+ }
+ }
+
+ public static class ObjectInstanceSetter extends CallTraceable {
+
+ protected Object foo = STRING_DEFAULT_VALUE;
+
+ public void setFoo(Object foo) {
+ register(FOO, foo);
+ }
+ }
+
+ public static class ObjectClassGetter extends CallTraceable {
+
+ protected static Object foo = STRING_VALUE;
+
+ public Object getFoo() {
+ register(FOO);
+ return STRING_DEFAULT_VALUE;
+ }
+ }
+
+ public static class ObjectClassSetter extends CallTraceable {
+
+ protected static Object foo = STRING_DEFAULT_VALUE;
+
+ public void setFoo(Object foo) {
+ register(FOO, foo);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/FixedValueConstantPoolTypesTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/FixedValueConstantPoolTypesTest.java
new file mode 100644
index 0000000..9215c45
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/FixedValueConstantPoolTypesTest.java
@@ -0,0 +1,249 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.test.utility.CallTraceable;
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class FixedValueConstantPoolTypesTest<T extends CallTraceable> {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ private static final String STRING_VALUE = "foo";
+
+ private static final boolean BOOLEAN_VALUE = true;
+
+ private static final byte BYTE_VALUE = 42;
+
+ private static final short SHORT_VALUE = 42;
+
+ private static final char CHAR_VALUE = '@';
+
+ private static final int INT_VALUE = 42;
+
+ private static final long LONG_VALUE = 42L;
+
+ private static final float FLOAT_VALUE = 42f;
+
+ private static final double DOUBLE_VALUE = 42d;
+
+ private static final Void NULL_VALUE = null;
+
+ private static final String STRING_DEFAULT_VALUE = "bar";
+
+ private static final boolean BOOLEAN_DEFAULT_VALUE = false;
+
+ private static final byte BYTE_DEFAULT_VALUE = 0;
+
+ private static final short SHORT_DEFAULT_VALUE = 0;
+
+ private static final char CHAR_DEFAULT_VALUE = 0;
+
+ private static final int INT_DEFAULT_VALUE = 0;
+
+ private static final long LONG_DEFAULT_VALUE = 0L;
+
+ private static final float FLOAT_DEFAULT_VALUE = 0f;
+
+ private static final double DOUBLE_DEFAULT_VALUE = 0d;
+
+ private final Object fixedValue;
+
+ private final Class<T> helperClass;
+
+ public FixedValueConstantPoolTypesTest(Object fixedValue, Class<T> helperClass) {
+ this.fixedValue = fixedValue;
+ this.helperClass = helperClass;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {STRING_VALUE, StringTarget.class},
+ {BOOLEAN_VALUE, BooleanTarget.class},
+ {BYTE_VALUE, ByteTarget.class},
+ {SHORT_VALUE, ShortTarget.class},
+ {CHAR_VALUE, CharTarget.class},
+ {INT_VALUE, IntTarget.class},
+ {LONG_VALUE, LongTarget.class},
+ {FLOAT_VALUE, FloatTarget.class},
+ {DOUBLE_VALUE, DoubleTarget.class}
+ });
+ }
+
+ @Test
+ public void testConstantPool() throws Exception {
+ DynamicType.Loaded<T> loaded = new ByteBuddy()
+ .subclass(helperClass)
+ .method(isDeclaredBy(helperClass))
+ .intercept(FixedValue.value(fixedValue))
+ .make()
+ .load(helperClass.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(2));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ T instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(StringTarget.class)));
+ assertThat(instance, instanceOf(helperClass));
+ assertThat(loaded.getLoaded().getDeclaredMethod(FOO).invoke(instance), is(fixedValue));
+ assertThat(loaded.getLoaded().getDeclaredMethod(BAR).invoke(instance), is(fixedValue));
+ instance.assertZeroCalls();
+ }
+
+ @Test
+ public void testStaticField() throws Exception {
+ DynamicType.Loaded<T> loaded = new ByteBuddy()
+ .subclass(helperClass)
+ .method(isDeclaredBy(helperClass))
+ .intercept(FixedValue.reference(fixedValue))
+ .make()
+ .load(helperClass.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(2));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(fixedValue == null ? 0 : 1));
+ T instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(StringTarget.class)));
+ assertThat(instance, instanceOf(helperClass));
+ assertThat(loaded.getLoaded().getDeclaredMethod(FOO).invoke(instance), is(fixedValue));
+ assertThat(loaded.getLoaded().getDeclaredMethod(BAR).invoke(instance), is(fixedValue));
+ instance.assertZeroCalls();
+ }
+
+ @SuppressWarnings("unused")
+ public static class StringTarget extends CallTraceable {
+
+ public String foo() {
+ register(FOO);
+ return STRING_DEFAULT_VALUE;
+ }
+
+ public Object bar() {
+ register(BAR);
+ return STRING_DEFAULT_VALUE;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class BooleanTarget extends CallTraceable {
+
+ public boolean foo() {
+ register(FOO);
+ return BOOLEAN_DEFAULT_VALUE;
+ }
+
+ public Boolean bar() {
+ register(BAR);
+ return BOOLEAN_DEFAULT_VALUE;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ByteTarget extends CallTraceable {
+
+ public byte foo() {
+ register(FOO);
+ return BYTE_DEFAULT_VALUE;
+ }
+
+ public Byte bar() {
+ register(BAR);
+ return BYTE_DEFAULT_VALUE;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ShortTarget extends CallTraceable {
+
+ public short foo() {
+ register(FOO);
+ return SHORT_DEFAULT_VALUE;
+ }
+
+ public Short bar() {
+ register(BAR);
+ return SHORT_DEFAULT_VALUE;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class CharTarget extends CallTraceable {
+
+ public char foo() {
+ register(FOO);
+ return CHAR_DEFAULT_VALUE;
+ }
+
+ public Character bar() {
+ register(BAR);
+ return CHAR_DEFAULT_VALUE;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IntTarget extends CallTraceable {
+
+ public int foo() {
+ register(FOO);
+ return INT_DEFAULT_VALUE;
+ }
+
+ public Integer bar() {
+ register(BAR);
+ return INT_DEFAULT_VALUE;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class LongTarget extends CallTraceable {
+
+ public long foo() {
+ register(FOO);
+ return LONG_DEFAULT_VALUE;
+ }
+
+ public Long bar() {
+ register(BAR);
+ return LONG_DEFAULT_VALUE;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FloatTarget extends CallTraceable {
+
+ public float foo() {
+ register(FOO);
+ return FLOAT_DEFAULT_VALUE;
+ }
+
+ public Float bar() {
+ register(BAR);
+ return FLOAT_DEFAULT_VALUE;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class DoubleTarget extends CallTraceable {
+
+ public double foo() {
+ register(FOO);
+ return DOUBLE_DEFAULT_VALUE;
+ }
+
+ public Double bar() {
+ register(BAR);
+ return DOUBLE_DEFAULT_VALUE;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/FixedValueTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/FixedValueTest.java
new file mode 100644
index 0000000..1e4e026
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/FixedValueTest.java
@@ -0,0 +1,329 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.test.utility.CallTraceable;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.utility.JavaConstant;
+import net.bytebuddy.utility.JavaType;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+
+import java.util.Arrays;
+import java.util.Iterator;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class FixedValueTest {
+
+ private static final String BAR = "bar";
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ private Bar bar;
+
+ private static Object makeMethodType(Class<?> returnType, Class<?>... parameterType) throws Exception {
+ return JavaType.METHOD_TYPE.load().getDeclaredMethod("methodType", Class.class, Class[].class).invoke(null, returnType, parameterType);
+ }
+
+ private static Object makeMethodHandle() throws Exception {
+ Object lookup = Class.forName("java.lang.invoke.MethodHandles").getDeclaredMethod("publicLookup").invoke(null);
+ return JavaType.METHOD_HANDLES_LOOKUP.load().getDeclaredMethod("findVirtual", Class.class, String.class, JavaType.METHOD_TYPE.load())
+ .invoke(lookup, Qux.class, BAR, makeMethodType(Object.class));
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ bar = new Bar();
+ }
+
+ @Test
+ public void testTypeDescriptionConstantPool() throws Exception {
+ Class<? extends Qux> qux = new ByteBuddy()
+ .subclass(Qux.class)
+ .method(isDeclaredBy(Qux.class))
+ .intercept(FixedValue.value(TypeDescription.OBJECT))
+ .make()
+ .load(Qux.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(qux.getDeclaredFields().length, is(0));
+ assertThat(qux.getDeclaredConstructor().newInstance().bar(), is((Object) Object.class));
+ }
+
+ @Test
+ public void testClassConstantPool() throws Exception {
+ Class<? extends Qux> qux = new ByteBuddy()
+ .subclass(Qux.class)
+ .method(isDeclaredBy(Qux.class))
+ .intercept(FixedValue.value(Object.class))
+ .make()
+ .load(Qux.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(qux.getDeclaredFields().length, is(0));
+ assertThat(qux.getDeclaredConstructor().newInstance().bar(), is((Object) Object.class));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(7)
+ public void testMethodTypeConstantPool() throws Exception {
+ Class<? extends Qux> qux = new ByteBuddy()
+ .subclass(Qux.class)
+ .method(isDeclaredBy(Qux.class))
+ .intercept(FixedValue.value(JavaConstant.MethodType.of(void.class, Object.class)))
+ .make()
+ .load(Qux.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(qux.getDeclaredFields().length, is(0));
+ assertThat(qux.getDeclaredConstructor().newInstance().bar(), is(makeMethodType(void.class, Object.class)));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(7)
+ public void testMethodTypeConstantPoolValue() throws Exception {
+ Class<? extends Qux> qux = new ByteBuddy()
+ .subclass(Qux.class)
+ .method(isDeclaredBy(Qux.class))
+ .intercept(FixedValue.value(makeMethodType(void.class, Object.class)))
+ .make()
+ .load(Qux.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(qux.getDeclaredFields().length, is(0));
+ assertThat(qux.getDeclaredConstructor().newInstance().bar(), is(makeMethodType(void.class, Object.class)));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(value = 7, hotSpot = 7)
+ public void testMethodHandleConstantPool() throws Exception {
+ Class<? extends Qux> qux = new ByteBuddy()
+ .subclass(Qux.class)
+ .method(isDeclaredBy(Qux.class))
+ .intercept(FixedValue.value(JavaConstant.MethodHandle.of(Qux.class.getDeclaredMethod("bar"))))
+ .make()
+ .load(Qux.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(qux.getDeclaredFields().length, is(0));
+ assertThat(JavaConstant.MethodHandle.ofLoaded(qux.getDeclaredConstructor().newInstance().bar()), is(JavaConstant.MethodHandle.ofLoaded(makeMethodHandle())));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(value = 7, hotSpot = 7)
+ public void testMethodHandleConstantPoolValue() throws Exception {
+ Class<? extends Qux> qux = new ByteBuddy()
+ .subclass(Qux.class)
+ .method(isDeclaredBy(Qux.class))
+ .intercept(FixedValue.value(makeMethodHandle()))
+ .make()
+ .load(Qux.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(qux.getDeclaredFields().length, is(0));
+ assertThat(JavaConstant.MethodHandle.ofLoaded(qux.getDeclaredConstructor().newInstance().bar()), is(JavaConstant.MethodHandle.ofLoaded(makeMethodHandle())));
+ }
+
+ @Test
+ public void testReferenceCall() throws Exception {
+ new ByteBuddy()
+ .subclass(Qux.class)
+ .method(isDeclaredBy(Qux.class))
+ .intercept(FixedValue.reference(bar))
+ .make();
+ }
+
+ @Test
+ public void testValueCall() throws Exception {
+ new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(FixedValue.reference(bar))
+ .make();
+ }
+
+ @Test
+ public void testNullValue() throws Exception {
+ Class<? extends Foo> foo = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(FixedValue.nullValue())
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(foo.getDeclaredFields().length, is(0));
+ assertThat(foo.getDeclaredMethods().length, is(1));
+ assertThat(foo.getDeclaredMethod(BAR).invoke(foo.getDeclaredConstructor().newInstance()), nullValue(Object.class));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNullValueNonAssignable() throws Exception {
+ new ByteBuddy()
+ .subclass(FooBar.class)
+ .method(isDeclaredBy(FooBar.class))
+ .intercept(FixedValue.nullValue())
+ .make();
+ }
+
+ @Test
+ public void testThisValue() throws Exception {
+ Class<? extends QuxBaz> quxbaz = new ByteBuddy()
+ .subclass(QuxBaz.class)
+ .method(isDeclaredBy(QuxBaz.class))
+ .intercept(FixedValue.self())
+ .make()
+ .load(QuxBaz.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(quxbaz.getDeclaredFields().length, is(0));
+ assertThat(quxbaz.getDeclaredMethods().length, is(1));
+ QuxBaz self = quxbaz.getDeclaredConstructor().newInstance();
+ assertThat(self.bar(), sameInstance((Object) self));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testThisValueStatic() throws Exception {
+ new ByteBuddy()
+ .redefine(FooBarQuxBaz.class)
+ .method(named("bar"))
+ .intercept(FixedValue.self())
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testThisValueNonAssignable() throws Exception {
+ new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(FixedValue.self())
+ .make();
+ }
+
+ @Test
+ public void testOriginType() throws Exception {
+ Class<? extends Baz> baz = new ByteBuddy()
+ .subclass(Baz.class)
+ .method(isDeclaredBy(Baz.class))
+ .intercept(FixedValue.originType())
+ .make()
+ .load(QuxBaz.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+ assertThat(baz.getDeclaredFields().length, is(0));
+ assertThat(baz.getDeclaredMethods().length, is(1));
+ assertThat(baz.getDeclaredMethod(BAR).invoke(baz.getDeclaredConstructor().newInstance()), is((Object) Baz.class));
+ }
+
+ @Test
+ public void testArgument() throws Exception {
+ Class<? extends FooQux> fooQux = new ByteBuddy()
+ .subclass(FooQux.class)
+ .method(isDeclaredBy(FooQux.class))
+ .intercept(FixedValue.argument(1))
+ .make()
+ .load(FooQux.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+
+ assertThat(fooQux.getDeclaredFields().length, is(0));
+ assertThat(fooQux.getDeclaredMethods().length, is(1));
+ assertThat(fooQux.getDeclaredMethod(BAR, Integer.class, String.class)
+ .invoke(fooQux.getDeclaredConstructor().newInstance(), 0, BAR), is((Object) BAR));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testArgumentNegative() throws Exception {
+ FixedValue.argument(-1);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testArgumentNotAssignable() throws Exception {
+ new ByteBuddy()
+ .subclass(FooQux.class)
+ .method(isDeclaredBy(FooQux.class))
+ .intercept(FixedValue.argument(0))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testArgumentNonExistent() throws Exception {
+ new ByteBuddy()
+ .subclass(FooQux.class)
+ .method(isDeclaredBy(FooQux.class))
+ .intercept(FixedValue.argument(2))
+ .make();
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ final Iterator<Class<?>> iterator = Arrays.<Class<?>>asList(Object.class, String.class, Integer.class).iterator();
+ ObjectPropertyAssertion.of(FixedValue.ForPoolValue.class).create(new ObjectPropertyAssertion.Creator<Class<?>>() {
+ @Override
+ public Class<?> create() {
+ return iterator.next();
+ }
+ }).skipSynthetic().apply();
+ ObjectPropertyAssertion.of(FixedValue.ForValue.class).apply();
+ ObjectPropertyAssertion.of(FixedValue.ForOriginType.class).apply();
+ ObjectPropertyAssertion.of(FixedValue.ForOriginType.Appender.class).apply();
+ ObjectPropertyAssertion.of(FixedValue.ForNullValue.class).apply();
+ ObjectPropertyAssertion.of(FixedValue.ForThisValue.class).apply();
+ ObjectPropertyAssertion.of(FixedValue.ForThisValue.Appender.class).apply();
+ ObjectPropertyAssertion.of(FixedValue.ForArgument.class).apply();
+ }
+
+ public static class Foo extends CallTraceable {
+
+ public Bar bar() {
+ register(BAR);
+ return new Bar();
+ }
+ }
+
+ public static class Bar {
+ /* empty */
+ }
+
+ public static class Qux extends CallTraceable {
+
+ public Object bar() {
+ register(BAR);
+ return null;
+ }
+ }
+
+ public static class Baz {
+
+ public Class<?> bar() {
+ return null;
+ }
+ }
+
+ public static class FooBar {
+
+ public void bar() {
+ /* empty */
+ }
+ }
+
+ public static class QuxBaz {
+
+ public Object bar() {
+ return null;
+ }
+ }
+
+ public static class FooBarQuxBaz {
+
+ public static Object bar() {
+ return null;
+ }
+ }
+
+ public static class FooQux {
+
+ public String bar(Integer arg0, String arg1) {
+ return null;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/ImplementationCompoundTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/ImplementationCompoundTest.java
new file mode 100644
index 0000000..f8c9a78
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/ImplementationCompoundTest.java
@@ -0,0 +1,77 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.agent.builder.AgentBuilder;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.Collections;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class ImplementationCompoundTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private Implementation first, second;
+
+ @Mock
+ private InstrumentedType instrumentedType;
+
+ @Mock
+ private Implementation.Target implementationTarget;
+
+ @Mock
+ private ByteCodeAppender byteCodeAppender;
+
+ private Implementation compound;
+
+ @Before
+ public void setUp() throws Exception {
+ compound = new Implementation.Compound(first, second);
+ }
+
+ @Test
+ public void testPrepare() throws Exception {
+ when(first.prepare(instrumentedType)).thenReturn(instrumentedType);
+ when(second.prepare(instrumentedType)).thenReturn(instrumentedType);
+ assertThat(compound.prepare(instrumentedType), is(instrumentedType));
+ verify(first).prepare(instrumentedType);
+ verify(second).prepare(instrumentedType);
+ verifyNoMoreInteractions(first);
+ verifyNoMoreInteractions(second);
+ }
+
+ @Test
+ public void testAppend() throws Exception {
+ when(first.appender(implementationTarget)).thenReturn(byteCodeAppender);
+ when(second.appender(implementationTarget)).thenReturn(byteCodeAppender);
+ assertThat(compound.appender(implementationTarget), notNullValue());
+ verify(first).appender(implementationTarget);
+ verify(second).appender(implementationTarget);
+ verifyNoMoreInteractions(first);
+ verifyNoMoreInteractions(second);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(Implementation.Compound.class).create(new ObjectPropertyAssertion.Creator<List<?>>() {
+ @Override
+ public List<?> create() {
+ return Collections.singletonList(mock(Implementation.class));
+ }
+ }).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/ImplementationContextDefaultOtherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/ImplementationContextDefaultOtherTest.java
new file mode 100644
index 0000000..463684b
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/ImplementationContextDefaultOtherTest.java
@@ -0,0 +1,113 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.dynamic.scaffold.TypeInitializer;
+import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.utility.RandomString;
+import org.junit.Test;
+import org.objectweb.asm.MethodVisitor;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ImplementationContextDefaultOtherTest {
+
+ @Test
+ public void testFactory() throws Exception {
+ assertThat(Implementation.Context.Default.Factory.INSTANCE.make(mock(TypeDescription.class),
+ mock(AuxiliaryType.NamingStrategy.class),
+ mock(TypeInitializer.class),
+ mock(ClassFileVersion.class),
+ mock(ClassFileVersion.class)), instanceOf(Implementation.Context.Default.class));
+ }
+
+ @Test
+ public void testEnabled() throws Exception {
+ assertThat(new Implementation.Context.Default(mock(TypeDescription.class),
+ mock(ClassFileVersion.class),
+ mock(AuxiliaryType.NamingStrategy.class),
+ mock(TypeInitializer.class),
+ mock(ClassFileVersion.class)).isEnabled(), is(true));
+ }
+
+ @Test
+ public void testInstrumentationGetter() throws Exception {
+ TypeDescription instrumentedType = mock(TypeDescription.class);
+ assertThat(new Implementation.Context.Default(instrumentedType,
+ mock(ClassFileVersion.class),
+ mock(AuxiliaryType.NamingStrategy.class),
+ mock(TypeInitializer.class),
+ mock(ClassFileVersion.class)).getInstrumentedType(), is(instrumentedType));
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testDefaultContext() throws Exception {
+ new Implementation.Context.Default.DelegationRecord(mock(MethodDescription.InDefinedShape.class), Visibility.PACKAGE_PRIVATE) {
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodDescription instrumentedMethod) {
+ throw new AssertionError();
+ }
+
+ @Override
+ protected Implementation.Context.Default.DelegationRecord with(MethodAccessorFactory.AccessType accessType) {
+ throw new AssertionError();
+ }
+ }.prepend(mock(ByteCodeAppender.class));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(Implementation.Context.Default.FieldCacheEntry.class).apply();
+ ObjectPropertyAssertion.of(Implementation.Context.Default.AccessorMethodDelegation.class).refine(new ObjectPropertyAssertion.Refinement<Implementation.SpecialMethodInvocation>() {
+ @Override
+ public void apply(Implementation.SpecialMethodInvocation mock) {
+ MethodDescription methodDescription = mock(MethodDescription.class);
+ when(methodDescription.getReturnType()).thenReturn(TypeDescription.Generic.OBJECT);
+ when(methodDescription.getDeclaringType()).thenReturn(TypeDescription.Generic.OBJECT);
+ when(methodDescription.getParameters()).thenReturn(new ParameterList.Empty());
+ when(methodDescription.getExceptionTypes()).thenReturn(new TypeList.Generic.Empty());
+ when(mock.getMethodDescription()).thenReturn(methodDescription);
+ }
+ }).refine(new ObjectPropertyAssertion.Refinement<TypeDescription>() {
+ @Override
+ public void apply(TypeDescription mock) {
+ when(mock.asErasure()).thenReturn(mock);
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(Implementation.Context.Default.FieldSetterDelegation.class).refine(new ObjectPropertyAssertion.Refinement<FieldDescription>() {
+ @Override
+ public void apply(FieldDescription mock) {
+ when(mock.getType()).thenReturn(TypeDescription.Generic.OBJECT);
+ }
+ }).refine(new ObjectPropertyAssertion.Refinement<TypeDescription>() {
+ @Override
+ public void apply(TypeDescription mock) {
+ when(mock.asErasure()).thenReturn(mock);
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(Implementation.Context.Default.FieldGetterDelegation.class).refine(new ObjectPropertyAssertion.Refinement<FieldDescription>() {
+ @Override
+ public void apply(FieldDescription mock) {
+ when(mock.getType()).thenReturn(TypeDescription.Generic.OBJECT);
+ }
+ }).refine(new ObjectPropertyAssertion.Refinement<TypeDescription>() {
+ @Override
+ public void apply(TypeDescription mock) {
+ when(mock.asErasure()).thenReturn(mock);
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(Implementation.Context.Default.Factory.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/ImplementationContextDefaultTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/ImplementationContextDefaultTest.java
new file mode 100644
index 0000000..00ec11f
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/ImplementationContextDefaultTest.java
@@ -0,0 +1,596 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.scaffold.TypeInitializer;
+import net.bytebuddy.implementation.attribute.AnnotationValueFilter;
+import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.test.utility.MockitoRule;
+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.mockito.Mockito;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.AdditionalMatchers.aryEq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class ImplementationContextDefaultTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux", BAZ = "baz";
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] {
+ {false, Opcodes.ACC_SYNTHETIC | Opcodes.ACC_FINAL, Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_FINAL | Opcodes.ACC_PRIVATE},
+ {true, Opcodes.ACC_SYNTHETIC | Opcodes.ACC_PUBLIC, Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_FINAL | Opcodes.ACC_PUBLIC},
+ });
+ }
+
+ private final boolean interfaceType;
+
+ private final int accessorMethodModifiers;
+
+ private final int cacheFieldModifiers;
+
+ public ImplementationContextDefaultTest(boolean interfaceType, int accessorMethodModifiers, int cacheFieldModifiers) {
+ this.interfaceType = interfaceType;
+ this.accessorMethodModifiers = accessorMethodModifiers;
+ this.cacheFieldModifiers = cacheFieldModifiers;
+ }
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription instrumentedType, firstDescription, secondDescription;
+
+ @Mock
+ private TypeInitializer typeInitializer, otherTypeInitializer, thirdTypeInitializer;
+
+ @Mock
+ private ClassFileVersion classFileVersion, auxiliaryClassFileVersion;
+
+ @Mock
+ private ClassVisitor classVisitor;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private FieldVisitor fieldVisitor;
+
+ @Mock
+ private AuxiliaryType auxiliaryType, otherAuxiliaryType;
+
+ @Mock
+ private DynamicType firstDynamicType, secondDynamicType;
+
+ @Mock
+ private TypeDescription.Generic firstFieldType, secondFieldType;
+
+ @Mock
+ private TypeDescription firstRawFieldType, secondRawFieldType;
+
+ @Mock
+ private StackManipulation firstFieldValue, secondFieldValue;
+
+ @Mock
+ private TypeDescription.Generic firstSpecialReturnType, secondSpecialReturnType;
+
+ @Mock
+ private TypeDescription firstRawSpecialReturnType, secondRawSpecialReturnType;
+
+ @Mock
+ private TypeDescription.Generic firstSpecialParameterType, secondSpecialParameterType;
+
+ @Mock
+ private TypeDescription firstRawSpecialParameterType, secondRawSpecialParameterType;
+
+ @Mock
+ private TypeDescription.Generic firstSpecialExceptionType, secondSpecialExceptionType;
+
+ @Mock
+ private TypeDescription firstRawSpecialExceptionType, secondRawSpecialExceptionType;
+
+ @Mock
+ private ByteCodeAppender injectedCodeAppender, terminationAppender;
+
+ @Mock
+ private Implementation.SpecialMethodInvocation firstSpecialInvocation, secondSpecialInvocation;
+
+ @Mock
+ private MethodDescription.InDefinedShape firstSpecialMethod, secondSpecialMethod;
+
+ @Mock
+ private AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy;
+
+ @Mock
+ private TypeDescription firstSpecialType, secondSpecialType;
+
+ @Mock
+ private FieldDescription.InDefinedShape firstField, secondField;
+
+ @Mock
+ private TypeDescription firstFieldDeclaringType, secondFieldDeclaringType;
+
+ private TypeList.Generic firstSpecialExceptionTypes, secondSpecialExceptionTypes;
+
+ @Mock
+ private AnnotationValueFilter.Factory annotationValueFilterFactory;
+
+ @Mock
+ private TypeInitializer.Drain drain;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ firstSpecialExceptionTypes = new TypeList.Generic.Explicit(firstSpecialExceptionType);
+ secondSpecialExceptionTypes = new TypeList.Generic.Explicit(secondSpecialExceptionType);
+ when(instrumentedType.getInternalName()).thenReturn(BAZ);
+ when(instrumentedType.asErasure()).thenReturn(instrumentedType);
+ when(instrumentedType.isInterface()).thenReturn(interfaceType);
+ when(auxiliaryType.make(any(String.class), any(ClassFileVersion.class), any(MethodAccessorFactory.class)))
+ .thenReturn(firstDynamicType);
+ when(firstDynamicType.getTypeDescription()).thenReturn(firstDescription);
+ when(otherAuxiliaryType.make(any(String.class), any(ClassFileVersion.class), any(MethodAccessorFactory.class)))
+ .thenReturn(secondDynamicType);
+ when(secondDynamicType.getTypeDescription()).thenReturn(secondDescription);
+ when(classVisitor.visitMethod(any(int.class), any(String.class), any(String.class), Mockito.<String>any(), Mockito.<String[]>any()))
+ .thenReturn(methodVisitor);
+ when(classVisitor.visitField(any(int.class), any(String.class), any(String.class), Mockito.<String>any(), Mockito.any()))
+ .thenReturn(fieldVisitor);
+ when(firstFieldValue.apply(any(MethodVisitor.class), any(Implementation.Context.class))).thenReturn(new StackManipulation.Size(0, 0));
+ when(secondFieldValue.apply(any(MethodVisitor.class), any(Implementation.Context.class))).thenReturn(new StackManipulation.Size(0, 0));
+ when(firstFieldType.getStackSize()).thenReturn(StackSize.ZERO);
+ when(firstFieldType.getSort()).thenReturn(TypeDefinition.Sort.NON_GENERIC);
+ when(firstFieldType.accept(any(TypeDescription.Generic.Visitor.class))).thenReturn(firstFieldType);
+ when(firstFieldType.asErasure()).thenReturn(firstRawFieldType);
+ when(firstFieldType.asRawType()).thenReturn(firstFieldType);
+ when(firstFieldType.asGenericType()).thenReturn(firstFieldType);
+ when(firstFieldType.accept(any(TypeDescription.Generic.Visitor.class))).thenReturn(firstFieldType);
+ when(firstRawFieldType.asGenericType()).thenReturn(firstFieldType);
+ when(firstRawFieldType.getDescriptor()).thenReturn(BAR);
+ when(secondFieldType.getStackSize()).thenReturn(StackSize.ZERO);
+ when(secondFieldType.getSort()).thenReturn(TypeDefinition.Sort.NON_GENERIC);
+ when(secondFieldType.accept(any(TypeDescription.Generic.Visitor.class))).thenReturn(secondFieldType);
+ when(secondFieldType.asErasure()).thenReturn(secondRawFieldType);
+ when(secondFieldType.asRawType()).thenReturn(secondFieldType);
+ when(secondFieldType.asGenericType()).thenReturn(secondFieldType);
+ when(secondFieldType.accept(any(TypeDescription.Generic.Visitor.class))).thenReturn(secondFieldType);
+ when(secondRawFieldType.asGenericType()).thenReturn(secondFieldType);
+ when(secondRawFieldType.getDescriptor()).thenReturn(QUX);
+ when(injectedCodeAppender.apply(any(MethodVisitor.class), any(Implementation.Context.class), any(MethodDescription.class)))
+ .thenReturn(new ByteCodeAppender.Size(0, 0));
+ when(terminationAppender.apply(any(MethodVisitor.class), any(Implementation.Context.class), any(MethodDescription.class)))
+ .thenReturn(new ByteCodeAppender.Size(0, 0));
+ when(firstSpecialInvocation.getMethodDescription()).thenReturn(firstSpecialMethod);
+ when(firstSpecialInvocation.getTypeDescription()).thenReturn(firstSpecialType);
+ when(firstSpecialMethod.getReturnType()).thenReturn(firstSpecialReturnType);
+ when(firstSpecialMethod.getInternalName()).thenReturn(FOO);
+ when(firstSpecialMethod.getExceptionTypes()).thenReturn(firstSpecialExceptionTypes);
+ when(firstRawSpecialParameterType.getDescriptor()).thenReturn(BAZ);
+ when(firstSpecialParameterType.getSort()).thenReturn(TypeDefinition.Sort.NON_GENERIC);
+ when(firstRawSpecialReturnType.getDescriptor()).thenReturn(QUX);
+ when(firstSpecialReturnType.getSort()).thenReturn(TypeDefinition.Sort.NON_GENERIC);
+ when(firstSpecialReturnType.getStackSize()).thenReturn(StackSize.ZERO);
+ when(firstSpecialReturnType.asRawType()).thenReturn(firstSpecialReturnType);
+ when(firstRawSpecialExceptionType.getInternalName()).thenReturn(FOO);
+ when(firstSpecialExceptionType.getSort()).thenReturn(TypeDefinition.Sort.NON_GENERIC);
+ when(firstSpecialExceptionType.asGenericType()).thenReturn(firstSpecialExceptionType);
+ when(firstSpecialExceptionType.asRawType()).thenReturn(firstSpecialExceptionType);
+ when(firstSpecialParameterType.getStackSize()).thenReturn(StackSize.ZERO);
+ when(firstSpecialParameterType.asGenericType()).thenReturn(firstSpecialParameterType);
+ when(firstSpecialParameterType.asRawType()).thenReturn(firstSpecialParameterType);
+ when(firstSpecialInvocation.apply(any(MethodVisitor.class), any(Implementation.Context.class))).thenReturn(new StackManipulation.Size(0, 0));
+ when(firstSpecialMethod.getParameters()).thenReturn(new ParameterList.Explicit.ForTypes(firstSpecialMethod, firstSpecialParameterType));
+ when(secondSpecialInvocation.getMethodDescription()).thenReturn(secondSpecialMethod);
+ when(secondSpecialInvocation.getTypeDescription()).thenReturn(secondSpecialType);
+ when(secondSpecialMethod.getInternalName()).thenReturn(BAR);
+ when(secondSpecialMethod.getReturnType()).thenReturn(secondSpecialReturnType);
+ when(secondSpecialMethod.getExceptionTypes()).thenReturn(secondSpecialExceptionTypes);
+ when(secondRawSpecialParameterType.getDescriptor()).thenReturn(BAR);
+ when(secondRawSpecialReturnType.getDescriptor()).thenReturn(FOO);
+ when(secondRawSpecialExceptionType.getInternalName()).thenReturn(BAZ);
+ when(secondSpecialExceptionType.getSort()).thenReturn(TypeDefinition.Sort.NON_GENERIC);
+ when(secondSpecialExceptionType.asGenericType()).thenReturn(secondSpecialExceptionType);
+ when(secondSpecialExceptionType.asRawType()).thenReturn(secondSpecialExceptionType);
+ when(secondSpecialParameterType.getStackSize()).thenReturn(StackSize.ZERO);
+ when(secondSpecialParameterType.getSort()).thenReturn(TypeDefinition.Sort.NON_GENERIC);
+ when(secondSpecialParameterType.asGenericType()).thenReturn(secondSpecialParameterType);
+ when(secondSpecialParameterType.asRawType()).thenReturn(secondSpecialParameterType);
+ when(secondSpecialReturnType.getStackSize()).thenReturn(StackSize.ZERO);
+ when(secondSpecialReturnType.getSort()).thenReturn(TypeDefinition.Sort.NON_GENERIC);
+ when(secondSpecialReturnType.asRawType()).thenReturn(secondSpecialReturnType);
+ when(secondSpecialInvocation.apply(any(MethodVisitor.class), any(Implementation.Context.class))).thenReturn(new StackManipulation.Size(0, 0));
+ when(secondSpecialMethod.getParameters()).thenReturn(new ParameterList.Explicit.ForTypes(secondSpecialMethod, secondSpecialParameterType));
+ when(firstField.getType()).thenReturn(firstFieldType);
+ when(firstField.getName()).thenReturn(FOO);
+ when(firstField.getInternalName()).thenReturn(FOO);
+ when(firstField.getDescriptor()).thenReturn(BAR);
+ when(firstField.getDeclaringType()).thenReturn(firstFieldDeclaringType);
+ when(firstField.asDefined()).thenReturn(firstField);
+ when(firstFieldDeclaringType.getInternalName()).thenReturn(QUX);
+ when(secondField.getType()).thenReturn(secondFieldType);
+ when(secondField.getName()).thenReturn(BAR);
+ when(secondField.getInternalName()).thenReturn(BAR);
+ when(secondField.getDescriptor()).thenReturn(FOO);
+ when(secondField.getDeclaringType()).thenReturn(secondFieldDeclaringType);
+ when(secondField.asDefined()).thenReturn(secondField);
+ when(secondFieldDeclaringType.getInternalName()).thenReturn(BAZ);
+ when(firstSpecialReturnType.asErasure()).thenReturn(firstRawSpecialReturnType);
+ when(secondSpecialReturnType.asErasure()).thenReturn(secondRawSpecialReturnType);
+ when(firstSpecialExceptionType.asErasure()).thenReturn(firstRawSpecialExceptionType);
+ when(secondSpecialExceptionType.asErasure()).thenReturn(secondRawSpecialExceptionType);
+ when(firstSpecialParameterType.asErasure()).thenReturn(firstRawSpecialParameterType);
+ when(secondSpecialParameterType.asErasure()).thenReturn(secondRawSpecialParameterType);
+ when(firstSpecialParameterType.accept(any(TypeDescription.Generic.Visitor.class))).thenReturn(firstSpecialParameterType);
+ when(secondSpecialParameterType.accept(any(TypeDescription.Generic.Visitor.class))).thenReturn(secondSpecialParameterType);
+ when(firstFieldDeclaringType.asErasure()).thenReturn(firstFieldDeclaringType);
+ when(secondFieldDeclaringType.asErasure()).thenReturn(secondFieldDeclaringType);
+ when(firstSpecialMethod.getDeclaringType()).thenReturn(firstSpecialType);
+ when(firstSpecialType.asErasure()).thenReturn(firstSpecialType);
+ when(secondSpecialMethod.getDeclaringType()).thenReturn(secondSpecialType);
+ when(secondSpecialType.asErasure()).thenReturn(secondSpecialType);
+ when(auxiliaryTypeNamingStrategy.name(instrumentedType)).thenReturn(FOO);
+ }
+
+ @Test
+ public void testInitialContextIsEmpty() throws Exception {
+ Implementation.Context.ExtractableView implementationContext = new Implementation.Context.Default(instrumentedType,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ typeInitializer,
+ auxiliaryClassFileVersion);
+ assertThat(implementationContext.getAuxiliaryTypes().size(), is(0));
+ implementationContext.drain(drain, classVisitor, annotationValueFilterFactory);
+ verifyZeroInteractions(classVisitor);
+ verify(drain).apply(classVisitor, typeInitializer, implementationContext);
+ verifyNoMoreInteractions(drain);
+ }
+
+ @Test
+ public void testAuxiliaryTypeRegistration() throws Exception {
+ Implementation.Context.ExtractableView implementationContext = new Implementation.Context.Default(instrumentedType,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ typeInitializer,
+ auxiliaryClassFileVersion);
+ assertThat(implementationContext.getAuxiliaryTypes().size(), is(0));
+ assertThat(implementationContext.register(auxiliaryType), is(firstDescription));
+ assertThat(implementationContext.getAuxiliaryTypes().size(), is(1));
+ assertThat(implementationContext.getAuxiliaryTypes().contains(firstDynamicType), is(true));
+ assertThat(implementationContext.register(otherAuxiliaryType), is(secondDescription));
+ assertThat(implementationContext.getAuxiliaryTypes().size(), is(2));
+ assertThat(implementationContext.getAuxiliaryTypes().contains(firstDynamicType), is(true));
+ assertThat(implementationContext.getAuxiliaryTypes().contains(secondDynamicType), is(true));
+ assertThat(implementationContext.register(auxiliaryType), is(firstDescription));
+ assertThat(implementationContext.getAuxiliaryTypes().size(), is(2));
+ assertThat(implementationContext.getAuxiliaryTypes().contains(firstDynamicType), is(true));
+ assertThat(implementationContext.getAuxiliaryTypes().contains(secondDynamicType), is(true));
+ }
+
+ @Test
+ public void testDrainEmpty() throws Exception {
+ Implementation.Context.ExtractableView implementationContext = new Implementation.Context.Default(instrumentedType,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ typeInitializer,
+ auxiliaryClassFileVersion);
+ implementationContext.drain(drain, classVisitor, annotationValueFilterFactory);
+ verifyZeroInteractions(classVisitor);
+ verify(drain).apply(classVisitor, typeInitializer, implementationContext);
+ verifyNoMoreInteractions(drain);
+ }
+
+ @Test
+ public void testDrainNoUserCodeNoInjectedCodeNoTypeInitializer() throws Exception {
+ Implementation.Context.ExtractableView implementationContext = new Implementation.Context.Default(instrumentedType,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ typeInitializer,
+ auxiliaryClassFileVersion);
+ implementationContext.drain(drain, classVisitor, annotationValueFilterFactory);
+ verifyZeroInteractions(classVisitor);
+ verifyZeroInteractions(typeInitializer);
+ verify(drain).apply(classVisitor, typeInitializer, implementationContext);
+ verifyNoMoreInteractions(drain);
+ }
+
+ @Test
+ public void testDrainUserCodeNoInjectedCodeNoTypeInitializer() throws Exception {
+ Implementation.Context.ExtractableView implementationContext = new Implementation.Context.Default(instrumentedType,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ typeInitializer,
+ auxiliaryClassFileVersion);
+ implementationContext.drain(drain, classVisitor, annotationValueFilterFactory);
+ verifyZeroInteractions(classVisitor);
+ verifyZeroInteractions(typeInitializer);
+ verify(drain).apply(classVisitor, typeInitializer, implementationContext);
+ verifyNoMoreInteractions(drain);
+ }
+
+ @Test
+ public void testDrainFieldCacheEntries() throws Exception {
+ Implementation.Context.ExtractableView implementationContext = new Implementation.Context.Default(instrumentedType,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ typeInitializer,
+ auxiliaryClassFileVersion);
+ FieldDescription firstField = implementationContext.cache(firstFieldValue, firstRawFieldType);
+ assertThat(implementationContext.cache(firstFieldValue, firstRawFieldType), is(firstField));
+ FieldDescription secondField = implementationContext.cache(secondFieldValue, secondRawFieldType);
+ assertThat(implementationContext.cache(secondFieldValue, secondRawFieldType), is(secondField));
+ assertThat(firstField.getName(), not(secondField.getName()));
+ when(typeInitializer.expandWith(any(ByteCodeAppender.class))).thenReturn(otherTypeInitializer);
+ when(otherTypeInitializer.expandWith(any(ByteCodeAppender.class))).thenReturn(thirdTypeInitializer);
+ when(thirdTypeInitializer.isDefined()).thenReturn(true);
+ implementationContext.drain(drain, classVisitor, annotationValueFilterFactory);
+ verify(classVisitor).visitField(eq(cacheFieldModifiers),
+ Mockito.startsWith(Implementation.Context.Default.FIELD_CACHE_PREFIX),
+ eq(BAR),
+ Mockito.<String>isNull(),
+ Mockito.isNull());
+ verify(classVisitor).visitField(eq(cacheFieldModifiers),
+ Mockito.startsWith(Implementation.Context.Default.FIELD_CACHE_PREFIX),
+ eq(QUX),
+ Mockito.<String>isNull(),
+ Mockito.isNull());
+ verify(typeInitializer).expandWith(any(ByteCodeAppender.class));
+ verify(otherTypeInitializer).expandWith(any(ByteCodeAppender.class));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testCannotRegisterFieldAfterDraining() throws Exception {
+ Implementation.Context.ExtractableView implementationContext = new Implementation.Context.Default(instrumentedType,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ typeInitializer,
+ auxiliaryClassFileVersion);
+ implementationContext.drain(drain, classVisitor, annotationValueFilterFactory);
+ verifyZeroInteractions(classVisitor);
+ verify(drain).apply(classVisitor, typeInitializer, implementationContext);
+ verifyNoMoreInteractions(drain);
+ implementationContext.cache(firstFieldValue, firstRawFieldType);
+ }
+
+ @Test
+ public void testAccessorMethodRegistration() throws Exception {
+ Implementation.Context.Default implementationContext = new Implementation.Context.Default(instrumentedType,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ typeInitializer,
+ auxiliaryClassFileVersion);
+ MethodDescription.InDefinedShape firstMethodDescription = implementationContext.registerAccessorFor(firstSpecialInvocation, MethodAccessorFactory.AccessType.DEFAULT);
+ assertThat(firstMethodDescription.getParameters(), is((ParameterList) new ParameterList.Explicit.ForTypes(firstMethodDescription, firstSpecialParameterType)));
+ assertThat(firstMethodDescription.getReturnType(), is(firstSpecialReturnType));
+ assertThat(firstMethodDescription.getInternalName(), startsWith(FOO));
+ assertThat(firstMethodDescription.getModifiers(), is(accessorMethodModifiers));
+ assertThat(firstMethodDescription.getExceptionTypes(), is(firstSpecialExceptionTypes));
+ assertThat(implementationContext.registerAccessorFor(firstSpecialInvocation, MethodAccessorFactory.AccessType.DEFAULT), is(firstMethodDescription));
+ when(secondSpecialMethod.isStatic()).thenReturn(true);
+ MethodDescription.InDefinedShape secondMethodDescription = implementationContext.registerAccessorFor(secondSpecialInvocation, MethodAccessorFactory.AccessType.DEFAULT);
+ assertThat(secondMethodDescription.getParameters(), is((ParameterList) new ParameterList.Explicit.ForTypes(secondMethodDescription, secondSpecialParameterType)));
+ assertThat(secondMethodDescription.getReturnType(), is(secondSpecialReturnType));
+ assertThat(secondMethodDescription.getInternalName(), startsWith(BAR));
+ assertThat(secondMethodDescription.getModifiers(), is(accessorMethodModifiers | Opcodes.ACC_STATIC));
+ assertThat(secondMethodDescription.getExceptionTypes(), is(secondSpecialExceptionTypes));
+ assertThat(implementationContext.registerAccessorFor(firstSpecialInvocation, MethodAccessorFactory.AccessType.DEFAULT), is(firstMethodDescription));
+ assertThat(implementationContext.registerAccessorFor(secondSpecialInvocation, MethodAccessorFactory.AccessType.DEFAULT), is(secondMethodDescription));
+ implementationContext.drain(drain, classVisitor, annotationValueFilterFactory);
+ verify(classVisitor).visitMethod(eq(accessorMethodModifiers), Mockito.startsWith(FOO),
+ eq("(" + BAZ + ")" + QUX), Mockito.<String>isNull(), aryEq(new String[]{FOO}));
+ verify(classVisitor).visitMethod(eq(accessorMethodModifiers | Opcodes.ACC_STATIC), Mockito.startsWith(BAR),
+ eq("(" + BAR + ")" + FOO), Mockito.<String>isNull(), aryEq(new String[]{BAZ}));
+ }
+
+ @Test
+ public void testAccessorMethodRegistrationWritesFirst() throws Exception {
+ Implementation.Context.Default implementationContext = new Implementation.Context.Default(instrumentedType,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ typeInitializer,
+ auxiliaryClassFileVersion);
+ MethodDescription firstMethodDescription = implementationContext.registerAccessorFor(firstSpecialInvocation, MethodAccessorFactory.AccessType.DEFAULT);
+ assertThat(implementationContext.registerAccessorFor(firstSpecialInvocation, MethodAccessorFactory.AccessType.DEFAULT), is(firstMethodDescription));
+ implementationContext.drain(drain, classVisitor, annotationValueFilterFactory);
+ verify(classVisitor).visitMethod(eq(accessorMethodModifiers), Mockito.startsWith(FOO),
+ eq("(" + BAZ + ")" + QUX), Mockito.<String>isNull(), aryEq(new String[]{FOO}));
+ verify(methodVisitor).visitCode();
+ verify(methodVisitor).visitVarInsn(Opcodes.ALOAD, 0);
+ verify(methodVisitor).visitVarInsn(Opcodes.ALOAD, 1);
+ verify(firstSpecialInvocation).apply(methodVisitor, implementationContext);
+ verify(methodVisitor).visitInsn(Opcodes.ARETURN);
+ verify(methodVisitor).visitMaxs(2, 1);
+ verify(methodVisitor).visitEnd();
+ }
+
+ @Test
+ public void testAccessorMethodRegistrationWritesSecond() throws Exception {
+ when(secondSpecialMethod.isStatic()).thenReturn(true);
+ Implementation.Context.Default implementationContext = new Implementation.Context.Default(instrumentedType,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ typeInitializer,
+ auxiliaryClassFileVersion);
+ MethodDescription secondMethodDescription = implementationContext.registerAccessorFor(secondSpecialInvocation, MethodAccessorFactory.AccessType.DEFAULT);
+ assertThat(implementationContext.registerAccessorFor(secondSpecialInvocation, MethodAccessorFactory.AccessType.DEFAULT), is(secondMethodDescription));
+ implementationContext.drain(drain, classVisitor, annotationValueFilterFactory);
+ verify(classVisitor).visitMethod(eq(accessorMethodModifiers | Opcodes.ACC_STATIC), Mockito.startsWith(BAR),
+ eq("(" + BAR + ")" + FOO), Mockito.<String>isNull(), aryEq(new String[]{BAZ}));
+ verify(methodVisitor).visitCode();
+ verify(methodVisitor).visitVarInsn(Opcodes.ALOAD, 0);
+ verify(secondSpecialInvocation).apply(methodVisitor, implementationContext);
+ verify(methodVisitor).visitInsn(Opcodes.ARETURN);
+ verify(methodVisitor).visitMaxs(1, 0);
+ verify(methodVisitor).visitEnd();
+ }
+
+ @Test
+ public void testFieldGetterRegistration() throws Exception {
+ Implementation.Context.Default implementationContext = new Implementation.Context.Default(instrumentedType,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ typeInitializer,
+ auxiliaryClassFileVersion);
+ MethodDescription firstFieldGetter = implementationContext.registerGetterFor(firstField, MethodAccessorFactory.AccessType.DEFAULT);
+ assertThat(firstFieldGetter.getParameters(), is((ParameterList) new ParameterList.Empty<ParameterDescription>()));
+ assertThat(firstFieldGetter.getReturnType(), is(firstFieldType));
+ assertThat(firstFieldGetter.getInternalName(), startsWith(FOO));
+ assertThat(firstFieldGetter.getModifiers(), is(accessorMethodModifiers));
+ assertThat(firstFieldGetter.getExceptionTypes(), is((TypeList.Generic) new TypeList.Generic.Empty()));
+ assertThat(implementationContext.registerGetterFor(firstField, MethodAccessorFactory.AccessType.DEFAULT), is(firstFieldGetter));
+ when(secondField.isStatic()).thenReturn(true);
+ MethodDescription secondFieldGetter = implementationContext.registerGetterFor(secondField, MethodAccessorFactory.AccessType.DEFAULT);
+ assertThat(secondFieldGetter.getParameters(), is((ParameterList) new ParameterList.Empty<ParameterDescription>()));
+ assertThat(secondFieldGetter.getReturnType(), is(secondFieldType));
+ assertThat(secondFieldGetter.getInternalName(), startsWith(BAR));
+ assertThat(secondFieldGetter.getModifiers(), is(accessorMethodModifiers | Opcodes.ACC_STATIC));
+ assertThat(secondFieldGetter.getExceptionTypes(), is((TypeList.Generic) new TypeList.Generic.Empty()));
+ assertThat(implementationContext.registerGetterFor(firstField, MethodAccessorFactory.AccessType.DEFAULT), is(firstFieldGetter));
+ assertThat(implementationContext.registerGetterFor(secondField, MethodAccessorFactory.AccessType.DEFAULT), is(secondFieldGetter));
+ implementationContext.drain(drain, classVisitor, annotationValueFilterFactory);
+ verify(classVisitor).visitMethod(eq(accessorMethodModifiers), Mockito.startsWith(FOO),
+ eq("()" + BAR), Mockito.<String>isNull(), Mockito.<String[]>isNull());
+ verify(classVisitor).visitMethod(eq(accessorMethodModifiers | Opcodes.ACC_STATIC), Mockito.startsWith(BAR),
+ eq("()" + QUX), Mockito.<String>isNull(), Mockito.<String[]>isNull());
+ }
+
+ @Test
+ public void testFieldGetterRegistrationWritesFirst() throws Exception {
+ Implementation.Context.Default implementationContext = new Implementation.Context.Default(instrumentedType,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ typeInitializer,
+ auxiliaryClassFileVersion);
+ MethodDescription firstMethodDescription = implementationContext.registerGetterFor(firstField, MethodAccessorFactory.AccessType.DEFAULT);
+ assertThat(implementationContext.registerGetterFor(firstField, MethodAccessorFactory.AccessType.DEFAULT), is(firstMethodDescription));
+ implementationContext.drain(drain, classVisitor, annotationValueFilterFactory);
+ verify(classVisitor).visitMethod(eq(accessorMethodModifiers), Mockito.startsWith(FOO),
+ eq("()" + BAR), Mockito.<String>isNull(), Mockito.<String[]>isNull());
+ verify(methodVisitor).visitCode();
+ verify(methodVisitor).visitVarInsn(Opcodes.ALOAD, 0);
+ verify(methodVisitor).visitFieldInsn(Opcodes.GETFIELD, QUX, FOO, BAR);
+ verify(methodVisitor).visitInsn(Opcodes.ARETURN);
+ verify(methodVisitor).visitMaxs(1, 1);
+ verify(methodVisitor).visitEnd();
+ }
+
+ @Test
+ public void testFieldGetterRegistrationWritesSecond() throws Exception {
+ when(secondField.isStatic()).thenReturn(true);
+ Implementation.Context.Default implementationContext = new Implementation.Context.Default(instrumentedType,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ typeInitializer,
+ auxiliaryClassFileVersion);
+ MethodDescription secondMethodDescription = implementationContext.registerGetterFor(secondField, MethodAccessorFactory.AccessType.DEFAULT);
+ assertThat(implementationContext.registerGetterFor(secondField, MethodAccessorFactory.AccessType.DEFAULT), is(secondMethodDescription));
+ implementationContext.drain(drain, classVisitor, annotationValueFilterFactory);
+ verify(classVisitor).visitMethod(eq(accessorMethodModifiers | Opcodes.ACC_STATIC), Mockito.startsWith(BAR),
+ eq("()" + QUX), Mockito.<String>isNull(), Mockito.<String[]>isNull());
+ verify(methodVisitor).visitCode();
+ verify(methodVisitor).visitFieldInsn(Opcodes.GETSTATIC, BAZ, BAR, FOO);
+ verify(methodVisitor).visitInsn(Opcodes.ARETURN);
+ verify(methodVisitor).visitMaxs(0, 0);
+ verify(methodVisitor).visitEnd();
+ }
+
+ @Test
+ public void testFieldSetterRegistration() throws Exception {
+ Implementation.Context.Default implementationContext = new Implementation.Context.Default(instrumentedType,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ typeInitializer,
+ auxiliaryClassFileVersion);
+ MethodDescription.InDefinedShape firstFieldSetter = implementationContext.registerSetterFor(firstField, MethodAccessorFactory.AccessType.DEFAULT);
+ assertThat(firstFieldSetter.getParameters(), is((ParameterList) new ParameterList.Explicit.ForTypes(firstFieldSetter, firstFieldType)));
+ assertThat(firstFieldSetter.getReturnType(), is(TypeDescription.Generic.VOID));
+ assertThat(firstFieldSetter.getInternalName(), startsWith(FOO));
+ assertThat(firstFieldSetter.getModifiers(), is(accessorMethodModifiers));
+ assertThat(firstFieldSetter.getExceptionTypes(), is((TypeList.Generic) new TypeList.Generic.Empty()));
+ assertThat(implementationContext.registerSetterFor(firstField, MethodAccessorFactory.AccessType.DEFAULT), is(firstFieldSetter));
+ when(secondField.isStatic()).thenReturn(true);
+ MethodDescription.InDefinedShape secondFieldSetter = implementationContext.registerSetterFor(secondField, MethodAccessorFactory.AccessType.DEFAULT);
+ assertThat(secondFieldSetter.getParameters(), is((ParameterList) new ParameterList.Explicit.ForTypes(secondFieldSetter, secondFieldType)));
+ assertThat(secondFieldSetter.getReturnType(), is(TypeDescription.Generic.VOID));
+ assertThat(secondFieldSetter.getInternalName(), startsWith(BAR));
+ assertThat(secondFieldSetter.getModifiers(), is(accessorMethodModifiers | Opcodes.ACC_STATIC));
+ assertThat(secondFieldSetter.getExceptionTypes(), is((TypeList.Generic) new TypeList.Generic.Empty()));
+ assertThat(implementationContext.registerSetterFor(firstField, MethodAccessorFactory.AccessType.DEFAULT), is(firstFieldSetter));
+ assertThat(implementationContext.registerSetterFor(secondField, MethodAccessorFactory.AccessType.DEFAULT), is(secondFieldSetter));
+ implementationContext.drain(drain, classVisitor, annotationValueFilterFactory);
+ verify(classVisitor).visitMethod(eq(accessorMethodModifiers), Mockito.startsWith(FOO),
+ eq("(" + BAR + ")V"), Mockito.<String>isNull(), Mockito.<String[]>isNull());
+ verify(classVisitor).visitMethod(eq(accessorMethodModifiers | Opcodes.ACC_STATIC), Mockito.startsWith(BAR),
+ eq("(" + QUX + ")V"), Mockito.<String>isNull(), Mockito.<String[]>isNull());
+ }
+
+ @Test
+ public void testFieldSetterRegistrationWritesFirst() throws Exception {
+ Implementation.Context.Default implementationContext = new Implementation.Context.Default(instrumentedType,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ typeInitializer,
+ auxiliaryClassFileVersion);
+ MethodDescription firstMethodDescription = implementationContext.registerSetterFor(firstField, MethodAccessorFactory.AccessType.DEFAULT);
+ assertThat(implementationContext.registerSetterFor(firstField, MethodAccessorFactory.AccessType.DEFAULT), is(firstMethodDescription));
+ implementationContext.drain(drain, classVisitor, annotationValueFilterFactory);
+ verify(classVisitor).visitMethod(eq(accessorMethodModifiers), Mockito.startsWith(FOO),
+ eq("(" + BAR + ")V"), Mockito.<String>isNull(), Mockito.<String[]>isNull());
+ verify(methodVisitor).visitCode();
+ verify(methodVisitor).visitVarInsn(Opcodes.ALOAD, 0);
+ verify(methodVisitor).visitVarInsn(Opcodes.ALOAD, 1);
+ verify(methodVisitor).visitFieldInsn(Opcodes.PUTFIELD, QUX, FOO, BAR);
+ verify(methodVisitor).visitInsn(Opcodes.RETURN);
+ verify(methodVisitor).visitMaxs(2, 1);
+ verify(methodVisitor).visitEnd();
+ }
+
+ @Test
+ public void testFieldSetterRegistrationWritesSecond() throws Exception {
+ when(secondField.isStatic()).thenReturn(true);
+ Implementation.Context.Default implementationContext = new Implementation.Context.Default(instrumentedType,
+ classFileVersion,
+ auxiliaryTypeNamingStrategy,
+ typeInitializer,
+ auxiliaryClassFileVersion);
+ MethodDescription secondMethodDescription = implementationContext.registerSetterFor(secondField, MethodAccessorFactory.AccessType.DEFAULT);
+ assertThat(implementationContext.registerSetterFor(secondField, MethodAccessorFactory.AccessType.DEFAULT), is(secondMethodDescription));
+ implementationContext.drain(drain, classVisitor, annotationValueFilterFactory);
+ verify(classVisitor).visitMethod(eq(accessorMethodModifiers | Opcodes.ACC_STATIC), Mockito.startsWith(BAR),
+ eq("(" + QUX + ")V"), Mockito.<String>isNull(), Mockito.<String[]>isNull());
+ verify(methodVisitor).visitCode();
+ verify(methodVisitor).visitVarInsn(Opcodes.ALOAD, 0);
+ verify(methodVisitor).visitFieldInsn(Opcodes.PUTSTATIC, BAZ, BAR, FOO);
+ verify(methodVisitor).visitInsn(Opcodes.RETURN);
+ verify(methodVisitor).visitMaxs(1, 0);
+ verify(methodVisitor).visitEnd();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/ImplementationContextDisabledTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/ImplementationContextDisabledTest.java
new file mode 100644
index 0000000..baedaec
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/ImplementationContextDisabledTest.java
@@ -0,0 +1,118 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.scaffold.TypeInitializer;
+import net.bytebuddy.dynamic.scaffold.TypeWriter;
+import net.bytebuddy.implementation.attribute.AnnotationValueFilter;
+import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.ClassVisitor;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ImplementationContextDisabledTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription instrumentedType;
+
+ @Mock
+ private ClassFileVersion classFileVersion;
+
+ @Mock
+ private TypeWriter.MethodPool methodPool;
+
+ @Mock
+ private TypeWriter.MethodPool.Record record;
+
+ @Before
+ public void setUp() throws Exception {
+ when(methodPool.target(new MethodDescription.Latent.TypeInitializer(instrumentedType))).thenReturn(record);
+ }
+
+ @Test
+ public void testFactory() throws Exception {
+ assertThat(Implementation.Context.Disabled.Factory.INSTANCE.make(instrumentedType,
+ mock(AuxiliaryType.NamingStrategy.class),
+ mock(TypeInitializer.class),
+ classFileVersion,
+ mock(ClassFileVersion.class)), is((Implementation.Context.ExtractableView) new Implementation.Context.Disabled(instrumentedType, classFileVersion)));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testFactoryWithTypeInitializer() throws Exception {
+ TypeInitializer typeInitializer = mock(TypeInitializer.class);
+ when(typeInitializer.isDefined()).thenReturn(true);
+ Implementation.Context.Disabled.Factory.INSTANCE.make(instrumentedType,
+ mock(AuxiliaryType.NamingStrategy.class),
+ typeInitializer,
+ mock(ClassFileVersion.class),
+ mock(ClassFileVersion.class));
+ }
+
+ @Test
+ public void testDisabled() throws Exception {
+ assertThat(new Implementation.Context.Disabled(instrumentedType, classFileVersion).isEnabled(), is(false));
+ }
+
+ @Test
+ public void testAuxiliaryTypes() throws Exception {
+ assertThat(new Implementation.Context.Disabled(instrumentedType, classFileVersion).getAuxiliaryTypes().size(), is(0));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testCannotCacheValue() throws Exception {
+ new Implementation.Context.Disabled(instrumentedType, classFileVersion).cache(mock(StackManipulation.class), mock(TypeDescription.class));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testCannotCreateFieldGetter() throws Exception {
+ new Implementation.Context.Disabled(instrumentedType, classFileVersion).registerGetterFor(mock(FieldDescription.class), MethodAccessorFactory.AccessType.DEFAULT);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testCannotCreateFieldSetter() throws Exception {
+ new Implementation.Context.Disabled(instrumentedType, classFileVersion).registerSetterFor(mock(FieldDescription.class), MethodAccessorFactory.AccessType.DEFAULT);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testCannotCreateMethodAccessor() throws Exception {
+ new Implementation.Context.Disabled(instrumentedType, classFileVersion).registerAccessorFor(mock(Implementation.SpecialMethodInvocation.class), MethodAccessorFactory.AccessType.DEFAULT);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testCannotRegisterAuxiliaryType() throws Exception {
+ new Implementation.Context.Disabled(instrumentedType, classFileVersion).register(mock(AuxiliaryType.class));
+ }
+
+ @Test
+ public void testClassFileVersion() throws Exception {
+ assertThat(new Implementation.Context.Disabled(instrumentedType, classFileVersion).getClassFileVersion(), is(classFileVersion));
+ }
+
+ @Test
+ public void testInstrumentationGetter() throws Exception {
+ assertThat(new Implementation.Context.Disabled(instrumentedType, classFileVersion).getInstrumentedType(), is(instrumentedType));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(Implementation.Context.Disabled.class).apply();
+ ObjectPropertyAssertion.of(Implementation.Context.Disabled.Factory.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/ImplementationSimpleTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/ImplementationSimpleTest.java
new file mode 100644
index 0000000..3b7f328
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/ImplementationSimpleTest.java
@@ -0,0 +1,12 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+public class ImplementationSimpleTest {
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(Implementation.Simple.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/ImplementationSpecialMethodInvocationIllegalTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/ImplementationSpecialMethodInvocationIllegalTest.java
new file mode 100644
index 0000000..5865af1
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/ImplementationSpecialMethodInvocationIllegalTest.java
@@ -0,0 +1,57 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.MethodVisitor;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+public class ImplementationSpecialMethodInvocationIllegalTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @After
+ public void tearDown() throws Exception {
+ verifyZeroInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testIsInvalid() throws Exception {
+ assertThat(Implementation.SpecialMethodInvocation.Illegal.INSTANCE.isValid(), is(false));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodDescriptionIllegal() throws Exception {
+ Implementation.SpecialMethodInvocation.Illegal.INSTANCE.getMethodDescription();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testTypeDescriptionIllegal() throws Exception {
+ Implementation.SpecialMethodInvocation.Illegal.INSTANCE.getTypeDescription();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testApplicationIllegal() throws Exception {
+ Implementation.SpecialMethodInvocation.Illegal.INSTANCE.apply(methodVisitor, implementationContext);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(Implementation.SpecialMethodInvocation.Illegal.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/ImplementationSpecialMethodInvocationSimpleTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/ImplementationSpecialMethodInvocationSimpleTest.java
new file mode 100644
index 0000000..1ae2340
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/ImplementationSpecialMethodInvocationSimpleTest.java
@@ -0,0 +1,52 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ImplementationSpecialMethodInvocationSimpleTest extends AbstractSpecialMethodInvocationTest {
+
+ @Override
+ protected Implementation.SpecialMethodInvocation make(MethodDescription methodDescription, TypeDescription typeDescription) {
+ return new Implementation.SpecialMethodInvocation.Simple(methodDescription, typeDescription, mock(StackManipulation.class));
+ }
+
+ @Test
+ public void testHashCode() throws Exception {
+ MethodDescription firstMethod = mock(MethodDescription.class), secondMethod = mock(MethodDescription.class);
+ MethodDescription.SignatureToken firstToken = mock(MethodDescription.SignatureToken.class), secondToken = mock(MethodDescription.SignatureToken.class);
+ when(firstMethod.asSignatureToken()).thenReturn(firstToken);
+ when(secondMethod.asSignatureToken()).thenReturn(secondToken);
+ TypeDescription firstType = mock(TypeDescription.class), secondType = mock(TypeDescription.class);
+ assertThat(new Implementation.SpecialMethodInvocation.Simple(firstMethod, firstType, mock(StackManipulation.class)).hashCode(),
+ is(new Implementation.SpecialMethodInvocation.Simple(firstMethod, firstType, mock(StackManipulation.class)).hashCode()));
+ assertThat(new Implementation.SpecialMethodInvocation.Simple(firstMethod, firstType, mock(StackManipulation.class)).hashCode(),
+ not(new Implementation.SpecialMethodInvocation.Simple(secondMethod, firstType, mock(StackManipulation.class)).hashCode()));
+ assertThat(new Implementation.SpecialMethodInvocation.Simple(firstMethod, firstType, mock(StackManipulation.class)).hashCode(),
+ not(new Implementation.SpecialMethodInvocation.Simple(firstMethod, secondType, mock(StackManipulation.class)).hashCode()));
+ }
+
+ @Test
+ public void testEquality() throws Exception {
+ MethodDescription firstMethod = mock(MethodDescription.class), secondMethod = mock(MethodDescription.class);
+ MethodDescription.SignatureToken firstToken = mock(MethodDescription.SignatureToken.class), secondToken = mock(MethodDescription.SignatureToken.class);
+ when(firstMethod.asSignatureToken()).thenReturn(firstToken);
+ when(secondMethod.asSignatureToken()).thenReturn(secondToken);
+ TypeDescription firstType = mock(TypeDescription.class), secondType = mock(TypeDescription.class);
+ assertThat(new Implementation.SpecialMethodInvocation.Simple(firstMethod, firstType, mock(StackManipulation.class)),
+ is(new Implementation.SpecialMethodInvocation.Simple(firstMethod, firstType, mock(StackManipulation.class))));
+ assertThat(new Implementation.SpecialMethodInvocation.Simple(firstMethod, firstType, mock(StackManipulation.class)),
+ not(new Implementation.SpecialMethodInvocation.Simple(secondMethod, firstType, mock(StackManipulation.class))));
+ assertThat(new Implementation.SpecialMethodInvocation.Simple(firstMethod, firstType, mock(StackManipulation.class)),
+ not(new Implementation.SpecialMethodInvocation.Simple(firstMethod, secondType, mock(StackManipulation.class))));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/ImplementationTargetAbstractBaseDefaultMethodInvocationTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/ImplementationTargetAbstractBaseDefaultMethodInvocationTest.java
new file mode 100644
index 0000000..d9f90e5
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/ImplementationTargetAbstractBaseDefaultMethodInvocationTest.java
@@ -0,0 +1,28 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class ImplementationTargetAbstractBaseDefaultMethodInvocationTest {
+
+ @Test
+ public void testEnabled() throws Exception {
+ assertThat(Implementation.Target.AbstractBase.DefaultMethodInvocation.of(ClassFileVersion.JAVA_V8),
+ is(Implementation.Target.AbstractBase.DefaultMethodInvocation.ENABLED));
+ }
+
+ @Test
+ public void testDisabled() throws Exception {
+ assertThat(Implementation.Target.AbstractBase.DefaultMethodInvocation.of(ClassFileVersion.JAVA_V7),
+ is(Implementation.Target.AbstractBase.DefaultMethodInvocation.DISABLED));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(Implementation.Target.AbstractBase.DefaultMethodInvocation.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/InvocationHandlerAdapterTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/InvocationHandlerAdapterTest.java
new file mode 100644
index 0000000..332860e
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/InvocationHandlerAdapterTest.java
@@ -0,0 +1,234 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.modifier.Ownership;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.test.utility.CallTraceable;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class InvocationHandlerAdapterTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux";
+
+ private static final int BAZ = 42;
+
+ @Test
+ public void testStaticAdapterWithoutCache() throws Exception {
+ Foo foo = new Foo();
+ DynamicType.Loaded<Bar> loaded = new ByteBuddy()
+ .subclass(Bar.class)
+ .method(isDeclaredBy(Bar.class))
+ .intercept(InvocationHandlerAdapter.of(foo).withoutMethodCache())
+ .make()
+ .load(Bar.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(1));
+ Bar instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.bar(FOO), is((Object) instance));
+ assertThat(foo.methods.size(), is(1));
+ assertThat(instance.bar(FOO), is((Object) instance));
+ assertThat(foo.methods.size(), is(2));
+ assertThat(foo.methods.get(0), not(sameInstance(foo.methods.get(1))));
+ instance.assertZeroCalls();
+ }
+
+ @Test
+ public void testStaticAdapterWithoutCacheForPrimitiveValue() throws Exception {
+ Qux qux = new Qux();
+ DynamicType.Loaded<Baz> loaded = new ByteBuddy()
+ .subclass(Baz.class)
+ .method(isDeclaredBy(Baz.class))
+ .intercept(InvocationHandlerAdapter.of(qux).withoutMethodCache())
+ .make()
+ .load(Bar.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(1));
+ Baz instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.bar(BAZ), is(BAZ * 2L));
+ instance.assertZeroCalls();
+ }
+
+ @Test
+ public void testStaticAdapterWithMethodCache() throws Exception {
+ Foo foo = new Foo();
+ DynamicType.Loaded<Bar> loaded = new ByteBuddy()
+ .subclass(Bar.class)
+ .method(isDeclaredBy(Bar.class))
+ .intercept(InvocationHandlerAdapter.of(foo))
+ .make()
+ .load(Bar.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(2));
+ Bar instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.bar(FOO), is((Object) instance));
+ assertThat(foo.methods.size(), is(1));
+ assertThat(instance.bar(FOO), is((Object) instance));
+ assertThat(foo.methods.size(), is(2));
+ assertThat(foo.methods.get(0), sameInstance(foo.methods.get(1)));
+ instance.assertZeroCalls();
+ }
+
+ @Test
+ public void testInstanceAdapterWithoutCache() throws Exception {
+ DynamicType.Loaded<Bar> loaded = new ByteBuddy()
+ .subclass(Bar.class)
+ .defineField(QUX, InvocationHandler.class, Visibility.PUBLIC)
+ .method(isDeclaredBy(Bar.class))
+ .intercept(InvocationHandlerAdapter.toField(QUX).withoutMethodCache())
+ .make()
+ .load(Bar.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(1));
+ Field field = loaded.getLoaded().getDeclaredField(QUX);
+ assertThat(field.getModifiers(), is(Modifier.PUBLIC));
+ field.setAccessible(true);
+ Bar instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ Foo foo = new Foo();
+ field.set(instance, foo);
+ assertThat(instance.bar(FOO), is((Object) instance));
+ assertThat(foo.methods.size(), is(1));
+ assertThat(instance.bar(FOO), is((Object) instance));
+ assertThat(foo.methods.size(), is(2));
+ assertThat(foo.methods.get(0), not(sameInstance(foo.methods.get(1))));
+ instance.assertZeroCalls();
+ }
+
+ @Test
+ public void testInstanceAdapterWithMethodCache() throws Exception {
+ DynamicType.Loaded<Bar> loaded = new ByteBuddy()
+ .subclass(Bar.class)
+ .defineField(QUX, InvocationHandler.class, Visibility.PUBLIC)
+ .method(isDeclaredBy(Bar.class))
+ .intercept(InvocationHandlerAdapter.toField(QUX))
+ .make()
+ .load(Bar.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(2));
+ Field field = loaded.getLoaded().getDeclaredField(QUX);
+ assertThat(field.getModifiers(), is(Modifier.PUBLIC));
+ field.setAccessible(true);
+ Bar instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ Foo foo = new Foo();
+ field.set(instance, foo);
+ assertThat(instance.bar(FOO), is((Object) instance));
+ assertThat(foo.methods.size(), is(1));
+ assertThat(instance.bar(FOO), is((Object) instance));
+ assertThat(foo.methods.size(), is(2));
+ assertThat(foo.methods.get(0), sameInstance(foo.methods.get(1)));
+ instance.assertZeroCalls();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testStaticMethod() throws Exception {
+ new ByteBuddy()
+ .subclass(Object.class)
+ .defineField(QUX, InvocationHandler.class)
+ .defineMethod(FOO, void.class, Ownership.STATIC)
+ .intercept(InvocationHandlerAdapter.toField(QUX))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonExistentField() throws Exception {
+ new ByteBuddy()
+ .subclass(Object.class)
+ .defineMethod(FOO, void.class)
+ .intercept(InvocationHandlerAdapter.toField(QUX))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIncompatibleFieldType() throws Exception {
+ new ByteBuddy()
+ .subclass(Object.class)
+ .defineField(QUX, Object.class)
+ .defineMethod(FOO, void.class)
+ .intercept(InvocationHandlerAdapter.toField(QUX))
+ .make();
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(InvocationHandlerAdapter.ForInstance.class).apply();
+ ObjectPropertyAssertion.of(InvocationHandlerAdapter.ForInstance.Appender.class).apply();
+ ObjectPropertyAssertion.of(InvocationHandlerAdapter.ForInstance.Appender.class).skipSynthetic().apply();
+ ObjectPropertyAssertion.of(InvocationHandlerAdapter.ForField.class).apply();
+ ObjectPropertyAssertion.of(InvocationHandlerAdapter.ForField.Appender.class).skipSynthetic().apply();
+ }
+
+ private static class Foo implements InvocationHandler {
+
+ private final String marker;
+
+ public List<Method> methods;
+
+ private Foo() {
+ marker = FOO;
+ methods = new ArrayList<Method>();
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ methods.add(method);
+ assertThat(args.length, is(1));
+ assertThat(args[0], is((Object) FOO));
+ assertThat(method.getName(), is(BAR));
+ assertThat(proxy, instanceOf(Bar.class));
+ return proxy;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && marker.equals(((Foo) other).marker);
+ }
+
+ @Override
+ public int hashCode() {
+ return marker.hashCode();
+ }
+ }
+
+ public static class Bar extends CallTraceable {
+
+ public Object bar(Object o) {
+ register(BAR);
+ return o;
+ }
+ }
+
+ private static class Qux implements InvocationHandler {
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ return ((Integer) args[0]) * 2L;
+ }
+ }
+
+ public static class Baz extends CallTraceable {
+
+ public long bar(int o) {
+ register(BAR);
+ return o;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/InvokeDynamicTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/InvokeDynamicTest.java
new file mode 100644
index 0000000..7d12635
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/InvokeDynamicTest.java
@@ -0,0 +1,559 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.enumeration.EnumerationDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.utility.JavaConstant;
+import net.bytebuddy.utility.JavaType;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class InvokeDynamicTest {
+
+ public static final String INSTANCE = "INSTANCE";
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux", BAZ = "baz";
+
+ private static final boolean BOOLEAN = true;
+
+ private static final byte BYTE = 42;
+
+ private static final short SHORT = 42;
+
+ private static final char CHARACTER = 42;
+
+ private static final int INTEGER = 42;
+
+ private static final long LONG = 42L;
+
+ private static final float FLOAT = 42f;
+
+ private static final double DOUBLE = 42d;
+
+ private static final Class<?> CLASS = Object.class;
+
+ private static final String STANDARD_ARGUMENT_BOOTSTRAP = "net.bytebuddy.test.precompiled.StandardArgumentBootstrap";
+
+ private static final String PARAMETER_BOOTSTRAP = "net.bytebuddy.test.precompiled.ParameterBootstrap";
+
+ private static final String ARGUMENT_BOOTSTRAP = "net.bytebuddy.test.precompiled.ArgumentBootstrap";
+
+ public static final String SAMPLE_ENUM = ARGUMENT_BOOTSTRAP + "$SampleEnum";
+
+ private static final String BOOTSTRAP_EXPLICIT_ARGUMENTS = "bootstrapExplicitArguments";
+
+ private static final String BOOTSTRAP_ARRAY_ARGUMENTS = "bootstrapArrayArguments";
+
+ private static final String ARGUMENTS_FIELD_NAME = "arguments";
+
+ private static final String BOOTSTRAP = "bootstrap";
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ private static Object makeMethodType(Class<?> returnType, Class<?>... parameterType) throws Exception {
+ return JavaType.METHOD_TYPE.load().getDeclaredMethod("methodType", Class.class, Class[].class).invoke(null, returnType, parameterType);
+ }
+
+ private static Object makeMethodHandle() throws Exception {
+ Object lookup = Class.forName("java.lang.invoke.MethodHandles").getDeclaredMethod("publicLookup").invoke(null);
+ return JavaType.METHOD_HANDLES_LOOKUP.load().getDeclaredMethod("findVirtual", Class.class, String.class, JavaType.METHOD_TYPE.load())
+ .invoke(lookup, Simple.class, FOO, makeMethodType(String.class));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(7)
+ public void testBootstrapMethod() throws Exception {
+ for (Method method : Class.forName(STANDARD_ARGUMENT_BOOTSTRAP).getDeclaredMethods()) {
+ if (method.getName().equals(FOO)) {
+ continue;
+ }
+ DynamicType.Loaded<Simple> dynamicType = new ByteBuddy()
+ .subclass(Simple.class)
+ .method(isDeclaredBy(Simple.class))
+ .intercept(InvokeDynamic.bootstrap(method).withoutArguments())
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(dynamicType.getLoaded().getDeclaredConstructor().newInstance().foo(), is(FOO));
+ }
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(7)
+ public void testBootstrapConstructor() throws Exception {
+ for (Constructor<?> constructor : Class.forName(STANDARD_ARGUMENT_BOOTSTRAP).getDeclaredConstructors()) {
+ DynamicType.Loaded<Simple> dynamicType = new ByteBuddy()
+ .subclass(Simple.class)
+ .method(isDeclaredBy(Simple.class))
+ .intercept(InvokeDynamic.bootstrap(constructor).withoutArguments())
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(dynamicType.getLoaded().getDeclaredConstructor().newInstance().foo(), is(FOO));
+ }
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(7)
+ public void testBootstrapWithArrayArgumentsWithoutArguments() throws Exception {
+ Class<?> type = Class.forName(PARAMETER_BOOTSTRAP);
+ Field field = type.getDeclaredField(ARGUMENTS_FIELD_NAME);
+ field.set(null, null);
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(type);
+ DynamicType.Loaded<Simple> dynamicType = new ByteBuddy()
+ .subclass(Simple.class)
+ .method(isDeclaredBy(Simple.class))
+ .intercept(InvokeDynamic.bootstrap(typeDescription.getDeclaredMethods().filter(named(BOOTSTRAP_ARRAY_ARGUMENTS)).getOnly())
+ .withoutArguments())
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(dynamicType.getLoaded().getDeclaredConstructor().newInstance().foo(), is(FOO));
+ Object[] arguments = (Object[]) field.get(null);
+ assertThat(arguments.length, is(0));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(value = 7, hotSpot = 7)
+ public void testBootstrapWithArrayArgumentsWithArguments() throws Exception {
+ Class<?> type = Class.forName(PARAMETER_BOOTSTRAP);
+ Field field = type.getDeclaredField(ARGUMENTS_FIELD_NAME);
+ field.set(null, null);
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(type);
+ DynamicType.Loaded<Simple> dynamicType = new ByteBuddy()
+ .subclass(Simple.class)
+ .method(isDeclaredBy(Simple.class))
+ .intercept(InvokeDynamic.bootstrap(typeDescription.getDeclaredMethods().filter(named(BOOTSTRAP_ARRAY_ARGUMENTS)).getOnly(),
+ INTEGER, LONG, FLOAT, DOUBLE, FOO, CLASS, makeMethodType(CLASS), makeMethodHandle())
+ .withoutArguments())
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(dynamicType.getLoaded().getDeclaredConstructor().newInstance().foo(), is(FOO));
+ Object[] arguments = (Object[]) field.get(null);
+ assertThat(arguments.length, is(8));
+ assertThat(arguments[0], is((Object) INTEGER));
+ assertThat(arguments[1], is((Object) LONG));
+ assertThat(arguments[2], is((Object) FLOAT));
+ assertThat(arguments[3], is((Object) DOUBLE));
+ assertThat(arguments[4], is((Object) FOO));
+ assertThat(arguments[5], is((Object) CLASS));
+ assertThat(arguments[6], is(makeMethodType(CLASS)));
+ assertThat(JavaConstant.MethodHandle.ofLoaded(arguments[7]), is(JavaConstant.MethodHandle.ofLoaded(makeMethodHandle())));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(value = 7, hotSpot = 7)
+ public void testBootstrapWithExplicitArgumentsWithArguments() throws Exception {
+ Class<?> type = Class.forName(PARAMETER_BOOTSTRAP);
+ Field field = type.getDeclaredField(ARGUMENTS_FIELD_NAME);
+ field.set(null, null);
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(type);
+ DynamicType.Loaded<Simple> dynamicType = new ByteBuddy()
+ .subclass(Simple.class)
+ .method(isDeclaredBy(Simple.class))
+ .intercept(InvokeDynamic.bootstrap(typeDescription.getDeclaredMethods().filter(named(BOOTSTRAP_EXPLICIT_ARGUMENTS)).getOnly(),
+ INTEGER, LONG, FLOAT, DOUBLE, FOO, CLASS, makeMethodType(CLASS), makeMethodHandle())
+ .withoutArguments())
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(dynamicType.getLoaded().getDeclaredConstructor().newInstance().foo(), is(FOO));
+ Object[] arguments = (Object[]) field.get(null);
+ assertThat(arguments.length, is(8));
+ assertThat(arguments[0], is((Object) INTEGER));
+ assertThat(arguments[1], is((Object) LONG));
+ assertThat(arguments[2], is((Object) FLOAT));
+ assertThat(arguments[3], is((Object) DOUBLE));
+ assertThat(arguments[4], is((Object) FOO));
+ assertThat(arguments[5], is((Object) CLASS));
+ assertThat(arguments[6], is(makeMethodType(CLASS)));
+ assertThat(JavaConstant.MethodHandle.ofLoaded(arguments[7]), is(JavaConstant.MethodHandle.ofLoaded(makeMethodHandle())));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ @JavaVersionRule.Enforce(7)
+ public void testBootstrapWithExplicitArgumentsWithoutArgumentsThrowsException() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(Class.forName(PARAMETER_BOOTSTRAP));
+ InvokeDynamic.bootstrap(typeDescription.getDeclaredMethods().filter(named(BOOTSTRAP_EXPLICIT_ARGUMENTS)).getOnly()).withoutArguments();
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(value = 7, hotSpot = 7)
+ public void testBootstrapOfMethodsWithParametersPrimitive() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(Class.forName(ARGUMENT_BOOTSTRAP));
+ Object value = new Object();
+ DynamicType.Loaded<Simple> dynamicType = new ByteBuddy()
+ .subclass(Simple.class)
+ .method(isDeclaredBy(Simple.class))
+ .intercept(InvokeDynamic.bootstrap(typeDescription.getDeclaredMethods().filter(named(BOOTSTRAP)).getOnly())
+ .invoke(FOO, String.class)
+ .withBooleanValue(BOOLEAN)
+ .withByteValue(BYTE)
+ .withShortValue(SHORT)
+ .withCharacterValue(CHARACTER)
+ .withIntegerValue(INTEGER)
+ .withLongValue(LONG)
+ .withFloatValue(FLOAT)
+ .withDoubleValue(DOUBLE)
+ .withType(new TypeDescription.ForLoadedType(CLASS))
+ .withEnumeration(new EnumerationDescription.ForLoadedEnumeration(makeEnum()))
+ .withInstance(JavaConstant.MethodType.ofLoaded(makeMethodType(CLASS)), JavaConstant.MethodHandle.ofLoaded(makeMethodHandle()))
+ .withValue(FOO, CLASS, makeEnum(), makeMethodType(CLASS), makeMethodHandle(), value))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(dynamicType.getLoaded().getDeclaredConstructor().newInstance().foo(),
+ is("" + BOOLEAN + BYTE + SHORT + CHARACTER + INTEGER + LONG + FLOAT + DOUBLE + CLASS + makeEnum() + makeMethodType(CLASS)
+ + makeMethodHandle() + FOO + CLASS + makeEnum() + makeMethodType(CLASS) + makeMethodHandle() + value));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(value = 7, hotSpot = 7)
+ public void testBootstrapOfMethodsWithParametersWrapperConstantPool() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(Class.forName(ARGUMENT_BOOTSTRAP));
+ Object value = new Object();
+ DynamicType.Loaded<Simple> dynamicType = new ByteBuddy()
+ .subclass(Simple.class)
+ .method(isDeclaredBy(Simple.class))
+ .intercept(InvokeDynamic.bootstrap(typeDescription.getDeclaredMethods().filter(named(BOOTSTRAP)).getOnly())
+ .invoke(BAR, String.class)
+ .withValue(BOOLEAN, BYTE, SHORT, CHARACTER, INTEGER, LONG, FLOAT, DOUBLE, FOO,
+ CLASS, makeEnum(), makeMethodType(CLASS), makeMethodHandle(), value))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(dynamicType.getLoaded().getDeclaredFields().length, is(1));
+ assertThat(dynamicType.getLoaded().getDeclaredConstructor().newInstance().foo(),
+ is("" + BOOLEAN + BYTE + SHORT + CHARACTER + INTEGER + LONG + FLOAT + DOUBLE + FOO + CLASS + makeEnum()
+ + makeMethodType(CLASS) + makeMethodHandle() + value));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(7)
+ public void testBootstrapOfMethodsWithParametersWrapperReference() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(Class.forName(ARGUMENT_BOOTSTRAP));
+ Object value = new Object();
+ DynamicType.Loaded<Simple> dynamicType = new ByteBuddy()
+ .subclass(Simple.class)
+ .method(isDeclaredBy(Simple.class))
+ .intercept(InvokeDynamic.bootstrap(typeDescription.getDeclaredMethods().filter(named(BOOTSTRAP)).getOnly())
+ .invoke(BAR, String.class)
+ .withReference(BOOLEAN, BYTE, SHORT, CHARACTER, INTEGER, LONG, FLOAT, DOUBLE, FOO, CLASS, makeEnum(), makeMethodType(CLASS))
+ .withReference(makeMethodHandle()).as(JavaType.METHOD_HANDLE.load()) // avoid direct method handle
+ .withReference(value))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(dynamicType.getLoaded().getDeclaredFields().length, is(14));
+ assertThat(dynamicType.getLoaded().getDeclaredConstructor().newInstance().foo(),
+ is("" + BOOLEAN + BYTE + SHORT + CHARACTER + INTEGER + LONG + FLOAT + DOUBLE + FOO + CLASS + makeEnum()
+ + makeMethodType(CLASS) + makeMethodHandle() + value));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(7)
+ public void testBootstrapWithFieldCreation() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(Class.forName(ARGUMENT_BOOTSTRAP));
+ DynamicType.Loaded<Simple> dynamicType = new ByteBuddy()
+ .subclass(Simple.class)
+ .defineField(FOO, String.class)
+ .method(isDeclaredBy(Simple.class))
+ .intercept(InvokeDynamic.bootstrap(typeDescription.getDeclaredMethods().filter(named(BOOTSTRAP)).getOnly())
+ .invoke(QUX, String.class)
+ .withField(FOO))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(dynamicType.getLoaded().getDeclaredFields().length, is(1));
+ Simple instance = dynamicType.getLoaded().getDeclaredConstructor().newInstance();
+ Field field = dynamicType.getLoaded().getDeclaredField(FOO);
+ field.setAccessible(true);
+ field.set(instance, FOO);
+ assertThat(instance.foo(), is(FOO));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(7)
+ public void testBootstrapWithFieldExplicitType() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(Class.forName(ARGUMENT_BOOTSTRAP));
+ DynamicType.Loaded<Simple> dynamicType = new ByteBuddy()
+ .subclass(Simple.class)
+ .defineField(FOO, Object.class)
+ .method(isDeclaredBy(Simple.class))
+ .intercept(InvokeDynamic.bootstrap(typeDescription.getDeclaredMethods().filter(named(BOOTSTRAP)).getOnly())
+ .invoke(QUX, String.class)
+ .withField(FOO).as(String.class)
+ .withAssigner(Assigner.DEFAULT, Assigner.Typing.DYNAMIC))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(dynamicType.getLoaded().getDeclaredFields().length, is(1));
+ Simple instance = dynamicType.getLoaded().getDeclaredConstructor().newInstance();
+ Field field = dynamicType.getLoaded().getDeclaredField(FOO);
+ field.setAccessible(true);
+ field.set(instance, FOO);
+ assertThat(instance.foo(), is(FOO));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ @JavaVersionRule.Enforce(7)
+ public void testBootstrapFieldNotExistent() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(Class.forName(ARGUMENT_BOOTSTRAP));
+ new ByteBuddy()
+ .subclass(Simple.class)
+ .method(isDeclaredBy(Simple.class))
+ .intercept(InvokeDynamic.bootstrap(typeDescription.getDeclaredMethods().filter(named(BOOTSTRAP)).getOnly())
+ .invoke(QUX, String.class)
+ .withField(FOO)
+ .withAssigner(Assigner.DEFAULT, Assigner.Typing.DYNAMIC))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ @JavaVersionRule.Enforce(7)
+ public void testBootstrapFieldNotAssignable() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(Class.forName(ARGUMENT_BOOTSTRAP));
+ new ByteBuddy()
+ .subclass(Simple.class)
+ .defineField(FOO, Object.class)
+ .method(isDeclaredBy(Simple.class))
+ .intercept(InvokeDynamic.bootstrap(typeDescription.getDeclaredMethods().filter(named(BOOTSTRAP)).getOnly())
+ .invoke(QUX, String.class)
+ .withField(FOO).as(String.class))
+ .make();
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(7)
+ public void testBootstrapWithFieldUse() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(Class.forName(ARGUMENT_BOOTSTRAP));
+ DynamicType.Loaded<SimpleWithField> dynamicType = new ByteBuddy()
+ .subclass(SimpleWithField.class)
+ .method(isDeclaredBy(SimpleWithField.class))
+ .intercept(InvokeDynamic.bootstrap(typeDescription.getDeclaredMethods().filter(named(BOOTSTRAP)).getOnly())
+ .invoke(QUX, String.class)
+ .withField(FOO))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(dynamicType.getLoaded().getDeclaredFields().length, is(0));
+ SimpleWithField instance = dynamicType.getLoaded().getDeclaredConstructor().newInstance();
+ Field field = SimpleWithField.class.getDeclaredField(FOO);
+ field.setAccessible(true);
+ field.set(instance, FOO);
+ assertThat(instance.foo(), is(FOO));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ @JavaVersionRule.Enforce(7)
+ public void testBootstrapWithFieldUseInvisible() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(Class.forName(ARGUMENT_BOOTSTRAP));
+ new ByteBuddy()
+ .subclass(SimpleWithFieldInvisible.class)
+ .method(isDeclaredBy(SimpleWithFieldInvisible.class))
+ .intercept(InvokeDynamic.bootstrap(typeDescription.getDeclaredMethods().filter(named(BOOTSTRAP)).getOnly())
+ .invoke(QUX, String.class)
+ .withField(FOO))
+ .make();
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(7)
+ public void testBootstrapWithNullValue() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(Class.forName(ARGUMENT_BOOTSTRAP));
+ DynamicType.Loaded<Simple> dynamicType = new ByteBuddy()
+ .subclass(Simple.class)
+ .method(isDeclaredBy(Simple.class))
+ .intercept(InvokeDynamic.bootstrap(typeDescription.getDeclaredMethods().filter(named(BOOTSTRAP)).getOnly())
+ .invoke(QUX, String.class)
+ .withNullValue(String.class))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(dynamicType.getLoaded().getDeclaredFields().length, is(0));
+ assertThat(dynamicType.getLoaded().getDeclaredConstructor().newInstance().foo(), nullValue(String.class));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(7)
+ public void testBootstrapWithThisValue() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(Class.forName(ARGUMENT_BOOTSTRAP));
+ DynamicType.Loaded<Simple> dynamicType = new ByteBuddy()
+ .subclass(Simple.class)
+ .method(isDeclaredBy(Simple.class))
+ .intercept(InvokeDynamic.bootstrap(typeDescription.getDeclaredMethods().filter(named(BOOTSTRAP)).getOnly())
+ .invoke(BAZ, String.class)
+ .withThis(Object.class))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(dynamicType.getLoaded().getDeclaredFields().length, is(0));
+ Simple simple = dynamicType.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(simple.foo(), is(simple.toString()));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(7)
+ public void testBootstrapWithArgument() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(Class.forName(ARGUMENT_BOOTSTRAP));
+ DynamicType.Loaded<SimpleWithArgument> dynamicType = new ByteBuddy()
+ .subclass(SimpleWithArgument.class)
+ .method(isDeclaredBy(SimpleWithArgument.class))
+ .intercept(InvokeDynamic.bootstrap(typeDescription.getDeclaredMethods().filter(named(BOOTSTRAP)).getOnly())
+ .invoke(QUX, String.class)
+ .withArgument(0))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(dynamicType.getLoaded().getDeclaredFields().length, is(0));
+ assertThat(dynamicType.getLoaded().getDeclaredConstructor().newInstance().foo(FOO), is(FOO));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ @JavaVersionRule.Enforce(7)
+ public void testNegativeArgumentThrowsException() throws Exception {
+ Class<?> type = Class.forName(ARGUMENT_BOOTSTRAP);
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(type);
+ InvokeDynamic.bootstrap(typeDescription.getDeclaredMethods().filter(named(BOOTSTRAP)).getOnly())
+ .invoke(QUX, String.class)
+ .withArgument(-1);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ @JavaVersionRule.Enforce(7)
+ public void testNonExistentArgumentThrowsException() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(Class.forName(ARGUMENT_BOOTSTRAP));
+ new ByteBuddy()
+ .subclass(SimpleWithArgument.class)
+ .method(isDeclaredBy(SimpleWithArgument.class))
+ .intercept(InvokeDynamic.bootstrap(typeDescription.getDeclaredMethods().filter(named(BOOTSTRAP)).getOnly())
+ .invoke(QUX, String.class)
+ .withArgument(1))
+ .make();
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(7)
+ public void testChainedInvocation() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(Class.forName(ARGUMENT_BOOTSTRAP));
+ DynamicType.Loaded<SimpleWithArgument> dynamicType = new ByteBuddy()
+ .subclass(SimpleWithArgument.class)
+ .method(isDeclaredBy(SimpleWithArgument.class))
+ .intercept(InvokeDynamic.bootstrap(typeDescription.getDeclaredMethods().filter(named(BOOTSTRAP)).getOnly())
+ .invoke(QUX, String.class)
+ .withArgument(0)
+ .andThen(FixedValue.value(BAZ)))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(dynamicType.getLoaded().getDeclaredFields().length, is(0));
+ assertThat(dynamicType.getLoaded().getDeclaredConstructor().newInstance().foo(FOO), is(BAZ));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(7)
+ public void testBootstrapWithImplicitArgument() throws Exception {
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(Class.forName(ARGUMENT_BOOTSTRAP));
+ DynamicType.Loaded<SimpleWithArgument> dynamicType = new ByteBuddy()
+ .subclass(SimpleWithArgument.class)
+ .method(isDeclaredBy(SimpleWithArgument.class))
+ .intercept(InvokeDynamic.bootstrap(typeDescription.getDeclaredMethods().filter(named(BOOTSTRAP)).getOnly())
+ .invoke(QUX, String.class)
+ .withMethodArguments())
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(dynamicType.getLoaded().getDeclaredFields().length, is(0));
+ assertThat(dynamicType.getLoaded().getDeclaredConstructor().newInstance().foo(FOO), is(FOO));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ @JavaVersionRule.Enforce(7)
+ public void testArgumentCannotAssignIllegalInstanceType() throws Exception {
+ Class<?> type = Class.forName(ARGUMENT_BOOTSTRAP);
+ TypeDescription typeDescription = new TypeDescription.ForLoadedType(type);
+ InvokeDynamic.bootstrap(typeDescription.getDeclaredMethods().filter(named(BOOTSTRAP)).getOnly())
+ .invoke(QUX, String.class)
+ .withReference(new Object()).as(String.class);
+ }
+
+ @SuppressWarnings("unchecked")
+ private Enum<?> makeEnum() throws Exception {
+ Class type = Class.forName(SAMPLE_ENUM);
+ return Enum.valueOf(type, INSTANCE);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(InvokeDynamic.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.Appender.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.WithImplicitTarget.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.WithImplicitArguments.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.InvocationProvider.Default.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.InvocationProvider.Default.Target.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.InvocationProvider.Target.Resolved.Simple.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.InvocationProvider.Target.ForMethodDescription.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.InvocationProvider.NameProvider.ForExplicitName.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.InvocationProvider.NameProvider.ForInterceptedMethod.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.InvocationProvider.ReturnTypeProvider.ForInterceptedMethod.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.InvocationProvider.ReturnTypeProvider.ForExplicitType.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.InvocationProvider.ArgumentProvider.ForBooleanConstant.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.InvocationProvider.ArgumentProvider.ForByteConstant.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.InvocationProvider.ArgumentProvider.ForShortConstant.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.InvocationProvider.ArgumentProvider.ForCharacterConstant.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.InvocationProvider.ArgumentProvider.ForIntegerConstant.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.InvocationProvider.ArgumentProvider.ForLongConstant.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.InvocationProvider.ArgumentProvider.ForFloatConstant.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.InvocationProvider.ArgumentProvider.ForDoubleConstant.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.InvocationProvider.ArgumentProvider.ForStringConstant.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.InvocationProvider.ArgumentProvider.ForClassConstant.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.InvocationProvider.ArgumentProvider.ForEnumerationValue.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.InvocationProvider.ArgumentProvider.ForField.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.InvocationProvider.ArgumentProvider.ForField.WithExplicitType.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.InvocationProvider.ArgumentProvider.ForInstance.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.InvocationProvider.ArgumentProvider.ForThisInstance.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.InvocationProvider.ArgumentProvider.ForJavaConstant.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.InvocationProvider.ArgumentProvider.ForMethodParameter.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.InvocationProvider.ArgumentProvider.ForMethodParameter.WithExplicitType.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.InvocationProvider.ArgumentProvider.ConstantPoolWrapper.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.InvocationProvider.ArgumentProvider.ConstantPoolWrapper.WrappingArgumentProvider.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.InvocationProvider.ArgumentProvider.ForInterceptedMethodParameters.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.InvocationProvider.ArgumentProvider.ForInterceptedMethodInstanceAndParameters.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.InvocationProvider.ArgumentProvider.Resolved.Simple.class).apply();
+ ObjectPropertyAssertion.of(InvokeDynamic.TerminationHandler.class).apply();
+ }
+
+ public static class Simple {
+
+ public String foo() {
+ return null;
+ }
+ }
+
+ public static class SimpleWithField {
+
+ public String foo;
+
+ public String foo() {
+ return null;
+ }
+ }
+
+ public static class SimpleWithFieldInvisible {
+
+ private String foo;
+
+ public String foo() {
+ return null;
+ }
+ }
+
+ public static class SimpleWithArgument {
+
+ public String foo(String arg) {
+ return null;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/LoadedTypeInitializerCompoundTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/LoadedTypeInitializerCompoundTest.java
new file mode 100644
index 0000000..f219fc3
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/LoadedTypeInitializerCompoundTest.java
@@ -0,0 +1,78 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.Collections;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class LoadedTypeInitializerCompoundTest {
+
+ private static final Class<?> TYPE = Object.class;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private LoadedTypeInitializer first, second;
+
+ @Mock
+ private InstrumentedType instrumentedType;
+
+ @Mock
+ private ByteCodeAppender byteCodeAppender;
+
+ private LoadedTypeInitializer compound;
+
+ @Before
+ public void setUp() throws Exception {
+ compound = new LoadedTypeInitializer.Compound(first, second);
+ }
+
+ @Test
+ public void testIsAlive() throws Exception {
+ when(first.isAlive()).thenReturn(true);
+ assertThat(compound.isAlive(), is(true));
+ verify(first).isAlive();
+ verifyNoMoreInteractions(first);
+ }
+
+ @Test
+ public void testIsNotAlive() throws Exception {
+ assertThat(compound.isAlive(), is(false));
+ verify(first).isAlive();
+ verify(second).isAlive();
+ verifyNoMoreInteractions(first);
+ verifyNoMoreInteractions(second);
+ }
+
+ @Test
+ public void testApply() throws Exception {
+ compound.onLoad(TYPE);
+ verify(first).onLoad(TYPE);
+ verify(second).onLoad(TYPE);
+ verifyNoMoreInteractions(first);
+ verifyNoMoreInteractions(second);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(LoadedTypeInitializer.Compound.class).create(new ObjectPropertyAssertion.Creator<List<?>>() {
+ @Override
+ public List<?> create() {
+ return Collections.singletonList(mock(LoadedTypeInitializer.class));
+ }
+ }).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/LoadedTypeInitializerForStaticFieldTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/LoadedTypeInitializerForStaticFieldTest.java
new file mode 100644
index 0000000..003ad41
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/LoadedTypeInitializerForStaticFieldTest.java
@@ -0,0 +1,79 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class LoadedTypeInitializerForStaticFieldTest {
+
+ private static final String FOO = "foo";
+
+ @Test
+ public void testAccessibleField() throws Exception {
+ Object object = new Object();
+ LoadedTypeInitializer loadedTypeInitializer = new LoadedTypeInitializer.ForStaticField(FOO, object);
+ assertThat(loadedTypeInitializer.isAlive(), is(true));
+ loadedTypeInitializer.onLoad(Foo.class);
+ assertThat(Foo.foo, is(object));
+ }
+
+ @Test
+ public void testNonAccessibleField() throws Exception {
+ Object object = new Object();
+ LoadedTypeInitializer loadedTypeInitializer = new LoadedTypeInitializer.ForStaticField(FOO, object);
+ assertThat(loadedTypeInitializer.isAlive(), is(true));
+ loadedTypeInitializer.onLoad(Bar.class);
+ assertThat(Bar.foo, is(object));
+ }
+
+ @Test
+ public void testNonAccessibleType() throws Exception {
+ Object object = new Object();
+ LoadedTypeInitializer loadedTypeInitializer = new LoadedTypeInitializer.ForStaticField(FOO, object);
+ assertThat(loadedTypeInitializer.isAlive(), is(true));
+ loadedTypeInitializer.onLoad(Qux.class);
+ assertThat(Qux.foo, is(object));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNonAssignableField() throws Exception {
+ new LoadedTypeInitializer.ForStaticField(FOO, new Object()).onLoad(FooBar.class);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(LoadedTypeInitializer.ForStaticField.class).apply();
+ }
+
+ @SuppressWarnings("unused")
+ public static class Foo {
+
+ public static Object foo;
+ }
+
+ @SuppressWarnings("unused")
+ public static class Bar {
+
+ private static Object foo;
+ }
+
+ @SuppressWarnings("unused")
+ private static class Qux {
+
+ public static Object foo;
+ }
+
+ @SuppressWarnings("unused")
+ private static class Baz {
+
+ String foo, bar;
+ }
+
+ @SuppressWarnings("unused")
+ public static class FooBar {
+
+ public static String foo;
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/LoadedTypeInitializerNoOp.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/LoadedTypeInitializerNoOp.java
new file mode 100644
index 0000000..b8f5ff4
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/LoadedTypeInitializerNoOp.java
@@ -0,0 +1,20 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class LoadedTypeInitializerNoOp {
+
+ @Test
+ public void testIsNotAlive() throws Exception {
+ assertThat(LoadedTypeInitializer.NoOp.INSTANCE.isAlive(), is(false));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(LoadedTypeInitializer.NoOp.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodAccessorFactoryAccessTypeTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodAccessorFactoryAccessTypeTest.java
new file mode 100644
index 0000000..a6bd871
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodAccessorFactoryAccessTypeTest.java
@@ -0,0 +1,22 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodAccessorFactoryAccessTypeTest {
+
+ @Test
+ public void testVisibility() throws Exception {
+ assertThat(MethodAccessorFactory.AccessType.DEFAULT.getVisibility(), is(Visibility.PACKAGE_PRIVATE));
+ assertThat(MethodAccessorFactory.AccessType.PUBLIC.getVisibility(), is(Visibility.PUBLIC));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodAccessorFactory.AccessType.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodAccessorFactoryIllegalTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodAccessorFactoryIllegalTest.java
new file mode 100644
index 0000000..a94d90d
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodAccessorFactoryIllegalTest.java
@@ -0,0 +1,43 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.MethodAccessorFactory;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+public class MethodAccessorFactoryIllegalTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private Implementation.SpecialMethodInvocation specialMethodInvocation;
+
+ @Mock
+ private FieldDescription fieldDescription;
+
+ @Test(expected = IllegalStateException.class)
+ public void testAccessorIsIllegal() throws Exception {
+ MethodAccessorFactory.Illegal.INSTANCE.registerAccessorFor(specialMethodInvocation, MethodAccessorFactory.AccessType.DEFAULT);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testGetterIsIllegal() throws Exception {
+ MethodAccessorFactory.Illegal.INSTANCE.registerSetterFor(fieldDescription, MethodAccessorFactory.AccessType.DEFAULT);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testSetterIsIllegal() throws Exception {
+ MethodAccessorFactory.Illegal.INSTANCE.registerGetterFor(fieldDescription, MethodAccessorFactory.AccessType.DEFAULT);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodAccessorFactory.Illegal.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodCallTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodCallTest.java
new file mode 100644
index 0000000..0e130ec
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodCallTest.java
@@ -0,0 +1,1245 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.constant.TextConstant;
+import net.bytebuddy.implementation.bytecode.member.MethodReturn;
+import net.bytebuddy.matcher.ElementMatchers;
+import net.bytebuddy.test.utility.*;
+import net.bytebuddy.utility.JavaConstant;
+import net.bytebuddy.utility.JavaType;
+import org.hamcrest.CoreMatchers;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.concurrent.Callable;
+
+import static net.bytebuddy.matcher.ElementMatchers.*;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class MethodCallTest {
+
+ private static final String FOO = "foo", BAR = "bar", INVOKE_FOO = "invokeFoo";
+
+ private static final String SINGLE_DEFAULT_METHOD = "net.bytebuddy.test.precompiled.SingleDefaultMethodInterface";
+
+ @Rule
+ public TestRule methodRule = new MockitoRule(this);
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ @Mock
+ private Assigner nonAssigner;
+
+ private static Object makeMethodType(Class<?> returnType, Class<?>... parameterType) throws Exception {
+ return JavaType.METHOD_TYPE.load().getDeclaredMethod("methodType", Class.class, Class[].class).invoke(null, returnType, parameterType);
+ }
+
+ private static Object makeMethodHandle() throws Exception {
+ Object lookup = Class.forName("java.lang.invoke.MethodHandles").getDeclaredMethod("publicLookup").invoke(null);
+ return JavaType.METHOD_HANDLES_LOOKUP.load().getDeclaredMethod("findStatic", Class.class, String.class, JavaType.METHOD_TYPE.load())
+ .invoke(lookup, Foo.class, BAR, makeMethodType(String.class, Object.class, Object.class));
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(nonAssigner.assign(Mockito.any(TypeDescription.Generic.class),
+ Mockito.any(TypeDescription.Generic.class),
+ Mockito.any(Assigner.Typing.class))).thenReturn(StackManipulation.Illegal.INSTANCE);
+ }
+
+ @Test
+ public void testStaticMethodInvocationWithoutArguments() throws Exception {
+ DynamicType.Loaded<SimpleMethod> loaded = new ByteBuddy()
+ .subclass(SimpleMethod.class)
+ .method(named(FOO))
+ .intercept(MethodCall.invoke(SimpleMethod.class.getDeclaredMethod(BAR)))
+ .make()
+ .load(SimpleMethod.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ SimpleMethod instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.foo(), is(BAR));
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(SimpleMethod.class)));
+ assertThat(instance, instanceOf(SimpleMethod.class));
+ }
+
+ @Test
+ public void testExternalStaticMethodInvocationWithoutArguments() throws Exception {
+ DynamicType.Loaded<SimpleMethod> loaded = new ByteBuddy()
+ .subclass(SimpleMethod.class)
+ .method(named(FOO))
+ .intercept(MethodCall.invoke(StaticExternalMethod.class.getDeclaredMethod(BAR)))
+ .make()
+ .load(SimpleMethod.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ SimpleMethod instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.foo(), is(BAR));
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(SimpleMethod.class)));
+ assertThat(instance, instanceOf(SimpleMethod.class));
+ }
+
+ @Test
+ public void testInstanceMethodInvocationWithoutArguments() throws Exception {
+ DynamicType.Loaded<InstanceMethod> loaded = new ByteBuddy()
+ .subclass(InstanceMethod.class)
+ .method(named(FOO))
+ .intercept(MethodCall.invoke(InstanceMethod.class.getDeclaredMethod(BAR)))
+ .make()
+ .load(SimpleMethod.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ InstanceMethod instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.foo(), is(BAR));
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(InstanceMethod.class)));
+ assertThat(instance, instanceOf(InstanceMethod.class));
+ }
+
+ @Test
+ public void testInstanceMethodInvocationWithoutArgumentsByMatcher() throws Exception {
+ DynamicType.Loaded<InstanceMethod> loaded = new ByteBuddy()
+ .subclass(InstanceMethod.class)
+ .method(named(FOO))
+ .intercept(MethodCall.invoke(named(BAR)))
+ .make()
+ .load(SimpleMethod.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ InstanceMethod instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.foo(), is(BAR));
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(InstanceMethod.class)));
+ assertThat(instance, instanceOf(InstanceMethod.class));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMatchedCallAmbiguous() throws Exception {
+ new ByteBuddy()
+ .subclass(InstanceMethod.class)
+ .method(named(FOO))
+ .intercept(MethodCall.invoke(ElementMatchers.any()))
+ .make();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testOnArgumentInvocationNegativeArgument() throws Exception {
+ MethodCall.invoke(Object.class.getDeclaredMethod("toString")).onArgument(-1);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testOnArgumentInvocationNonExisting() throws Exception {
+ new ByteBuddy()
+ .subclass(ArgumentCall.class)
+ .method(isDeclaredBy(ArgumentCall.class))
+ .intercept(MethodCall.invoke(Object.class.getDeclaredMethod("toString")).onArgument(10))
+ .make();
+ }
+
+ @Test
+ public void testInvokeOnArgument() throws Exception {
+ DynamicType.Loaded<ArgumentCall> loaded = new ByteBuddy()
+ .subclass(ArgumentCall.class)
+ .method(isDeclaredBy(ArgumentCall.class))
+ .intercept(MethodCall.invoke(ArgumentCall.Target.class.getDeclaredMethod("foo")).onArgument(0))
+ .make()
+ .load(ArgumentCall.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ ArgumentCall instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.foo(new ArgumentCall.Target(BAR)), is(BAR));
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(InstanceMethod.class)));
+ assertThat(instance, instanceOf(ArgumentCall.class));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testInvokeOnArgumentNonAssignable() throws Exception {
+ new ByteBuddy()
+ .subclass(ArgumentCallDynamic.class)
+ .method(isDeclaredBy(ArgumentCallDynamic.class))
+ .intercept(MethodCall.invoke(ArgumentCallDynamic.Target.class.getDeclaredMethod("foo")).onArgument(0))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testInvokeOnArgumentNonVirtual() throws Exception {
+ new ByteBuddy()
+ .subclass(ArgumentCallDynamic.class)
+ .method(isDeclaredBy(ArgumentCallDynamic.class))
+ .intercept(MethodCall.invoke(NonVirtual.class.getDeclaredMethod("foo")).onArgument(0))
+ .make();
+ }
+
+ @Test
+ public void testInvokeOnArgumentDynamic() throws Exception {
+ DynamicType.Loaded<ArgumentCallDynamic> loaded = new ByteBuddy()
+ .subclass(ArgumentCallDynamic.class)
+ .method(isDeclaredBy(ArgumentCallDynamic.class))
+ .intercept(MethodCall.invoke(ArgumentCallDynamic.Target.class.getDeclaredMethod("foo")).onArgument(0)
+ .withAssigner(Assigner.DEFAULT, Assigner.Typing.DYNAMIC))
+ .make()
+ .load(ArgumentCallDynamic.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ ArgumentCallDynamic instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.foo(new ArgumentCallDynamic.Target(BAR)), is(BAR));
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(InstanceMethod.class)));
+ assertThat(instance, instanceOf(ArgumentCallDynamic.class));
+ }
+
+ @Test
+ public void testSuperConstructorInvocationWithoutArguments() throws Exception {
+ DynamicType.Loaded<Object> loaded = new ByteBuddy()
+ .subclass(Object.class)
+ .constructor(ElementMatchers.any())
+ .intercept(MethodCall.invoke(Object.class.getDeclaredConstructor()).onSuper())
+ .make()
+ .load(Object.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(0));
+ assertThat(loaded.getLoaded().getDeclaredConstructors().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ Object instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(Object.class)));
+ assertThat(instance, instanceOf(Object.class));
+ }
+
+ @Test
+ public void testObjectConstruction() throws Exception {
+ DynamicType.Loaded<SelfReference> loaded = new ByteBuddy()
+ .subclass(SelfReference.class)
+ .method(isDeclaredBy(SelfReference.class))
+ .intercept(MethodCall.construct(SelfReference.class.getDeclaredConstructor()))
+ .make()
+ .load(SelfReference.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredConstructors().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ SelfReference instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ SelfReference created = instance.foo();
+ assertThat(created.getClass(), CoreMatchers.<Class<?>>is(SelfReference.class));
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(SelfReference.class)));
+ assertThat(instance, instanceOf(SelfReference.class));
+ assertThat(created, not(instance));
+ }
+
+ @Test
+ public void testSelfInvocation() throws Exception {
+ SuperMethodInvocation delegate = mock(SuperMethodInvocation.class);
+ when(delegate.foo()).thenReturn(FOO);
+ DynamicType.Loaded<SuperMethodInvocation> loaded = new ByteBuddy()
+ .subclass(SuperMethodInvocation.class)
+ .method(takesArguments(0).and(named(FOO)))
+ .intercept(MethodCall.invokeSelf().on(delegate))
+ .make()
+ .load(SuperMethodInvocation.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredMethod(FOO), not(nullValue(Method.class)));
+ assertThat(loaded.getLoaded().getDeclaredConstructors().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(1));
+ SuperMethodInvocation instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(SuperMethodInvocation.class)));
+ assertThat(instance, instanceOf(SuperMethodInvocation.class));
+ assertThat(instance.foo(), is(FOO));
+ verify(delegate).foo();
+ verifyNoMoreInteractions(delegate);
+ }
+
+ @Test
+ public void testSuperInvocation() throws Exception {
+ DynamicType.Loaded<SuperMethodInvocation> loaded = new ByteBuddy()
+ .subclass(SuperMethodInvocation.class)
+ .method(takesArguments(0).and(named(FOO)))
+ .intercept(MethodCall.invokeSuper())
+ .make()
+ .load(SuperMethodInvocation.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredMethod(FOO), not(nullValue(Method.class)));
+ assertThat(loaded.getLoaded().getDeclaredConstructors().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ SuperMethodInvocation instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(SuperMethodInvocation.class)));
+ assertThat(instance, instanceOf(SuperMethodInvocation.class));
+ assertThat(instance.foo(), is(FOO));
+ }
+
+ @Test
+ public void testWithExplicitArgumentConstantPool() throws Exception {
+ DynamicType.Loaded<MethodCallWithExplicitArgument> loaded = new ByteBuddy()
+ .subclass(MethodCallWithExplicitArgument.class)
+ .method(isDeclaredBy(MethodCallWithExplicitArgument.class))
+ .intercept(MethodCall.invokeSuper().with(FOO))
+ .make()
+ .load(MethodCallWithExplicitArgument.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredMethod(FOO, String.class), not(nullValue(Method.class)));
+ assertThat(loaded.getLoaded().getDeclaredConstructors().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ MethodCallWithExplicitArgument instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(MethodCallWithExplicitArgument.class)));
+ assertThat(instance, instanceOf(MethodCallWithExplicitArgument.class));
+ assertThat(instance.foo(BAR), is(FOO));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testWithExplicitArgumentConstantPoolNonAssignable() throws Exception {
+ new ByteBuddy()
+ .subclass(MethodCallWithExplicitArgument.class)
+ .method(isDeclaredBy(MethodCallWithExplicitArgument.class))
+ .intercept(MethodCall.invokeSuper().with(FOO).withAssigner(nonAssigner, Assigner.Typing.STATIC))
+ .make();
+ }
+
+ @Test
+ public void testWithExplicitArgumentStackManipulation() throws Exception {
+ DynamicType.Loaded<MethodCallWithExplicitArgument> loaded = new ByteBuddy()
+ .subclass(MethodCallWithExplicitArgument.class)
+ .method(isDeclaredBy(MethodCallWithExplicitArgument.class))
+ .intercept(MethodCall.invokeSuper().with(new TextConstant(FOO), String.class))
+ .make()
+ .load(MethodCallWithExplicitArgument.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredMethod(FOO, String.class), not(nullValue(Method.class)));
+ assertThat(loaded.getLoaded().getDeclaredConstructors().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ MethodCallWithExplicitArgument instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(MethodCallWithExplicitArgument.class)));
+ assertThat(instance, instanceOf(MethodCallWithExplicitArgument.class));
+ assertThat(instance.foo(BAR), is(FOO));
+ }
+
+ @Test
+ public void testWithExplicitArgumentField() throws Exception {
+ DynamicType.Loaded<MethodCallWithExplicitArgument> loaded = new ByteBuddy()
+ .subclass(MethodCallWithExplicitArgument.class)
+ .method(isDeclaredBy(MethodCallWithExplicitArgument.class))
+ .intercept(MethodCall.invokeSuper().withReference(FOO))
+ .make()
+ .load(MethodCallWithExplicitArgument.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredMethod(FOO, String.class), not(nullValue(Method.class)));
+ assertThat(loaded.getLoaded().getDeclaredConstructors().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(1));
+ MethodCallWithExplicitArgument instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(MethodCallWithExplicitArgument.class)));
+ assertThat(instance, instanceOf(MethodCallWithExplicitArgument.class));
+ assertThat(instance.foo(BAR), is(FOO));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testWithExplicitArgumentFieldNonAssignable() throws Exception {
+ new ByteBuddy()
+ .subclass(MethodCallWithExplicitArgument.class)
+ .method(isDeclaredBy(MethodCallWithExplicitArgument.class))
+ .intercept(MethodCall.invokeSuper().withReference(FOO).withAssigner(nonAssigner, Assigner.Typing.STATIC))
+ .make();
+ }
+
+ @Test
+ public void testWithArgument() throws Exception {
+ DynamicType.Loaded<MethodCallWithExplicitArgument> loaded = new ByteBuddy()
+ .subclass(MethodCallWithExplicitArgument.class)
+ .method(isDeclaredBy(MethodCallWithExplicitArgument.class))
+ .intercept(MethodCall.invokeSuper().withArgument(0))
+ .make()
+ .load(MethodCallWithExplicitArgument.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredMethod(FOO, String.class), not(nullValue(Method.class)));
+ assertThat(loaded.getLoaded().getDeclaredConstructors().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ MethodCallWithExplicitArgument instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(MethodCallWithExplicitArgument.class)));
+ assertThat(instance, instanceOf(MethodCallWithExplicitArgument.class));
+ assertThat(instance.foo(BAR), is(BAR));
+ }
+
+ @Test
+ public void testWithAllArguments() throws Exception {
+ DynamicType.Loaded<MethodCallWithExplicitArgument> loaded = new ByteBuddy()
+ .subclass(MethodCallWithExplicitArgument.class)
+ .method(isDeclaredBy(MethodCallWithExplicitArgument.class))
+ .intercept(MethodCall.invokeSuper().withAllArguments())
+ .make()
+ .load(MethodCallWithExplicitArgument.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredMethod(FOO, String.class), not(nullValue(Method.class)));
+ assertThat(loaded.getLoaded().getDeclaredConstructors().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ MethodCallWithExplicitArgument instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(MethodCallWithExplicitArgument.class)));
+ assertThat(instance, instanceOf(MethodCallWithExplicitArgument.class));
+ assertThat(instance.foo(BAR), is(BAR));
+ }
+
+ @Test
+ public void testWithAllArgumentsTwoArguments() throws Exception {
+ DynamicType.Loaded<MethodCallWithTwoExplicitArguments> loaded = new ByteBuddy()
+ .subclass(MethodCallWithTwoExplicitArguments.class)
+ .method(isDeclaredBy(MethodCallWithTwoExplicitArguments.class))
+ .intercept(MethodCall.invokeSuper().withAllArguments())
+ .make()
+ .load(MethodCallWithTwoExplicitArguments.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredMethod(FOO, String.class, String.class), not(nullValue(Method.class)));
+ assertThat(loaded.getLoaded().getDeclaredConstructors().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ MethodCallWithTwoExplicitArguments instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(MethodCallWithTwoExplicitArguments.class)));
+ assertThat(instance, instanceOf(MethodCallWithTwoExplicitArguments.class));
+ assertThat(instance.foo(FOO, BAR), is(FOO + BAR));
+ }
+
+ @Test
+ public void testWithArgumentsAsArray() throws Exception {
+ DynamicType.Loaded<ArrayConsuming> loaded = new ByteBuddy()
+ .subclass(ArrayConsuming.class)
+ .method(named(FOO))
+ .intercept(MethodCall.invoke(ArrayConsuming.class.getDeclaredMethod(BAR, String[].class)).withArgumentArray())
+ .make()
+ .load(ArrayConsuming.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredMethod(FOO, String.class, String.class), not(nullValue(Method.class)));
+ assertThat(loaded.getLoaded().getDeclaredConstructors().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ ArrayConsuming instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(ArrayConsuming.class)));
+ assertThat(instance, instanceOf(ArrayConsuming.class));
+ assertThat(instance.foo(FOO, BAR), is(FOO + BAR));
+ }
+
+ @Test
+ public void testWithArgumentsFromArray() throws Exception {
+ DynamicType.Loaded<MethodCallWithExplicitArgument> loaded = new ByteBuddy()
+ .subclass(MethodCallWithExplicitArgument.class)
+ .implement(MethodCallDelegator.class)
+ .intercept(MethodCall.invoke(MethodCallWithExplicitArgument.class.getDeclaredMethod("foo", String.class)).withArgumentArrayElements(0, 1))
+ .make()
+ .load(MethodCallDelegator.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredMethod(INVOKE_FOO, String[].class), not(nullValue(Method.class)));
+ assertThat(loaded.getLoaded().getDeclaredConstructors().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ MethodCallDelegator instance = (MethodCallDelegator) loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(MethodCallWithExplicitArgument.class)));
+ assertThat(instance, instanceOf(MethodCallDelegator.class));
+ assertThat(instance.invokeFoo(BAR), is(BAR));
+ }
+
+ @Test
+ public void testWithArgumentsFromArrayComplete() throws Exception {
+ DynamicType.Loaded<MethodCallWithExplicitArgument> loaded = new ByteBuddy()
+ .subclass(MethodCallWithExplicitArgument.class)
+ .implement(MethodCallDelegator.class)
+ .intercept(MethodCall.invoke(MethodCallWithExplicitArgument.class.getDeclaredMethod("foo", String.class)).withArgumentArrayElements(0))
+ .make()
+ .load(MethodCallDelegator.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredMethod(INVOKE_FOO, String[].class), not(nullValue(Method.class)));
+ assertThat(loaded.getLoaded().getDeclaredConstructors().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ MethodCallDelegator instance = (MethodCallDelegator) loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(MethodCallWithExplicitArgument.class)));
+ assertThat(instance, instanceOf(MethodCallDelegator.class));
+ assertThat(instance.invokeFoo(BAR), is(BAR));
+ }
+
+ @Test
+ public void testWithArgumentsFromArrayExplicitSize() throws Exception {
+ DynamicType.Loaded<MethodCallWithExplicitArgument> loaded = new ByteBuddy()
+ .subclass(MethodCallWithExplicitArgument.class)
+ .implement(MethodCallDelegator.class)
+ .intercept(MethodCall.invoke(MethodCallWithExplicitArgument.class.getDeclaredMethod("foo", String.class)).withArgumentArrayElements(0, 1, 1))
+ .make()
+ .load(MethodCallDelegator.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredMethod(INVOKE_FOO, String[].class), not(nullValue(Method.class)));
+ assertThat(loaded.getLoaded().getDeclaredConstructors().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ MethodCallDelegator instance = (MethodCallDelegator) loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(MethodCallWithExplicitArgument.class)));
+ assertThat(instance, instanceOf(MethodCallDelegator.class));
+ assertThat(instance.invokeFoo(FOO, BAR, FOO), is(BAR));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testWithArgumentsFromArrayDoesNotExist() throws Exception {
+ new ByteBuddy()
+ .subclass(MethodCallWithExplicitArgument.class)
+ .implement(MethodCallDelegator.class)
+ .intercept(MethodCall.invoke(MethodCallWithExplicitArgument.class.getDeclaredMethod("foo", String.class)).withArgumentArrayElements(1, 1))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testWithArgumentsFromArrayDoesNotExistCompleteArray() throws Exception {
+ new ByteBuddy()
+ .subclass(MethodCallWithExplicitArgument.class)
+ .implement(MethodCallDelegator.class)
+ .intercept(MethodCall.invoke(MethodCallWithExplicitArgument.class.getDeclaredMethod("foo", String.class)).withArgumentArrayElements(1))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testWithArgumentsFromArrayIllegalType() throws Exception {
+ new ByteBuddy()
+ .subclass(MethodCallWithExplicitArgument.class)
+ .implement(IllegalMethodCallDelegator.class)
+ .intercept(MethodCall.invoke(MethodCallWithExplicitArgument.class.getDeclaredMethod("foo", String.class)).withArgumentArrayElements(0, 1))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testWithArgumentsFromArrayIllegalTypeCompleteArray() throws Exception {
+ new ByteBuddy()
+ .subclass(MethodCallWithExplicitArgument.class)
+ .implement(IllegalMethodCallDelegator.class)
+ .intercept(MethodCall.invoke(MethodCallWithExplicitArgument.class.getDeclaredMethod("foo", String.class)).withArgumentArrayElements(0))
+ .make();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNegativeIndex() throws Exception {
+ MethodCall.invoke(MethodCallWithExplicitArgument.class.getDeclaredMethod("foo", String.class)).withArgumentArrayElements(-1, 1);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNegativeIndexComplete() throws Exception {
+ MethodCall.invoke(MethodCallWithExplicitArgument.class.getDeclaredMethod("foo", String.class)).withArgumentArrayElements(-1);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNegativeStartIndex() throws Exception {
+ MethodCall.invoke(MethodCallWithExplicitArgument.class.getDeclaredMethod("foo", String.class)).withArgumentArrayElements(0, -1, 1);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNegativeSize() throws Exception {
+ MethodCall.invoke(MethodCallWithExplicitArgument.class.getDeclaredMethod("foo", String.class)).withArgumentArrayElements(0, 1, -1);
+ }
+
+ @Test
+ public void testSameSize() throws Exception {
+ MethodCall methodCall = MethodCall.invoke(MethodCallWithExplicitArgument.class.getDeclaredMethod("foo", String.class));
+ assertThat(methodCall.withArgumentArrayElements(0, 0), sameInstance(methodCall));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testWithTooBigParameter() throws Exception {
+ new ByteBuddy()
+ .subclass(MethodCallWithExplicitArgument.class)
+ .method(isDeclaredBy(MethodCallWithExplicitArgument.class))
+ .intercept(MethodCall.invokeSuper().withArgument(1))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testWithParameterNonAssignable() throws Exception {
+ new ByteBuddy()
+ .subclass(MethodCallWithExplicitArgument.class)
+ .method(isDeclaredBy(MethodCallWithExplicitArgument.class))
+ .intercept(MethodCall.invokeSuper().withArgument(0).withAssigner(nonAssigner, Assigner.Typing.STATIC))
+ .make();
+ }
+
+ @Test
+ public void testWithField() throws Exception {
+ DynamicType.Loaded<MethodCallWithField> loaded = new ByteBuddy()
+ .subclass(MethodCallWithField.class)
+ .method(isDeclaredBy(MethodCallWithField.class))
+ .intercept(MethodCall.invokeSuper().withField(FOO))
+ .make()
+ .load(MethodCallWithField.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredMethod(FOO, String.class), not(nullValue(Method.class)));
+ assertThat(loaded.getLoaded().getDeclaredConstructors().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ MethodCallWithField instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ instance.foo = FOO;
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(MethodCallWithField.class)));
+ assertThat(instance, instanceOf(MethodCallWithField.class));
+ assertThat(instance.foo(BAR), is(FOO));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testWithFieldNotExist() throws Exception {
+ new ByteBuddy()
+ .subclass(MethodCallWithField.class)
+ .method(isDeclaredBy(MethodCallWithField.class))
+ .intercept(MethodCall.invokeSuper().withField(BAR))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testWithFieldNonAssignable() throws Exception {
+ new ByteBuddy()
+ .subclass(MethodCallWithField.class)
+ .method(isDeclaredBy(MethodCallWithField.class))
+ .intercept(MethodCall.invokeSuper().withField(FOO).withAssigner(nonAssigner, Assigner.Typing.STATIC))
+ .make();
+ }
+
+ @Test
+ public void testWithFieldHierarchyVisibility() throws Exception {
+ DynamicType.Loaded<InvisibleMethodCallWithField> loaded = new ByteBuddy()
+ .subclass(InvisibleMethodCallWithField.class)
+ .method(isDeclaredBy(InvisibleMethodCallWithField.class))
+ .intercept(MethodCall.invokeSuper().withField(FOO))
+ .make()
+ .load(InvisibleMethodCallWithField.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredMethod(FOO, String.class), not(nullValue(Method.class)));
+ assertThat(loaded.getLoaded().getDeclaredConstructors().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ InvisibleMethodCallWithField instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ ((InvisibleBase) instance).foo = FOO;
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(InvisibleMethodCallWithField.class)));
+ assertThat(instance, instanceOf(InvisibleMethodCallWithField.class));
+ assertThat(instance.foo(BAR), is(FOO));
+ }
+
+ @Test
+ public void testWithThis() throws Exception {
+ DynamicType.Loaded<MethodCallWithThis> loaded = new ByteBuddy()
+ .subclass(MethodCallWithThis.class)
+ .method(isDeclaredBy(MethodCallWithThis.class))
+ .intercept(MethodCall.invokeSuper().withThis())
+ .make()
+ .load(MethodCallWithThis.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredMethod(FOO, MethodCallWithThis.class), not(nullValue(Method.class)));
+ assertThat(loaded.getLoaded().getDeclaredConstructors().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ MethodCallWithThis instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(MethodCallWithThis.class)));
+ assertThat(instance, instanceOf(MethodCallWithThis.class));
+ assertThat(instance.foo(null), is(instance));
+ }
+
+ @Test
+ public void testWithOwnType() throws Exception {
+ DynamicType.Loaded<MethodCallWithOwnType> loaded = new ByteBuddy()
+ .subclass(MethodCallWithOwnType.class)
+ .method(isDeclaredBy(MethodCallWithOwnType.class))
+ .intercept(MethodCall.invokeSuper().withOwnType())
+ .make()
+ .load(MethodCallWithOwnType.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredMethod(FOO, Class.class), not(nullValue(Method.class)));
+ assertThat(loaded.getLoaded().getDeclaredConstructors().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ MethodCallWithOwnType instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(MethodCallWithThis.class)));
+ assertThat(instance, instanceOf(MethodCallWithOwnType.class));
+ assertThat(instance.foo(null), CoreMatchers.<Class<?>>is(loaded.getLoaded()));
+ }
+
+ @Test
+ public void testImplementationAppendingMethod() throws Exception {
+ DynamicType.Loaded<MethodCallAppending> loaded = new ByteBuddy()
+ .subclass(MethodCallAppending.class)
+ .method(isDeclaredBy(MethodCallAppending.class))
+ .intercept(MethodCall.invokeSuper().andThen(new Implementation.Simple(new TextConstant(FOO), MethodReturn.REFERENCE)))
+ .make()
+ .load(MethodCallAppending.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredMethod(FOO), not(nullValue(Method.class)));
+ assertThat(loaded.getLoaded().getDeclaredConstructors().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ MethodCallAppending instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(MethodCallAppending.class)));
+ assertThat(instance, instanceOf(MethodCallAppending.class));
+ assertThat(instance.foo(), is((Object) FOO));
+ instance.assertOnlyCall(FOO);
+ }
+
+ @Test
+ public void testImplementationAppendingConstructor() throws Exception {
+ DynamicType.Loaded<MethodCallAppending> loaded = new ByteBuddy()
+ .subclass(MethodCallAppending.class)
+ .method(isDeclaredBy(MethodCallAppending.class))
+ .intercept(MethodCall.construct(Object.class.getDeclaredConstructor())
+ .andThen(new Implementation.Simple(new TextConstant(FOO), MethodReturn.REFERENCE)))
+ .make()
+ .load(MethodCallAppending.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredMethod(FOO), not(nullValue(Method.class)));
+ assertThat(loaded.getLoaded().getDeclaredConstructors().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ MethodCallAppending instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(MethodCallAppending.class)));
+ assertThat(instance, instanceOf(MethodCallAppending.class));
+ assertThat(instance.foo(), is((Object) FOO));
+ instance.assertZeroCalls();
+ }
+
+ @Test
+ public void testWithExplicitTarget() throws Exception {
+ Object target = new Object();
+ DynamicType.Loaded<ExplicitTarget> loaded = new ByteBuddy()
+ .subclass(ExplicitTarget.class)
+ .method(isDeclaredBy(ExplicitTarget.class))
+ .intercept(MethodCall.invoke(Object.class.getDeclaredMethod("toString")).on(target))
+ .make()
+ .load(ExplicitTarget.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredMethod(FOO), not(nullValue(Method.class)));
+ assertThat(loaded.getLoaded().getDeclaredConstructors().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(1));
+ ExplicitTarget instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(ExplicitTarget.class)));
+ assertThat(instance, instanceOf(ExplicitTarget.class));
+ assertThat(instance.foo(), is(target.toString()));
+ }
+
+ @Test
+ public void testWithFieldTarget() throws Exception {
+ Object target = new Object();
+ DynamicType.Loaded<ExplicitTarget> loaded = new ByteBuddy()
+ .subclass(ExplicitTarget.class)
+ .defineField(FOO, Object.class, Visibility.PUBLIC)
+ .method(isDeclaredBy(ExplicitTarget.class))
+ .intercept(MethodCall.invoke(Object.class.getDeclaredMethod("toString")).onField(FOO))
+ .make()
+ .load(ExplicitTarget.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredMethod(FOO), not(nullValue(Method.class)));
+ assertThat(loaded.getLoaded().getDeclaredConstructors().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(1));
+ ExplicitTarget instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ loaded.getLoaded().getDeclaredField(FOO).set(instance, target);
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(ExplicitTarget.class)));
+ assertThat(instance, instanceOf(ExplicitTarget.class));
+ assertThat(instance.foo(), is(target.toString()));
+ }
+
+ @Test
+ public void testUnloadedType() throws Exception {
+ DynamicType.Loaded<SimpleMethod> loaded = new ByteBuddy()
+ .subclass(SimpleMethod.class)
+ .method(named(FOO))
+ .intercept(MethodCall.invoke(Foo.class.getDeclaredMethod(BAR, Object.class, Object.class)).with(TypeDescription.OBJECT, TypeDescription.STRING))
+ .make()
+ .load(SimpleMethod.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ SimpleMethod instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.foo(), is("" + Object.class + String.class));
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(SimpleMethod.class)));
+ assertThat(instance, instanceOf(SimpleMethod.class));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(value = 7, hotSpot = 7)
+ public void testJava7Types() throws Exception {
+ DynamicType.Loaded<SimpleMethod> loaded = new ByteBuddy()
+ .subclass(SimpleMethod.class)
+ .method(named(FOO))
+ .intercept(MethodCall.invoke(Foo.class.getDeclaredMethod(BAR, Object.class, Object.class)).with(makeMethodHandle(), makeMethodType(void.class)))
+ .make()
+ .load(SimpleMethod.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ SimpleMethod instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.foo(), is("" + makeMethodHandle() + makeMethodType(void.class)));
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(SimpleMethod.class)));
+ assertThat(instance, instanceOf(SimpleMethod.class));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(value = 7, hotSpot = 7)
+ public void testJava7TypesExplicit() throws Exception {
+ DynamicType.Loaded<SimpleMethod> loaded = new ByteBuddy()
+ .subclass(SimpleMethod.class)
+ .method(named(FOO))
+ .intercept(MethodCall.invoke(Foo.class.getDeclaredMethod(BAR, Object.class, Object.class))
+ .with(JavaConstant.MethodHandle.ofLoaded(makeMethodHandle()), JavaConstant.MethodType.ofLoaded(makeMethodType(void.class))))
+ .make()
+ .load(SimpleMethod.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ SimpleMethod instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.foo(), is("" + makeMethodHandle() + makeMethodType(void.class)));
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(SimpleMethod.class)));
+ assertThat(instance, instanceOf(SimpleMethod.class));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testDefaultMethod() throws Exception {
+ DynamicType.Loaded<Object> loaded = new ByteBuddy()
+ .subclass(Object.class)
+ .implement(Class.forName(SINGLE_DEFAULT_METHOD))
+ .method(ElementMatchers.not(isDeclaredBy(Object.class)))
+ .intercept(MethodCall.invoke(Class.forName(SINGLE_DEFAULT_METHOD).getDeclaredMethod(FOO)).onDefault())
+ .make()
+ .load(Class.forName(SINGLE_DEFAULT_METHOD).getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ Method method = loaded.getLoaded().getDeclaredMethod(FOO);
+ Object instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(method.invoke(instance), is((Object) FOO));
+ }
+
+ @Test
+ public void testCallable() throws Exception {
+ Traceable traceable = new Traceable();
+ DynamicType.Loaded<SimpleStringMethod> loaded = new ByteBuddy()
+ .subclass(SimpleStringMethod.class)
+ .method(isDeclaredBy(SimpleStringMethod.class))
+ .intercept(MethodCall.call(traceable))
+ .make()
+ .load(SimpleStringMethod.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoaded().getDeclaredConstructor().newInstance().foo(), is(FOO));
+ traceable.assertOnlyCall(FOO);
+ }
+
+ @Test
+ public void testRunnable() throws Exception {
+ Traceable traceable = new Traceable();
+ DynamicType.Loaded<SimpleStringMethod> loaded = new ByteBuddy()
+ .subclass(SimpleStringMethod.class)
+ .method(isDeclaredBy(SimpleStringMethod.class))
+ .intercept(MethodCall.run(traceable))
+ .make()
+ .load(SimpleStringMethod.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoaded().getDeclaredConstructor().newInstance().foo(), nullValue(String.class));
+ traceable.assertOnlyCall(FOO);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testDefaultMethodNotCompatible() throws Exception {
+ new ByteBuddy()
+ .subclass(Object.class)
+ .method(isDeclaredBy(Object.class))
+ .intercept(MethodCall.invoke(String.class.getDeclaredMethod("toString")).onDefault())
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodTypeIncompatible() throws Exception {
+ new ByteBuddy()
+ .subclass(InstanceMethod.class)
+ .method(isDeclaredBy(InstanceMethod.class))
+ .intercept(MethodCall.invoke(String.class.getDeclaredMethod("toLowerCase")))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testArgumentIncompatibleTooFew() throws Exception {
+ new ByteBuddy()
+ .subclass(InstanceMethod.class)
+ .method(named(FOO))
+ .intercept(MethodCall.invoke(StaticIncompatibleExternalMethod.class.getDeclaredMethod("bar", String.class)))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testArgumentIncompatibleTooMany() throws Exception {
+ new ByteBuddy()
+ .subclass(InstanceMethod.class)
+ .method(named(FOO))
+ .intercept(MethodCall.invoke(StaticIncompatibleExternalMethod.class.getDeclaredMethod("bar", String.class)).with(FOO, BAR))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testArgumentIncompatibleNotAssignable() throws Exception {
+ new ByteBuddy()
+ .subclass(InstanceMethod.class)
+ .method(named(FOO))
+ .intercept(MethodCall.invoke(StaticIncompatibleExternalMethod.class.getDeclaredMethod("bar", String.class)).with(new Object()))
+ .make();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testConstructNonConstructorThrowsException() throws Exception {
+ MethodCall.construct(mock(MethodDescription.class));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testIllegalIndex() throws Exception {
+ MethodCall.invokeSuper().withArgument(-1);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodCallNonVirtual() throws Exception {
+ new ByteBuddy()
+ .subclass(InstanceMethod.class)
+ .method(named(FOO))
+ .intercept(MethodCall.invoke(StaticIncompatibleExternalMethod.class.getDeclaredMethod("bar", String.class)).on(new StaticIncompatibleExternalMethod()).with(FOO))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodCallIncompatibleInstance() throws Exception {
+ new ByteBuddy()
+ .subclass(InstanceMethod.class)
+ .method(named(FOO))
+ .intercept(MethodCall.invoke(StaticIncompatibleExternalMethod.class.getDeclaredMethod("bar", String.class)).on(new Object()).with(FOO))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodCallNonVisibleType() throws Exception {
+ new ByteBuddy()
+ .subclass(Object.class)
+ .method(isDeclaredBy(Object.class))
+ .intercept(MethodCall.invoke(PackagePrivateType.class.getDeclaredMethod("foo")).on(new PackagePrivateType()))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodCallStaticTargetNonVisibleType() throws Exception {
+ new ByteBuddy()
+ .subclass(Object.class)
+ .method(isDeclaredBy(Object.class))
+ .intercept(MethodCall.invoke(PackagePrivateType.class.getDeclaredMethod("bar")))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodCallSuperCallNonInvokable() throws Exception {
+ new ByteBuddy()
+ .subclass(Object.class)
+ .method(isDeclaredBy(Object.class))
+ .intercept(MethodCall.invoke(Bar.class.getDeclaredMethod("bar")).onSuper())
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodCallDefaultCallNonInvokable() throws Exception {
+ new ByteBuddy()
+ .subclass(Object.class)
+ .method(isDeclaredBy(Object.class))
+ .intercept(MethodCall.invoke(Bar.class.getDeclaredMethod("bar")).onDefault())
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMethodCallFieldDoesNotExist() throws Exception {
+ new ByteBuddy()
+ .subclass(ExplicitTarget.class)
+ .method(isDeclaredBy(ExplicitTarget.class))
+ .intercept(MethodCall.invoke(Object.class.getDeclaredMethod("toString")).onField(FOO))
+ .make();
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodCall.class).apply();
+ ObjectPropertyAssertion.of(MethodCall.WithoutSpecifiedTarget.class).apply();
+ ObjectPropertyAssertion.of(MethodCall.Appender.class).apply();
+ ObjectPropertyAssertion.of(MethodCall.MethodLocator.ForExplicitMethod.class).apply();
+ ObjectPropertyAssertion.of(MethodCall.MethodLocator.ForInstrumentedMethod.class).apply();
+ ObjectPropertyAssertion.of(MethodCall.MethodLocator.ForElementMatcher.class).apply();
+ ObjectPropertyAssertion.of(MethodCall.MethodInvoker.ForContextualInvocation.class).apply();
+ final Iterator<Class<?>> iterator = Arrays.<Class<?>>asList(String.class, Object.class).iterator();
+ ObjectPropertyAssertion.of(MethodCall.MethodInvoker.ForVirtualInvocation.class).create(new ObjectPropertyAssertion.Creator<Class<?>>() {
+ @Override
+ public Class<?> create() {
+ return iterator.next();
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(MethodCall.MethodInvoker.ForVirtualInvocation.WithImplicitType.class).apply();
+ ObjectPropertyAssertion.of(MethodCall.MethodInvoker.ForSuperMethodInvocation.class).apply();
+ ObjectPropertyAssertion.of(MethodCall.MethodInvoker.ForDefaultMethodInvocation.class).apply();
+ ObjectPropertyAssertion.of(MethodCall.TerminationHandler.class).apply();
+ ObjectPropertyAssertion.of(MethodCall.TargetHandler.ForValue.class).apply();
+ ObjectPropertyAssertion.of(MethodCall.TargetHandler.ForField.class).apply();
+ ObjectPropertyAssertion.of(MethodCall.TargetHandler.ForSelfOrStaticInvocation.class).apply();
+ ObjectPropertyAssertion.of(MethodCall.TargetHandler.ForConstructingInvocation.class).apply();
+ ObjectPropertyAssertion.of(MethodCall.TargetHandler.ForMethodParameter.class).apply();
+ ObjectPropertyAssertion.of(MethodCall.ArgumentLoader.ForNullConstant.class).apply();
+ ObjectPropertyAssertion.of(MethodCall.ArgumentLoader.ForThisReference.class).apply();
+ ObjectPropertyAssertion.of(MethodCall.ArgumentLoader.ForThisReference.Factory.class).apply();
+ ObjectPropertyAssertion.of(MethodCall.ArgumentLoader.ForInstrumentedType.class).apply();
+ ObjectPropertyAssertion.of(MethodCall.ArgumentLoader.ForInstrumentedType.Factory.class).apply();
+ ObjectPropertyAssertion.of(MethodCall.ArgumentLoader.ForInstance.class).apply();
+ ObjectPropertyAssertion.of(MethodCall.ArgumentLoader.ForInstance.Factory.class).apply();
+ ObjectPropertyAssertion.of(MethodCall.ArgumentLoader.ForField.class).apply();
+ ObjectPropertyAssertion.of(MethodCall.ArgumentLoader.ForField.Factory.class).apply();
+// ObjectPropertyAssertion.of(MethodCall.ArgumentLoader.ForStackManipulation.class).apply();
+ ObjectPropertyAssertion.of(MethodCall.ArgumentLoader.ForMethodParameter.class).apply();
+ ObjectPropertyAssertion.of(MethodCall.ArgumentLoader.ForMethodParameter.Factory.class).apply();
+ ObjectPropertyAssertion.of(MethodCall.ArgumentLoader.ForMethodParameter.OfInstrumentedMethod.class).apply();
+ ObjectPropertyAssertion.of(MethodCall.ArgumentLoader.ForMethodParameterArray.class).apply();
+ ObjectPropertyAssertion.of(MethodCall.ArgumentLoader.ForMethodParameterArray.Factory.class).apply();
+ ObjectPropertyAssertion.of(MethodCall.ArgumentLoader.ForMethodParameterArrayElement.class).apply();
+ ObjectPropertyAssertion.of(MethodCall.ArgumentLoader.ForMethodParameterArrayElement.OfParameter.class).apply();
+ ObjectPropertyAssertion.of(MethodCall.ArgumentLoader.ForMethodParameterArrayElement.OfInvokedMethod.class).apply();
+ }
+
+ public static class SimpleMethod {
+
+ public String foo() {
+ return null;
+ }
+
+ public String bar() {
+ return BAR;
+ }
+ }
+
+ public static class StaticExternalMethod {
+
+ public static String bar() {
+ return BAR;
+ }
+ }
+
+ public static class InstanceMethod {
+
+ public String foo() {
+ return null;
+ }
+
+ public String bar() {
+ return BAR;
+ }
+ }
+
+ public abstract static class ArgumentCall {
+
+ public abstract String foo(Target target);
+
+ public static class Target {
+
+ private final String value;
+
+ public Target(String value) {
+ this.value = value;
+ }
+
+ public String foo() {
+ return value;
+ }
+ }
+ }
+
+ public abstract static class ArgumentCallDynamic {
+
+ public abstract String foo(Object target);
+
+ public static class Target {
+
+ private final String value;
+
+ public Target(String value) {
+ this.value = value;
+ }
+
+ public String foo() {
+ return value;
+ }
+ }
+ }
+
+ public static class SelfReference {
+
+ public SelfReference foo() {
+ return null;
+ }
+ }
+
+ public static class SuperMethodInvocation {
+
+ public String foo() {
+ return FOO;
+ }
+ }
+
+ public static class MethodCallWithExplicitArgument {
+
+ public String foo(String value) {
+ return value;
+ }
+ }
+
+ public static class MethodCallWithTwoExplicitArguments {
+
+ public String foo(String first, String second) {
+ return first + second;
+ }
+ }
+
+ public interface MethodCallDelegator {
+
+ String invokeFoo(String... argument);
+ }
+
+ public interface IllegalMethodCallDelegator {
+
+ String invokeFoo(String argument);
+ }
+
+ @SuppressWarnings("unused")
+ public static class MethodCallWithField {
+
+ public String foo;
+
+ public String foo(String value) {
+ return value;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class InvisibleMethodCallWithField extends InvisibleBase {
+
+ private String foo;
+
+ public String foo(String value) {
+ return value;
+ }
+ }
+
+ public static class InvisibleBase {
+
+ public String foo;
+ }
+
+ public static class MethodCallWithThis {
+
+ public MethodCallWithThis foo(MethodCallWithThis value) {
+ return value;
+ }
+ }
+
+ public static class MethodCallWithOwnType {
+
+ public Class<?> foo(Class<?> value) {
+ return value;
+ }
+ }
+
+ public static class MethodCallAppending extends CallTraceable {
+
+ public Object foo() {
+ register(FOO);
+ return null;
+ }
+ }
+
+ public static class ExplicitTarget {
+
+ public String foo() {
+ return null;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class StaticIncompatibleExternalMethod {
+
+ public static String bar(String value) {
+ return null;
+ }
+ }
+
+ public static class Foo {
+
+ public static String bar(Object arg0, Object arg1) {
+ return "" + arg0 + arg1;
+ }
+ }
+
+ static class PackagePrivateType {
+
+ public String foo() {
+ return FOO;
+ }
+
+ public static String bar() {
+ return BAR;
+ }
+ }
+
+ public static class Bar {
+
+ public void bar() {
+ /* empty */
+ }
+ }
+
+ public static class SimpleStringMethod {
+
+ public String foo() {
+ return null;
+ }
+ }
+
+ public static class Traceable extends CallTraceable implements Runnable, Callable<String> {
+
+ @Override
+ public String call() throws Exception {
+ register(FOO);
+ return FOO;
+ }
+
+ @Override
+ public void run() {
+ register(FOO);
+ }
+ }
+
+ public static class NonVirtual {
+
+ public static void foo() {
+ /* empty */
+ }
+ }
+
+ public abstract static class ArrayConsuming {
+
+ public abstract String foo(String arg1, String arg2);
+
+ public String bar(String[] arg) {
+ return arg[0] + arg[1];
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodCallTypeTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodCallTypeTest.java
new file mode 100644
index 0000000..8348956
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodCallTypeTest.java
@@ -0,0 +1,160 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+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.mockito.Mockito;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collection;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+ at RunWith(Parameterized.class)
+public class MethodCallTypeTest {
+
+ private static final String FOO = "foo";
+
+ private static final String STRING_VALUE = "foo";
+
+ private static final Bar ENUM_VALUE = Bar.INSTANCE;
+
+ private static final Class<?> CLASS_VALUE = Object.class;
+
+ private static final boolean BOOLEAN_VALUE = true;
+
+ private static final byte BYTE_VALUE = 42;
+
+ private static final short SHORT_VALUE = 42;
+
+ private static final char CHAR_VALUE = '@';
+
+ private static final int INT_VALUE = 42;
+
+ private static final long LONG_VALUE = 42L;
+
+ private static final float FLOAT_VALUE = 42f;
+
+ private static final double DOUBLE_VALUE = 42d;
+
+ private static final Object NULL_CONSTANT = null;
+
+ private static final Object REFERENCE_VALUE = new Object();
+
+ private final Object value;
+
+ private final boolean definesFieldReference;
+
+ private final boolean definesFieldConstantPool;
+
+ @Rule
+ public TestRule methodRule = new MockitoRule(this);
+
+ @Mock
+ private Assigner nonAssigner;
+
+ public MethodCallTypeTest(Object value, boolean definesFieldReference, boolean definesFieldConstantPool) {
+ this.value = value;
+ this.definesFieldReference = definesFieldReference;
+ this.definesFieldConstantPool = definesFieldConstantPool;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {BOOLEAN_VALUE, true, false},
+ {BYTE_VALUE, true, false},
+ {SHORT_VALUE, true, false},
+ {CHAR_VALUE, true, false},
+ {INT_VALUE, true, false},
+ {LONG_VALUE, true, false},
+ {FLOAT_VALUE, true, false},
+ {DOUBLE_VALUE, true, false},
+ {NULL_CONSTANT, false, false},
+ {STRING_VALUE, true, false},
+ {CLASS_VALUE, true, false},
+ {ENUM_VALUE, true, false},
+ {REFERENCE_VALUE, true, true}
+ });
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(nonAssigner.assign(Mockito.any(TypeDescription.Generic.class), Mockito.any(TypeDescription.Generic.class), Mockito.any(Assigner.Typing.class)))
+ .thenReturn(StackManipulation.Illegal.INSTANCE);
+ }
+
+ @Test
+ public void testFieldConstantPool() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodCall.invokeSuper().with(value))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredMethod(FOO, Object.class), not(nullValue(Method.class)));
+ assertThat(loaded.getLoaded().getDeclaredConstructors().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(definesFieldConstantPool ? 1 : 0));
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(Foo.class)));
+ assertThat(instance, instanceOf(Foo.class));
+ assertThat(instance.foo(new Object()), is(value));
+ }
+
+ @Test
+ public void testFieldReference() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodCall.invokeSuper().withReference(value))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredMethod(FOO, Object.class), not(nullValue(Method.class)));
+ assertThat(loaded.getLoaded().getDeclaredConstructors().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(definesFieldReference ? 1 : 0));
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(Foo.class)));
+ assertThat(instance, instanceOf(Foo.class));
+ assertThat(instance.foo(new Object()), sameInstance(value));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonAssignable() throws Exception {
+ new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodCall.invokeSuper().with(value).withAssigner(nonAssigner, Assigner.Typing.STATIC))
+ .make();
+ }
+
+ public enum Bar {
+ INSTANCE;
+ }
+
+ public static class Foo {
+
+ public Object foo(Object value) {
+ return value;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationAllArgumentsTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationAllArgumentsTest.java
new file mode 100644
index 0000000..d214008
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationAllArgumentsTest.java
@@ -0,0 +1,128 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.bind.annotation.AllArguments;
+import org.junit.Test;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodDelegationAllArgumentsTest {
+
+ private static final int FOO = 42, BAR = 21;
+
+ private static final String QUX = "qux", BAZ = "baz", FOOBAR = "foobar";
+
+ @Test
+ public void testStrictBindable() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.to(Bar.class))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.foo(FOO, BAR), is((Object) (QUX + FOO + BAR)));
+ }
+
+ @Test
+ public void testStrictBindableObjectType() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.to(FooBar.class))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.foo(FOO, BAR), is((Object) (QUX + FOO + BAR)));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testStrictNonBindableThrowsException() throws Exception {
+ new ByteBuddy()
+ .subclass(Qux.class).method(isDeclaredBy(Qux.class))
+ .intercept(MethodDelegation.to(BazStrict.class))
+ .make();
+ }
+
+ @Test
+ public void testSlackNonBindable() throws Exception {
+ DynamicType.Loaded<Qux> loaded = new ByteBuddy()
+ .subclass(Qux.class)
+ .method(isDeclaredBy(Qux.class))
+ .intercept(MethodDelegation.to(BazSlack.class))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Qux instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.foo(FOOBAR, BAZ), is((Object) (QUX + BAZ)));
+ }
+
+ @Test
+ public void testIncludeSelf() throws Exception {
+ DynamicType.Loaded<Qux> loaded = new ByteBuddy()
+ .subclass(Qux.class)
+ .method(isDeclaredBy(Qux.class))
+ .intercept(MethodDelegation.to(IncludeSelf.class))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Qux instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.foo(QUX, BAZ), is((Object) instance));
+ }
+
+ public static class Foo {
+
+ public Object foo(int i1, Integer i2) {
+ return null;
+ }
+ }
+
+ public static class Bar {
+
+ public static String qux(@AllArguments int[] args) {
+ return QUX + args[0] + args[1];
+ }
+ }
+
+ public static class FooBar {
+
+ public static String qux(@AllArguments Object args) {
+ return QUX + ((Object[]) args)[0] + ((Object[]) args)[1];
+ }
+ }
+
+ public static class Qux {
+
+ public Object foo(Object o, String s) {
+ return null;
+ }
+ }
+
+ public static class BazStrict {
+
+ public static String qux(@AllArguments String[] args) {
+ assertThat(args.length, is(2));
+ return QUX + args[0] + args[1];
+ }
+ }
+
+ public static class BazSlack {
+
+ public static String qux(@AllArguments(AllArguments.Assignment.SLACK) String[] args) {
+ assertThat(args.length, is(1));
+ return QUX + args[0];
+ }
+ }
+
+ public static class IncludeSelf {
+
+ public static Object intercept(@AllArguments(includeSelf = true) Object[] args) {
+ assertThat(args.length, is(3));
+ assertThat(args[1], is((Object) QUX));
+ assertThat(args[2], is((Object) BAZ));
+ return args[0];
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationArgumentTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationArgumentTest.java
new file mode 100644
index 0000000..e9cdcd8
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationArgumentTest.java
@@ -0,0 +1,78 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.bind.annotation.Argument;
+import org.junit.Test;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodDelegationArgumentTest {
+
+ private static final String FOO = "bar", QUX = "qux", BAZ = "baz";
+
+ private static final int BAR = 42;
+
+ @Test
+ public void testArgument() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.to(Bar.class))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.foo(FOO, BAR), is((Object) (QUX + FOO + BAR)));
+ }
+
+ @Test
+ public void testHierarchyDelegation() throws Exception {
+ new ByteBuddy()
+ .subclass(Baz.class)
+ .method(named("foo"))
+ .intercept(MethodDelegation.to(new Qux()))
+ .make()
+ .load(Baz.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance()
+ .foo();
+ }
+
+ public static class Foo {
+
+ public Object foo(String s, Integer i) {
+ return null;
+ }
+ }
+
+ public static class Bar {
+
+ public static String qux(@Argument(1) Integer i, @Argument(0) String s) {
+ return QUX + s + i;
+ }
+
+ public static String baz(String s, Object o) {
+ return BAZ + s + o;
+ }
+ }
+
+ public static class Baz {
+
+ public void foo() {
+
+ }
+ }
+
+ public static class Qux extends Baz {
+
+ @Override
+ public void foo() {
+ super.foo();
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationBindingPriorityTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationBindingPriorityTest.java
new file mode 100644
index 0000000..b29d98a
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationBindingPriorityTest.java
@@ -0,0 +1,49 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.bind.annotation.BindingPriority;
+import org.junit.Test;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodDelegationBindingPriorityTest {
+
+ private static final String FOO = "FOO", BAR = "bar";
+
+ private static final int PRIORITY = 10;
+
+ @Test
+ public void testBindingPriority() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.to(Bar.class))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.foo(), is(FOO));
+ }
+
+ public static class Foo {
+
+ public String foo() {
+ return null;
+ }
+ }
+
+ public static class Bar {
+
+ @BindingPriority(PRIORITY)
+ public static String bar() {
+ return FOO;
+ }
+
+ public static String qux() {
+ return BAR;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationChainedTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationChainedTest.java
new file mode 100644
index 0000000..5fe6a28
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationChainedTest.java
@@ -0,0 +1,75 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.bytecode.constant.TextConstant;
+import net.bytebuddy.implementation.bytecode.member.MethodReturn;
+import org.junit.Test;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodDelegationChainedTest {
+
+ private static final String FOO = "foo";
+
+ @Test
+ public void testChainingVoid() throws Exception {
+ VoidInterceptor voidInterceptor = new VoidInterceptor();
+ DynamicType.Loaded<Foo> dynamicType = new ByteBuddy().subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.withDefaultConfiguration()
+ .filter(isDeclaredBy(VoidInterceptor.class))
+ .to(voidInterceptor)
+ .andThen(new Implementation.Simple(new TextConstant(FOO), MethodReturn.REFERENCE)))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(dynamicType.getLoaded().getDeclaredConstructor().newInstance().foo(), is(FOO));
+ assertThat(voidInterceptor.intercepted, is(true));
+ }
+
+ @Test
+ public void testChainingNonVoid() throws Exception {
+ NonVoidInterceptor nonVoidInterceptor = new NonVoidInterceptor();
+
+ DynamicType.Loaded<Foo> dynamicType = new ByteBuddy().subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.
+ withDefaultConfiguration()
+ .filter(isDeclaredBy(NonVoidInterceptor.class))
+ .to(nonVoidInterceptor)
+ .andThen(new Implementation.Simple(new TextConstant(FOO), MethodReturn.REFERENCE)))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(dynamicType.getLoaded().getDeclaredConstructor().newInstance().foo(), is(FOO));
+ assertThat(nonVoidInterceptor.intercepted, is(true));
+ }
+
+ public static class Foo {
+
+ public String foo() {
+ return null;
+ }
+ }
+
+ public class VoidInterceptor {
+
+ private boolean intercepted = false;
+
+ public void intercept() {
+ intercepted = true;
+ }
+ }
+
+ public class NonVoidInterceptor {
+
+ private boolean intercepted = false;
+
+ public Integer intercept() {
+ intercepted = true;
+ return 0;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationCheckedExceptionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationCheckedExceptionTest.java
new file mode 100644
index 0000000..d9617f6
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationCheckedExceptionTest.java
@@ -0,0 +1,35 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import org.junit.Test;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+
+public class MethodDelegationCheckedExceptionTest {
+
+ @Test(expected = Exception.class)
+ public void testUndeclaredCheckedException() throws Exception {
+ new ByteBuddy().subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.to(Foo.class))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance()
+ .bar();
+ }
+
+ @SuppressWarnings("unused")
+ public static class Foo {
+
+ public static void doThrow() throws Exception {
+ throw new Exception();
+ }
+
+ public void bar() {
+ /* do nothing */
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationConstructionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationConstructionTest.java
new file mode 100644
index 0000000..3e08eba
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationConstructionTest.java
@@ -0,0 +1,333 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.test.utility.CallTraceable;
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.Matcher;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.Collection;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class MethodDelegationConstructionTest<T extends CallTraceable> {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ private static final byte BYTE_MULTIPLICATOR = 3;
+
+ private static final short SHORT_MULTIPLICATOR = 3;
+
+ private static final char CHAR_MULTIPLICATOR = 3;
+
+ private static final int INT_MULTIPLICATOR = 3;
+
+ private static final long LONG_MULTIPLICATOR = 3L;
+
+ private static final float FLOAT_MULTIPLICATOR = 3f;
+
+ private static final double DOUBLE_MULTIPLICATOR = 3d;
+
+ private static final boolean DEFAULT_BOOLEAN = false;
+
+ private static final byte DEFAULT_BYTE = 1;
+
+ private static final short DEFAULT_SHORT = 1;
+
+ private static final char DEFAULT_CHAR = 1;
+
+ private static final int DEFAULT_INT = 1;
+
+ private static final long DEFAULT_LONG = 1L;
+
+ private static final float DEFAULT_FLOAT = 1f;
+
+ private static final double DEFAULT_DOUBLE = 1d;
+
+ private final Class<T> sourceType;
+
+ private final Class<?> targetType;
+
+ private final Class<?>[] parameterTypes;
+
+ private final Object[] arguments;
+
+ private final Matcher<?> matcher;
+
+ public MethodDelegationConstructionTest(Class<T> sourceType,
+ Class<?> targetType,
+ Class<?>[] parameterTypes,
+ Object[] arguments,
+ Matcher<?> matcher) {
+ this.sourceType = sourceType;
+ this.targetType = targetType;
+ this.parameterTypes = parameterTypes;
+ this.arguments = arguments;
+ this.matcher = matcher;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {BooleanSource.class, BooleanTarget.class, new Class<?>[]{boolean.class}, new Object[]{DEFAULT_BOOLEAN}, is(!DEFAULT_BOOLEAN)},
+ {ByteSource.class, ByteTarget.class, new Class<?>[]{byte.class}, new Object[]{DEFAULT_BYTE}, is((byte) (DEFAULT_BYTE * BYTE_MULTIPLICATOR))},
+ {ShortSource.class, ShortTarget.class, new Class<?>[]{short.class}, new Object[]{DEFAULT_SHORT}, is((short) (DEFAULT_SHORT * SHORT_MULTIPLICATOR))},
+ {CharSource.class, CharTarget.class, new Class<?>[]{char.class}, new Object[]{DEFAULT_CHAR}, is((char) (DEFAULT_CHAR * CHAR_MULTIPLICATOR))},
+ {IntSource.class, IntTarget.class, new Class<?>[]{int.class}, new Object[]{DEFAULT_INT}, is(DEFAULT_INT * INT_MULTIPLICATOR)},
+ {LongSource.class, LongTarget.class, new Class<?>[]{long.class}, new Object[]{DEFAULT_LONG}, is(DEFAULT_LONG * LONG_MULTIPLICATOR)},
+ {FloatSource.class, FloatTarget.class, new Class<?>[]{float.class}, new Object[]{DEFAULT_FLOAT}, is(DEFAULT_FLOAT * FLOAT_MULTIPLICATOR)},
+ {DoubleSource.class, DoubleTarget.class, new Class<?>[]{double.class}, new Object[]{DEFAULT_DOUBLE}, is(DEFAULT_DOUBLE * DOUBLE_MULTIPLICATOR)},
+ {VoidSource.class, VoidTarget.class, new Class<?>[0], new Object[0], nullValue()},
+ {StringSource.class, StringTarget.class, new Class<?>[]{String.class}, new Object[]{FOO}, is(FOO + BAR)},
+ });
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testConstruction() throws Exception {
+ DynamicType.Loaded<T> loaded = new ByteBuddy()
+ .subclass(sourceType)
+ .method(isDeclaredBy(sourceType))
+ .intercept(MethodDelegation.toConstructor(targetType))
+ .make()
+ .load(sourceType.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ T instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(sourceType)));
+ assertThat(instance, instanceOf(sourceType));
+ Object value = loaded.getLoaded().getDeclaredMethod(FOO, parameterTypes).invoke(instance, arguments);
+ assertThat(value, instanceOf(targetType));
+ Field field = targetType.getDeclaredField("value");
+ field.setAccessible(true);
+ assertThat(field.get(value), (Matcher) matcher);
+ instance.assertZeroCalls();
+ }
+
+ public static class BooleanSource extends CallTraceable {
+
+ @SuppressWarnings("unused")
+ public BooleanTarget foo(boolean b) {
+ register(FOO);
+ return null;
+ }
+ }
+
+ public static class BooleanTarget {
+
+ @SuppressWarnings("unused")
+ private final boolean value;
+
+ public BooleanTarget(boolean value) {
+ this.value = !value;
+ }
+ }
+
+ public static class ByteSource extends CallTraceable {
+
+ @SuppressWarnings("unused")
+ public ByteTarget foo(byte b) {
+ register(FOO);
+ return null;
+ }
+ }
+
+ public static class ByteTarget {
+
+ @SuppressWarnings("unused")
+ private final byte value;
+
+ public ByteTarget(byte b) {
+ value = bar(b);
+ }
+
+ private static byte bar(byte b) {
+ return (byte) (b * BYTE_MULTIPLICATOR);
+ }
+ }
+
+ public static class ShortSource extends CallTraceable {
+
+ @SuppressWarnings("unused")
+ public ShortTarget foo(short s) {
+ register(FOO);
+ return null;
+ }
+ }
+
+ public static class ShortTarget {
+
+ @SuppressWarnings("unused")
+ private final short value;
+
+ public ShortTarget(short s) {
+ this.value = bar(s);
+ }
+
+ private static short bar(short s) {
+ return (short) (s * SHORT_MULTIPLICATOR);
+ }
+ }
+
+ public static class CharSource extends CallTraceable {
+
+ @SuppressWarnings("unused")
+ public CharTarget foo(char s) {
+ register(FOO);
+ return null;
+ }
+ }
+
+ public static class CharTarget {
+
+ @SuppressWarnings("unused")
+ private final char value;
+
+ public CharTarget(char c) {
+ this.value = bar(c);
+ }
+
+ private static char bar(char c) {
+ return (char) (c * CHAR_MULTIPLICATOR);
+ }
+ }
+
+ public static class IntSource extends CallTraceable {
+
+ @SuppressWarnings("unused")
+ public IntTarget foo(int i) {
+ register(FOO);
+ return null;
+ }
+ }
+
+ public static class IntTarget {
+
+ @SuppressWarnings("unused")
+ private final int value;
+
+ public IntTarget(int i) {
+ this.value = bar(i);
+ }
+
+ private static int bar(int i) {
+ return i * INT_MULTIPLICATOR;
+ }
+ }
+
+ public static class LongSource extends CallTraceable {
+
+ @SuppressWarnings("unused")
+ public LongTarget foo(long l) {
+ register(FOO);
+ return null;
+ }
+ }
+
+ public static class LongTarget {
+
+ @SuppressWarnings("unused")
+ private final long value;
+
+ public LongTarget(long l) {
+ this.value = bar(l);
+ }
+
+ private static long bar(long l) {
+ return l * LONG_MULTIPLICATOR;
+ }
+ }
+
+ public static class FloatSource extends CallTraceable {
+
+ @SuppressWarnings("unused")
+ public FloatTarget foo(float f) {
+ register(FOO);
+ return null;
+ }
+ }
+
+ public static class FloatTarget {
+
+ @SuppressWarnings("unused")
+ private final float value;
+
+ public FloatTarget(float f) {
+ this.value = bar(f);
+ }
+
+ private static float bar(float f) {
+ return f * FLOAT_MULTIPLICATOR;
+ }
+ }
+
+ public static class DoubleSource extends CallTraceable {
+
+ @SuppressWarnings("unused")
+ public DoubleTarget foo(double d) {
+ register(FOO);
+ return null;
+ }
+ }
+
+ public static class DoubleTarget {
+
+ @SuppressWarnings("unused")
+
+ private final double value;
+
+ public DoubleTarget(double d) {
+ this.value = bar(d);
+ }
+
+ public static double bar(double d) {
+ return d * DOUBLE_MULTIPLICATOR;
+ }
+ }
+
+ public static class VoidSource extends CallTraceable {
+
+ public VoidTarget foo() {
+ register(FOO);
+ return null;
+ }
+ }
+
+ public static class VoidTarget {
+
+ @SuppressWarnings("unused")
+ private final Void value = null;
+ }
+
+ public static class StringSource extends CallTraceable {
+
+ public StringTarget foo(String s) {
+ register(FOO);
+ return null;
+ }
+ }
+
+ public static class StringTarget {
+
+ @SuppressWarnings("unused")
+ private final String value;
+
+ public StringTarget(String s) {
+ this.value = bar(s);
+ }
+
+ public static String bar(String s) {
+ return s + BAR;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationDefaultCallTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationDefaultCallTest.java
new file mode 100644
index 0000000..7cdb953
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationDefaultCallTest.java
@@ -0,0 +1,159 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.bind.annotation.DefaultCall;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import org.hamcrest.CoreMatchers;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.concurrent.Callable;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodDelegationDefaultCallTest {
+
+ private static final String FOO = "foo", QUX = "qux";
+
+ private static final String SINGLE_DEFAULT_METHOD = "net.bytebuddy.test.precompiled.SingleDefaultMethodInterface";
+
+ private static final String CONFLICTING_INTERFACE = "net.bytebuddy.test.precompiled.SingleDefaultMethodConflictingInterface";
+
+ private static final String PREFERRING_INTERCEPTOR = "net.bytebuddy.test.precompiled.SingleDefaultMethodPreferringInterceptor";
+
+ private static final String CONFLICTING_PREFERRING_INTERCEPTOR = "net.bytebuddy.test.precompiled.SingleDefaultMethodConflictingPreferringInterceptor";
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testRunnableDefaultCall() throws Exception {
+ DynamicType.Loaded<?> loaded = new ByteBuddy()
+ .subclass(Object.class)
+ .implement(Class.forName(SINGLE_DEFAULT_METHOD))
+ .intercept(MethodDelegation.to(RunnableClass.class))
+ .make()
+ .load(Class.forName(SINGLE_DEFAULT_METHOD).getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Object instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ Method method = loaded.getLoaded().getMethod(FOO);
+ assertThat(method.invoke(instance), is((Object) QUX));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testCallableDefaultCall() throws Exception {
+ DynamicType.Loaded<?> loaded = new ByteBuddy()
+ .subclass(Object.class)
+ .implement(Class.forName(SINGLE_DEFAULT_METHOD))
+ .intercept(MethodDelegation.to(CallableClass.class))
+ .make()
+ .load(Class.forName(SINGLE_DEFAULT_METHOD).getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Object instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ Method method = loaded.getLoaded().getMethod(FOO);
+ assertThat(method.invoke(instance), is((Object) FOO));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ @JavaVersionRule.Enforce(8)
+ public void testImplicitAmbiguousDefaultCallIsBoundToFirst() throws Exception {
+ new ByteBuddy()
+ .subclass(Object.class)
+ .implement(Class.forName(SINGLE_DEFAULT_METHOD), Class.forName(CONFLICTING_INTERFACE))
+ .defineMethod(FOO, Object.class, Visibility.PUBLIC)
+ .intercept(MethodDelegation.to(CallableClass.class))
+ .make();
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testExplicitDefaultCall() throws Exception {
+ DynamicType.Loaded<?> loaded = new ByteBuddy()
+ .subclass(Object.class)
+ .implement(Class.forName(SINGLE_DEFAULT_METHOD), Class.forName(CONFLICTING_INTERFACE))
+ .intercept(MethodDelegation.to(Class.forName(PREFERRING_INTERCEPTOR)))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Object instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ Method method = loaded.getLoaded().getMethod(FOO);
+ assertThat(method.invoke(instance), is((Object) FOO));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testExplicitDefaultCallToOtherInterface() throws Exception {
+ DynamicType.Loaded<?> loaded = new ByteBuddy()
+ .subclass(Object.class)
+ .implement(Class.forName(SINGLE_DEFAULT_METHOD), Class.forName(CONFLICTING_INTERFACE))
+ .intercept(MethodDelegation.to(Class.forName(CONFLICTING_PREFERRING_INTERCEPTOR)))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Object instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ Method method = loaded.getLoaded().getMethod(FOO);
+ assertThat(method.invoke(instance), is((Object) QUX));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ @JavaVersionRule.Enforce(8)
+ public void testIllegalDefaultCallThrowsException() throws Exception {
+ new ByteBuddy()
+ .subclass(Object.class)
+ .implement(Class.forName(SINGLE_DEFAULT_METHOD))
+ .intercept(MethodDelegation.to(IllegalAnnotation.class))
+ .make();
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testSerializableProxy() throws Exception {
+ DynamicType.Loaded<?> loaded = new ByteBuddy()
+ .subclass(Object.class)
+ .implement(Class.forName(SINGLE_DEFAULT_METHOD))
+ .intercept(MethodDelegation.to(SerializationCheck.class))
+ .make()
+ .load(Class.forName(SINGLE_DEFAULT_METHOD).getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Object instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ Method method = loaded.getLoaded().getMethod(FOO);
+ assertThat(method.invoke(instance), is((Object) FOO));
+ }
+
+ public static class RunnableClass {
+
+ public static Object foo(@DefaultCall Runnable runnable) {
+ assertThat(runnable, CoreMatchers.not(instanceOf(Serializable.class)));
+ runnable.run();
+ return QUX;
+ }
+ }
+
+ public static class CallableClass {
+
+ public static String bar(@DefaultCall Callable<String> callable) throws Exception {
+ assertThat(callable, CoreMatchers.not(instanceOf(Serializable.class)));
+ return callable.call();
+ }
+ }
+
+ public static class IllegalAnnotation {
+
+ public static String bar(@DefaultCall String value) throws Exception {
+ return value;
+ }
+ }
+
+ public static class SerializationCheck {
+
+ public static String bar(@DefaultCall(serializableProxy = true) Callable<String> callable) throws Exception {
+ assertThat(callable, instanceOf(Serializable.class));
+ return callable.call();
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationDefaultMethodTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationDefaultMethodTest.java
new file mode 100644
index 0000000..c381e4b
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationDefaultMethodTest.java
@@ -0,0 +1,114 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.bind.annotation.DefaultMethod;
+import net.bytebuddy.implementation.bind.annotation.This;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodDelegationDefaultMethodTest {
+
+ private static final String FOO = "foo", QUX = "qux";
+
+ private static final String SINGLE_DEFAULT_METHOD = "net.bytebuddy.test.precompiled.SingleDefaultMethodInterface";
+
+ private static final String CONFLICTING_INTERFACE = "net.bytebuddy.test.precompiled.SingleDefaultMethodConflictingInterface";
+
+ private static final String PREFERRING_INTERCEPTOR = "net.bytebuddy.test.precompiled.SingleDefaultMethodPreferringInterceptor";
+
+ private static final String CONFLICTING_PREFERRING_INTERCEPTOR = "net.bytebuddy.test.precompiled.SingleDefaultMethodConflictingPreferringInterceptor";
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testCallableDefaultCall() throws Exception {
+ DynamicType.Loaded<?> loaded = new ByteBuddy()
+ .subclass(Object.class)
+ .implement(Class.forName(SINGLE_DEFAULT_METHOD))
+ .intercept(MethodDelegation.to(SampleClass.class))
+ .make()
+ .load(Class.forName(SINGLE_DEFAULT_METHOD).getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Object instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ Method method = loaded.getLoaded().getMethod(FOO);
+ assertThat(method.invoke(instance), is((Object) FOO));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ @JavaVersionRule.Enforce(8)
+ public void testImplicitAmbiguousDefaultCallIsBoundToFirst() throws Exception {
+ new ByteBuddy()
+ .subclass(Object.class)
+ .implement(Class.forName(SINGLE_DEFAULT_METHOD), Class.forName(CONFLICTING_INTERFACE))
+ .defineMethod(FOO, Object.class, Visibility.PUBLIC)
+ .intercept(MethodDelegation.to(SampleClass.class))
+ .make();
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testExplicitDefaultCall() throws Exception {
+ DynamicType.Loaded<?> loaded = new ByteBuddy()
+ .subclass(Object.class)
+ .implement(Class.forName(SINGLE_DEFAULT_METHOD), Class.forName(CONFLICTING_INTERFACE))
+ .intercept(MethodDelegation.to(Class.forName(PREFERRING_INTERCEPTOR)))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Object instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ Method method = loaded.getLoaded().getMethod(FOO);
+ assertThat(method.invoke(instance), is((Object) FOO));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testExplicitDefaultCallToOtherInterface() throws Exception {
+ DynamicType.Loaded<?> loaded = new ByteBuddy()
+ .subclass(Object.class)
+ .implement(Class.forName(SINGLE_DEFAULT_METHOD), Class.forName(CONFLICTING_INTERFACE))
+ .intercept(MethodDelegation.to(Class.forName(CONFLICTING_PREFERRING_INTERCEPTOR)))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Object instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ Method method = loaded.getLoaded().getMethod(FOO);
+ assertThat(method.invoke(instance), is((Object) QUX));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ @JavaVersionRule.Enforce(8)
+ public void testIllegalDefaultCallThrowsException() throws Exception {
+ new ByteBuddy()
+ .subclass(Object.class)
+ .implement(Class.forName(SINGLE_DEFAULT_METHOD))
+ .intercept(MethodDelegation.to(IllegalAnnotation.class))
+ .make();
+ }
+
+ public static class SampleClass {
+
+ public static String bar(@DefaultMethod Method method, @This Object target) throws Exception {
+ if (!Modifier.isPublic(method.getModifiers())) {
+ throw new AssertionError();
+ }
+ return (String) method.invoke(target);
+ }
+ }
+
+ public static class IllegalAnnotation {
+
+ public static String bar(@DefaultMethod String value) throws Exception {
+ return value;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationDefaultTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationDefaultTest.java
new file mode 100644
index 0000000..446c8c2
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationDefaultTest.java
@@ -0,0 +1,93 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.bind.annotation.Default;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodDelegationDefaultTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ private static final String DEFAULT_INTERFACE = "net.bytebuddy.test.precompiled.DelegationDefaultInterface";
+
+ private static final String DELEGATION_TARGET = "net.bytebuddy.test.precompiled.DelegationDefaultTarget";
+
+ private static final String DELEGATION_TARGET_SERIALIZABLE = "net.bytebuddy.test.precompiled.DelegationDefaultTargetSerializable";
+
+ private static final String DELEGATION_TARGET_EXPLICIT = "net.bytebuddy.test.precompiled.DelegationDefaultTargetExplicit";
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testDefaultInterface() throws Exception {
+ DynamicType.Loaded<?> loaded = new ByteBuddy()
+ .subclass(Object.class)
+ .implement(Class.forName(DEFAULT_INTERFACE))
+ .intercept(MethodDelegation.to(Class.forName(DELEGATION_TARGET)))
+ .make()
+ .load(Class.forName(DEFAULT_INTERFACE).getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Object instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass().getDeclaredMethod(FOO).invoke(instance), is((Object) (FOO + BAR)));
+ }
+
+ @Test(expected = AbstractMethodError.class)
+ @JavaVersionRule.Enforce(8)
+ public void testNoDefaultInterface() throws Exception {
+ DynamicType.Loaded<?> loaded = new ByteBuddy()
+ .subclass(Object.class)
+ .implement(DelegationNoDefaultInterface.class)
+ .intercept(MethodDelegation.to(DelegationNoDefaultInterfaceInterceptor.class))
+ .make()
+ .load(DelegationNoDefaultInterface.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ DelegationNoDefaultInterface instance = (DelegationNoDefaultInterface) loaded.getLoaded().getDeclaredConstructor().newInstance();
+ instance.foo();
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testDefaultInterfaceSerializableProxy() throws Exception {
+ DynamicType.Loaded<?> loaded = new ByteBuddy()
+ .subclass(Object.class)
+ .implement(Class.forName(DEFAULT_INTERFACE))
+ .intercept(MethodDelegation.to(Class.forName(DELEGATION_TARGET_SERIALIZABLE)))
+ .make()
+ .load(Class.forName(DEFAULT_INTERFACE).getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Object instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass().getDeclaredMethod(FOO).invoke(instance), is((Object) (FOO + BAR)));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testDefaultInterfaceExplicitProxyType() throws Exception {
+ DynamicType.Loaded<?> loaded = new ByteBuddy()
+ .subclass(Object.class)
+ .implement(Class.forName(DEFAULT_INTERFACE))
+ .intercept(MethodDelegation.to(Class.forName(DELEGATION_TARGET_EXPLICIT)))
+ .make()
+ .load(Class.forName(DEFAULT_INTERFACE).getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Object instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass().getDeclaredMethod(FOO).invoke(instance), is((Object) (FOO + BAR)));
+ }
+
+ public interface DelegationNoDefaultInterface {
+
+ String foo();
+ }
+
+ public static class DelegationNoDefaultInterfaceInterceptor {
+
+ public static String intercept(@Default DelegationNoDefaultInterface proxy) {
+ return proxy.foo();
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationExceptionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationExceptionTest.java
new file mode 100644
index 0000000..086b1c3
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationExceptionTest.java
@@ -0,0 +1,65 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.test.packaging.PackagePrivateMethod;
+import org.junit.Test;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+
+public class MethodDelegationExceptionTest {
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNoMethod() throws Exception {
+ new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.to(Bar.class))
+ .make();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNoVisibleMethod() throws Exception {
+ new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.to(new PackagePrivateMethod()))
+ .make();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNoCompatibleMethod() throws Exception {
+ new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.to(Qux.class))
+ .make();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testArray() throws Exception {
+ MethodDelegation.to(int[].class);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testPrimitive() throws Exception {
+ MethodDelegation.to(int.class);
+ }
+
+ public static class Foo {
+
+ public void bar() {
+ /* do nothing */
+ }
+ }
+
+ public static class Bar {
+ /* empty */
+ }
+
+ public static class Qux {
+
+ public static void foo(Object o) {
+ /* do nothing */
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationFieldProxyTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationFieldProxyTest.java
new file mode 100644
index 0000000..e9d3172
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationFieldProxyTest.java
@@ -0,0 +1,584 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.bind.annotation.Argument;
+import net.bytebuddy.implementation.bind.annotation.FieldProxy;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.Serializable;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodDelegationFieldProxyTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux";
+
+ @Before
+ public void setUp() throws Exception {
+ ExplicitStatic.foo = FOO;
+ }
+
+ @Test
+ public void testExplicitFieldAccessSimplex() throws Exception {
+ DynamicType.Loaded<Explicit> loaded = new ByteBuddy()
+ .subclass(Explicit.class)
+ .method(isDeclaredBy(Explicit.class))
+ .intercept(MethodDelegation.withDefaultConfiguration().withBinders(FieldProxy.Binder.install(Get.class, Set.class)).to(Swap.class))
+ .make()
+ .load(Explicit.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Explicit explicit = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(explicit.foo, is(FOO));
+ explicit.swap();
+ assertThat(explicit.foo, is(FOO + BAR));
+ }
+
+ @Test
+ public void testExplicitFieldAccessDuplex() throws Exception {
+ DynamicType.Loaded<Explicit> loaded = new ByteBuddy()
+ .subclass(Explicit.class)
+ .method(isDeclaredBy(Explicit.class))
+ .intercept(MethodDelegation.withDefaultConfiguration().withBinders(FieldProxy.Binder.install(GetSet.class)).to(SwapDuplex.class))
+ .make()
+ .load(Explicit.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Explicit explicit = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(explicit.foo, is(FOO));
+ explicit.swap();
+ assertThat(explicit.foo, is(FOO + BAR));
+ }
+
+ @Test
+ public void testExplicitFieldAccessSerializableSimplex() throws Exception {
+ DynamicType.Loaded<Explicit> loaded = new ByteBuddy()
+ .subclass(Explicit.class)
+ .method(isDeclaredBy(Explicit.class))
+ .intercept(MethodDelegation.withDefaultConfiguration().withBinders(FieldProxy.Binder.install(Get.class, Set.class)).to(SwapSerializable.class))
+ .make()
+ .load(Explicit.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Explicit explicit = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(explicit.foo, is(FOO));
+ explicit.swap();
+ assertThat(explicit.foo, is(FOO + BAR));
+ }
+
+ @Test
+ public void testExplicitFieldAccessSerializableDuplex() throws Exception {
+ DynamicType.Loaded<Explicit> loaded = new ByteBuddy()
+ .subclass(Explicit.class)
+ .method(isDeclaredBy(Explicit.class))
+ .intercept(MethodDelegation.withDefaultConfiguration().withBinders(FieldProxy.Binder.install(GetSet.class)).to(SwapSerializableDuplex.class))
+ .make()
+ .load(Explicit.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Explicit explicit = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(explicit.foo, is(FOO));
+ explicit.swap();
+ assertThat(explicit.foo, is(FOO + BAR));
+ }
+
+ @Test
+ public void testExplicitFieldAccessStatic() throws Exception {
+ DynamicType.Loaded<ExplicitStatic> loaded = new ByteBuddy()
+ .subclass(ExplicitStatic.class)
+ .method(isDeclaredBy(ExplicitStatic.class))
+ .intercept(MethodDelegation.withDefaultConfiguration().withBinders(FieldProxy.Binder.install(Get.class, Set.class)).to(Swap.class))
+ .make()
+ .load(ExplicitStatic.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ ExplicitStatic explicit = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(ExplicitStatic.foo, is(FOO));
+ explicit.swap();
+ assertThat(ExplicitStatic.foo, is(FOO + BAR));
+ }
+
+ @Test
+ public void testExplicitFieldAccessStaticDuplex() throws Exception {
+ DynamicType.Loaded<ExplicitStatic> loaded = new ByteBuddy()
+ .subclass(ExplicitStatic.class)
+ .method(isDeclaredBy(ExplicitStatic.class))
+ .intercept(MethodDelegation.withDefaultConfiguration().withBinders(FieldProxy.Binder.install(GetSet.class)).to(SwapDuplex.class))
+ .make()
+ .load(ExplicitStatic.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ ExplicitStatic explicit = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(ExplicitStatic.foo, is(FOO));
+ explicit.swap();
+ assertThat(ExplicitStatic.foo, is(FOO + BAR));
+ }
+
+ @Test
+ public void testImplicitFieldGetterAccess() throws Exception {
+ DynamicType.Loaded<ImplicitGetter> loaded = new ByteBuddy()
+ .subclass(ImplicitGetter.class)
+ .method(isDeclaredBy(ImplicitGetter.class))
+ .intercept(MethodDelegation.withDefaultConfiguration().withBinders(FieldProxy.Binder.install(Get.class, Set.class)).to(GetInterceptor.class))
+ .make()
+ .load(ImplicitGetter.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ ImplicitGetter implicitGetter = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(implicitGetter.foo, is(FOO));
+ assertThat(implicitGetter.getFoo(), is(FOO + BAR));
+ assertThat(implicitGetter.foo, is(FOO + BAR));
+ }
+
+ @Test
+ public void testImplicitFieldSetterAccess() throws Exception {
+ DynamicType.Loaded<ImplicitSetter> loaded = new ByteBuddy()
+ .subclass(ImplicitSetter.class)
+ .method(isDeclaredBy(ImplicitSetter.class))
+ .intercept(MethodDelegation.withDefaultConfiguration().withBinders(FieldProxy.Binder.install(Get.class, Set.class)).to(SetInterceptor.class))
+ .make()
+ .load(ImplicitSetter.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ ImplicitSetter implicitSetter = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(implicitSetter.foo, is(FOO));
+ implicitSetter.setFoo(BAR);
+ assertThat(implicitSetter.foo, is(FOO + BAR));
+ }
+
+ @Test
+ public void testImplicitFieldGetterAccessDuplex() throws Exception {
+ DynamicType.Loaded<ImplicitGetter> loaded = new ByteBuddy()
+ .subclass(ImplicitGetter.class)
+ .method(isDeclaredBy(ImplicitGetter.class))
+ .intercept(MethodDelegation.withDefaultConfiguration().withBinders(FieldProxy.Binder.install(GetSet.class)).to(GetInterceptorDuplex.class))
+ .make()
+ .load(ImplicitGetter.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ ImplicitGetter implicitGetter = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(implicitGetter.foo, is(FOO));
+ assertThat(implicitGetter.getFoo(), is(FOO + BAR));
+ assertThat(implicitGetter.foo, is(FOO + BAR));
+ }
+
+ @Test
+ public void testImplicitFieldSetterAccessDuplex() throws Exception {
+ DynamicType.Loaded<ImplicitSetter> loaded = new ByteBuddy()
+ .subclass(ImplicitSetter.class)
+ .method(isDeclaredBy(ImplicitSetter.class))
+ .intercept(MethodDelegation.withDefaultConfiguration().withBinders(FieldProxy.Binder.install(GetSet.class)).to(SetInterceptorDuplex.class))
+ .make()
+ .load(ImplicitSetter.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ ImplicitSetter implicitSetter = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(implicitSetter.foo, is(FOO));
+ implicitSetter.setFoo(BAR);
+ assertThat(implicitSetter.foo, is(FOO + BAR));
+ }
+
+ @Test
+ public void testExplicitFieldAccessImplicitTarget() throws Exception {
+ DynamicType.Loaded<ExplicitInherited> loaded = new ByteBuddy()
+ .subclass(ExplicitInherited.class)
+ .method(isDeclaredBy(ExplicitInherited.class))
+ .intercept(MethodDelegation.withDefaultConfiguration().withBinders(FieldProxy.Binder.install(Get.class, Set.class)).to(Swap.class))
+ .make()
+ .load(ExplicitInherited.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ ExplicitInherited explicitInherited = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(((Explicit) explicitInherited).foo, is(FOO));
+ assertThat(explicitInherited.foo, is(QUX));
+ explicitInherited.swap();
+ assertThat(((Explicit) explicitInherited).foo, is(FOO));
+ assertThat(explicitInherited.foo, is(QUX + BAR));
+ }
+
+ @Test
+ public void testExplicitFieldAccessExplicitTarget() throws Exception {
+ DynamicType.Loaded<ExplicitInherited> loaded = new ByteBuddy()
+ .subclass(ExplicitInherited.class)
+ .method(isDeclaredBy(ExplicitInherited.class))
+ .intercept(MethodDelegation.withDefaultConfiguration().withBinders(FieldProxy.Binder.install(Get.class, Set.class)).to(SwapInherited.class))
+ .make()
+ .load(ExplicitInherited.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ ExplicitInherited explicitInherited = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(((Explicit) explicitInherited).foo, is(FOO));
+ assertThat(explicitInherited.foo, is(QUX));
+ explicitInherited.swap();
+ assertThat(((Explicit) explicitInherited).foo, is(FOO + BAR));
+ assertThat(explicitInherited.foo, is(QUX));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testFinalFieldSimplex() throws Exception {
+ new ByteBuddy()
+ .subclass(FinalField.class)
+ .method(isDeclaredBy(FinalField.class))
+ .intercept(MethodDelegation.withDefaultConfiguration().withBinders(FieldProxy.Binder.install(Get.class, Set.class)).to(Swap.class))
+ .make();
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testFinalFieldDuplex() throws Exception {
+ DynamicType.Loaded<FinalField> loaded = new ByteBuddy()
+ .subclass(FinalField.class)
+ .method(isDeclaredBy(FinalField.class))
+ .intercept(MethodDelegation.withDefaultConfiguration().withBinders(FieldProxy.Binder.install(GetSet.class)).to(SwapDuplex.class))
+ .make()
+ .load(FinalField.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ loaded.getLoaded().getDeclaredConstructor().newInstance().method();
+ }
+
+ @Test(expected = ClassCastException.class)
+ public void testIncompatibleGetterTypeThrowsException() throws Exception {
+ DynamicType.Loaded<Explicit> loaded = new ByteBuddy()
+ .subclass(Explicit.class)
+ .method(isDeclaredBy(Explicit.class))
+ .intercept(MethodDelegation.withDefaultConfiguration().withBinders(FieldProxy.Binder.install(Get.class, Set.class)).to(GetterIncompatible.class))
+ .make()
+ .load(Explicit.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Explicit explicit = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ explicit.swap();
+ }
+
+ @Test(expected = ClassCastException.class)
+ public void testIncompatibleSetterTypeThrowsException() throws Exception {
+ DynamicType.Loaded<Explicit> loaded = new ByteBuddy()
+ .subclass(Explicit.class)
+ .method(isDeclaredBy(Explicit.class))
+ .intercept(MethodDelegation.withDefaultConfiguration().withBinders(FieldProxy.Binder.install(Get.class, Set.class)).to(SetterIncompatible.class))
+ .make()
+ .load(Explicit.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Explicit explicit = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ explicit.swap();
+ }
+
+ @Test(expected = ClassCastException.class)
+ public void testIncompatibleTypeThrowsExceptionGetDuplex() throws Exception {
+ DynamicType.Loaded<Explicit> loaded = new ByteBuddy()
+ .subclass(Explicit.class)
+ .method(isDeclaredBy(Explicit.class))
+ .intercept(MethodDelegation.withDefaultConfiguration().withBinders(FieldProxy.Binder.install(GetSet.class)).to(GetterIncompatibleDuplex.class))
+ .make()
+ .load(Explicit.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Explicit explicit = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ explicit.swap();
+ }
+
+ @Test(expected = ClassCastException.class)
+ public void testIncompatibleTypeThrowsExceptionSetDuplex() throws Exception {
+ DynamicType.Loaded<Explicit> loaded = new ByteBuddy()
+ .subclass(Explicit.class)
+ .method(isDeclaredBy(Explicit.class))
+ .intercept(MethodDelegation.withDefaultConfiguration().withBinders(FieldProxy.Binder.install(GetSet.class)).to(SetterIncompatibleDuplex.class))
+ .make()
+ .load(Explicit.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Explicit explicit = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ explicit.swap();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testGetterTypeDoesNotDeclareCorrectMethodThrowsException() throws Exception {
+ FieldProxy.Binder.install(Serializable.class, Set.class);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSetterTypeDoesNotDeclareCorrectMethodThrowsException() throws Exception {
+ FieldProxy.Binder.install(Get.class, Serializable.class);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testGetterTypeDoesInheritFromOtherTypeThrowsException() throws Exception {
+ FieldProxy.Binder.install(GetInherited.class, Set.class);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSetterTypeDoesInheritFromOtherTypeThrowsException() throws Exception {
+ FieldProxy.Binder.install(Get.class, SetInherited.class);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testGetterTypeNonPublicThrowsException() throws Exception {
+ FieldProxy.Binder.install(GetPrivate.class, Set.class);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSetterTypeNonPublicThrowsException() throws Exception {
+ FieldProxy.Binder.install(Get.class, SetPrivate.class);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSetterTypeIncorrectSignatureThrowsException() throws Exception {
+ FieldProxy.Binder.install(GetIncorrect.class, Set.class);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testGetterTypeIncorrectSignatureThrowsException() throws Exception {
+ FieldProxy.Binder.install(Get.class, SetIncorrect.class);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testGetterTypeNotInterfaceThrowsException() throws Exception {
+ FieldProxy.Binder.install(Object.class, Set.class);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSetterTypeNotInterfaceThrowsException() throws Exception {
+ FieldProxy.Binder.install(Get.class, Object.class);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testDuplexTooManyMethodsThrowsException() throws Exception {
+ FieldProxy.Binder.install(GetSetTooManyMethods.class);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testDuplexNonPublicThrowsException() throws Exception {
+ FieldProxy.Binder.install(GetSetNonPublic.class);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testDuplexInheritedThrowsException() throws Exception {
+ FieldProxy.Binder.install(GetSetInherited.class);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSetterTypeIncorrectSignatureDuplexThrowsException() throws Exception {
+ FieldProxy.Binder.install(GetSetSetIncorrect.class);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testGetterTypeIncorrectSignatureDuplexThrowsException() throws Exception {
+ FieldProxy.Binder.install(GetSetGetIncorrect.class);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testTypeNotInterfaceDuplexThrowsException() throws Exception {
+ FieldProxy.Binder.install(Object.class);
+ }
+
+ public interface Get<T> {
+
+ T get();
+ }
+
+ public interface Set<T> {
+
+ void set(T value);
+ }
+
+ public interface GetInherited<T> extends Get<T> {
+ /* empty */
+ }
+
+ public interface SetInherited<T> extends Set<T> {
+ /* empty */
+ }
+
+ private interface GetPrivate<T> {
+
+ T get();
+ }
+
+ private interface SetPrivate<T> {
+
+ void set(T value);
+ }
+
+ public interface GetIncorrect<T> {
+
+ T get(Object value);
+ }
+
+ public interface SetIncorrect<T> {
+
+ Object set(T value);
+ }
+
+ public interface GetSet<T> {
+
+ T get();
+
+ void set(T value);
+ }
+
+ public interface GetSetGetIncorrect<T> {
+
+ String get();
+
+ void set(T value);
+ }
+
+ public interface GetSetSetIncorrect<T> {
+
+ T get();
+
+ void set(String value);
+ }
+
+ interface GetSetNonPublic<T> {
+
+ T get();
+
+ void set(T value);
+ }
+
+ public interface GetSetTooManyMethods<T> {
+
+ T get();
+
+ void set(String value);
+
+ void set(T value);
+ }
+
+ public interface GetSetInherited<T> extends GetSet<T> {
+ /* empty */
+ }
+
+ public static class Swap {
+
+ public static void swap(@FieldProxy(FOO) Get<String> getter, @FieldProxy(FOO) Set<String> setter) {
+ assertThat(getter, not(instanceOf(Serializable.class)));
+ assertThat(setter, not(instanceOf(Serializable.class)));
+ setter.set(getter.get() + BAR);
+ }
+ }
+
+ public static class SwapDuplex {
+
+ public static void swap(@FieldProxy(FOO) GetSet<String> accessor) {
+ assertThat(accessor, not(instanceOf(Serializable.class)));
+ accessor.set(accessor.get() + BAR);
+ }
+ }
+
+ public static class Explicit {
+
+ protected String foo = FOO;
+
+ public void swap() {
+ /* do nothing */
+ }
+ }
+
+ public static class ExplicitInherited extends Explicit {
+
+ protected String foo = QUX;
+
+ @Override
+ public void swap() {
+ /* do nothing */
+ }
+ }
+
+ public static class SwapInherited {
+
+ public static void swap(@FieldProxy(value = FOO, declaringType = Explicit.class) Get<String> getter,
+ @FieldProxy(value = FOO, declaringType = Explicit.class) Set<String> setter) {
+ assertThat(getter, not(instanceOf(Serializable.class)));
+ assertThat(setter, not(instanceOf(Serializable.class)));
+ setter.set(getter.get() + BAR);
+ }
+ }
+
+ public static class ExplicitStatic {
+
+ protected static String foo;
+
+ public void swap() {
+ /* do nothing */
+ }
+ }
+
+ public static class ImplicitGetter {
+
+ protected String foo = FOO;
+
+ public String getFoo() {
+ return null;
+ }
+
+
+ }
+
+ public static class GetInterceptor {
+
+ public static String get(@FieldProxy Get<String> getter, @FieldProxy Set<String> setter) {
+ setter.set(getter.get() + BAR);
+ return getter.get();
+ }
+ }
+
+ public static class SetInterceptor {
+
+ public static void set(@Argument(0) String value, @FieldProxy Get<String> getter, @FieldProxy Set<String> setter) {
+ setter.set(getter.get() + value);
+ }
+ }
+
+ public static class GetInterceptorDuplex {
+
+ public static String get(@FieldProxy GetSet<String> accessor) {
+ accessor.set(accessor.get() + BAR);
+ return accessor.get();
+ }
+ }
+
+ public static class SetInterceptorDuplex {
+
+ public static void set(@Argument(0) String value, @FieldProxy GetSet<String> accessor) {
+ accessor.set(accessor.get() + value);
+ }
+ }
+
+ public static class ImplicitSetter {
+
+ protected String foo = FOO;
+
+ public void setFoo(String value) {
+ /* do nothing */
+ }
+ }
+
+ public static class FinalField {
+
+ protected final String foo = FOO;
+
+ public void method() {
+ /* do nothing */
+ }
+ }
+
+ public static class SwapSerializable {
+
+ public static void swap(@FieldProxy(value = FOO, serializableProxy = true) Get<String> getter,
+ @FieldProxy(value = FOO, serializableProxy = true) Set<String> setter) {
+ assertThat(getter, instanceOf(Serializable.class));
+ assertThat(setter, instanceOf(Serializable.class));
+ setter.set(getter.get() + BAR);
+ }
+ }
+
+ public static class SwapSerializableDuplex {
+
+ public static void swap(@FieldProxy(value = FOO, serializableProxy = true) GetSet<String> accessor) {
+ assertThat(accessor, instanceOf(Serializable.class));
+ accessor.set(accessor.get() + BAR);
+ }
+ }
+
+ public static class GetterIncompatible {
+
+ public static void swap(@FieldProxy(FOO) Get<Integer> getter, @FieldProxy(FOO) Set<String> setter) {
+ Integer value = getter.get();
+ }
+ }
+
+ public static class SetterIncompatible {
+
+ public static void swap(@FieldProxy(FOO) Get<String> getter, @FieldProxy(FOO) Set<Integer> setter) {
+ setter.set(0);
+ }
+ }
+
+ public static class GetterIncompatibleDuplex {
+
+ public static void swap(@FieldProxy(FOO) GetSet<Integer> accessor) {
+ Integer value = accessor.get();
+ }
+ }
+
+ public static class SetterIncompatibleDuplex {
+
+ public static void swap(@FieldProxy(FOO) GetSet<Integer> accessor) {
+ accessor.set(0);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationFieldValueTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationFieldValueTest.java
new file mode 100644
index 0000000..498dfe7
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationFieldValueTest.java
@@ -0,0 +1,218 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.bind.annotation.FieldValue;
+import net.bytebuddy.implementation.bind.annotation.RuntimeType;
+import org.junit.Test;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodDelegationFieldValueTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ @Test
+ public void testLegalFieldAccess() throws Exception {
+ DynamicType.Loaded<SimpleField> loaded = new ByteBuddy()
+ .subclass(SimpleField.class)
+ .method(isDeclaredBy(SimpleField.class))
+ .intercept(MethodDelegation.to(SimpleInterceptor.class))
+ .make()
+ .load(SimpleField.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ SimpleField instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ instance.foo = FOO;
+ assertThat(instance.foo(), is((Object) FOO));
+ instance.foo = BAR;
+ assertThat(instance.foo(), is((Object) BAR));
+ }
+
+ @Test
+ public void testLegalFieldAccessStatic() throws Exception {
+ DynamicType.Loaded<SimpleStaticField> loaded = new ByteBuddy()
+ .subclass(SimpleStaticField.class)
+ .method(isDeclaredBy(SimpleStaticField.class))
+ .intercept(MethodDelegation.to(SimpleInterceptor.class))
+ .make()
+ .load(SimpleStaticField.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ SimpleStaticField instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ SimpleStaticField.foo = FOO;
+ assertThat(instance.foo(), is((Object) FOO));
+ SimpleStaticField.foo = BAR;
+ assertThat(instance.foo(), is((Object) BAR));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNonAssignableFieldAccess() throws Exception {
+ new ByteBuddy()
+ .subclass(SimpleField.class)
+ .method(isDeclaredBy(SimpleField.class))
+ .intercept(MethodDelegation.to(NonAssignableInterceptor.class))
+ .make();
+ }
+
+ @Test
+ public void testLegalFieldAccessDynamicTyping() throws Exception {
+ DynamicType.Loaded<SimpleStaticField> loaded = new ByteBuddy()
+ .subclass(SimpleStaticField.class)
+ .method(isDeclaredBy(SimpleStaticField.class))
+ .intercept(MethodDelegation.to(DynamicInterceptor.class))
+ .make()
+ .load(SimpleStaticField.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ SimpleStaticField instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ SimpleStaticField.foo = FOO;
+ assertThat(instance.foo(), is((Object) FOO));
+ SimpleStaticField.foo = BAR;
+ assertThat(instance.foo(), is((Object) BAR));
+ }
+
+ @Test
+ public void testExtendedFieldMostSpecific() throws Exception {
+ DynamicType.Loaded<ExtendedField> loaded = new ByteBuddy()
+ .subclass(ExtendedField.class)
+ .method(isDeclaredBy(ExtendedField.class))
+ .intercept(MethodDelegation.to(SimpleInterceptor.class))
+ .make()
+ .load(ExtendedField.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ ExtendedField instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ instance.foo = FOO;
+ assertThat(instance.foo(), is((Object) FOO));
+ instance.foo = BAR;
+ assertThat(instance.foo(), is((Object) BAR));
+ }
+
+ @Test
+ public void testExtendedFieldSkipsNonVisible() throws Exception {
+ DynamicType.Loaded<ExtendedPrivateField> loaded = new ByteBuddy()
+ .subclass(ExtendedPrivateField.class)
+ .method(isDeclaredBy(ExtendedPrivateField.class))
+ .intercept(MethodDelegation.to(SimpleInterceptor.class))
+ .make()
+ .load(ExtendedPrivateField.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ SimpleField instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ instance.foo = FOO;
+ assertThat(instance.foo(), is((Object) FOO));
+ instance.foo = BAR;
+ assertThat(instance.foo(), is((Object) BAR));
+ }
+
+ @Test
+ public void testExtendedFieldExplicitType() throws Exception {
+ DynamicType.Loaded<ExtendedField> loaded = new ByteBuddy()
+ .subclass(ExtendedField.class)
+ .method(isDeclaredBy(ExtendedField.class))
+ .intercept(MethodDelegation.to(ExplicitInterceptor.class))
+ .make()
+ .load(ExtendedField.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ SimpleField instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ instance.foo = FOO;
+ assertThat(instance.foo(), is((Object) FOO));
+ instance.foo = BAR;
+ assertThat(instance.foo(), is((Object) BAR));
+ }
+
+ @Test
+ public void testAccessor() throws Exception {
+ DynamicType.Loaded<SimpleFieldAccessor> loaded = new ByteBuddy()
+ .subclass(SimpleFieldAccessor.class)
+ .method(isDeclaredBy(SimpleFieldAccessor.class))
+ .intercept(MethodDelegation.to(SimpleAccessorInterceptor.class))
+ .make()
+ .load(SimpleFieldAccessor.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ SimpleFieldAccessor instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ instance.foo = FOO;
+ assertThat(instance.getFoo(), is((Object) FOO));
+ instance.foo = BAR;
+ instance.setFoo(FOO);
+ assertThat(instance.foo, is((Object) BAR));
+ }
+
+ public static class SimpleField {
+
+ public Object foo;
+
+ public Object foo() {
+ return null;
+ }
+ }
+
+ public static class SimpleStaticField {
+
+ public static Object foo;
+
+ public Object foo() {
+ return null;
+ }
+ }
+
+ public static class SimpleInterceptor {
+
+ public static Object intercept(@FieldValue(FOO) Object value) {
+ return value;
+ }
+ }
+
+ public static class NonAssignableInterceptor {
+
+ public static Object intercept(@FieldValue(FOO) String value) {
+ return value;
+ }
+ }
+
+ public static class DynamicInterceptor {
+
+ public static Object intercept(@RuntimeType @FieldValue(FOO) String value) {
+ return value;
+ }
+ }
+
+ public static class ExtendedField extends SimpleField {
+
+ public Object foo;
+
+ @Override
+ public Object foo() {
+ return null;
+ }
+ }
+
+ public static class ExtendedPrivateField extends SimpleField {
+
+ private Object foo;
+
+ @Override
+ public Object foo() {
+ return null;
+ }
+ }
+
+ public static class ExplicitInterceptor {
+
+ public static Object intercept(@FieldValue(value = FOO, declaringType = SimpleField.class) Object value) {
+ return value;
+ }
+ }
+
+ public static class SimpleFieldAccessor {
+
+ public Object foo;
+
+ public Object getFoo() {
+ return foo;
+ }
+
+ public void setFoo(Object foo) {
+ this.foo = foo;
+ }
+ }
+
+ public static class SimpleAccessorInterceptor {
+
+ public static Object intercept(@FieldValue Object value) {
+ return value;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationIgnoreForBindingTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationIgnoreForBindingTest.java
new file mode 100644
index 0000000..222871b
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationIgnoreForBindingTest.java
@@ -0,0 +1,47 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.bind.annotation.IgnoreForBinding;
+import org.junit.Test;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodDelegationIgnoreForBindingTest {
+
+ private static final String FOO = "FOO", BAR = "bar";
+
+ @Test
+ public void testIgnoreForBinding() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.to(Bar.class))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.foo(), is(FOO));
+ }
+
+ public static class Foo {
+
+ public String foo() {
+ return null;
+ }
+ }
+
+ public static class Bar {
+
+ public static String bar() {
+ return FOO;
+ }
+
+ @IgnoreForBinding
+ public static String qux() {
+ return BAR;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationMorphTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationMorphTest.java
new file mode 100644
index 0000000..a7fcaf3
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationMorphTest.java
@@ -0,0 +1,263 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.bind.annotation.Morph;
+import net.bytebuddy.test.utility.CallTraceable;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import org.hamcrest.CoreMatchers;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+
+import java.io.Serializable;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodDelegationMorphTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux";
+
+ private static final int BAZ = 42;
+
+ private static final String DEFAULT_INTERFACE = "net.bytebuddy.test.precompiled.MorphDefaultInterface";
+
+ private static final String DEFAULT_INTERFACE_TARGET_EXPLICIT = "net.bytebuddy.test.precompiled.MorphDefaultDelegationTargetExplicit";
+
+ private static final String DEFAULT_INTERFACE_TARGET_IMPLICIT = "net.bytebuddy.test.precompiled.MorphDefaultDelegationTargetImplicit";
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ @Test
+ public void testMorph() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.withDefaultConfiguration()
+ .withBinders(Morph.Binder.install(Morphing.class))
+ .to(new SimpleMorph(QUX)))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.foo(FOO), is(QUX + BAR));
+ }
+
+ @Test
+ public void testMorphVoid() throws Exception {
+ SimpleMorph simpleMorph = new SimpleMorph(QUX);
+ DynamicType.Loaded<Bar> loaded = new ByteBuddy()
+ .subclass(Bar.class)
+ .method(isDeclaredBy(Bar.class))
+ .intercept(MethodDelegation.withDefaultConfiguration()
+ .withBinders(Morph.Binder.install(Morphing.class))
+ .to(simpleMorph))
+ .make()
+ .load(Bar.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Bar instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ instance.foo();
+ instance.assertOnlyCall(FOO);
+ simpleMorph.assertOnlyCall(BAR);
+ }
+
+ @Test
+ public void testMorphPrimitive() throws Exception {
+ DynamicType.Loaded<Qux> loaded = new ByteBuddy()
+ .subclass(Qux.class)
+ .method(isDeclaredBy(Qux.class))
+ .intercept(MethodDelegation.withDefaultConfiguration()
+ .withBinders(Morph.Binder.install(Morphing.class))
+ .to(new PrimitiveMorph(BAZ)))
+ .make()
+ .load(Qux.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Qux instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.foo(0), is(BAZ * 2L));
+ }
+
+ @Test
+ public void testMorphSerializable() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.withDefaultConfiguration()
+ .withBinders(Morph.Binder.install(Morphing.class))
+ .to(SimpleMorphSerializable.class))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.foo(FOO), is(QUX + BAR));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMorphIllegal() throws Exception {
+ new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.withDefaultConfiguration()
+ .withBinders(Morph.Binder.install(Morphing.class))
+ .to(SimpleMorphIllegal.class))
+ .make();
+ }
+
+ @Test(expected = ClassCastException.class)
+ public void testMorphToIncompatibleTypeThrowsException() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.withDefaultConfiguration()
+ .withBinders(Morph.Binder.install(Morphing.class))
+ .to(new SimpleMorph(new Object())))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ instance.foo(QUX);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testMorphTypeDoesNotDeclareCorrectMethodThrowsException() throws Exception {
+ Morph.Binder.install(Serializable.class);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testMorphTypeDoesInheritFromOtherTypeThrowsException() throws Exception {
+ Morph.Binder.install(InheritingMorphingType.class);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testMorphTypeIsNotPublicThrowsException() throws Exception {
+ Morph.Binder.install(PackagePrivateMorphing.class);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testPipeTypeDoesNotDeclareCorrectMethodSignatureThrowsException() throws Exception {
+ Morph.Binder.install(WrongParametersMorphing.class);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testPipeTypeIsNotInterfaceThrowsException() throws Exception {
+ MethodDelegation.withDefaultConfiguration().withBinders(Morph.Binder.install(Object.class)).to(new SimpleMorph(QUX));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testDefaultMethodExplicit() throws Exception {
+ DynamicType.Loaded<Object> loaded = new ByteBuddy()
+ .subclass(Object.class)
+ .implement(Class.forName(DEFAULT_INTERFACE))
+ .intercept(MethodDelegation.withDefaultConfiguration()
+ .withBinders(Morph.Binder.install(Morphing.class))
+ .to(Class.forName(DEFAULT_INTERFACE_TARGET_EXPLICIT)))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Object instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass().getDeclaredMethod(FOO, String.class)
+ .invoke(instance, QUX), is((Object) (FOO + BAR)));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testDefaultMethodImplicit() throws Exception {
+ DynamicType.Loaded<Object> loaded = new ByteBuddy()
+ .subclass(Object.class)
+ .implement(Class.forName(DEFAULT_INTERFACE))
+ .intercept(MethodDelegation.withDefaultConfiguration()
+ .withBinders(Morph.Binder.install(Morphing.class))
+ .to(Class.forName(DEFAULT_INTERFACE_TARGET_IMPLICIT)))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Object instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass().getDeclaredMethod(FOO, String.class)
+ .invoke(instance, QUX), is((Object) (FOO + BAR)));
+ }
+
+ public interface Morphing<T> {
+
+ T morph(Object... arguments);
+ }
+
+ public interface InheritingMorphingType<T> extends Morphing<T> {
+ /* empty */
+ }
+
+ @SuppressWarnings("unused")
+ private interface PackagePrivateMorphing<T> {
+
+ T morph(Object... arguments);
+ }
+
+ @SuppressWarnings("unused")
+ private interface WrongParametersMorphing<T> {
+
+ T morph(Object arguments);
+ }
+
+ public static class Foo {
+
+ public String foo(String foo) {
+ return foo + BAR;
+ }
+ }
+
+ public static class Bar extends CallTraceable {
+
+ public void foo() {
+ register(FOO);
+ }
+ }
+
+ public static class Qux {
+
+ public long foo(int argument) {
+ return argument * 2L;
+ }
+ }
+
+ public static class SimpleMorph extends CallTraceable {
+
+ private final Object[] arguments;
+
+ public SimpleMorph(Object... arguments) {
+ this.arguments = arguments;
+ }
+
+ public String intercept(@Morph Morphing<String> morphing) {
+ register(BAR);
+ assertThat(morphing, CoreMatchers.not(instanceOf(Serializable.class)));
+ return morphing.morph(arguments);
+ }
+ }
+
+ public static class PrimitiveMorph {
+
+ private final int argument;
+
+ public PrimitiveMorph(int argument) {
+ this.argument = argument;
+ }
+
+ public Long intercept(@Morph Morphing<Long> morphing) {
+ assertThat(morphing, CoreMatchers.not(instanceOf(Serializable.class)));
+ return morphing.morph(argument);
+ }
+ }
+
+ public static class SimpleMorphSerializable {
+
+ public static String intercept(@Morph(serializableProxy = true) Morphing<String> morphing) {
+ assertThat(morphing, instanceOf(Serializable.class));
+ return morphing.morph(QUX);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class SimpleMorphIllegal {
+
+ public static String intercept(@Morph Void morphing) {
+ return null;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationOriginTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationOriginTest.java
new file mode 100644
index 0000000..847542d
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationOriginTest.java
@@ -0,0 +1,304 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.bind.annotation.Origin;
+import net.bytebuddy.matcher.ElementMatchers;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodDelegationOriginTest {
+
+ private static final String FOO = "foo", TYPE = "TYPE";
+
+ private static final String ORIGIN_METHOD_HANDLE = "net.bytebuddy.test.precompiled.OriginMethodHandle";
+
+ private static final String ORIGIN_METHOD_TYPE = "net.bytebuddy.test.precompiled.OriginMethodType";
+
+ private static final String ORIGIN_EXECUTABLE = "net.bytebuddy.test.precompiled.OriginExecutable";
+
+ private static final String ORIGIN_EXECUTABLE_CACHED = "net.bytebuddy.test.precompiled.OriginExecutableWithCache";
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ @Test
+ public void testOriginClass() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.to(OriginClass.class))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.foo(), instanceOf(Class.class));
+ assertThat(instance.foo(), is((Object) Foo.class));
+ }
+
+ @Test
+ public void testOriginMethodWithoutCache() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.to(OriginMethod.class))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ Object method = instance.foo();
+ assertThat(method, instanceOf(Method.class));
+ assertThat(method, is((Object) Foo.class.getDeclaredMethod(FOO)));
+ assertThat(method, not(sameInstance(instance.foo())));
+ }
+
+ @Test
+ public void testOriginMethodWithCache() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.to(OriginMethodWithCache.class))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ Object method = instance.foo();
+ assertThat(method, instanceOf(Method.class));
+ assertThat(method, is((Object) Foo.class.getDeclaredMethod(FOO)));
+ assertThat(method, sameInstance(instance.foo()));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testOriginConstructorWithoutCache() throws Exception {
+ OriginConstructor originConstructor = new OriginConstructor();
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .constructor(ElementMatchers.any())
+ .intercept(SuperMethodCall.INSTANCE.andThen(MethodDelegation.to(originConstructor)))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(originConstructor.constructor, instanceOf(Constructor.class));
+ assertThat(originConstructor.constructor, is((Constructor) loaded.getLoaded().getDeclaredConstructor()));
+ Constructor<?> previous = originConstructor.constructor;
+ loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(originConstructor.constructor, instanceOf(Constructor.class));
+ assertThat(originConstructor.constructor, is((Constructor) loaded.getLoaded().getDeclaredConstructor()));
+ assertThat(originConstructor.constructor, not(sameInstance((Constructor) previous)));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testOriginConstructorWithCache() throws Exception {
+ OriginConstructorWithCache originConstructor = new OriginConstructorWithCache();
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .constructor(ElementMatchers.any())
+ .intercept(SuperMethodCall.INSTANCE.andThen(MethodDelegation.to(originConstructor)))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(originConstructor.constructor, instanceOf(Constructor.class));
+ assertThat(originConstructor.constructor, is((Constructor) loaded.getLoaded().getDeclaredConstructor()));
+ Constructor<?> previous = originConstructor.constructor;
+ loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(originConstructor.constructor, instanceOf(Constructor.class));
+ assertThat(originConstructor.constructor, sameInstance((Constructor) previous));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testOriginExecutableOnMethodWithoutCache() throws Exception {
+ Object origin = Class.forName(ORIGIN_EXECUTABLE).getDeclaredConstructor().newInstance();
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.to(origin))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ Object method = instance.foo();
+ assertThat(method, instanceOf(Method.class));
+ assertThat(method, is((Object) Foo.class.getDeclaredMethod(FOO)));
+ assertThat(method, not(sameInstance(instance.foo())));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testOriginExecutableOnMethodWithCache() throws Exception {
+ Object origin = Class.forName(ORIGIN_EXECUTABLE_CACHED).getDeclaredConstructor().newInstance();
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.to(origin))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ Object method = instance.foo();
+ assertThat(method, instanceOf(Method.class));
+ assertThat(method, is((Object) Foo.class.getDeclaredMethod(FOO)));
+ assertThat(method, sameInstance(instance.foo()));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ @JavaVersionRule.Enforce(8)
+ public void testOriginExecutableConstructorWithoutCache() throws Exception {
+ Object originConstructor = Class.forName(ORIGIN_EXECUTABLE).getDeclaredConstructor().newInstance();
+ Field constructor = Class.forName(ORIGIN_EXECUTABLE).getDeclaredField("executable");
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .constructor(ElementMatchers.any())
+ .intercept(SuperMethodCall.INSTANCE.andThen(MethodDelegation.to(originConstructor)))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(constructor.get(originConstructor), instanceOf(Constructor.class));
+ assertThat(constructor.get(originConstructor), is((Object) loaded.getLoaded().getDeclaredConstructor()));
+ Object previous = constructor.get(originConstructor);
+ loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(constructor.get(originConstructor), instanceOf(Constructor.class));
+ assertThat(constructor.get(originConstructor), is((Object) loaded.getLoaded().getDeclaredConstructor()));
+ assertThat(constructor.get(originConstructor), not(sameInstance(previous)));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ @JavaVersionRule.Enforce(8)
+ public void testOriginExecutableConstructorWithCache() throws Exception {
+ Object originConstructor = Class.forName(ORIGIN_EXECUTABLE_CACHED).getDeclaredConstructor().newInstance();
+ Field constructor = Class.forName(ORIGIN_EXECUTABLE_CACHED).getDeclaredField("executable");
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .constructor(ElementMatchers.any())
+ .intercept(SuperMethodCall.INSTANCE.andThen(MethodDelegation.to(originConstructor)))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(constructor.get(originConstructor), instanceOf(Constructor.class));
+ assertThat(constructor.get(originConstructor), is((Object) loaded.getLoaded().getDeclaredConstructor()));
+ Object previous = constructor.get(originConstructor);
+ loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(constructor.get(originConstructor), instanceOf(Constructor.class));
+ assertThat(constructor.get(originConstructor), sameInstance(previous));
+ }
+
+ @Test
+ public void testOriginString() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.to(OriginString.class))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.foo(), instanceOf(String.class));
+ assertThat(instance.foo(), is((Object) Foo.class.getDeclaredMethod(FOO).toString()));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(7)
+ public void testOriginMethodHandle() throws Throwable {
+ Class<?> originMethodHandle = Class.forName(ORIGIN_METHOD_HANDLE);
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.to(originMethodHandle))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.foo(), instanceOf((Class<?>) originMethodHandle.getDeclaredField(TYPE).get(null)));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(7)
+ public void testOriginMethodType() throws Throwable {
+ Class<?> originMethodType = Class.forName(ORIGIN_METHOD_TYPE);
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.to(originMethodType))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.foo(), instanceOf((Class<?>) originMethodType.getDeclaredField(TYPE).get(null)));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testOriginIllegal() throws Exception {
+ new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.to(OriginIllegal.class))
+ .make();
+ }
+
+ public static class Foo {
+
+ public Object foo() {
+ return null;
+ }
+ }
+
+ public static class OriginClass {
+
+ public static Object foo(@Origin Class<?> type) {
+ return type;
+ }
+ }
+
+ public static class OriginMethod {
+
+ public static Object foo(@Origin(cache = false) Method method) {
+ return method;
+ }
+ }
+
+ public static class OriginMethodWithCache {
+
+ public static Object foo(@Origin(cache = true) Method method) {
+ return method;
+ }
+ }
+
+ public static class OriginConstructor {
+
+ private Constructor<?> constructor;
+
+ public void foo(@Origin(cache = false) Constructor<?> constructor) {
+ this.constructor = constructor;
+ }
+ }
+
+ public static class OriginConstructorWithCache {
+
+ private Constructor<?> constructor;
+
+ public void foo(@Origin(cache = true) Constructor<?> constructor) {
+ this.constructor = constructor;
+ }
+ }
+
+ public static class OriginString {
+
+ public static Object foo(@Origin String string) {
+ return string;
+ }
+ }
+
+ public static class OriginIllegal {
+
+ public static Object foo(@Origin Object object) {
+ return object;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationOtherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationOtherTest.java
new file mode 100644
index 0000000..19d55ff
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationOtherTest.java
@@ -0,0 +1,83 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.modifier.Ownership;
+import net.bytebuddy.dynamic.scaffold.TypeValidation;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import static net.bytebuddy.matcher.ElementMatchers.any;
+import static net.bytebuddy.matcher.ElementMatchers.isToString;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodDelegationOtherTest {
+
+ @Test(expected = IllegalStateException.class)
+ public void testDelegationToInvisibleInstanceThrowsException() throws Exception {
+ new ByteBuddy()
+ .subclass(Object.class)
+ .method(isToString())
+ .intercept(MethodDelegation.to(new Foo()))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testDelegationToInvisibleFieldTypeThrowsException() throws Exception {
+ new ByteBuddy()
+ .with(TypeValidation.DISABLED)
+ .subclass(Object.class)
+ .defineField("foo", Foo.class)
+ .method(isToString())
+ .intercept(MethodDelegation.toField("foo"))
+ .make();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testDelegationWithIllegalType() throws Exception {
+ MethodDelegation.to(new Object(), String.class);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testFieldDoesNotExist() throws Exception {
+ new ByteBuddy()
+ .subclass(Object.class)
+ .method(any())
+ .intercept(MethodDelegation.toField("foo"))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testCannotDelegateToInstanceFieldFromStaticMethod() throws Exception {
+ new ByteBuddy()
+ .subclass(Object.class)
+ .defineField("foo", Object.class)
+ .defineMethod("bar", void.class, Ownership.STATIC)
+ .intercept(MethodDelegation.toField("foo"))
+ .make();
+ }
+
+ @Test
+ public void testEmptyConfiguration() throws Exception {
+ assertThat(MethodDelegation.withEmptyConfiguration()
+ .withBinders(TargetMethodAnnotationDrivenBinder.ParameterBinder.DEFAULTS)
+ .withResolvers(MethodDelegationBinder.AmbiguityResolver.DEFAULT), is(MethodDelegation.withDefaultConfiguration()));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodDelegation.class).apply();
+ ObjectPropertyAssertion.of(MethodDelegation.WithCustomProperties.class).apply();
+ ObjectPropertyAssertion.of(MethodDelegation.Appender.class).apply();
+ ObjectPropertyAssertion.of(MethodDelegation.ImplementationDelegate.ForField.WithInstance.class).apply();
+ ObjectPropertyAssertion.of(MethodDelegation.ImplementationDelegate.ForField.WithLookup.class).apply();
+ ObjectPropertyAssertion.of(MethodDelegation.ImplementationDelegate.ForConstruction.class).apply();
+ ObjectPropertyAssertion.of(MethodDelegation.ImplementationDelegate.ForStaticMethod.class).apply();
+ }
+
+ static class Foo {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationPipeTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationPipeTest.java
new file mode 100644
index 0000000..a8a835e
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationPipeTest.java
@@ -0,0 +1,276 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.bind.annotation.Pipe;
+import net.bytebuddy.implementation.bind.annotation.RuntimeType;
+import net.bytebuddy.matcher.ElementMatchers;
+import net.bytebuddy.test.utility.CallTraceable;
+import org.junit.Test;
+
+import java.io.Serializable;
+import java.util.concurrent.Callable;
+
+import static net.bytebuddy.matcher.ElementMatchers.isClone;
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodDelegationPipeTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux";
+
+ private static final int BAZ = 42;
+
+ @Test
+ public void testPipeToIdenticalType() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.withDefaultConfiguration()
+ .withBinders(Pipe.Binder.install(ForwardingType.class))
+ .to(new ForwardingInterceptor(new Foo(FOO))))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.foo(QUX), is(FOO + QUX));
+ }
+
+ @Test
+ public void testPipeToIdenticalTypeVoid() throws Exception {
+ DynamicType.Loaded<Qux> loaded = new ByteBuddy()
+ .subclass(Qux.class)
+ .method(isDeclaredBy(Qux.class))
+ .intercept(MethodDelegation.withDefaultConfiguration()
+ .withBinders(Pipe.Binder.install(ForwardingType.class))
+ .to(new ForwardingInterceptor(new Qux())))
+ .make()
+ .load(Qux.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Qux instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ instance.foo();
+ instance.assertZeroCalls();
+ }
+
+ @Test
+ public void testPipeToIdenticalTypePrimitive() throws Exception {
+ DynamicType.Loaded<Baz> loaded = new ByteBuddy()
+ .subclass(Baz.class)
+ .method(isDeclaredBy(Baz.class))
+ .intercept(MethodDelegation.withDefaultConfiguration()
+ .withBinders(Pipe.Binder.install(ForwardingType.class))
+ .to(new PrimitiveForwardingInterceptor(new Baz())))
+ .make()
+ .load(Baz.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Baz instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.foo(BAZ), is(BAZ * 2L));
+ instance.assertZeroCalls();
+ }
+
+ @Test
+ public void testPipeToSubtype() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.withDefaultConfiguration()
+ .withBinders(Pipe.Binder.install(ForwardingType.class))
+ .to(new ForwardingInterceptor(new Bar(FOO))))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.foo(QUX), is(FOO + QUX));
+ }
+
+ @Test
+ public void testPipeSerialization() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.withDefaultConfiguration()
+ .withBinders(Pipe.Binder.install(ForwardingType.class))
+ .to(new SerializableForwardingInterceptor(new Foo(FOO))))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.foo(QUX), is(FOO + QUX));
+ }
+
+ @Test(expected = ClassCastException.class)
+ public void testPipeToIncompatibleTypeThrowsException() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.withDefaultConfiguration()
+ .withBinders(Pipe.Binder.install(ForwardingType.class))
+ .to(new ForwardingInterceptor(new Object())))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ instance.foo(QUX);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testPipeTypeDoesNotDeclareCorrectMethodThrowsException() throws Exception {
+ MethodDelegation.withDefaultConfiguration()
+ .withBinders(Pipe.Binder.install(Serializable.class))
+ .to(new ForwardingInterceptor(new Object()));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testPipeTypeDoesInheritFromOtherTypeThrowsException() throws Exception {
+ MethodDelegation.withDefaultConfiguration()
+ .withBinders(Pipe.Binder.install(InheritingForwardingType.class))
+ .to(new ForwardingInterceptor(new Object()));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testPipeTypeIsNotPublicThrowsException() throws Exception {
+ MethodDelegation.withDefaultConfiguration()
+ .withBinders(Pipe.Binder.install(PackagePrivateForwardingType.class))
+ .to(new ForwardingInterceptor(new Object()));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testPipeTypeDoesNotDeclareCorrectMethodSignatureThrowsException() throws Exception {
+ MethodDelegation.withDefaultConfiguration()
+ .withBinders(Pipe.Binder.install(WrongParametersForwardingType.class))
+ .to(new ForwardingInterceptor(new Object()));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testPipeTypeIsNotInterfaceThrowsException() throws Exception {
+ MethodDelegation.withDefaultConfiguration()
+ .withBinders(Pipe.Binder.install(Object.class))
+ .to(new ForwardingInterceptor(new Object()));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testPipeTypeOnTargetInterceptorThrowsException() throws Exception {
+ new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.withDefaultConfiguration()
+ .withBinders(Pipe.Binder.install(ForwardingType.class))
+ .to(WrongParameterTypeTarget.class))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testPipeToPreotectedMethod() throws Exception {
+ new ByteBuddy()
+ .subclass(Object.class)
+ .method(isClone())
+ .intercept(MethodDelegation.withDefaultConfiguration()
+ .withBinders(Pipe.Binder.install(ForwardingType.class))
+ .to(new ForwardingInterceptor(new Object())))
+ .make();
+ }
+
+ public interface ForwardingType<T, S> {
+
+ S doPipe(T target);
+ }
+
+ public interface InheritingForwardingType extends ForwardingType<Object, Object> {
+ /* empty */
+ }
+
+ interface PackagePrivateForwardingType<T, S> {
+
+ S doPipe(T target);
+ }
+
+ public interface WrongParametersForwardingType<T extends Number, S> {
+
+ S doPipe(T target);
+ }
+
+ public static class ForwardingInterceptor {
+
+ private final Object target;
+
+ public ForwardingInterceptor(Object target) {
+ this.target = target;
+ }
+
+ public String intercept(@Pipe ForwardingType<Object, String> pipe) {
+ assertThat(pipe, not(instanceOf(Serializable.class)));
+ return pipe.doPipe(target);
+ }
+ }
+
+ public static class Foo {
+
+ private final String prefix;
+
+ public Foo() {
+ prefix = BAR;
+ }
+
+ public Foo(String prefix) {
+ this.prefix = prefix;
+ }
+
+ public String foo(String input) {
+ return prefix + input;
+ }
+ }
+
+ public static class Bar extends Foo {
+
+ public Bar(String prefix) {
+ super(prefix);
+ }
+ }
+
+ public static class Qux extends CallTraceable {
+
+ public void foo() {
+ register(FOO);
+ }
+ }
+
+ public static class PrimitiveForwardingInterceptor {
+
+ private final Object target;
+
+ public PrimitiveForwardingInterceptor(Object target) {
+ this.target = target;
+ }
+
+ @RuntimeType
+ public Object intercept(@Pipe ForwardingType<Object, Object> pipe) {
+ assertThat(pipe, not(instanceOf(Serializable.class)));
+ return pipe.doPipe(target);
+ }
+ }
+
+ public static class Baz extends CallTraceable {
+
+ public long foo(int value) {
+ register(FOO);
+ return value * 2L;
+ }
+ }
+
+ public static class WrongParameterTypeTarget {
+
+ public static String intercept(@Pipe Callable<?> pipe) {
+ return null;
+ }
+ }
+
+ public static class SerializableForwardingInterceptor {
+
+ private final Object target;
+
+ public SerializableForwardingInterceptor(Object target) {
+ this.target = target;
+ }
+
+ public String intercept(@Pipe(serializableProxy = true) ForwardingType<Object, String> pipe) {
+ assertThat(pipe, instanceOf(Serializable.class));
+ return pipe.doPipe(target);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationRuntimeTypeTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationRuntimeTypeTest.java
new file mode 100644
index 0000000..1bd6305
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationRuntimeTypeTest.java
@@ -0,0 +1,43 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.bind.annotation.RuntimeType;
+import org.junit.Test;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodDelegationRuntimeTypeTest {
+
+ private static final String FOO = "FOO";
+
+ @Test
+ public void testRuntimeType() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.to(Bar.class))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.foo(FOO), is(FOO));
+ }
+
+ public static class Foo {
+
+ public String foo(Object o) {
+ return (String) o;
+ }
+ }
+
+ public static class Bar {
+
+ @RuntimeType
+ public static Object foo(@RuntimeType String s) {
+ return s;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationStubValueTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationStubValueTest.java
new file mode 100644
index 0000000..2bb2e2d
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationStubValueTest.java
@@ -0,0 +1,111 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.bind.annotation.RuntimeType;
+import net.bytebuddy.implementation.bind.annotation.StubValue;
+import org.junit.Test;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodDelegationStubValueTest {
+
+ @Test
+ public void testVoidMethod() throws Exception {
+ DynamicType.Loaded<VoidMethod> loaded = new ByteBuddy()
+ .subclass(VoidMethod.class)
+ .method(isDeclaredBy(VoidMethod.class))
+ .intercept(MethodDelegation.to(new Interceptor(null)))
+ .make()
+ .load(VoidMethod.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ VoidMethod instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ instance.foo();
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ DynamicType.Loaded<ReferenceMethod> loaded = new ByteBuddy()
+ .subclass(ReferenceMethod.class)
+ .method(isDeclaredBy(ReferenceMethod.class))
+ .intercept(MethodDelegation.to(new Interceptor(null)))
+ .make()
+ .load(ReferenceMethod.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ ReferenceMethod instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.foo(), nullValue(Object.class));
+ }
+
+ @Test
+ public void testLongValue() throws Exception {
+ DynamicType.Loaded<LongMethod> loaded = new ByteBuddy()
+ .subclass(LongMethod.class)
+ .method(isDeclaredBy(LongMethod.class))
+ .intercept(MethodDelegation.to(new Interceptor(0L)))
+ .make()
+ .load(LongMethod.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ LongMethod instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.foo(), is(0L));
+ }
+
+ @Test
+ public void tesIntegerValue() throws Exception {
+ DynamicType.Loaded<IntegerMethod> loaded = new ByteBuddy()
+ .subclass(IntegerMethod.class)
+ .method(isDeclaredBy(IntegerMethod.class))
+ .intercept(MethodDelegation.to(new Interceptor(0)))
+ .make()
+ .load(LongMethod.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ IntegerMethod instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.foo(), is(0));
+ }
+
+ public static class VoidMethod {
+
+ public void foo() {
+ throw new AssertionError();
+ }
+ }
+
+ public static class ReferenceMethod {
+
+ public Object foo() {
+ throw new AssertionError();
+ }
+ }
+
+ public static class LongMethod {
+
+ public long foo() {
+ throw new AssertionError();
+ }
+ }
+
+ public static class IntegerMethod {
+
+ public int foo() {
+ throw new AssertionError();
+ }
+ }
+
+ public static class Interceptor {
+
+ private final Object expectedValue;
+
+ public Interceptor(Object expectedValue) {
+ this.expectedValue = expectedValue;
+ }
+
+ @RuntimeType
+ public Object intercept(@StubValue Object value) {
+ if (expectedValue == null) {
+ assertThat(value, nullValue());
+ } else {
+ assertThat(value, is(expectedValue));
+ }
+ return value;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationSuperCallTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationSuperCallTest.java
new file mode 100644
index 0000000..a3bd581
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationSuperCallTest.java
@@ -0,0 +1,239 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.bind.annotation.RuntimeType;
+import net.bytebuddy.implementation.bind.annotation.SuperCall;
+import net.bytebuddy.test.utility.CallTraceable;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import org.hamcrest.CoreMatchers;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.concurrent.Callable;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodDelegationSuperCallTest {
+
+ private static final String SINGLE_DEFAULT_METHOD = "net.bytebuddy.test.precompiled.SingleDefaultMethodInterface";
+
+ private static final String CONFLICTING_INTERFACE = "net.bytebuddy.test.precompiled.SingleDefaultMethodConflictingInterface";
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ @Test
+ public void testRunnableSuperCall() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.to(RunnableClass.class))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.value, is(BAR));
+ instance.foo();
+ assertThat(instance.value, is(FOO));
+ }
+
+ @Test
+ public void testCallableSuperCall() throws Exception {
+ DynamicType.Loaded<Bar> loaded = new ByteBuddy()
+ .subclass(Bar.class)
+ .method(isDeclaredBy(Bar.class))
+ .intercept(MethodDelegation.to(CallableClass.class))
+ .make()
+ .load(Bar.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Bar instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.bar(), is(FOO));
+ }
+
+ @Test
+ public void testVoidToNonVoidSuperCall() throws Exception {
+ DynamicType.Loaded<VoidTest> loaded = new ByteBuddy()
+ .subclass(VoidTest.class)
+ .method(isDeclaredBy(VoidTest.class))
+ .intercept(MethodDelegation.to(NonVoidTarget.class))
+ .make()
+ .load(VoidTest.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ VoidTest instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ instance.foo();
+ instance.assertOnlyCall(FOO);
+ }
+
+ @Test
+ public void testRuntimeTypeSuperCall() throws Exception {
+ DynamicType.Loaded<RuntimeTypeTest> loaded = new ByteBuddy()
+ .subclass(RuntimeTypeTest.class)
+ .method(isDeclaredBy(RuntimeTypeTest.class))
+ .intercept(MethodDelegation.to(RuntimeTypeTarget.class))
+ .make()
+ .load(RuntimeTypeTest.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ RuntimeTypeTest instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.foo(), is(FOO));
+ }
+
+ @Test
+ public void testSerializableProxy() throws Exception {
+ DynamicType.Loaded<Bar> loaded = new ByteBuddy()
+ .subclass(Bar.class)
+ .method(isDeclaredBy(Bar.class))
+ .intercept(MethodDelegation.to(SerializationCheck.class))
+ .make()
+ .load(Bar.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Bar instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.bar(), is(FOO));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testDefaultMethodFallback() throws Exception {
+ DynamicType.Loaded<?> loaded = new ByteBuddy()
+ .subclass(Object.class)
+ .implement(Class.forName(SINGLE_DEFAULT_METHOD))
+ .intercept(MethodDelegation.to(NonVoidTarget.class))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Object instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ Method method = loaded.getLoaded().getMethod(FOO);
+ assertThat(method.invoke(instance), is((Object) FOO));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ @JavaVersionRule.Enforce(8)
+ public void testDefaultMethodFallbackDisabled() throws Exception {
+ new ByteBuddy()
+ .subclass(Object.class)
+ .implement(Class.forName(SINGLE_DEFAULT_METHOD))
+ .intercept(MethodDelegation.to(NoFallback.class))
+ .make();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ @JavaVersionRule.Enforce(8)
+ public void testDefaultMethodFallbackAmbiguous() throws Exception {
+ new ByteBuddy()
+ .subclass(Object.class)
+ .implement(Class.forName(SINGLE_DEFAULT_METHOD), Class.forName(CONFLICTING_INTERFACE))
+ .intercept(MethodDelegation.to(NonVoidTarget.class))
+ .make();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testAbstractMethodNonBindable() throws Exception {
+ new ByteBuddy()
+ .subclass(Qux.class)
+ .method(isDeclaredBy(Qux.class))
+ .intercept(MethodDelegation.to(CallableClass.class))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testWrongTypeThrowsException() throws Exception {
+ new ByteBuddy()
+ .subclass(Bar.class)
+ .method(isDeclaredBy(Bar.class))
+ .intercept(MethodDelegation.to(IllegalAnnotation.class))
+ .make();
+ }
+
+ public static class Foo {
+
+ public String value = BAR;
+
+ public void foo() {
+ value = FOO;
+ }
+ }
+
+ public static class RunnableClass {
+
+ public static void foo(@SuperCall Runnable runnable) {
+ assertThat(runnable, CoreMatchers.not(instanceOf(Serializable.class)));
+ runnable.run();
+ }
+ }
+
+ public static class Bar {
+
+ public String bar() {
+ return FOO;
+ }
+ }
+
+ public static class CallableClass {
+
+ public static String bar(@SuperCall Callable<String> callable) throws Exception {
+ assertThat(callable, CoreMatchers.not(instanceOf(Serializable.class)));
+ return callable.call();
+ }
+ }
+
+ public abstract static class Qux {
+
+ public abstract String bar();
+ }
+
+ public static class VoidTest extends CallTraceable {
+
+ public void foo() {
+ register(FOO);
+ }
+ }
+
+ public static class NonVoidTarget {
+
+ public static Object foo(@SuperCall Callable<?> zuper) throws Exception {
+ return zuper.call();
+ }
+ }
+
+ public static class RuntimeTypeTest {
+
+ public String foo() {
+ return FOO;
+ }
+ }
+
+ public static class RuntimeTypeTarget {
+
+ @RuntimeType
+ public static Object foo(@SuperCall Callable<?> zuper) throws Exception {
+ return zuper.call();
+ }
+ }
+
+ public static class IllegalAnnotation {
+
+ public static String bar(@SuperCall String value) throws Exception {
+ return value;
+ }
+ }
+
+ public static class SerializationCheck {
+
+ public static String bar(@SuperCall(serializableProxy = true) Callable<String> callable) throws Exception {
+ assertThat(callable, instanceOf(Serializable.class));
+ return callable.call();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class NoFallback {
+
+ @RuntimeType
+ public static Object foo(@SuperCall(fallbackToDefault = false) Callable<?> zuper) throws Exception {
+ return null;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationSuperMethodTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationSuperMethodTest.java
new file mode 100644
index 0000000..2d06d03
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationSuperMethodTest.java
@@ -0,0 +1,172 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.bind.annotation.RuntimeType;
+import net.bytebuddy.implementation.bind.annotation.SuperMethod;
+import net.bytebuddy.implementation.bind.annotation.This;
+import net.bytebuddy.test.utility.CallTraceable;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodDelegationSuperMethodTest {
+
+ private static final String SINGLE_DEFAULT_METHOD = "net.bytebuddy.test.precompiled.SingleDefaultMethodInterface";
+
+ private static final String CONFLICTING_INTERFACE = "net.bytebuddy.test.precompiled.SingleDefaultMethodConflictingInterface";
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ @Test
+ public void testRunnableSuperCall() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.to(SampleClass.class))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.value, is(BAR));
+ instance.foo();
+ assertThat(instance.value, is(FOO));
+ }
+
+ @Test
+ public void testVoidToNonVoidSuperCall() throws Exception {
+ DynamicType.Loaded<VoidTest> loaded = new ByteBuddy()
+ .subclass(VoidTest.class)
+ .method(isDeclaredBy(VoidTest.class))
+ .intercept(MethodDelegation.to(NonVoidTarget.class))
+ .make()
+ .load(VoidTest.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ VoidTest instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ instance.foo();
+ instance.assertOnlyCall(FOO);
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testDefaultMethodFallback() throws Exception {
+ DynamicType.Loaded<?> loaded = new ByteBuddy()
+ .subclass(Object.class)
+ .implement(Class.forName(SINGLE_DEFAULT_METHOD))
+ .intercept(MethodDelegation.to(NonVoidTarget.class))
+ .make()
+ .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Object instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ Method method = loaded.getLoaded().getMethod(FOO);
+ assertThat(method.invoke(instance), is((Object) FOO));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ @JavaVersionRule.Enforce(8)
+ public void testDefaultMethodFallbackDisabled() throws Exception {
+ new ByteBuddy()
+ .subclass(Object.class)
+ .implement(Class.forName(SINGLE_DEFAULT_METHOD))
+ .intercept(MethodDelegation.to(NoFallback.class))
+ .make();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ @JavaVersionRule.Enforce(8)
+ public void testDefaultMethodFallbackAmbiguous() throws Exception {
+ new ByteBuddy()
+ .subclass(Object.class)
+ .implement(Class.forName(SINGLE_DEFAULT_METHOD), Class.forName(CONFLICTING_INTERFACE))
+ .intercept(MethodDelegation.to(NonVoidTarget.class))
+ .make();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testAbstractMethodNonBindable() throws Exception {
+ new ByteBuddy()
+ .subclass(Qux.class)
+ .method(isDeclaredBy(Qux.class))
+ .intercept(MethodDelegation.to(SampleClass.class))
+ .make();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testWrongTypeThrowsException() throws Exception {
+ new ByteBuddy()
+ .subclass(Bar.class)
+ .method(isDeclaredBy(Bar.class))
+ .intercept(MethodDelegation.to(IllegalAnnotation.class))
+ .make();
+ }
+
+ public static class Foo {
+
+ public String value = BAR;
+
+ public void foo() {
+ value = FOO;
+ }
+ }
+
+ public static class SampleClass {
+
+ public static void foo(@SuperMethod Method method, @This Object target) throws Exception {
+ method.invoke(target);
+ }
+ }
+
+ public static class Bar {
+
+ public String bar() {
+ return FOO;
+ }
+ }
+
+ public abstract static class Qux {
+
+ public abstract String bar();
+ }
+
+ public static class VoidTest extends CallTraceable {
+
+ public void foo() {
+ register(FOO);
+ }
+ }
+
+ public static class NonVoidTarget {
+
+ public static Object foo(@SuperMethod Method method, @This Object target) throws Exception {
+ if (!Modifier.isPublic(method.getModifiers())) {
+ throw new AssertionError();
+ }
+ return method.invoke(target);
+ }
+ }
+
+ public static class IllegalAnnotation {
+
+ public static String bar(@SuperMethod String value) throws Exception {
+ return value;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class NoFallback {
+
+ @RuntimeType
+ public static Object foo(@SuperMethod(fallbackToDefault = false) Method method) throws Exception {
+ return null;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationSuperTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationSuperTest.java
new file mode 100644
index 0000000..dd7ef47
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationSuperTest.java
@@ -0,0 +1,243 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.modifier.TypeManifestation;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.TargetType;
+import net.bytebuddy.dynamic.loading.ByteArrayClassLoader;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.dynamic.loading.PackageDefinitionStrategy;
+import net.bytebuddy.implementation.bind.annotation.Super;
+import net.bytebuddy.test.utility.ClassFileExtraction;
+import org.junit.Test;
+
+import java.io.Serializable;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodDelegationSuperTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux";
+
+ @Test
+ public void testSuperInstance() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.to(Baz.class))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.qux(), is((Object) (FOO + QUX)));
+ }
+
+ @Test
+ public void testSuperInterface() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.to(FooBar.class))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.qux(), is((Object) (FOO + QUX)));
+ }
+
+ @Test
+ public void testSuperInstanceUnsafe() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.to(QuxBaz.class))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.qux(), is((Object) (FOO + QUX)));
+ }
+
+ @Test
+ public void testBridgeMethodResolution() throws Exception {
+ DynamicType.Loaded<Bar> loaded = new ByteBuddy()
+ .subclass(Bar.class)
+ .method(isDeclaredBy(Bar.class))
+ .intercept(MethodDelegation.to(GenericBaz.class))
+ .make()
+ .load(Bar.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Bar instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.qux(BAR), is(BAR + QUX));
+ }
+
+ @Test(expected = AbstractMethodError.class)
+ public void testSuperCallOnAbstractMethod() throws Exception {
+ DynamicType.Loaded<FooBarQuxBaz> loaded = new ByteBuddy()
+ .subclass(FooBarQuxBaz.class)
+ .method(isDeclaredBy(FooBarQuxBaz.class))
+ .intercept(MethodDelegation.to(FooBar.class))
+ .make()
+ .load(FooBarQuxBaz.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ loaded.getLoaded().getDeclaredConstructor().newInstance().qux();
+ }
+
+ @Test
+ public void testSerializableProxy() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.to(SerializationCheck.class))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.qux(), is((Object) (FOO + QUX)));
+ }
+
+ @Test
+ public void testTargetTypeProxy() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.to(TargetTypeTest.class))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.qux(), is((Object) (FOO + QUX)));
+ }
+
+ @Test
+ public void testExplicitTypeProxy() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.to(ExplicitTypeTest.class))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.qux(), is((Object) (FOO + QUX)));
+ }
+
+ @Test
+ public void testFinalType() throws Exception {
+ ClassLoader classLoader = new ByteArrayClassLoader(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassFileExtraction.of(SimpleInterceptor.class));
+ Class<?> type = new ByteBuddy()
+ .rebase(FinalType.class)
+ .modifiers(TypeManifestation.PLAIN, Visibility.PUBLIC)
+ .method(named(FOO)).intercept(ExceptionMethod.throwing(RuntimeException.class))
+ .method(named(BAR)).intercept(MethodDelegation.to(SimpleInterceptor.class))
+ .make()
+ .load(classLoader, ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded();
+ assertThat(type.getDeclaredMethod(BAR).invoke(type.getDeclaredConstructor().newInstance()), is((Object) FOO));
+ }
+
+ public interface Qux {
+
+ Object qux();
+ }
+
+ public static class Foo implements Qux {
+
+ @Override
+ public Object qux() {
+ return FOO;
+ }
+ }
+
+ public static class Baz {
+
+ public static String baz(@Super Foo foo) {
+ assertThat(foo, not(instanceOf(Serializable.class)));
+ return foo.qux() + QUX;
+ }
+ }
+
+ public static class FooBar {
+
+ public static String baz(@Super Qux foo) {
+ assertThat(foo, not(instanceOf(Serializable.class)));
+ return foo.qux() + QUX;
+ }
+ }
+
+ public static class QuxBaz {
+
+ public static String baz(@Super(strategy = Super.Instantiation.UNSAFE) Foo foo) {
+ assertThat(foo, not(instanceOf(Serializable.class)));
+ return foo.qux() + QUX;
+ }
+ }
+
+ public abstract static class FooBarQuxBaz implements Qux {
+
+ @Override
+ public abstract Object qux();
+ }
+
+ public static class GenericBase<T> {
+
+ public T qux(T value) {
+ return value;
+ }
+ }
+
+ public static class Bar extends GenericBase<String> {
+
+ @Override
+ public String qux(String value) {
+ return super.qux(value);
+ }
+ }
+
+ public static class GenericBaz {
+
+ public static String baz(String value, @Super GenericBase<String> foo) {
+ assertThat(foo, not(instanceOf(Serializable.class)));
+ return foo.qux(value) + QUX;
+ }
+ }
+
+ public static class SerializationCheck {
+
+ public static String baz(@Super(serializableProxy = true) Foo foo) {
+ assertThat(foo, instanceOf(Serializable.class));
+ return foo.qux() + QUX;
+ }
+ }
+
+ public static class TargetTypeTest {
+
+ public static String baz(@Super(proxyType = TargetType.class) Object proxy) throws Exception {
+ assertThat(proxy, instanceOf(Foo.class));
+ return Foo.class.getDeclaredMethod(QUX).invoke(proxy) + QUX;
+ }
+ }
+
+ public static class ExplicitTypeTest {
+
+ public static String baz(@Super(proxyType = Qux.class) Object proxy) throws Exception {
+ assertThat(proxy, instanceOf(Qux.class));
+ assertThat(proxy, not(instanceOf(Foo.class)));
+ return Qux.class.getDeclaredMethod(QUX).invoke(proxy) + QUX;
+ }
+ }
+
+ public static final class FinalType {
+
+ public Object foo() {
+ return FOO;
+ }
+
+ public Object bar() {
+ return null;
+ }
+ }
+
+ public static class SimpleInterceptor {
+
+ public static Object intercept(@Super FinalType finalType) {
+ return finalType.foo();
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationTest.java
new file mode 100644
index 0000000..3437954
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationTest.java
@@ -0,0 +1,351 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.test.utility.CallTraceable;
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.Matcher;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Collection;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class MethodDelegationTest<T extends CallTraceable> {
+
+ private static final String FOO = "foo", BAR = "bar", FIELD_NAME = "qux";
+
+ private static final byte BYTE_MULTIPLICATOR = 3;
+
+ private static final short SHORT_MULTIPLICATOR = 3;
+
+ private static final char CHAR_MULTIPLICATOR = 3;
+
+ private static final int INT_MULTIPLICATOR = 3;
+
+ private static final long LONG_MULTIPLICATOR = 3L;
+
+ private static final float FLOAT_MULTIPLICATOR = 3f;
+
+ private static final double DOUBLE_MULTIPLICATOR = 3d;
+
+ private static final boolean DEFAULT_BOOLEAN = false;
+
+ private static final byte DEFAULT_BYTE = 1;
+
+ private static final short DEFAULT_SHORT = 1;
+
+ private static final char DEFAULT_CHAR = 1;
+
+ private static final int DEFAULT_INT = 1;
+
+ private static final long DEFAULT_LONG = 1L;
+
+ private static final float DEFAULT_FLOAT = 1f;
+
+ private static final double DEFAULT_DOUBLE = 1d;
+
+ private final Class<T> sourceType;
+
+ private final Class<?> targetType;
+
+ private final Class<?>[] parameterTypes;
+
+ private final Object[] arguments;
+
+ private final Matcher<?> matcher;
+
+ public MethodDelegationTest(Class<T> sourceType,
+ Class<?> targetType,
+ Class<?>[] parameterTypes,
+ Object[] arguments,
+ Matcher<?> matcher) {
+ this.sourceType = sourceType;
+ this.targetType = targetType;
+ this.parameterTypes = parameterTypes;
+ this.arguments = arguments;
+ this.matcher = matcher;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {BooleanSource.class, BooleanTarget.class, new Class<?>[]{boolean.class}, new Object[]{DEFAULT_BOOLEAN}, is(!DEFAULT_BOOLEAN)},
+ {ByteSource.class, ByteTarget.class, new Class<?>[]{byte.class}, new Object[]{DEFAULT_BYTE}, is((byte) (DEFAULT_BYTE * BYTE_MULTIPLICATOR))},
+ {ShortSource.class, ShortTarget.class, new Class<?>[]{short.class}, new Object[]{DEFAULT_SHORT}, is((short) (DEFAULT_SHORT * SHORT_MULTIPLICATOR))},
+ {CharSource.class, CharTarget.class, new Class<?>[]{char.class}, new Object[]{DEFAULT_CHAR}, is((char) (DEFAULT_CHAR * CHAR_MULTIPLICATOR))},
+ {IntSource.class, IntTarget.class, new Class<?>[]{int.class}, new Object[]{DEFAULT_INT}, is(DEFAULT_INT * INT_MULTIPLICATOR)},
+ {LongSource.class, LongTarget.class, new Class<?>[]{long.class}, new Object[]{DEFAULT_LONG}, is(DEFAULT_LONG * LONG_MULTIPLICATOR)},
+ {FloatSource.class, FloatTarget.class, new Class<?>[]{float.class}, new Object[]{DEFAULT_FLOAT}, is(DEFAULT_FLOAT * FLOAT_MULTIPLICATOR)},
+ {DoubleSource.class, DoubleTarget.class, new Class<?>[]{double.class}, new Object[]{DEFAULT_DOUBLE}, is(DEFAULT_DOUBLE * DOUBLE_MULTIPLICATOR)},
+ {VoidSource.class, VoidTarget.class, new Class<?>[0], new Object[0], nullValue()},
+ {StringSource.class, StringTarget.class, new Class<?>[]{String.class}, new Object[]{FOO}, is(FOO + BAR)},
+ });
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testStaticMethodBinding() throws Exception {
+ DynamicType.Loaded<T> loaded = new ByteBuddy()
+ .subclass(sourceType)
+ .method(isDeclaredBy(sourceType))
+ .intercept(MethodDelegation.to(targetType))
+ .make()
+ .load(sourceType.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(0));
+ T instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(sourceType)));
+ assertThat(instance, instanceOf(sourceType));
+ assertThat(loaded.getLoaded().getDeclaredMethod(FOO, parameterTypes).invoke(instance, arguments), (Matcher) matcher);
+ instance.assertZeroCalls();
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testStaticFieldBinding() throws Exception {
+ DynamicType.Loaded<T> loaded = new ByteBuddy()
+ .subclass(sourceType)
+ .method(isDeclaredBy(sourceType))
+ .intercept(MethodDelegation.withDefaultConfiguration()
+ .filter(isDeclaredBy(targetType))
+ .to(targetType.getDeclaredConstructor().newInstance()))
+ .make()
+ .load(sourceType.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(1));
+ T instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(sourceType)));
+ assertThat(instance, instanceOf(sourceType));
+ assertThat(loaded.getLoaded().getDeclaredMethod(FOO, parameterTypes).invoke(instance, arguments), (Matcher) matcher);
+ instance.assertZeroCalls();
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testInstanceFieldBinding() throws Exception {
+ DynamicType.Loaded<T> loaded = new ByteBuddy()
+ .subclass(sourceType)
+ .defineField(FIELD_NAME, targetType, Visibility.PUBLIC)
+ .method(isDeclaredBy(sourceType))
+ .intercept(MethodDelegation.withDefaultConfiguration()
+ .filter(isDeclaredBy(targetType))
+ .toField(FIELD_NAME))
+ .make()
+ .load(sourceType.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ assertThat(loaded.getLoaded().getDeclaredFields().length, is(1));
+ T instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ Field field = loaded.getLoaded().getDeclaredField(FIELD_NAME);
+ assertThat(field.getModifiers(), is(Modifier.PUBLIC));
+ assertThat(field.getType(), CoreMatchers.<Class<?>>is(targetType));
+ field.setAccessible(true);
+ field.set(instance, targetType.getDeclaredConstructor().newInstance());
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(sourceType)));
+ assertThat(instance, instanceOf(sourceType));
+ assertThat(loaded.getLoaded().getDeclaredMethod(FOO, parameterTypes).invoke(instance, arguments), (Matcher) matcher);
+ instance.assertZeroCalls();
+ }
+
+ public static class BooleanSource extends CallTraceable {
+
+ public boolean foo(boolean b) {
+ register(FOO);
+ return b;
+ }
+ }
+
+ public static class BooleanTarget {
+
+ public static boolean bar(boolean b) {
+ return !b;
+ }
+
+ public boolean qux(boolean b) {
+ return bar(b);
+ }
+ }
+
+ public static class ByteSource extends CallTraceable {
+
+ public byte foo(byte b) {
+ register(FOO);
+ return b;
+ }
+ }
+
+ public static class ByteTarget {
+
+ public static byte bar(byte b) {
+ return (byte) (b * BYTE_MULTIPLICATOR);
+ }
+
+ public byte qux(byte b) {
+ return bar(b);
+ }
+ }
+
+ public static class ShortSource extends CallTraceable {
+
+ public short foo(short s) {
+ register(FOO);
+ return s;
+ }
+ }
+
+ public static class ShortTarget {
+
+ public static short bar(short s) {
+ return (short) (s * SHORT_MULTIPLICATOR);
+ }
+
+ public short qux(short s) {
+ return bar(s);
+ }
+ }
+
+ public static class CharSource extends CallTraceable {
+
+ public char foo(char s) {
+ register(FOO);
+ return s;
+ }
+ }
+
+ public static class CharTarget {
+
+ public static char bar(char c) {
+ return (char) (c * CHAR_MULTIPLICATOR);
+ }
+
+ public char qux(char c) {
+ return bar(c);
+ }
+ }
+
+ public static class IntSource extends CallTraceable {
+
+ public int foo(int i) {
+ register(FOO);
+ return i;
+ }
+ }
+
+ public static class IntTarget {
+
+ public static int bar(int i) {
+ return i * INT_MULTIPLICATOR;
+ }
+
+ public int qux(int i) {
+ return bar(i);
+ }
+ }
+
+ public static class LongSource extends CallTraceable {
+
+ public long foo(long l) {
+ register(FOO);
+ return l;
+ }
+ }
+
+ public static class LongTarget {
+
+ public static long bar(long l) {
+ return l * LONG_MULTIPLICATOR;
+ }
+
+ public long qux(long l) {
+ return bar(l);
+ }
+ }
+
+ public static class FloatSource extends CallTraceable {
+
+ public float foo(float f) {
+ register(FOO);
+ return f;
+ }
+ }
+
+ public static class FloatTarget {
+
+ public static float bar(float f) {
+ return f * FLOAT_MULTIPLICATOR;
+ }
+
+ public float qux(float f) {
+ return bar(f);
+ }
+ }
+
+ public static class DoubleSource extends CallTraceable {
+
+ public double foo(double d) {
+ register(FOO);
+ return d;
+ }
+ }
+
+ public static class DoubleTarget {
+
+ public static double bar(double d) {
+ return d * DOUBLE_MULTIPLICATOR;
+ }
+
+ public double qux(double d) {
+ return bar(d);
+ }
+ }
+
+ public static class VoidSource extends CallTraceable {
+
+ public void foo() {
+ register(FOO);
+ }
+ }
+
+ public static class VoidTarget {
+
+ public static void bar() {
+ /* empty */
+ }
+
+ public void qux() {
+ bar();
+ }
+ }
+
+ public static class StringSource extends CallTraceable {
+
+ public String foo(String s) {
+ register(FOO);
+ return s;
+ }
+ }
+
+ public static class StringTarget {
+
+ public static String bar(String s) {
+ return s + BAR;
+ }
+
+ public String qux(String s) {
+ return bar(s);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationThisTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationThisTest.java
new file mode 100644
index 0000000..42c2d81
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/MethodDelegationThisTest.java
@@ -0,0 +1,44 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.bind.annotation.This;
+import org.junit.Test;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodDelegationThisTest {
+
+ @Test
+ public void testThis() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(MethodDelegation.to(Bar.class))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.foo(), is((Object) instance));
+ }
+
+ public static class Foo {
+
+ public Object foo() {
+ return null;
+ }
+ }
+
+ public static class Bar {
+
+ public static Object qux(@This Foo foo) {
+ return foo;
+ }
+
+ public static Object baz(@This Void v) {
+ return v;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/ModifierReviewableTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/ModifierReviewableTest.java
new file mode 100644
index 0000000..ea01755
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/ModifierReviewableTest.java
@@ -0,0 +1,110 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.description.ModifierReviewable;
+import net.bytebuddy.description.modifier.*;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Opcodes;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class ModifierReviewableTest {
+
+ private final SimpleModifierReviewable simpleModifierReviewable;
+
+ private final Method method;
+
+ private final Object expected;
+
+ public ModifierReviewableTest(int modifiers, String methodName, Object expected) throws Exception {
+ simpleModifierReviewable = new SimpleModifierReviewable(modifiers);
+ method = ModifierReviewable.AbstractBase.class.getMethod(methodName);
+ this.expected = expected;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {Opcodes.ACC_ABSTRACT, "isAbstract", true},
+ {Opcodes.ACC_ANNOTATION, "isAnnotation", true},
+ {Opcodes.ACC_BRIDGE, "isBridge", true},
+ {Opcodes.ACC_DEPRECATED, "isDeprecated", true},
+ {Opcodes.ACC_ENUM, "isEnum", true},
+ {Opcodes.ACC_FINAL, "isFinal", true},
+ {Opcodes.ACC_INTERFACE, "isInterface", true},
+ {Opcodes.ACC_MANDATED, "isMandated", true},
+ {Opcodes.ACC_NATIVE, "isNative", true},
+ {Opcodes.ACC_PRIVATE, "isPrivate", true},
+ {Opcodes.ACC_PROTECTED, "isProtected", true},
+ {Opcodes.ACC_PUBLIC, "isPublic", true},
+ {Opcodes.ACC_STATIC, "isStatic", true},
+ {Opcodes.ACC_STRICT, "isStrict", true},
+ {Opcodes.ACC_SYNCHRONIZED, "isSynchronized", true},
+ {Opcodes.ACC_SYNTHETIC, "isSynthetic", true},
+ {Opcodes.ACC_TRANSIENT, "isTransient", true},
+ {Opcodes.ACC_VARARGS, "isVarArgs", true},
+ {Opcodes.ACC_VOLATILE, "isVolatile", true},
+ {ModifierReviewable.EMPTY_MASK, "isPackagePrivate", true},
+ {Opcodes.ACC_SYNTHETIC, "getSyntheticState", SyntheticState.SYNTHETIC},
+ {ModifierReviewable.EMPTY_MASK, "getSyntheticState", SyntheticState.PLAIN},
+ {Opcodes.ACC_PUBLIC, "getVisibility", Visibility.PUBLIC},
+ {ModifierReviewable.EMPTY_MASK, "getVisibility", Visibility.PACKAGE_PRIVATE},
+ {Opcodes.ACC_PROTECTED, "getVisibility", Visibility.PROTECTED},
+ {Opcodes.ACC_PRIVATE, "getVisibility", Visibility.PRIVATE},
+ {Opcodes.ACC_STATIC, "getOwnership", Ownership.STATIC},
+ {ModifierReviewable.EMPTY_MASK, "getOwnership", Ownership.MEMBER},
+ {Opcodes.ACC_ENUM, "getEnumerationState", EnumerationState.ENUMERATION},
+ {ModifierReviewable.EMPTY_MASK, "getEnumerationState", EnumerationState.PLAIN},
+ {Opcodes.ACC_ABSTRACT, "getTypeManifestation", TypeManifestation.ABSTRACT},
+ {Opcodes.ACC_FINAL, "getTypeManifestation", TypeManifestation.FINAL},
+ {Opcodes.ACC_ABSTRACT | Opcodes.ACC_INTERFACE, "getTypeManifestation", TypeManifestation.INTERFACE},
+ {Opcodes.ACC_ABSTRACT | Opcodes.ACC_INTERFACE | Opcodes.ACC_ANNOTATION, "getTypeManifestation", TypeManifestation.ANNOTATION},
+ {ModifierReviewable.EMPTY_MASK, "getTypeManifestation", TypeManifestation.PLAIN},
+ {Opcodes.ACC_FINAL, "getFieldManifestation", FieldManifestation.FINAL},
+ {Opcodes.ACC_VOLATILE, "getFieldManifestation", FieldManifestation.VOLATILE},
+ {Opcodes.ACC_TRANSIENT, "getFieldPersistence", FieldPersistence.TRANSIENT},
+ {ModifierReviewable.EMPTY_MASK, "getFieldPersistence", FieldPersistence.PLAIN},
+ {Opcodes.ACC_SYNCHRONIZED, "getSynchronizationState", SynchronizationState.SYNCHRONIZED},
+ {ModifierReviewable.EMPTY_MASK, "getSynchronizationState", SynchronizationState.PLAIN},
+ {Opcodes.ACC_FINAL, "getParameterManifestation", ParameterManifestation.FINAL},
+ {ModifierReviewable.EMPTY_MASK, "getParameterManifestation", ParameterManifestation.PLAIN},
+ {Opcodes.ACC_MANDATED, "getProvisioningState", ProvisioningState.MANDATED},
+ {ModifierReviewable.EMPTY_MASK, "getProvisioningState", ProvisioningState.PLAIN},
+ {Opcodes.ACC_BRIDGE, "getMethodManifestation", MethodManifestation.BRIDGE},
+ {Opcodes.ACC_ABSTRACT, "getMethodManifestation", MethodManifestation.ABSTRACT},
+ {Opcodes.ACC_FINAL, "getMethodManifestation", MethodManifestation.FINAL},
+ {Opcodes.ACC_NATIVE, "getMethodManifestation", MethodManifestation.NATIVE},
+ {Opcodes.ACC_NATIVE | Opcodes.ACC_FINAL, "getMethodManifestation", MethodManifestation.FINAL_NATIVE},
+ {Opcodes.ACC_BRIDGE | Opcodes.ACC_FINAL, "getMethodManifestation", MethodManifestation.FINAL_BRIDGE},
+ {ModifierReviewable.EMPTY_MASK, "getMethodManifestation", MethodManifestation.PLAIN},
+ {Opcodes.ACC_STRICT, "getMethodStrictness", MethodStrictness.STRICT},
+ {ModifierReviewable.EMPTY_MASK, "getMethodStrictness", MethodStrictness.PLAIN}
+ });
+ }
+
+ @Test
+ public void testModifierProperty() throws Exception {
+ assertThat(method.invoke(simpleModifierReviewable), is(expected));
+ }
+
+ private static class SimpleModifierReviewable extends ModifierReviewable.AbstractBase {
+
+ private final int modifiers;
+
+ private SimpleModifierReviewable(int modifiers) {
+ this.modifiers = modifiers;
+ }
+
+ @Override
+ public int getModifiers() {
+ return modifiers;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/StubMethodOtherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/StubMethodOtherTest.java
new file mode 100644
index 0000000..40ba1b1
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/StubMethodOtherTest.java
@@ -0,0 +1,33 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+public class StubMethodOtherTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private InstrumentedType instrumentedType;
+
+ @Test
+ public void testPreparation() throws Exception {
+ assertThat(StubMethod.INSTANCE.prepare(instrumentedType), is(instrumentedType));
+ verifyZeroInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(StubMethod.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/StubMethodTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/StubMethodTest.java
new file mode 100644
index 0000000..d94c696
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/StubMethodTest.java
@@ -0,0 +1,193 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.test.utility.CallTraceable;
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.Matcher;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class StubMethodTest {
+
+ private static final String OBJECT_METHOD = "reference";
+
+ private static final String BOOLEAN_METHOD = "aBoolean";
+
+ private static final String BYTE_METHOD = "aByte";
+
+ private static final String SHORT_METHOD = "aShort";
+
+ private static final String CHAR_METHOD = "aChar";
+
+ private static final String INT_METHOD = "aInt";
+
+ private static final String LONG_METHOD = "aLong";
+
+ private static final String FLOAT_METHOD = "aFloat";
+
+ private static final String DOUBLE_METHOD = "aDouble";
+
+ private static final String VOID_METHOD = "aVoid";
+
+ private static final String PARAMETERS_METHOD = "parameters";
+
+ private static final String STRING_VALUE = "foo";
+
+ private static final boolean BOOLEAN_VALUE = true;
+
+ private static final byte BYTE_VALUE = 42;
+
+ private static final short SHORT_VALUE = 42;
+
+ private static final char CHAR_VALUE = '@';
+
+ private static final int INT_VALUE = 42;
+
+ private static final long LONG_VALUE = 42L;
+
+ private static final float FLOAT_VALUE = 42f;
+
+ private static final double DOUBLE_VALUE = 42d;
+
+ private static final String STRING_DEFAULT_VALUE = null;
+
+ private static final boolean BOOLEAN_DEFAULT_VALUE = false;
+
+ private static final byte BYTE_DEFAULT_VALUE = 0;
+
+ private static final short SHORT_DEFAULT_VALUE = 0;
+
+ private static final char CHAR_DEFAULT_VALUE = 0;
+
+ private static final int INT_DEFAULT_VALUE = 0;
+
+ private static final long LONG_DEFAULT_VALUE = 0L;
+
+ private static final float FLOAT_DEFAULT_VALUE = 0f;
+
+ private static final double DOUBLE_DEFAULT_VALUE = 0d;
+
+ private final Matcher<?> matcher;
+
+ private final String methodName;
+
+ private final Class<?>[] methodParameterTypes;
+
+ private final Object[] methodArguments;
+
+ public StubMethodTest(Matcher<?> matcher,
+ String methodName,
+ Class<?>[] methodParameterTypes,
+ Object[] methodArguments) {
+ this.matcher = matcher;
+ this.methodName = methodName;
+ this.methodParameterTypes = methodParameterTypes;
+ this.methodArguments = methodArguments;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {is(STRING_DEFAULT_VALUE), OBJECT_METHOD, new Class<?>[0], new Object[0]},
+ {is(BOOLEAN_DEFAULT_VALUE), BOOLEAN_METHOD, new Class<?>[0], new Object[0]},
+ {is(BYTE_DEFAULT_VALUE), BYTE_METHOD, new Class<?>[0], new Object[0]},
+ {is(SHORT_DEFAULT_VALUE), SHORT_METHOD, new Class<?>[0], new Object[0]},
+ {is(CHAR_DEFAULT_VALUE), CHAR_METHOD, new Class<?>[0], new Object[0]},
+ {is(INT_DEFAULT_VALUE), INT_METHOD, new Class<?>[0], new Object[0]},
+ {is(LONG_DEFAULT_VALUE), LONG_METHOD, new Class<?>[0], new Object[0]},
+ {is(FLOAT_DEFAULT_VALUE), FLOAT_METHOD, new Class<?>[0], new Object[0]},
+ {is(DOUBLE_DEFAULT_VALUE), DOUBLE_METHOD, new Class<?>[0], new Object[0]},
+ {nullValue(), VOID_METHOD, new Class<?>[0], new Object[0]},
+ {nullValue(), PARAMETERS_METHOD,
+ new Class<?>[]{long.class, float.class, int.class, double.class, Object.class},
+ new Object[]{LONG_VALUE, FLOAT_VALUE, INT_VALUE, DOUBLE_VALUE, STRING_VALUE}}
+ });
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testInstrumentedMethod() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(StubMethod.INSTANCE)
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(11));
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(Foo.class)));
+ assertThat(instance, instanceOf(Foo.class));
+ assertThat(loaded.getLoaded().getDeclaredMethod(methodName, methodParameterTypes)
+ .invoke(instance, methodArguments), (Matcher) matcher);
+ instance.assertZeroCalls();
+ }
+
+ @SuppressWarnings("unused")
+ public static class Foo extends CallTraceable {
+
+ public Object reference() {
+ register(OBJECT_METHOD);
+ return STRING_VALUE;
+ }
+
+ public boolean aBoolean() {
+ register(BOOLEAN_METHOD);
+ return BOOLEAN_VALUE;
+ }
+
+ public byte aByte() {
+ register(BYTE_METHOD);
+ return BYTE_VALUE;
+ }
+
+ public short aShort() {
+ register(SHORT_METHOD);
+ return SHORT_VALUE;
+ }
+
+ public char aChar() {
+ register(CHAR_METHOD);
+ return CHAR_VALUE;
+ }
+
+ public int aInt() {
+ register(INT_METHOD);
+ return INT_VALUE;
+ }
+
+ public long aLong() {
+ register(LONG_METHOD);
+ return LONG_VALUE;
+ }
+
+ public float aFloat() {
+ register(FLOAT_METHOD);
+ return FLOAT_VALUE;
+ }
+
+ public double aDouble() {
+ register(DOUBLE_METHOD);
+ return DOUBLE_VALUE;
+ }
+
+ public void aVoid() {
+ register(VOID_METHOD);
+ }
+
+ public void parameters(long l, float f, int i, double d, Object o) {
+ register(PARAMETERS_METHOD, l, f, i, d, o);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/SuperMethodCallOtherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/SuperMethodCallOtherTest.java
new file mode 100644
index 0000000..775e732
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/SuperMethodCallOtherTest.java
@@ -0,0 +1,202 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.implementation.bytecode.constant.TextConstant;
+import net.bytebuddy.implementation.bytecode.member.MethodReturn;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.test.utility.CallTraceable;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.MethodVisitor;
+
+import java.lang.reflect.Method;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static net.bytebuddy.matcher.ElementMatchers.not;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class SuperMethodCallOtherTest {
+
+ private static final String SINGLE_DEFAULT_METHOD = "net.bytebuddy.test.precompiled.SingleDefaultMethodInterface";
+
+ private static final String SINGLE_DEFAULT_METHOD_CLASS = "net.bytebuddy.test.precompiled.SingleDefaultMethodClass";
+
+ private static final String CONFLICTING_INTERFACE = "net.bytebuddy.test.precompiled.SingleDefaultMethodConflictingInterface";
+
+ private static final String FOO = "foo";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ @Mock
+ private InstrumentedType instrumentedType;
+
+ @Mock
+ private TypeDescription typeDescription, rawSuperClass, returnType, declaringType;
+
+ @Mock
+ private TypeDescription.Generic superClass, genericReturnType;
+
+ @Mock
+ private Implementation.Target implementationTarget;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @Mock
+ private MethodDescription methodDescription;
+
+ @Mock
+ private MethodDescription.SignatureToken token;
+
+ @Mock
+ private MethodList superClassMethods;
+
+ @Before
+ public void setUp() throws Exception {
+ when(implementationTarget.getInstrumentedType()).thenReturn(typeDescription);
+ when(methodDescription.asSignatureToken()).thenReturn(token);
+ when(genericReturnType.asErasure()).thenReturn(returnType);
+ when(superClass.asErasure()).thenReturn(rawSuperClass);
+ }
+
+ @Test
+ public void testPreparation() throws Exception {
+ assertThat(SuperMethodCall.INSTANCE.prepare(instrumentedType), is(instrumentedType));
+ verifyZeroInteractions(instrumentedType);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ @SuppressWarnings("unchecked")
+ public void testConstructor() throws Exception {
+ when(typeDescription.getSuperClass()).thenReturn(superClass);
+ when(methodDescription.isConstructor()).thenReturn(true);
+ when(rawSuperClass.getDeclaredMethods()).thenReturn(superClassMethods);
+ when(superClassMethods.filter(any(ElementMatcher.class))).thenReturn(superClassMethods);
+ when(implementationTarget.invokeDominant(token)).thenReturn(Implementation.SpecialMethodInvocation.Illegal.INSTANCE);
+ SuperMethodCall.INSTANCE.appender(implementationTarget).apply(methodVisitor, implementationContext, methodDescription);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ @SuppressWarnings("unchecked")
+ public void testStaticMethod() throws Exception {
+ when(typeDescription.getSuperClass()).thenReturn(superClass);
+ when(methodDescription.isStatic()).thenReturn(true);
+ when(methodDescription.getParameters()).thenReturn((ParameterList) new ParameterList.Empty<ParameterDescription>());
+ when(methodDescription.getReturnType()).thenReturn(genericReturnType);
+ when(returnType.getStackSize()).thenReturn(StackSize.SINGLE);
+ when(rawSuperClass.getDeclaredMethods()).thenReturn(superClassMethods);
+ when(superClassMethods.filter(any(ElementMatcher.class))).thenReturn(superClassMethods);
+ when(implementationTarget.invokeDominant(token)).thenReturn(Implementation.SpecialMethodInvocation.Illegal.INSTANCE);
+ SuperMethodCall.INSTANCE.appender(implementationTarget).apply(methodVisitor, implementationContext, methodDescription);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ @SuppressWarnings("unchecked")
+ public void testNoSuper() throws Exception {
+ when(typeDescription.getSuperClass()).thenReturn(superClass);
+ when(methodDescription.getParameters()).thenReturn((ParameterList) new ParameterList.Empty<ParameterDescription>());
+ when(methodDescription.getReturnType()).thenReturn(genericReturnType);
+ when(methodDescription.getDeclaringType()).thenReturn(declaringType);
+ when(declaringType.getStackSize()).thenReturn(StackSize.SINGLE);
+ when(returnType.getStackSize()).thenReturn(StackSize.SINGLE);
+ when(rawSuperClass.getDeclaredMethods()).thenReturn(superClassMethods);
+ when(superClassMethods.filter(any(ElementMatcher.class))).thenReturn(superClassMethods);
+ when(implementationTarget.invokeDominant(token)).thenReturn(Implementation.SpecialMethodInvocation.Illegal.INSTANCE);
+ SuperMethodCall.INSTANCE.appender(implementationTarget).apply(methodVisitor, implementationContext, methodDescription);
+ }
+
+ @Test
+ public void testAndThen() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(SuperMethodCall.INSTANCE.andThen(new Implementation.Simple(new TextConstant(FOO), MethodReturn.REFERENCE)))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ Foo foo = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(foo.foo(), is(FOO));
+ foo.assertOnlyCall(FOO);
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testUnambiguousDirectDefaultMethod() throws Exception {
+ DynamicType.Loaded<?> loaded = new ByteBuddy()
+ .subclass(Object.class)
+ .implement(Class.forName(SINGLE_DEFAULT_METHOD))
+ .intercept(SuperMethodCall.INSTANCE)
+ .make()
+ .load(Class.forName(SINGLE_DEFAULT_METHOD).getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ Method method = loaded.getLoaded().getDeclaredMethod(FOO);
+ Object instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(method.invoke(instance), is((Object) FOO));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testInheritedDefaultMethod() throws Exception {
+ DynamicType.Loaded<?> loaded = new ByteBuddy()
+ .subclass(Class.forName(SINGLE_DEFAULT_METHOD_CLASS))
+ .method(not(isDeclaredBy(Object.class)))
+ .intercept(SuperMethodCall.INSTANCE)
+ .make()
+ .load(Class.forName(SINGLE_DEFAULT_METHOD_CLASS).getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(1));
+ Method method = loaded.getLoaded().getDeclaredMethod(FOO);
+ Object instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(method.invoke(instance), is((Object) FOO));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ @JavaVersionRule.Enforce(8)
+ public void testAmbiguousDirectDefaultMethodThrowsException() throws Exception {
+ new ByteBuddy()
+ .subclass(Object.class)
+ .implement(Class.forName(SINGLE_DEFAULT_METHOD), Class.forName(CONFLICTING_INTERFACE))
+ .intercept(SuperMethodCall.INSTANCE)
+ .make()
+ .load(Class.forName(SINGLE_DEFAULT_METHOD).getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(SuperMethodCall.class).apply();
+ ObjectPropertyAssertion.of(SuperMethodCall.WithoutReturn.class).apply();
+ ObjectPropertyAssertion.of(SuperMethodCall.Appender.class).apply();
+ ObjectPropertyAssertion.of(SuperMethodCall.Appender.TerminationHandler.class).apply();
+ }
+
+ public static class Foo extends CallTraceable {
+
+ public String foo() {
+ register(FOO);
+ return null;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/SuperMethodCallTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/SuperMethodCallTest.java
new file mode 100644
index 0000000..f534a08
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/SuperMethodCallTest.java
@@ -0,0 +1,175 @@
+package net.bytebuddy.implementation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.test.utility.CallTraceable;
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.Matcher;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class SuperMethodCallTest {
+
+ private static final String OBJECT_METHOD = "reference";
+
+ private static final String BOOLEAN_METHOD = "aBoolean";
+
+ private static final String BYTE_METHOD = "aByte";
+
+ private static final String SHORT_METHOD = "aShort";
+
+ private static final String CHAR_METHOD = "aChar";
+
+ private static final String INT_METHOD = "aInt";
+
+ private static final String LONG_METHOD = "aLong";
+
+ private static final String FLOAT_METHOD = "aFloat";
+
+ private static final String DOUBLE_METHOD = "aDouble";
+
+ private static final String VOID_METHOD = "aVoid";
+
+ private static final String PARAMETERS_METHOD = "parameters";
+
+ private static final String STRING_VALUE = "foo";
+
+ private static final boolean BOOLEAN_VALUE = true;
+
+ private static final byte BYTE_VALUE = 42;
+
+ private static final short SHORT_VALUE = 42;
+
+ private static final char CHAR_VALUE = '@';
+
+ private static final int INT_VALUE = 42;
+
+ private static final long LONG_VALUE = 42L;
+
+ private static final float FLOAT_VALUE = 42f;
+
+ private static final double DOUBLE_VALUE = 42d;
+
+ private final Matcher<?> matcher;
+
+ private final String methodName;
+
+ private final Class<?>[] methodParameterTypes;
+
+ private final Object[] methodArguments;
+
+ public SuperMethodCallTest(Matcher<?> matcher,
+ String methodName,
+ Class<?>[] methodParameterTypes,
+ Object[] methodArguments) {
+ this.matcher = matcher;
+ this.methodName = methodName;
+ this.methodParameterTypes = methodParameterTypes;
+ this.methodArguments = methodArguments;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {is(STRING_VALUE), OBJECT_METHOD, new Class<?>[0], new Object[0]},
+ {is(BOOLEAN_VALUE), BOOLEAN_METHOD, new Class<?>[0], new Object[0]},
+ {is(BYTE_VALUE), BYTE_METHOD, new Class<?>[0], new Object[0]},
+ {is(SHORT_VALUE), SHORT_METHOD, new Class<?>[0], new Object[0]},
+ {is(CHAR_VALUE), CHAR_METHOD, new Class<?>[0], new Object[0]},
+ {is(INT_VALUE), INT_METHOD, new Class<?>[0], new Object[0]},
+ {is(LONG_VALUE), LONG_METHOD, new Class<?>[0], new Object[0]},
+ {is(FLOAT_VALUE), FLOAT_METHOD, new Class<?>[0], new Object[0]},
+ {is(DOUBLE_VALUE), DOUBLE_METHOD, new Class<?>[0], new Object[0]},
+ {nullValue(), VOID_METHOD, new Class<?>[0], new Object[0]},
+ {nullValue(), PARAMETERS_METHOD,
+ new Class<?>[]{long.class, float.class, int.class, double.class, Object.class},
+ new Object[]{LONG_VALUE, FLOAT_VALUE, INT_VALUE, DOUBLE_VALUE, STRING_VALUE}}
+ });
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testInstrumentedMethod() throws Exception {
+ DynamicType.Loaded<Foo> loaded = new ByteBuddy()
+ .subclass(Foo.class)
+ .method(isDeclaredBy(Foo.class))
+ .intercept(SuperMethodCall.INSTANCE)
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER);
+ assertThat(loaded.getLoadedAuxiliaryTypes().size(), is(0));
+ assertThat(loaded.getLoaded().getDeclaredMethods().length, is(11));
+ Foo instance = loaded.getLoaded().getDeclaredConstructor().newInstance();
+ assertThat(instance.getClass(), not(CoreMatchers.<Class<?>>is(Foo.class)));
+ assertThat(instance, instanceOf(Foo.class));
+ assertThat(loaded.getLoaded().getDeclaredMethod(methodName, methodParameterTypes)
+ .invoke(instance, methodArguments), (Matcher) matcher);
+ instance.assertOnlyCall(methodName, methodArguments);
+ }
+
+ @SuppressWarnings("unused")
+ public static class Foo extends CallTraceable {
+
+ public Object reference() {
+ register(OBJECT_METHOD);
+ return STRING_VALUE;
+ }
+
+ public boolean aBoolean() {
+ register(BOOLEAN_METHOD);
+ return BOOLEAN_VALUE;
+ }
+
+ public byte aByte() {
+ register(BYTE_METHOD);
+ return BYTE_VALUE;
+ }
+
+ public short aShort() {
+ register(SHORT_METHOD);
+ return SHORT_VALUE;
+ }
+
+ public char aChar() {
+ register(CHAR_METHOD);
+ return CHAR_VALUE;
+ }
+
+ public int aInt() {
+ register(INT_METHOD);
+ return INT_VALUE;
+ }
+
+ public long aLong() {
+ register(LONG_METHOD);
+ return LONG_VALUE;
+ }
+
+ public float aFloat() {
+ register(FLOAT_METHOD);
+ return FLOAT_VALUE;
+ }
+
+ public double aDouble() {
+ register(DOUBLE_METHOD);
+ return DOUBLE_VALUE;
+ }
+
+ public void aVoid() {
+ register(VOID_METHOD);
+ }
+
+ public void parameters(long l, float f, int i, double d, Object o) {
+ register(PARAMETERS_METHOD, l, f, i, d, o);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/AbstractAttributeAppenderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/AbstractAttributeAppenderTest.java
new file mode 100644
index 0000000..ac16e5a
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/AbstractAttributeAppenderTest.java
@@ -0,0 +1,92 @@
+package net.bytebuddy.implementation.attribute;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import static org.mockito.Mockito.*;
+
+public abstract class AbstractAttributeAppenderTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ protected TypeDescription instrumentedType, typeErasure;
+
+ @Mock
+ protected AnnotationValueFilter annotationValueFilter;
+
+ @Mock
+ protected TypeDescription.Generic.OfNonGenericType simpleAnnotatedType;
+
+ @Mock
+ protected TypeDescription.Generic.OfTypeVariable annotatedTypeVariable;
+
+ @Mock
+ protected TypeDescription.Generic.OfNonGenericType annotatedTypeVariableBound;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ when(annotationValueFilter.isRelevant(any(AnnotationDescription.class), any(MethodDescription.InDefinedShape.class))).thenReturn(true);
+ when(annotatedTypeVariable.accept(any(TypeDescription.Generic.Visitor.class))).thenCallRealMethod();
+ when(annotatedTypeVariable.getUpperBounds()).thenReturn(new TypeList.Generic.Explicit(annotatedTypeVariableBound));
+ when(annotatedTypeVariable.asGenericType()).thenReturn(annotatedTypeVariable);
+ when(annotatedTypeVariableBound.accept(any(TypeDescription.Generic.Visitor.class))).thenCallRealMethod();
+ when(annotatedTypeVariableBound.getSort()).thenReturn(TypeDefinition.Sort.VARIABLE);
+ when(annotatedTypeVariableBound.asGenericType()).thenReturn(annotatedTypeVariableBound);
+ when(simpleAnnotatedType.accept(any(TypeDescription.Generic.Visitor.class))).thenCallRealMethod();
+ when(simpleAnnotatedType.asGenericType()).thenReturn(simpleAnnotatedType);
+ when(simpleAnnotatedType.asErasure()).thenReturn(typeErasure);
+ when(annotatedTypeVariable.asErasure()).thenReturn(typeErasure);
+ when(annotatedTypeVariableBound.asErasure()).thenReturn(typeErasure);
+ }
+
+ @Retention(RetentionPolicy.SOURCE)
+ protected @interface Qux {
+
+ class Instance implements Qux {
+
+ @Override
+ public Class<? extends Annotation> annotationType() {
+ return Qux.class;
+ }
+ }
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ protected @interface Baz {
+
+ class Instance implements Baz {
+
+ @Override
+ public Class<? extends Annotation> annotationType() {
+ return Baz.class;
+ }
+ }
+ }
+
+ @Retention(RetentionPolicy.CLASS)
+ protected @interface QuxBaz {
+
+ class Instance implements QuxBaz {
+
+ @Override
+ public Class<? extends Annotation> annotationType() {
+ return QuxBaz.class;
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/AbstractFieldAttributeAppenderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/AbstractFieldAttributeAppenderTest.java
new file mode 100644
index 0000000..c226837
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/AbstractFieldAttributeAppenderTest.java
@@ -0,0 +1,16 @@
+package net.bytebuddy.implementation.attribute;
+
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.objectweb.asm.FieldVisitor;
+
+public abstract class AbstractFieldAttributeAppenderTest extends AbstractAttributeAppenderTest {
+
+ @Mock(answer = Answers.RETURNS_MOCKS)
+ protected FieldVisitor fieldVisitor;
+
+ @Mock
+ protected FieldDescription fieldDescription;
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/AbstractMethodAttributeAppenderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/AbstractMethodAttributeAppenderTest.java
new file mode 100644
index 0000000..6a61962
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/AbstractMethodAttributeAppenderTest.java
@@ -0,0 +1,15 @@
+package net.bytebuddy.implementation.attribute;
+
+import net.bytebuddy.description.method.MethodDescription;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.objectweb.asm.MethodVisitor;
+
+public class AbstractMethodAttributeAppenderTest extends AbstractAttributeAppenderTest {
+
+ @Mock(answer = Answers.RETURNS_MOCKS)
+ protected MethodVisitor methodVisitor;
+
+ @Mock
+ protected MethodDescription methodDescription;
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/AbstractTypeAttributeAppenderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/AbstractTypeAttributeAppenderTest.java
new file mode 100644
index 0000000..d5dc2d2
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/AbstractTypeAttributeAppenderTest.java
@@ -0,0 +1,25 @@
+package net.bytebuddy.implementation.attribute;
+
+import net.bytebuddy.description.type.TypeDescription;
+import org.junit.Before;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.objectweb.asm.ClassVisitor;
+
+import static org.mockito.Mockito.when;
+
+public abstract class AbstractTypeAttributeAppenderTest extends AbstractAttributeAppenderTest {
+
+ @Mock(answer = Answers.RETURNS_MOCKS)
+ protected ClassVisitor classVisitor;
+
+ @Mock
+ protected TypeDescription.Generic typeDescription;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ when(instrumentedType.asGenericType()).thenReturn(typeDescription);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/AnnotationAppenderDefaultTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/AnnotationAppenderDefaultTest.java
new file mode 100644
index 0000000..c7637f5
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/AnnotationAppenderDefaultTest.java
@@ -0,0 +1,380 @@
+package net.bytebuddy.implementation.attribute;
+
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.asm.AsmVisitorWrapper;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.loading.ByteArrayClassLoader;
+import net.bytebuddy.dynamic.loading.PackageDefinitionStrategy;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.hamcrest.CoreMatchers;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.*;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.security.ProtectionDomain;
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class AnnotationAppenderDefaultTest {
+
+ private static final String BAR = "net.bytebuddy.test.Bar";
+
+ private static final String FOOBAR = "foobar";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ @Mock
+ private AnnotationAppender.Target target;
+
+ @Mock
+ private AnnotationValueFilter valueFilter;
+
+ @Mock
+ private Retention retention;
+
+ private AnnotationAppender annotationAppender;
+
+ @Before
+ public void setUp() throws Exception {
+ annotationAppender = new AnnotationAppender.Default(target);
+ }
+
+ @Test
+ public void testNoArgumentAnnotation() throws Exception {
+ Class<?> bar = makeTypeWithAnnotation(new Foo.Instance());
+ assertThat(bar.getAnnotations().length, is(1));
+ assertThat(bar.isAnnotationPresent(Foo.class), is(true));
+ }
+
+ @Test
+ public void testNoArgumentAnnotationSourceCodeRetention() throws Exception {
+ Class<?> bar = makeTypeWithAnnotation(new FooSourceCodeRetention.Instance());
+ assertThat(bar.getAnnotations().length, is(0));
+ }
+
+ @Test
+ public void testNoArgumentAnnotationByteCodeRetention() throws Exception {
+ Class<?> bar = makeTypeWithAnnotation(new FooByteCodeRetention.Instance());
+ assertThat(bar.getAnnotations().length, is(0));
+ }
+
+ @Test
+ public void testNoArgumentAnnotationNoRetention() throws Exception {
+ Class<?> bar = makeTypeWithAnnotation(new FooNoRetention.Instance());
+ assertThat(bar.getAnnotations().length, is(0));
+ }
+
+ @Test
+ public void testSingleArgumentAnnotation() throws Exception {
+ Class<?> bar = makeTypeWithAnnotation(new Qux.Instance(FOOBAR));
+ assertThat(bar.getAnnotations().length, is(1));
+ assertThat(bar.isAnnotationPresent(Qux.class), is(true));
+ assertThat(bar.getAnnotation(Qux.class).value(), is(FOOBAR));
+ }
+
+ @Test
+ public void testMultipleArgumentAnnotation() throws Exception {
+ int[] array = {2, 3, 4};
+ Class<?> bar = makeTypeWithAnnotation(new Baz.Instance(FOOBAR, array, new Foo.Instance(), Baz.Enum.VALUE, Void.class));
+ assertThat(bar.getAnnotations().length, is(1));
+ assertThat(bar.isAnnotationPresent(Baz.class), is(true));
+ assertThat(bar.getAnnotation(Baz.class).value(), is(FOOBAR));
+ assertThat(bar.getAnnotation(Baz.class).array(), is(array));
+ assertThat(bar.getAnnotation(Baz.class).annotation(), is((Foo) new Foo.Instance()));
+ assertThat(bar.getAnnotation(Baz.class).enumeration(), is(Baz.Enum.VALUE));
+ assertThat(bar.getAnnotation(Baz.class).type(), CoreMatchers.<Class<?>>is(Void.class));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testNoArgumentTypeAnnotation() throws Exception {
+ Class<?> bar = makeTypeWithSuperClassAnnotation(new Foo.Instance());
+ TypeDescription.Generic.AnnotationReader annotationReader = TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveSuperClassType(bar);
+ assertThat(annotationReader.asList().size(), is(1));
+ assertThat(annotationReader.asList().isAnnotationPresent(Foo.class), is(true));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testNoArgumentTypeAnnotationSourceCodeRetention() throws Exception {
+ Class<?> bar = makeTypeWithSuperClassAnnotation(new FooSourceCodeRetention.Instance());
+ TypeDescription.Generic.AnnotationReader annotationReader = TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveSuperClassType(bar);
+ assertThat(annotationReader.asList().size(), is(0));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testNoArgumentTypeAnnotationByteCodeRetention() throws Exception {
+ Class<?> bar = makeTypeWithSuperClassAnnotation(new FooByteCodeRetention.Instance());
+ TypeDescription.Generic.AnnotationReader annotationReader = TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveSuperClassType(bar);
+ assertThat(annotationReader.asList().size(), is(0));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testNoArgumentTypeAnnotationNoRetention() throws Exception {
+ Class<?> bar = makeTypeWithSuperClassAnnotation(new FooNoRetention.Instance());
+ TypeDescription.Generic.AnnotationReader annotationReader = TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveSuperClassType(bar);
+ assertThat(annotationReader.asList().size(), is(0));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testSingleTypeArgumentAnnotation() throws Exception {
+ Class<?> bar = makeTypeWithSuperClassAnnotation(new Qux.Instance(FOOBAR));
+ TypeDescription.Generic.AnnotationReader annotationReader = TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveSuperClassType(bar);
+ assertThat(annotationReader.asList().size(), is(1));
+ assertThat(annotationReader.asList().isAnnotationPresent(Qux.class), is(true));
+ assertThat(annotationReader.asList().ofType(Qux.class).load().value(), is(FOOBAR));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testMultipleTypeArgumentAnnotation() throws Exception {
+ int[] array = {2, 3, 4};
+ Class<?> bar = makeTypeWithSuperClassAnnotation(new Baz.Instance(FOOBAR, array, new Foo.Instance(), Baz.Enum.VALUE, Void.class));
+ TypeDescription.Generic.AnnotationReader annotationReader = TypeDescription.Generic.AnnotationReader.DISPATCHER.resolveSuperClassType(bar);
+ assertThat(annotationReader.asList().size(), is(1));
+ assertThat(annotationReader.asList().isAnnotationPresent(Baz.class), is(true));
+ assertThat(annotationReader.asList().ofType(Baz.class).load().value(), is(FOOBAR));
+ assertThat(annotationReader.asList().ofType(Baz.class).load().array(), is(array));
+ assertThat(annotationReader.asList().ofType(Baz.class).load().annotation(), is((Foo) new Foo.Instance()));
+ assertThat(annotationReader.asList().ofType(Baz.class).load().enumeration(), is(Baz.Enum.VALUE));
+ assertThat(annotationReader.asList().ofType(Baz.class).load().type(), CoreMatchers.<Class<?>>is(Void.class));
+ }
+
+ private Class<?> makeTypeWithAnnotation(Annotation annotation) throws Exception {
+ when(valueFilter.isRelevant(any(AnnotationDescription.class), any(MethodDescription.InDefinedShape.class))).thenReturn(true);
+ ClassWriter classWriter = new ClassWriter(AsmVisitorWrapper.NO_FLAGS);
+ classWriter.visit(ClassFileVersion.ofThisVm().getMinorMajorVersion(),
+ Opcodes.ACC_PUBLIC,
+ BAR.replace('.', '/'),
+ null,
+ Type.getInternalName(Object.class),
+ null);
+ AnnotationVisitor annotationVisitor = classWriter.visitAnnotation(Type.getDescriptor(annotation.annotationType()), true);
+ when(target.visit(any(String.class), anyBoolean())).thenReturn(annotationVisitor);
+ AnnotationDescription annotationDescription = AnnotationDescription.ForLoadedAnnotation.of(annotation);
+ annotationAppender.append(annotationDescription, valueFilter);
+ classWriter.visitEnd();
+ Class<?> bar = new ByteArrayClassLoader(getClass().getClassLoader(), Collections.singletonMap(BAR, classWriter.toByteArray())).loadClass(BAR);
+ assertThat(bar.getName(), is(BAR));
+ assertThat(bar.getSuperclass(), CoreMatchers.<Class<?>>is(Object.class));
+ return bar;
+ }
+
+ private Class<?> makeTypeWithSuperClassAnnotation(Annotation annotation) throws Exception {
+ when(valueFilter.isRelevant(any(AnnotationDescription.class), any(MethodDescription.InDefinedShape.class))).thenReturn(true);
+ ClassWriter classWriter = new ClassWriter(AsmVisitorWrapper.NO_FLAGS);
+ classWriter.visit(ClassFileVersion.ofThisVm().getMinorMajorVersion(),
+ Opcodes.ACC_PUBLIC,
+ BAR.replace('.', '/'),
+ null,
+ Type.getInternalName(Object.class),
+ null);
+ AnnotationVisitor annotationVisitor = classWriter.visitTypeAnnotation(TypeReference.newSuperTypeReference(-1).getValue(),
+ null,
+ Type.getDescriptor(annotation.annotationType()),
+ true);
+ when(target.visit(any(String.class), anyBoolean())).thenReturn(annotationVisitor);
+ AnnotationDescription annotationDescription = AnnotationDescription.ForLoadedAnnotation.of(annotation);
+ annotationAppender.append(annotationDescription, valueFilter);
+ classWriter.visitEnd();
+ Class<?> bar = new ByteArrayClassLoader(getClass().getClassLoader(), Collections.singletonMap(BAR, classWriter.toByteArray())).loadClass(BAR);
+ assertThat(bar.getName(), is(BAR));
+ assertThat(bar.getSuperclass(), CoreMatchers.<Class<?>>is(Object.class));
+ return bar;
+ }
+
+ @Test
+ public void testSourceRetentionAnnotation() throws Exception {
+ AnnotationVisitor annotationVisitor = mock(AnnotationVisitor.class);
+ when(target.visit(anyString(), anyBoolean())).thenReturn(annotationVisitor);
+ AnnotationDescription annotationDescription = mock(AnnotationDescription.class);
+ when(annotationDescription.getRetention()).thenReturn(RetentionPolicy.SOURCE);
+ annotationAppender.append(annotationDescription, valueFilter);
+ verifyZeroInteractions(valueFilter);
+ verifyZeroInteractions(annotationVisitor);
+ }
+
+ @Test
+ public void testSourceRetentionTypeAnnotation() throws Exception {
+ AnnotationVisitor annotationVisitor = mock(AnnotationVisitor.class);
+ when(target.visit(anyString(), anyBoolean())).thenReturn(annotationVisitor);
+ AnnotationDescription annotationDescription = mock(AnnotationDescription.class);
+ when(annotationDescription.getRetention()).thenReturn(RetentionPolicy.SOURCE);
+ annotationAppender.append(annotationDescription, valueFilter, 0, null);
+ verifyZeroInteractions(valueFilter);
+ verifyZeroInteractions(annotationVisitor);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AnnotationAppender.Default.class).apply();
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface Foo {
+
+ @SuppressWarnings("all")
+ class Instance implements Foo {
+
+ @Override
+ public Class<? extends Annotation> annotationType() {
+ return Foo.class;
+ }
+ }
+ }
+
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FooSourceCodeRetention {
+
+ @SuppressWarnings("all")
+ class Instance implements FooSourceCodeRetention {
+
+ @Override
+ public Class<? extends Annotation> annotationType() {
+ return FooSourceCodeRetention.class;
+ }
+ }
+ }
+
+ @Retention(RetentionPolicy.CLASS)
+ public @interface FooByteCodeRetention {
+
+ @SuppressWarnings("all")
+ class Instance implements FooByteCodeRetention {
+
+ @Override
+ public Class<? extends Annotation> annotationType() {
+ return FooByteCodeRetention.class;
+ }
+ }
+ }
+
+ public @interface FooNoRetention {
+
+ @SuppressWarnings("all")
+ class Instance implements FooNoRetention {
+
+ @Override
+ public Class<? extends Annotation> annotationType() {
+ return FooNoRetention.class;
+ }
+ }
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface Qux {
+
+ String value();
+
+ @SuppressWarnings("all")
+ class Instance implements Qux {
+
+ private final String value;
+
+ public Instance(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public String value() {
+ return value;
+ }
+
+ @Override
+ public Class<? extends Annotation> annotationType() {
+ return Qux.class;
+ }
+ }
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface Baz {
+
+ String value();
+
+ int[] array();
+
+ Foo annotation();
+
+ Enum enumeration();
+
+ Class<?> type();
+
+ enum Enum {
+ VALUE
+ }
+
+ @SuppressWarnings("all")
+ class Instance implements Baz {
+
+ private final String value;
+
+ private final int[] array;
+
+ private final Foo annotation;
+
+ private final Enum enumeration;
+
+ private final Class<?> type;
+
+ public Instance(String value, int[] array, Foo annotation, Enum enumeration, Class<?> type) {
+ this.value = value;
+ this.array = array;
+ this.annotation = annotation;
+ this.enumeration = enumeration;
+ this.type = type;
+ }
+
+ @Override
+ public String value() {
+ return value;
+ }
+
+ @Override
+ public int[] array() {
+ return array;
+ }
+
+ @Override
+ public Foo annotation() {
+ return annotation;
+ }
+
+ @Override
+ public Enum enumeration() {
+ return enumeration;
+ }
+
+ @Override
+ public Class<?> type() {
+ return type;
+ }
+
+ @Override
+ public Class<? extends Annotation> annotationType() {
+ return Baz.class;
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/AnnotationAppenderForTypeAnnotationsTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/AnnotationAppenderForTypeAnnotationsTest.java
new file mode 100644
index 0000000..a934dfa
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/AnnotationAppenderForTypeAnnotationsTest.java
@@ -0,0 +1,152 @@
+package net.bytebuddy.implementation.attribute;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+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 org.objectweb.asm.TypeReference;
+
+import java.util.Random;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class AnnotationAppenderForTypeAnnotationsTest {
+
+ private static final String FOO = "foo";
+
+ private static final int BAR = 42;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private AnnotationAppender annotationAppender, result;
+
+ @Mock
+ private AnnotationValueFilter annotationValueFilter;
+
+ @Mock
+ private AnnotationDescription annotationDescription;
+
+ @Mock
+ private TypeDescription.Generic typeDescription, second, third;
+
+ @Mock
+ private TypeDescription erasure;
+
+ private TypeDescription.Generic.Visitor<AnnotationAppender> visitor;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ when(annotationAppender.append(eq(annotationDescription), eq(annotationValueFilter), eq(BAR), any(String.class))).thenReturn(result);
+ when(typeDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.Explicit(annotationDescription));
+ when(second.accept(any(TypeDescription.Generic.Visitor.class))).thenReturn(result);
+ when(third.accept(any(TypeDescription.Generic.Visitor.class))).thenReturn(result);
+ when(second.asGenericType()).thenReturn(second);
+ when(third.asGenericType()).thenReturn(third);
+ when(typeDescription.asErasure()).thenReturn(erasure);
+ visitor = new AnnotationAppender.ForTypeAnnotations(annotationAppender, annotationValueFilter, BAR, FOO);
+ }
+
+ @After
+ @SuppressWarnings("unchecked")
+ public void tearDown() throws Exception {
+ verifyZeroInteractions(annotationDescription);
+ verifyZeroInteractions(annotationValueFilter);
+ }
+
+ @Test
+ public void testGenericArray() throws Exception {
+ when(typeDescription.getComponentType()).thenReturn(second);
+ assertThat(visitor.onGenericArray(typeDescription), is(result));
+ verify(annotationAppender).append(annotationDescription, annotationValueFilter, BAR, FOO);
+ verifyNoMoreInteractions(annotationAppender);
+ verify(second).accept(new AnnotationAppender.ForTypeAnnotations(result, annotationValueFilter, BAR, FOO + "["));
+ }
+
+ @Test
+ public void testWildcardUpperBound() throws Exception {
+ when(typeDescription.getLowerBounds()).thenReturn(new TypeList.Generic.Empty());
+ when(typeDescription.getUpperBounds()).thenReturn(new TypeList.Generic.Explicit(second));
+ assertThat(visitor.onWildcard(typeDescription), is(result));
+ verify(annotationAppender).append(annotationDescription, annotationValueFilter, BAR, FOO);
+ verifyNoMoreInteractions(annotationAppender);
+ verify(second).accept(new AnnotationAppender.ForTypeAnnotations(result, annotationValueFilter, BAR, FOO + "*"));
+ }
+
+ @Test
+ public void testWildcardLowerBound() throws Exception {
+ when(typeDescription.getLowerBounds()).thenReturn(new TypeList.Generic.Explicit(second));
+ when(typeDescription.getUpperBounds()).thenReturn(new TypeList.Generic.Explicit(TypeDescription.Generic.OBJECT));
+ assertThat(visitor.onWildcard(typeDescription), is(result));
+ verify(annotationAppender).append(annotationDescription, annotationValueFilter, BAR, FOO);
+ verifyNoMoreInteractions(annotationAppender);
+ verify(second).accept(new AnnotationAppender.ForTypeAnnotations(result, annotationValueFilter, BAR, FOO + "*"));
+ }
+
+ @Test
+ public void testTypeVariable() throws Exception {
+ assertThat(visitor.onTypeVariable(typeDescription), is(result));
+ verify(annotationAppender).append(annotationDescription, annotationValueFilter, BAR, FOO);
+ verifyNoMoreInteractions(annotationAppender);
+ }
+
+ @Test
+ public void testNonGenericArray() throws Exception {
+ when(typeDescription.isArray()).thenReturn(true);
+ when(typeDescription.getComponentType()).thenReturn(second);
+ assertThat(visitor.onNonGenericType(typeDescription), is(result));
+ verify(annotationAppender).append(annotationDescription, annotationValueFilter, BAR, FOO);
+ verifyNoMoreInteractions(annotationAppender);
+ verify(second).accept(new AnnotationAppender.ForTypeAnnotations(result, annotationValueFilter, BAR, FOO + "["));
+ }
+
+ @Test
+ public void testNonGeneric() throws Exception {
+ assertThat(visitor.onNonGenericType(typeDescription), is(result));
+ verify(annotationAppender).append(annotationDescription, annotationValueFilter, BAR, FOO);
+ verifyNoMoreInteractions(annotationAppender);
+ }
+
+ @Test
+ public void testParameterized() throws Exception {
+ when(erasure.getSegmentCount()).thenReturn(1);
+ when(typeDescription.getTypeArguments()).thenReturn(new TypeList.Generic.Explicit(second));
+ when(typeDescription.getOwnerType()).thenReturn(third);
+ assertThat(visitor.onParameterizedType(typeDescription), is(result));
+ verify(annotationAppender).append(annotationDescription, annotationValueFilter, BAR, FOO + ".");
+ verifyNoMoreInteractions(annotationAppender);
+ verify(second).accept(new AnnotationAppender.ForTypeAnnotations(result, annotationValueFilter, BAR, FOO + ".0;"));
+ verify(third).accept(new AnnotationAppender.ForTypeAnnotations(result, annotationValueFilter, BAR, FOO + ""));
+ }
+
+ @Test
+ public void testParameterizedNoOwner() throws Exception {
+ when(typeDescription.getTypeArguments()).thenReturn(new TypeList.Generic.Explicit(second));
+ assertThat(visitor.onParameterizedType(typeDescription), is(result));
+ verify(annotationAppender).append(annotationDescription, annotationValueFilter, BAR, FOO);
+ verifyNoMoreInteractions(annotationAppender);
+ verify(second).accept(new AnnotationAppender.ForTypeAnnotations(result, annotationValueFilter, BAR, FOO + "0;"));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AnnotationAppender.ForTypeAnnotations.class).refine(new ObjectPropertyAssertion.Refinement<TypeReference>() {
+ @Override
+ public void apply(TypeReference mock) {
+ when(mock.getValue()).thenReturn(new Random().nextInt());
+ }
+ }).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/AnnotationAppenderTargetTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/AnnotationAppenderTargetTest.java
new file mode 100644
index 0000000..5f75488
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/AnnotationAppenderTargetTest.java
@@ -0,0 +1,97 @@
+package net.bytebuddy.implementation.attribute;
+
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.TypePath;
+
+import static org.mockito.Mockito.*;
+
+public class AnnotationAppenderTargetTest {
+
+ private static final String FOO = "foo", QUX = "qux";
+
+ private static final int BAR = 42;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private FieldVisitor fieldVisitor;
+
+ @Mock
+ private ClassVisitor classVisitor;
+
+ @Test
+ public void testOnField() throws Exception {
+ new AnnotationAppender.Target.OnField(fieldVisitor).visit(FOO, true);
+ verify(fieldVisitor).visitAnnotation(FOO, true);
+ verifyNoMoreInteractions(fieldVisitor);
+ }
+
+ @Test
+ public void testOnType() throws Exception {
+ new AnnotationAppender.Target.OnType(classVisitor).visit(FOO, true);
+ verify(classVisitor).visitAnnotation(FOO, true);
+ verifyNoMoreInteractions(classVisitor);
+ }
+
+ @Test
+ public void testOnMethod() throws Exception {
+ new AnnotationAppender.Target.OnMethod(methodVisitor).visit(FOO, true);
+ verify(methodVisitor).visitAnnotation(FOO, true);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testOnMethodParameter() throws Exception {
+ new AnnotationAppender.Target.OnMethodParameter(methodVisitor, 0).visit(FOO, true);
+ verify(methodVisitor).visitParameterAnnotation(0, FOO, true);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testTypeAnnotationOnField() throws Exception {
+ new AnnotationAppender.Target.OnField(fieldVisitor).visit(FOO, true, BAR, QUX);
+ verify(fieldVisitor).visitTypeAnnotation(eq(BAR), any(TypePath.class), eq(FOO), eq(true));
+ verifyNoMoreInteractions(fieldVisitor);
+ }
+
+ @Test
+ public void testTypeAnnotationOnType() throws Exception {
+ new AnnotationAppender.Target.OnType(classVisitor).visit(FOO, true, BAR, QUX);
+ verify(classVisitor).visitTypeAnnotation(eq(BAR), any(TypePath.class), eq(FOO), eq(true));
+ verifyNoMoreInteractions(classVisitor);
+ }
+
+ @Test
+ public void testTypeAnnotationOnMethod() throws Exception {
+ new AnnotationAppender.Target.OnMethod(methodVisitor).visit(FOO, true, BAR, QUX);
+ verify(methodVisitor).visitTypeAnnotation(eq(BAR), any(TypePath.class), eq(FOO), eq(true));
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testTypeAnnotationOnMethodParameter() throws Exception {
+ new AnnotationAppender.Target.OnMethodParameter(methodVisitor, 0).visit(FOO, true, BAR, QUX);
+ verify(methodVisitor).visitTypeAnnotation(eq(BAR), any(TypePath.class), eq(FOO), eq(true));
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AnnotationAppender.Target.OnField.class).apply();
+ ObjectPropertyAssertion.of(AnnotationAppender.Target.OnMethod.class).apply();
+ ObjectPropertyAssertion.of(AnnotationAppender.Target.OnMethodParameter.class).apply();
+ ObjectPropertyAssertion.of(AnnotationAppender.Target.OnType.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/AnnotationRetentionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/AnnotationRetentionTest.java
new file mode 100644
index 0000000..2fcef6f
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/AnnotationRetentionTest.java
@@ -0,0 +1,42 @@
+package net.bytebuddy.implementation.attribute;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.CoreMatchers.is;
+
+ at RunWith(Parameterized.class)
+public class AnnotationRetentionTest {
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {AnnotationRetention.ENABLED, true},
+ {AnnotationRetention.DISABLED, false}
+ });
+ }
+
+ private final AnnotationRetention annotationRetention;
+
+ private final boolean enabled;
+
+ public AnnotationRetentionTest(AnnotationRetention annotationRetention, boolean enabled) {
+ this.annotationRetention = annotationRetention;
+ this.enabled = enabled;
+ }
+
+ @Test
+ public void testEnabled() throws Exception {
+ assertThat(annotationRetention.isEnabled(), is(enabled));
+ }
+
+ @Test
+ public void testRetention() throws Exception {
+ assertThat(AnnotationRetention.of(enabled), is(annotationRetention));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/AnnotationValueFilterDefaultTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/AnnotationValueFilterDefaultTest.java
new file mode 100644
index 0000000..1ba0d16
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/AnnotationValueFilterDefaultTest.java
@@ -0,0 +1,92 @@
+package net.bytebuddy.implementation.attribute;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationValue;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class AnnotationValueFilterDefaultTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private AnnotationDescription annotationDescription;
+
+ @Mock
+ private MethodDescription.InDefinedShape methodDescription;
+
+ @Test
+ public void testAppendsDefaults() throws Exception {
+ AnnotationDescription annotationDescription = mock(AnnotationDescription.class);
+ MethodDescription.InDefinedShape methodDescription = mock(MethodDescription.InDefinedShape.class);
+ assertThat(AnnotationValueFilter.Default.APPEND_DEFAULTS.isRelevant(annotationDescription, methodDescription), is(true));
+ verifyZeroInteractions(annotationDescription);
+ verifyZeroInteractions(methodDescription);
+ }
+
+ @Test
+ public void testSkipDefaultsNoDefault() throws Exception {
+ assertThat(AnnotationValueFilter.Default.SKIP_DEFAULTS.isRelevant(annotationDescription, methodDescription), is(true));
+ verifyZeroInteractions(annotationDescription);
+ verify(methodDescription).getDefaultValue();
+ verifyNoMoreInteractions(methodDescription);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testSkipDefaultsNoEquality() throws Exception {
+ when(methodDescription.getDefaultValue()).thenReturn(mock(AnnotationValue.class));
+ when(annotationDescription.getValue(methodDescription)).thenReturn(mock(AnnotationValue.class));
+ assertThat(AnnotationValueFilter.Default.SKIP_DEFAULTS.isRelevant(annotationDescription, methodDescription), is(true));
+ verify(annotationDescription).getValue(methodDescription);
+ verifyNoMoreInteractions(annotationDescription);
+ verify(methodDescription).getDefaultValue();
+ verifyNoMoreInteractions(methodDescription);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testSkipDefaultsEquality() throws Exception {
+ AnnotationValue<?, ?> annotationValue = mock(AnnotationValue.class);
+ when(methodDescription.getDefaultValue()).thenReturn((AnnotationValue) annotationValue);
+ when(annotationDescription.getValue(methodDescription)).thenReturn((AnnotationValue) annotationValue);
+ assertThat(AnnotationValueFilter.Default.SKIP_DEFAULTS.isRelevant(annotationDescription, methodDescription), is(false));
+ verify(annotationDescription).getValue(methodDescription);
+ verifyNoMoreInteractions(annotationDescription);
+ verify(methodDescription).getDefaultValue();
+ verifyNoMoreInteractions(methodDescription);
+ }
+
+ @Test
+ public void testFactory() throws Exception {
+ assertThat(AnnotationValueFilter.Default.SKIP_DEFAULTS.on(mock(FieldDescription.class)),
+ is((AnnotationValueFilter) AnnotationValueFilter.Default.SKIP_DEFAULTS));
+ assertThat(AnnotationValueFilter.Default.SKIP_DEFAULTS.on(mock(MethodDescription.class)),
+ is((AnnotationValueFilter) AnnotationValueFilter.Default.SKIP_DEFAULTS));
+ assertThat(AnnotationValueFilter.Default.SKIP_DEFAULTS.on(mock(TypeDescription.class)),
+ is((AnnotationValueFilter) AnnotationValueFilter.Default.SKIP_DEFAULTS));
+ assertThat(AnnotationValueFilter.Default.APPEND_DEFAULTS.on(mock(FieldDescription.class)),
+ is((AnnotationValueFilter) AnnotationValueFilter.Default.APPEND_DEFAULTS));
+ assertThat(AnnotationValueFilter.Default.APPEND_DEFAULTS.on(mock(MethodDescription.class)),
+ is((AnnotationValueFilter) AnnotationValueFilter.Default.APPEND_DEFAULTS));
+ assertThat(AnnotationValueFilter.Default.APPEND_DEFAULTS.on(mock(TypeDescription.class)),
+ is((AnnotationValueFilter) AnnotationValueFilter.Default.APPEND_DEFAULTS));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AnnotationAppender.Default.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/FieldAttributeAppenderCompoundTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/FieldAttributeAppenderCompoundTest.java
new file mode 100644
index 0000000..2cf8869
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/FieldAttributeAppenderCompoundTest.java
@@ -0,0 +1,38 @@
+package net.bytebuddy.implementation.attribute;
+
+import net.bytebuddy.agent.builder.AgentBuilder;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.util.Collections;
+import java.util.List;
+
+import static org.mockito.Mockito.*;
+
+public class FieldAttributeAppenderCompoundTest extends AbstractFieldAttributeAppenderTest {
+
+ @Mock
+ private FieldAttributeAppender first, second;
+
+ @Test
+ public void testApplication() throws Exception {
+ FieldAttributeAppender fieldAttributeAppender = new FieldAttributeAppender.Compound(first, second);
+ fieldAttributeAppender.apply(fieldVisitor, fieldDescription, annotationValueFilter);
+ verify(first).apply(fieldVisitor, fieldDescription, annotationValueFilter);
+ verifyNoMoreInteractions(first);
+ verify(second).apply(fieldVisitor, fieldDescription, annotationValueFilter);
+ verifyNoMoreInteractions(second);
+ verifyZeroInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(FieldAttributeAppender.Compound.class).create(new ObjectPropertyAssertion.Creator<List<?>>() {
+ @Override
+ public List<?> create() {
+ return Collections.singletonList(mock(FieldAttributeAppender.class));
+ }
+ }).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/FieldAttributeAppenderFactoryCompoundTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/FieldAttributeAppenderFactoryCompoundTest.java
new file mode 100644
index 0000000..f07f193
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/FieldAttributeAppenderFactoryCompoundTest.java
@@ -0,0 +1,50 @@
+package net.bytebuddy.implementation.attribute;
+
+import net.bytebuddy.agent.builder.AgentBuilder;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.util.Collections;
+import java.util.List;
+
+import static org.mockito.Mockito.*;
+
+public class FieldAttributeAppenderFactoryCompoundTest extends AbstractFieldAttributeAppenderTest {
+
+ @Mock
+ private FieldAttributeAppender.Factory firstFactory, secondFactory;
+
+ @Mock
+ private FieldAttributeAppender first, second;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ when(firstFactory.make(instrumentedType)).thenReturn(first);
+ when(secondFactory.make(instrumentedType)).thenReturn(second);
+ }
+
+ @Test
+ public void testApplication() throws Exception {
+ FieldAttributeAppender fieldAttributeAppender = new FieldAttributeAppender.Factory.Compound(firstFactory, secondFactory).make(instrumentedType);
+ fieldAttributeAppender.apply(fieldVisitor, fieldDescription, annotationValueFilter);
+ verify(first).apply(fieldVisitor, fieldDescription, annotationValueFilter);
+ verifyNoMoreInteractions(first);
+ verify(second).apply(fieldVisitor, fieldDescription, annotationValueFilter);
+ verifyNoMoreInteractions(second);
+ verifyZeroInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(FieldAttributeAppender.Factory.Compound.class).create(new ObjectPropertyAssertion.Creator<List<?>>() {
+ @Override
+ public List<?> create() {
+ return Collections.singletonList(mock(FieldAttributeAppender.Factory.class));
+ }
+ }).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/FieldAttributeAppenderForAnnotationsTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/FieldAttributeAppenderForAnnotationsTest.java
new file mode 100644
index 0000000..e275588
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/FieldAttributeAppenderForAnnotationsTest.java
@@ -0,0 +1,65 @@
+package net.bytebuddy.implementation.attribute;
+
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+import org.objectweb.asm.Type;
+
+import java.lang.annotation.Annotation;
+
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class FieldAttributeAppenderForAnnotationsTest extends AbstractFieldAttributeAppenderTest {
+
+ @Test
+ public void testAnnotationAppenderNoRetention() throws Exception {
+ new FieldAttributeAppender.Explicit(new AnnotationList.ForLoadedAnnotations(new Qux.Instance())).apply(fieldVisitor, fieldDescription, annotationValueFilter);
+ verifyZeroInteractions(fieldVisitor);
+ verifyZeroInteractions(fieldDescription);
+ }
+
+ @Test
+ public void testAnnotationAppenderRuntimeRetention() throws Exception {
+ new FieldAttributeAppender.Explicit(new AnnotationList.ForLoadedAnnotations(new Baz.Instance())).apply(fieldVisitor, fieldDescription, annotationValueFilter);
+ verify(fieldVisitor).visitAnnotation(Type.getDescriptor(Baz.class), true);
+ verifyNoMoreInteractions(fieldVisitor);
+ verifyZeroInteractions(fieldDescription);
+ }
+
+ @Test
+ public void testAnnotationAppenderByteCodeRetention() throws Exception {
+ new FieldAttributeAppender.Explicit(new AnnotationList.ForLoadedAnnotations(new QuxBaz.Instance())).apply(fieldVisitor, fieldDescription, annotationValueFilter);
+ verify(fieldVisitor).visitAnnotation(Type.getDescriptor(QuxBaz.class), false);
+ verifyNoMoreInteractions(fieldVisitor);
+ verifyZeroInteractions(fieldDescription);
+ }
+
+ @Test
+ public void testFactory() throws Exception {
+ FieldAttributeAppender.Explicit fieldAttributeAppender = new FieldAttributeAppender.Explicit(new AnnotationList.ForLoadedAnnotations(new QuxBaz.Instance()));
+ assertThat(fieldAttributeAppender.make(instrumentedType), sameInstance((FieldAttributeAppender) fieldAttributeAppender));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(FieldAttributeAppender.Explicit.class).generate(new ObjectPropertyAssertion.Generator<Annotation>() {
+ @Override
+ public Class<? extends Annotation> generate() {
+ return SimpleAnnotation.class;
+ }
+ }).refine(new ObjectPropertyAssertion.Refinement<SimpleAnnotation>() {
+ @Override
+ public void apply(SimpleAnnotation mock) {
+ doReturn(SimpleAnnotation.class).when(mock).annotationType();
+ when(mock.value()).thenReturn("annotation" + System.identityHashCode(mock));
+ }
+ }).apply();
+ }
+
+ public @interface SimpleAnnotation {
+
+ String value();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/FieldAttributeAppenderForInstrumentedFieldTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/FieldAttributeAppenderForInstrumentedFieldTest.java
new file mode 100644
index 0000000..436cf55
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/FieldAttributeAppenderForInstrumentedFieldTest.java
@@ -0,0 +1,99 @@
+package net.bytebuddy.implementation.attribute;
+
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.type.TypeDescription;
+import org.junit.Test;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.TypeReference;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class FieldAttributeAppenderForInstrumentedFieldTest extends AbstractFieldAttributeAppenderTest {
+
+ @Test
+ public void testFactory() throws Exception {
+ assertThat(FieldAttributeAppender.ForInstrumentedField.INSTANCE.make(instrumentedType),
+ is((FieldAttributeAppender) FieldAttributeAppender.ForInstrumentedField.INSTANCE));
+ }
+
+ @Test
+ public void testAnnotationAppenderNoRetention() throws Exception {
+ when(fieldDescription.getType()).thenReturn(TypeDescription.Generic.OBJECT);
+ when(fieldDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Qux.Instance()));
+ FieldAttributeAppender.ForInstrumentedField.INSTANCE.apply(fieldVisitor, fieldDescription, annotationValueFilter);
+ verifyZeroInteractions(fieldVisitor);
+ verify(fieldDescription).getDeclaredAnnotations();
+ verify(fieldDescription).getType();
+ verifyNoMoreInteractions(fieldDescription);
+ }
+
+ @Test
+ public void testAnnotationAppenderRuntimeRetention() throws Exception {
+ when(fieldDescription.getType()).thenReturn(TypeDescription.Generic.OBJECT);
+ when(fieldDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Baz.Instance()));
+ FieldAttributeAppender.ForInstrumentedField.INSTANCE.apply(fieldVisitor, fieldDescription, annotationValueFilter);
+ verify(fieldVisitor).visitAnnotation(Type.getDescriptor(Baz.class), true);
+ verifyNoMoreInteractions(fieldVisitor);
+ verify(fieldDescription).getDeclaredAnnotations();
+ verify(fieldDescription).getType();
+ verifyNoMoreInteractions(fieldDescription);
+ }
+
+ @Test
+ public void testAnnotationAppenderByteCodeRetention() throws Exception {
+ when(fieldDescription.getType()).thenReturn(TypeDescription.Generic.OBJECT);
+ when(fieldDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new QuxBaz.Instance()));
+ FieldAttributeAppender.ForInstrumentedField.INSTANCE.apply(fieldVisitor, fieldDescription, annotationValueFilter);
+ verify(fieldVisitor).visitAnnotation(Type.getDescriptor(QuxBaz.class), false);
+ verifyNoMoreInteractions(fieldVisitor);
+ verify(fieldDescription).getDeclaredAnnotations();
+ verify(fieldDescription).getType();
+ verifyNoMoreInteractions(fieldDescription);
+ }
+
+ @Test
+ public void testFieldTypeTypeAnnotationNoRetention() throws Exception {
+ when(fieldDescription.getType()).thenReturn(simpleAnnotatedType);
+ when(simpleAnnotatedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Qux.Instance()));
+ when(fieldDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ FieldAttributeAppender.ForInstrumentedField.INSTANCE.apply(fieldVisitor, fieldDescription, annotationValueFilter);
+ verifyZeroInteractions(fieldVisitor);
+ verify(fieldDescription).getDeclaredAnnotations();
+ verify(fieldDescription).getType();
+ verifyNoMoreInteractions(fieldDescription);
+ }
+
+ @Test
+ public void testFieldTypeTypeAnnotationRuntimeRetention() throws Exception {
+ when(fieldDescription.getType()).thenReturn(simpleAnnotatedType);
+ when(simpleAnnotatedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Baz.Instance()));
+ when(fieldDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ FieldAttributeAppender.ForInstrumentedField.INSTANCE.apply(fieldVisitor, fieldDescription, annotationValueFilter);
+ verify(fieldVisitor).visitTypeAnnotation(TypeReference.newTypeReference(TypeReference.FIELD).getValue(),
+ null,
+ Type.getDescriptor(Baz.class),
+ true);
+ verifyNoMoreInteractions(fieldVisitor);
+ verify(fieldDescription).getDeclaredAnnotations();
+ verify(fieldDescription).getType();
+ verifyNoMoreInteractions(fieldDescription);
+ }
+
+ @Test
+ public void testFieldTypeTypeAnnotationByteCodeRetention() throws Exception {
+ when(fieldDescription.getType()).thenReturn(simpleAnnotatedType);
+ when(simpleAnnotatedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new QuxBaz.Instance()));
+ when(fieldDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ FieldAttributeAppender.ForInstrumentedField.INSTANCE.apply(fieldVisitor, fieldDescription, annotationValueFilter);
+ verify(fieldVisitor).visitTypeAnnotation(TypeReference.newTypeReference(TypeReference.FIELD).getValue(),
+ null,
+ Type.getDescriptor(QuxBaz.class),
+ false);
+ verifyNoMoreInteractions(fieldVisitor);
+ verify(fieldDescription).getDeclaredAnnotations();
+ verify(fieldDescription).getType();
+ verifyNoMoreInteractions(fieldDescription);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/FieldAttributeAppenderNoOpTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/FieldAttributeAppenderNoOpTest.java
new file mode 100644
index 0000000..45fab51
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/FieldAttributeAppenderNoOpTest.java
@@ -0,0 +1,29 @@
+package net.bytebuddy.implementation.attribute;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+public class FieldAttributeAppenderNoOpTest extends AbstractFieldAttributeAppenderTest {
+
+ @Test
+ public void testApplication() throws Exception {
+ FieldAttributeAppender.NoOp.INSTANCE.apply(fieldVisitor, fieldDescription, annotationValueFilter);
+ verifyZeroInteractions(fieldVisitor);
+ verifyZeroInteractions(fieldDescription);
+ }
+
+ @Test
+ public void testFactory() throws Exception {
+ assertThat(FieldAttributeAppender.NoOp.INSTANCE.make(instrumentedType), sameInstance((FieldAttributeAppender) FieldAttributeAppender.NoOp.INSTANCE));
+ verifyZeroInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(FieldAttributeAppender.NoOp.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/MethodAttributeAppenderCompoundTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/MethodAttributeAppenderCompoundTest.java
new file mode 100644
index 0000000..5443b6e
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/MethodAttributeAppenderCompoundTest.java
@@ -0,0 +1,37 @@
+package net.bytebuddy.implementation.attribute;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.util.Collections;
+import java.util.List;
+
+import static org.mockito.Mockito.*;
+
+public class MethodAttributeAppenderCompoundTest extends AbstractMethodAttributeAppenderTest {
+
+ @Mock
+ private MethodAttributeAppender first, second;
+
+ @Test
+ public void testApplication() throws Exception {
+ MethodAttributeAppender methodAttributeAppender = new MethodAttributeAppender.Compound(first, second);
+ methodAttributeAppender.apply(methodVisitor, methodDescription, annotationValueFilter);
+ verify(first).apply(methodVisitor, methodDescription, annotationValueFilter);
+ verifyNoMoreInteractions(first);
+ verify(second).apply(methodVisitor, methodDescription, annotationValueFilter);
+ verifyNoMoreInteractions(second);
+ verifyZeroInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodAttributeAppender.Compound.class).create(new ObjectPropertyAssertion.Creator<List<?>>() {
+ @Override
+ public List<?> create() {
+ return Collections.singletonList(mock(MethodAttributeAppender.class));
+ }
+ }).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/MethodAttributeAppenderExplicitTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/MethodAttributeAppenderExplicitTest.java
new file mode 100644
index 0000000..873e309
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/MethodAttributeAppenderExplicitTest.java
@@ -0,0 +1,160 @@
+package net.bytebuddy.implementation.attribute;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.utility.RandomString;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.objectweb.asm.Type;
+
+import java.lang.annotation.Annotation;
+
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class MethodAttributeAppenderExplicitTest extends AbstractMethodAttributeAppenderTest {
+
+ private static final int PARAMETER_INDEX = 0;
+
+ @Mock
+ private MethodDescription methodDescription;
+
+ @Mock
+ private ParameterDescription parameterDescription;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ super.setUp();
+ when(methodDescription.getParameters()).thenReturn((ParameterList) new ParameterList.Explicit<ParameterDescription>(parameterDescription));
+ when(annotationValueFilter.isRelevant(any(AnnotationDescription.class), any(MethodDescription.InDefinedShape.class))).thenReturn(true);
+ }
+
+ @Test
+ public void testAnnotationAppenderNoRetention() throws Exception {
+ new MethodAttributeAppender.Explicit(new AnnotationList.ForLoadedAnnotations(new Qux.Instance()))
+ .apply(methodVisitor, methodDescription, annotationValueFilter);
+ verifyZeroInteractions(methodVisitor);
+ verifyZeroInteractions(methodDescription);
+ }
+
+ @Test
+ public void testAnnotationAppenderRuntimeRetention() throws Exception {
+ new MethodAttributeAppender.Explicit(new AnnotationList.ForLoadedAnnotations(new Baz.Instance()))
+ .apply(methodVisitor, methodDescription, annotationValueFilter);
+ verify(methodVisitor).visitAnnotation(Type.getDescriptor(Baz.class), true);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(methodDescription);
+ }
+
+ @Test
+ public void testAnnotationAppenderByteCodeRetention() throws Exception {
+ new MethodAttributeAppender.Explicit(new AnnotationList.ForLoadedAnnotations(new QuxBaz.Instance()))
+ .apply(methodVisitor, methodDescription, annotationValueFilter);
+ verify(methodVisitor).visitAnnotation(Type.getDescriptor(QuxBaz.class), false);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(methodDescription);
+ }
+
+ @Test
+ public void testAnnotationAppenderForParameterNoRetention() throws Exception {
+ new MethodAttributeAppender.Explicit(PARAMETER_INDEX, new AnnotationList.ForLoadedAnnotations(new Qux.Instance()))
+ .apply(methodVisitor, methodDescription, annotationValueFilter);
+ verifyZeroInteractions(methodVisitor);
+ verify(methodDescription).getParameters();
+ verifyNoMoreInteractions(methodDescription);
+ }
+
+ @Test
+ public void testAnnotationAppenderForParameterRuntimeRetention() throws Exception {
+ new MethodAttributeAppender.Explicit(PARAMETER_INDEX, new AnnotationList.ForLoadedAnnotations(new Baz.Instance()))
+ .apply(methodVisitor, methodDescription, annotationValueFilter);
+ verify(methodVisitor).visitParameterAnnotation(PARAMETER_INDEX, Type.getDescriptor(Baz.class), true);
+ verifyNoMoreInteractions(methodVisitor);
+ verify(methodDescription).getParameters();
+ verifyNoMoreInteractions(methodDescription);
+ }
+
+ @Test
+ public void testAnnotationAppenderForParameterByteCodeRetention() throws Exception {
+ new MethodAttributeAppender.Explicit(PARAMETER_INDEX, new AnnotationList.ForLoadedAnnotations(new QuxBaz.Instance()))
+ .apply(methodVisitor, methodDescription, annotationValueFilter);
+ verify(methodVisitor).visitParameterAnnotation(PARAMETER_INDEX, Type.getDescriptor(QuxBaz.class), false);
+ verifyNoMoreInteractions(methodVisitor);
+ verify(methodDescription).getParameters();
+ verifyNoMoreInteractions(methodDescription);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testAnnotationAppenderNotEnoughParameters() throws Exception {
+ new MethodAttributeAppender.Explicit(PARAMETER_INDEX + 1, new AnnotationList.ForLoadedAnnotations(new Baz.Instance()))
+ .apply(methodVisitor, methodDescription, annotationValueFilter);
+ }
+
+ @Test
+ public void testFactory() throws Exception {
+ MethodAttributeAppender.Explicit methodAttributeAppender = new MethodAttributeAppender.Explicit(new AnnotationList.ForLoadedAnnotations(new Qux.Instance()));
+ assertThat(methodAttributeAppender.make(instrumentedType), sameInstance((MethodAttributeAppender) methodAttributeAppender));
+ verifyZeroInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testOfMethod() throws Exception {
+ when(methodDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Baz.Instance()));
+ when(parameterDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Baz.Instance()));
+ MethodAttributeAppender methodAttributeAppender = MethodAttributeAppender.Explicit.of(methodDescription).make(instrumentedType);
+ methodAttributeAppender.apply(methodVisitor, methodDescription, annotationValueFilter);
+ verify(methodVisitor).visitAnnotation(Type.getDescriptor(Baz.class), true);
+ verify(methodVisitor).visitParameterAnnotation(PARAMETER_INDEX, Type.getDescriptor(Baz.class), true);
+ verifyNoMoreInteractions(methodVisitor);
+ verify(methodDescription, times(2)).getParameters();
+ verify(methodDescription).getDeclaredAnnotations();
+ verifyNoMoreInteractions(methodDescription);
+ verify(parameterDescription).getIndex();
+ verify(parameterDescription).getDeclaredAnnotations();
+ verifyNoMoreInteractions(parameterDescription);
+ verifyZeroInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodAttributeAppender.Explicit.class).create(new ObjectPropertyAssertion.Creator<Annotation>() {
+ @Override
+ public Annotation create() {
+ return new SimpleAnnotation.Instance(RandomString.make());
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(MethodAttributeAppender.Explicit.Target.OnMethod.class).apply();
+ ObjectPropertyAssertion.of(MethodAttributeAppender.Explicit.Target.OnMethodParameter.class).apply();
+ }
+
+ public @interface SimpleAnnotation {
+
+ String value();
+
+ class Instance implements SimpleAnnotation {
+
+ private final String value;
+
+ public Instance(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public String value() {
+ return value;
+ }
+
+ @Override
+ public Class<? extends Annotation> annotationType() {
+ return SimpleAnnotation.class;
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/MethodAttributeAppenderFactoryCompoundTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/MethodAttributeAppenderFactoryCompoundTest.java
new file mode 100644
index 0000000..bbe1bce
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/MethodAttributeAppenderFactoryCompoundTest.java
@@ -0,0 +1,50 @@
+package net.bytebuddy.implementation.attribute;
+
+import net.bytebuddy.agent.builder.AgentBuilder;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.util.Collections;
+import java.util.List;
+
+import static org.mockito.Mockito.*;
+
+public class MethodAttributeAppenderFactoryCompoundTest extends AbstractMethodAttributeAppenderTest {
+
+ @Mock
+ private MethodAttributeAppender.Factory firstFactory, secondFactory;
+
+ @Mock
+ private MethodAttributeAppender first, second;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ when(firstFactory.make(instrumentedType)).thenReturn(first);
+ when(secondFactory.make(instrumentedType)).thenReturn(second);
+ }
+
+ @Test
+ public void testApplication() throws Exception {
+ MethodAttributeAppender methodAttributeAppender = new MethodAttributeAppender.Factory.Compound(firstFactory, secondFactory).make(instrumentedType);
+ methodAttributeAppender.apply(methodVisitor, methodDescription, annotationValueFilter);
+ verify(first).apply(methodVisitor, methodDescription, annotationValueFilter);
+ verifyNoMoreInteractions(first);
+ verify(second).apply(methodVisitor, methodDescription, annotationValueFilter);
+ verifyNoMoreInteractions(second);
+ verifyZeroInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodAttributeAppender.Factory.Compound.class).create(new ObjectPropertyAssertion.Creator<List<?>>() {
+ @Override
+ public List<?> create() {
+ return Collections.singletonList(mock(MethodAttributeAppender.Factory.class));
+ }
+ }).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/MethodAttributeAppenderForInstrumentedMethodOtherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/MethodAttributeAppenderForInstrumentedMethodOtherTest.java
new file mode 100644
index 0000000..95abfca
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/MethodAttributeAppenderForInstrumentedMethodOtherTest.java
@@ -0,0 +1,128 @@
+package net.bytebuddy.implementation.attribute;
+
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.TypeReference;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class MethodAttributeAppenderForInstrumentedMethodOtherTest extends AbstractMethodAttributeAppenderTest {
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testReceiverTypeTypeAnnotationsIgnored() throws Exception {
+ when(methodDescription.getParameters()).thenReturn((ParameterList) new ParameterList.Empty<ParameterDescription>());
+ when(methodDescription.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(methodDescription.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(methodDescription.getExceptionTypes()).thenReturn(new TypeList.Generic.Empty());
+ when(methodDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(methodDescription.getReceiverType()).thenReturn(simpleAnnotatedType);
+ when(simpleAnnotatedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new QuxBaz.Instance()));
+ MethodAttributeAppender.ForInstrumentedMethod.EXCLUDING_RECEIVER.apply(methodVisitor, methodDescription, annotationValueFilter);
+ verifyZeroInteractions(methodVisitor);
+ verify(methodDescription).getDeclaredAnnotations();
+ verify(methodDescription).getParameters();
+ verify(methodDescription).getReturnType();
+ verify(methodDescription).getExceptionTypes();
+ verify(methodDescription).getTypeVariables();
+ verifyNoMoreInteractions(methodDescription);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testReceiverTypeTypeAnnotationsNoRetention() throws Exception {
+ when(methodDescription.getParameters()).thenReturn((ParameterList) new ParameterList.Empty<ParameterDescription>());
+ when(methodDescription.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(methodDescription.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(methodDescription.getExceptionTypes()).thenReturn(new TypeList.Generic.Empty());
+ when(methodDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(methodDescription.getReceiverType()).thenReturn(simpleAnnotatedType);
+ when(simpleAnnotatedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Qux.Instance()));
+ MethodAttributeAppender.ForInstrumentedMethod.INCLUDING_RECEIVER.apply(methodVisitor, methodDescription, annotationValueFilter);
+ verifyZeroInteractions(methodVisitor);
+ verify(methodDescription).getDeclaredAnnotations();
+ verify(methodDescription).getParameters();
+ verify(methodDescription).getReturnType();
+ verify(methodDescription).getExceptionTypes();
+ verify(methodDescription).getTypeVariables();
+ verify(methodDescription).getReceiverType();
+ verifyNoMoreInteractions(methodDescription);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testReceiverTypeTypeAnnotationsRuntimeRetention() throws Exception {
+ when(methodDescription.getParameters()).thenReturn((ParameterList) new ParameterList.Empty<ParameterDescription>());
+ when(methodDescription.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(methodDescription.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(methodDescription.getExceptionTypes()).thenReturn(new TypeList.Generic.Empty());
+ when(methodDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(methodDescription.getReceiverType()).thenReturn(simpleAnnotatedType);
+ when(simpleAnnotatedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Baz.Instance()));
+ MethodAttributeAppender.ForInstrumentedMethod.INCLUDING_RECEIVER.apply(methodVisitor, methodDescription, annotationValueFilter);
+ verify(methodVisitor).visitTypeAnnotation(TypeReference.newTypeReference(TypeReference.METHOD_RECEIVER).getValue(),
+ null,
+ Type.getDescriptor(AbstractAttributeAppenderTest.Baz.class),
+ true);
+ verifyNoMoreInteractions(methodVisitor);
+ verify(methodDescription).getDeclaredAnnotations();
+ verify(methodDescription).getParameters();
+ verify(methodDescription).getReturnType();
+ verify(methodDescription).getExceptionTypes();
+ verify(methodDescription).getTypeVariables();
+ verify(methodDescription).getReceiverType();
+ verifyNoMoreInteractions(methodDescription);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testReceiverTypeTypeAnnotationsClassFileRetention() throws Exception {
+ when(methodDescription.getParameters()).thenReturn((ParameterList) new ParameterList.Empty<ParameterDescription>());
+ when(methodDescription.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(methodDescription.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(methodDescription.getExceptionTypes()).thenReturn(new TypeList.Generic.Empty());
+ when(methodDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(methodDescription.getReceiverType()).thenReturn(simpleAnnotatedType);
+ when(simpleAnnotatedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new QuxBaz.Instance()));
+ MethodAttributeAppender.ForInstrumentedMethod.INCLUDING_RECEIVER.apply(methodVisitor, methodDescription, annotationValueFilter);
+ verify(methodVisitor).visitTypeAnnotation(TypeReference.newTypeReference(TypeReference.METHOD_RECEIVER).getValue(),
+ null,
+ Type.getDescriptor(AbstractAttributeAppenderTest.QuxBaz.class),
+ false);
+ verifyNoMoreInteractions(methodVisitor);
+ verify(methodDescription).getDeclaredAnnotations();
+ verify(methodDescription).getParameters();
+ verify(methodDescription).getReturnType();
+ verify(methodDescription).getExceptionTypes();
+ verify(methodDescription).getTypeVariables();
+ verify(methodDescription).getReceiverType();
+ verifyNoMoreInteractions(methodDescription);
+ }
+
+ @Test
+ public void testIncludingFactory() throws Exception {
+ TypeDescription typeDescription = mock(TypeDescription.class);
+ assertThat(MethodAttributeAppender.ForInstrumentedMethod.INCLUDING_RECEIVER.make(typeDescription),
+ is((MethodAttributeAppender) MethodAttributeAppender.ForInstrumentedMethod.INCLUDING_RECEIVER));
+ }
+
+ @Test
+ public void testExcludingFactory() throws Exception {
+ TypeDescription typeDescription = mock(TypeDescription.class);
+ assertThat(MethodAttributeAppender.ForInstrumentedMethod.EXCLUDING_RECEIVER.make(typeDescription),
+ is((MethodAttributeAppender) MethodAttributeAppender.ForInstrumentedMethod.EXCLUDING_RECEIVER));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodAttributeAppender.ForInstrumentedMethod.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/MethodAttributeAppenderForInstrumentedMethodTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/MethodAttributeAppenderForInstrumentedMethodTest.java
new file mode 100644
index 0000000..798f0e4
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/MethodAttributeAppenderForInstrumentedMethodTest.java
@@ -0,0 +1,380 @@
+package net.bytebuddy.implementation.attribute;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.TypeReference;
+
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class MethodAttributeAppenderForInstrumentedMethodTest extends AbstractMethodAttributeAppenderTest {
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {MethodAttributeAppender.ForInstrumentedMethod.EXCLUDING_RECEIVER},
+ {MethodAttributeAppender.ForInstrumentedMethod.INCLUDING_RECEIVER}
+ });
+ }
+
+ private final MethodAttributeAppender methodAttributeAppender;
+
+ public MethodAttributeAppenderForInstrumentedMethodTest(MethodAttributeAppender methodAttributeAppender) {
+ this.methodAttributeAppender = methodAttributeAppender;
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ verify(methodDescription).getDeclaredAnnotations();
+ verify(methodDescription).getParameters();
+ verify(methodDescription).getReturnType();
+ verify(methodDescription).getExceptionTypes();
+ verify(methodDescription).getTypeVariables();
+ if (methodAttributeAppender == MethodAttributeAppender.ForInstrumentedMethod.INCLUDING_RECEIVER) {
+ verify(methodDescription).getReceiverType();
+ }
+ verifyNoMoreInteractions(methodDescription);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMethodAnnotationNoRetention() throws Exception {
+ when(annotationValueFilter.isRelevant(any(AnnotationDescription.class), any(MethodDescription.InDefinedShape.class))).thenReturn(true);
+ when(methodDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Qux.Instance()));
+ when(methodDescription.getParameters()).thenReturn((ParameterList) new ParameterList.Empty<ParameterDescription>());
+ when(methodDescription.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(methodDescription.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(methodDescription.getExceptionTypes()).thenReturn(new TypeList.Generic.Empty());
+ methodAttributeAppender.apply(methodVisitor, methodDescription, annotationValueFilter);
+ verifyZeroInteractions(methodVisitor);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMethodAnnotationRuntimeRetention() throws Exception {
+ when(annotationValueFilter.isRelevant(any(AnnotationDescription.class), any(MethodDescription.InDefinedShape.class))).thenReturn(true);
+ when(methodDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Baz.Instance()));
+ when(methodDescription.getParameters()).thenReturn((ParameterList) new ParameterList.Empty<ParameterDescription>());
+ when(methodDescription.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(methodDescription.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(methodDescription.getExceptionTypes()).thenReturn(new TypeList.Generic.Empty());
+ methodAttributeAppender.apply(methodVisitor, methodDescription, annotationValueFilter);
+ verify(methodVisitor).visitAnnotation(Type.getDescriptor(Baz.class), true);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMethodAnnotationClassFileRetention() throws Exception {
+ when(annotationValueFilter.isRelevant(any(AnnotationDescription.class), any(MethodDescription.InDefinedShape.class))).thenReturn(true);
+ when(methodDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new QuxBaz.Instance()));
+ when(methodDescription.getParameters()).thenReturn((ParameterList) new ParameterList.Empty<ParameterDescription>());
+ when(methodDescription.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(methodDescription.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(methodDescription.getExceptionTypes()).thenReturn(new TypeList.Generic.Empty());
+ methodAttributeAppender.apply(methodVisitor, methodDescription, annotationValueFilter);
+ verify(methodVisitor).visitAnnotation(Type.getDescriptor(QuxBaz.class), false);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMethodParameterAnnotationNoRetention() throws Exception {
+ when(annotationValueFilter.isRelevant(any(AnnotationDescription.class), any(MethodDescription.InDefinedShape.class))).thenReturn(true);
+ when(methodDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ ParameterDescription parameterDescription = mock(ParameterDescription.class);
+ when(parameterDescription.getType()).thenReturn(TypeDescription.Generic.OBJECT);
+ when(parameterDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Qux.Instance()));
+ when(methodDescription.getParameters()).thenReturn((ParameterList) new ParameterList.Explicit<ParameterDescription>(parameterDescription));
+ when(methodDescription.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(methodDescription.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(methodDescription.getExceptionTypes()).thenReturn(new TypeList.Generic.Empty());
+ methodAttributeAppender.apply(methodVisitor, methodDescription, annotationValueFilter);
+ verifyZeroInteractions(methodVisitor);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMethodParameterAnnotationRuntimeRetention() throws Exception {
+ when(annotationValueFilter.isRelevant(any(AnnotationDescription.class), any(MethodDescription.InDefinedShape.class))).thenReturn(true);
+ when(methodDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ ParameterDescription parameterDescription = mock(ParameterDescription.class);
+ when(parameterDescription.getType()).thenReturn(TypeDescription.Generic.OBJECT);
+ when(parameterDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Baz.Instance()));
+ when(methodDescription.getParameters()).thenReturn((ParameterList) new ParameterList.Explicit<ParameterDescription>(parameterDescription));
+ when(methodDescription.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(methodDescription.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(methodDescription.getExceptionTypes()).thenReturn(new TypeList.Generic.Empty());
+ methodAttributeAppender.apply(methodVisitor, methodDescription, annotationValueFilter);
+ verify(methodVisitor).visitParameterAnnotation(0, Type.getDescriptor(Baz.class), true);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMethodParameterAnnotationClassFileRetention() throws Exception {
+ when(annotationValueFilter.isRelevant(any(AnnotationDescription.class), any(MethodDescription.InDefinedShape.class))).thenReturn(true);
+ when(methodDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ ParameterDescription parameterDescription = mock(ParameterDescription.class);
+ when(parameterDescription.getType()).thenReturn(TypeDescription.Generic.OBJECT);
+ when(parameterDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new QuxBaz.Instance()));
+ when(methodDescription.getParameters()).thenReturn((ParameterList) new ParameterList.Explicit<ParameterDescription>(parameterDescription));
+ when(methodDescription.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(methodDescription.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(methodDescription.getExceptionTypes()).thenReturn(new TypeList.Generic.Empty());
+ methodAttributeAppender.apply(methodVisitor, methodDescription, annotationValueFilter);
+ verify(methodVisitor).visitParameterAnnotation(0, Type.getDescriptor(QuxBaz.class), false);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMethodReturnTypeTypeAnnotationNoRetention() throws Exception {
+ when(annotationValueFilter.isRelevant(any(AnnotationDescription.class), any(MethodDescription.InDefinedShape.class))).thenReturn(true);
+ when(methodDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(methodDescription.getParameters()).thenReturn((ParameterList) new ParameterList.Empty<ParameterDescription>());
+ when(methodDescription.getReturnType()).thenReturn(simpleAnnotatedType);
+ when(simpleAnnotatedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Qux.Instance()));
+ when(methodDescription.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(methodDescription.getExceptionTypes()).thenReturn(new TypeList.Generic.Empty());
+ methodAttributeAppender.apply(methodVisitor, methodDescription, annotationValueFilter);
+ verifyZeroInteractions(methodVisitor);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMethodReturnTypeTypeAnnotationRuntimeRetention() throws Exception {
+ when(annotationValueFilter.isRelevant(any(AnnotationDescription.class), any(MethodDescription.InDefinedShape.class))).thenReturn(true);
+ when(methodDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(methodDescription.getParameters()).thenReturn((ParameterList) new ParameterList.Empty<ParameterDescription>());
+ when(methodDescription.getReturnType()).thenReturn(simpleAnnotatedType);
+ when(methodDescription.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(methodDescription.getExceptionTypes()).thenReturn(new TypeList.Generic.Empty());
+ when(simpleAnnotatedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Baz.Instance()));
+ methodAttributeAppender.apply(methodVisitor, methodDescription, annotationValueFilter);
+ verify(methodVisitor).visitTypeAnnotation(TypeReference.newTypeReference(TypeReference.METHOD_RETURN).getValue(),
+ null,
+ Type.getDescriptor(Baz.class),
+ true);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMethodReturnTypeTypeAnnotationClassFileRetention() throws Exception {
+ when(annotationValueFilter.isRelevant(any(AnnotationDescription.class), any(MethodDescription.InDefinedShape.class))).thenReturn(true);
+ when(methodDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(methodDescription.getParameters()).thenReturn((ParameterList) new ParameterList.Empty<ParameterDescription>());
+ when(methodDescription.getReturnType()).thenReturn(simpleAnnotatedType);
+ when(methodDescription.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(methodDescription.getExceptionTypes()).thenReturn(new TypeList.Generic.Empty());
+ when(simpleAnnotatedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new QuxBaz.Instance()));
+ methodAttributeAppender.apply(methodVisitor, methodDescription, annotationValueFilter);
+ verify(methodVisitor).visitTypeAnnotation(TypeReference.newTypeReference(TypeReference.METHOD_RETURN).getValue(),
+ null,
+ Type.getDescriptor(QuxBaz.class),
+ false);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMethodExceptionTypeTypeAnnotationNoRetention() throws Exception {
+ when(annotationValueFilter.isRelevant(any(AnnotationDescription.class), any(MethodDescription.InDefinedShape.class))).thenReturn(true);
+ when(methodDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(methodDescription.getParameters()).thenReturn((ParameterList) new ParameterList.Empty<ParameterDescription>());
+ when(methodDescription.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(methodDescription.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(methodDescription.getExceptionTypes()).thenReturn(new TypeList.Generic.Explicit(simpleAnnotatedType));
+ when(simpleAnnotatedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Qux.Instance()));
+ methodAttributeAppender.apply(methodVisitor, methodDescription, annotationValueFilter);
+ verifyZeroInteractions(methodVisitor);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMethodExceptionTypeTypeAnnotationRuntimeRetention() throws Exception {
+ when(annotationValueFilter.isRelevant(any(AnnotationDescription.class), any(MethodDescription.InDefinedShape.class))).thenReturn(true);
+ when(methodDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(methodDescription.getParameters()).thenReturn((ParameterList) new ParameterList.Empty<ParameterDescription>());
+ when(methodDescription.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(methodDescription.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(methodDescription.getExceptionTypes()).thenReturn(new TypeList.Generic.Explicit(simpleAnnotatedType));
+ when(simpleAnnotatedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Baz.Instance()));
+ methodAttributeAppender.apply(methodVisitor, methodDescription, annotationValueFilter);
+ verify(methodVisitor).visitTypeAnnotation(TypeReference.newExceptionReference(0).getValue(),
+ null,
+ Type.getDescriptor(Baz.class),
+ true);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMethodExceptionTypeTypeAnnotationClassFileRetention() throws Exception {
+ when(annotationValueFilter.isRelevant(any(AnnotationDescription.class), any(MethodDescription.InDefinedShape.class))).thenReturn(true);
+ when(methodDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(methodDescription.getParameters()).thenReturn((ParameterList) new ParameterList.Empty<ParameterDescription>());
+ when(methodDescription.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(methodDescription.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(methodDescription.getExceptionTypes()).thenReturn(new TypeList.Generic.Explicit(simpleAnnotatedType));
+ when(simpleAnnotatedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new QuxBaz.Instance()));
+ methodAttributeAppender.apply(methodVisitor, methodDescription, annotationValueFilter);
+ verify(methodVisitor).visitTypeAnnotation(TypeReference.newExceptionReference(0).getValue(),
+ null,
+ Type.getDescriptor(QuxBaz.class),
+ false);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMethodParameterTypeTypeAnnotationNoRetention() throws Exception {
+ when(annotationValueFilter.isRelevant(any(AnnotationDescription.class), any(MethodDescription.InDefinedShape.class))).thenReturn(true);
+ when(methodDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ ParameterDescription parameterDescription = mock(ParameterDescription.class);
+ when(parameterDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(parameterDescription.getType()).thenReturn(simpleAnnotatedType);
+ when(methodDescription.getParameters()).thenReturn((ParameterList) new ParameterList.Explicit<ParameterDescription>(parameterDescription));
+ when(methodDescription.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(methodDescription.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(methodDescription.getExceptionTypes()).thenReturn(new TypeList.Generic.Empty());
+ when(simpleAnnotatedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Qux.Instance()));
+ methodAttributeAppender.apply(methodVisitor, methodDescription, annotationValueFilter);
+ verifyZeroInteractions(methodVisitor);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMethodParameterTypeTypeAnnotationRuntimeRetention() throws Exception {
+ when(annotationValueFilter.isRelevant(any(AnnotationDescription.class), any(MethodDescription.InDefinedShape.class))).thenReturn(true);
+ when(methodDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ ParameterDescription parameterDescription = mock(ParameterDescription.class);
+ when(parameterDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(parameterDescription.getType()).thenReturn(simpleAnnotatedType);
+ when(methodDescription.getParameters()).thenReturn((ParameterList) new ParameterList.Explicit<ParameterDescription>(parameterDescription));
+ when(methodDescription.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(methodDescription.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(methodDescription.getExceptionTypes()).thenReturn(new TypeList.Generic.Empty());
+ when(simpleAnnotatedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Baz.Instance()));
+ methodAttributeAppender.apply(methodVisitor, methodDescription, annotationValueFilter);
+ verify(methodVisitor).visitTypeAnnotation(TypeReference.newFormalParameterReference(0).getValue(),
+ null,
+ Type.getDescriptor(Baz.class),
+ true);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMethodParameterTypeTypeAnnotationClassFileRetention() throws Exception {
+ when(annotationValueFilter.isRelevant(any(AnnotationDescription.class), any(MethodDescription.InDefinedShape.class))).thenReturn(true);
+ when(methodDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ ParameterDescription parameterDescription = mock(ParameterDescription.class);
+ when(parameterDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(parameterDescription.getType()).thenReturn(simpleAnnotatedType);
+ when(methodDescription.getParameters()).thenReturn((ParameterList) new ParameterList.Explicit<ParameterDescription>(parameterDescription));
+ when(methodDescription.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(methodDescription.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(methodDescription.getExceptionTypes()).thenReturn(new TypeList.Generic.Empty());
+ when(simpleAnnotatedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new QuxBaz.Instance()));
+ methodAttributeAppender.apply(methodVisitor, methodDescription, annotationValueFilter);
+ verify(methodVisitor).visitTypeAnnotation(TypeReference.newFormalParameterReference(0).getValue(),
+ null,
+ Type.getDescriptor(QuxBaz.class),
+ false);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testTypeVariableTypeAnnotationNoRetention() throws Exception {
+ when(annotatedTypeVariable.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Qux.Instance()));
+ when(annotatedTypeVariableBound.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Qux.Instance()));
+ when(methodDescription.getParameters()).thenReturn((ParameterList) new ParameterList.Empty<ParameterDescription>());
+ when(methodDescription.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(methodDescription.getTypeVariables()).thenReturn(new TypeList.Generic.Explicit(annotatedTypeVariable));
+ when(methodDescription.getExceptionTypes()).thenReturn(new TypeList.Generic.Empty());
+ when(methodDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ methodAttributeAppender.apply(methodVisitor, methodDescription, annotationValueFilter);
+ verifyZeroInteractions(methodVisitor);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testTypeVariableTypeAnnotationRuntimeRetention() throws Exception {
+ when(annotatedTypeVariable.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Baz.Instance()));
+ when(annotatedTypeVariableBound.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Baz.Instance()));
+ when(methodDescription.getParameters()).thenReturn((ParameterList) new ParameterList.Empty<ParameterDescription>());
+ when(methodDescription.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(methodDescription.getTypeVariables()).thenReturn(new TypeList.Generic.Explicit(annotatedTypeVariable));
+ when(methodDescription.getExceptionTypes()).thenReturn(new TypeList.Generic.Empty());
+ when(methodDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ methodAttributeAppender.apply(methodVisitor, methodDescription, annotationValueFilter);
+ verify(methodVisitor).visitTypeAnnotation(TypeReference.newTypeParameterReference(TypeReference.METHOD_TYPE_PARAMETER, 0).getValue(),
+ null,
+ Type.getDescriptor(Baz.class),
+ true);
+ verify(methodVisitor).visitTypeAnnotation(TypeReference.newTypeParameterBoundReference(TypeReference.METHOD_TYPE_PARAMETER_BOUND, 0, 0).getValue(),
+ null,
+ Type.getDescriptor(Baz.class),
+ true);
+ verifyZeroInteractions(methodVisitor);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testTypeVariableTypeAnnotations() throws Exception {
+ when(annotatedTypeVariable.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new QuxBaz.Instance()));
+ when(annotatedTypeVariableBound.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new QuxBaz.Instance()));
+ when(methodDescription.getParameters()).thenReturn((ParameterList) new ParameterList.Empty<ParameterDescription>());
+ when(methodDescription.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(methodDescription.getTypeVariables()).thenReturn(new TypeList.Generic.Explicit(annotatedTypeVariable));
+ when(methodDescription.getExceptionTypes()).thenReturn(new TypeList.Generic.Empty());
+ when(methodDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ methodAttributeAppender.apply(methodVisitor, methodDescription, annotationValueFilter);
+ verify(methodVisitor).visitTypeAnnotation(TypeReference.newTypeParameterReference(TypeReference.METHOD_TYPE_PARAMETER, 0).getValue(),
+ null,
+ Type.getDescriptor(QuxBaz.class),
+ false);
+ verify(methodVisitor).visitTypeAnnotation(TypeReference.newTypeParameterBoundReference(TypeReference.METHOD_TYPE_PARAMETER_BOUND, 0, 0).getValue(),
+ null,
+ Type.getDescriptor(QuxBaz.class),
+ false);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testJdkTypeIsFiltered() throws Exception {
+ when(annotationValueFilter.isRelevant(any(AnnotationDescription.class), any(MethodDescription.InDefinedShape.class))).thenReturn(true);
+ AnnotationDescription annotationDescription = mock(AnnotationDescription.class);
+ TypeDescription annotationType = mock(TypeDescription.class);
+ when(annotationType.getDeclaredMethods()).thenReturn(new MethodList.Empty<MethodDescription.InDefinedShape>());
+ when(annotationDescription.getRetention()).thenReturn(RetentionPolicy.RUNTIME);
+ when(annotationDescription.getAnnotationType()).thenReturn(annotationType);
+ when(annotationType.getActualName()).thenReturn("jdk.internal.Sample");
+ when(methodDescription.getDeclaredAnnotations()).thenReturn(new AnnotationList.Explicit(annotationDescription));
+ when(methodDescription.getParameters()).thenReturn((ParameterList) new ParameterList.Empty<ParameterDescription>());
+ when(methodDescription.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(methodDescription.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(methodDescription.getExceptionTypes()).thenReturn(new TypeList.Generic.Empty());
+ methodAttributeAppender.apply(methodVisitor, methodDescription, annotationValueFilter);
+ verifyZeroInteractions(methodVisitor);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/MethodAttributeAppenderForReceiverTypeTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/MethodAttributeAppenderForReceiverTypeTest.java
new file mode 100644
index 0000000..2cd61a2
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/MethodAttributeAppenderForReceiverTypeTest.java
@@ -0,0 +1,53 @@
+package net.bytebuddy.implementation.attribute;
+
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.TypeReference;
+
+import static org.mockito.Mockito.*;
+
+public class MethodAttributeAppenderForReceiverTypeTest extends AbstractMethodAttributeAppenderTest {
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testReceiverTypeAnnotationNoRetention() throws Exception {
+ when(simpleAnnotatedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Qux.Instance()));
+ new MethodAttributeAppender.ForReceiverType(simpleAnnotatedType).apply(methodVisitor, methodDescription, annotationValueFilter);
+ verifyZeroInteractions(methodVisitor);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(methodDescription);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMethodAnnotationRuntimeRetention() throws Exception {
+ when(simpleAnnotatedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Baz.Instance()));
+ new MethodAttributeAppender.ForReceiverType(simpleAnnotatedType).apply(methodVisitor, methodDescription, annotationValueFilter);
+ verify(methodVisitor).visitTypeAnnotation(TypeReference.newTypeReference(TypeReference.METHOD_RECEIVER).getValue(),
+ null,
+ Type.getDescriptor(Baz.class),
+ true);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(methodDescription);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMethodAnnotationClassFileRetention() throws Exception {
+ when(simpleAnnotatedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new QuxBaz.Instance()));
+ new MethodAttributeAppender.ForReceiverType(simpleAnnotatedType).apply(methodVisitor, methodDescription, annotationValueFilter);
+ verify(methodVisitor).visitTypeAnnotation(TypeReference.newTypeReference(TypeReference.METHOD_RECEIVER).getValue(),
+ null,
+ Type.getDescriptor(QuxBaz.class),
+ false);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(methodDescription);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodAttributeAppender.ForReceiverType.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/MethodAttributeAppenderNoOpTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/MethodAttributeAppenderNoOpTest.java
new file mode 100644
index 0000000..81bacba
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/MethodAttributeAppenderNoOpTest.java
@@ -0,0 +1,29 @@
+package net.bytebuddy.implementation.attribute;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+public class MethodAttributeAppenderNoOpTest extends AbstractMethodAttributeAppenderTest {
+
+ @Test
+ public void testApplication() throws Exception {
+ MethodAttributeAppender.NoOp.INSTANCE.apply(methodVisitor, methodDescription, annotationValueFilter);
+ verifyZeroInteractions(methodVisitor);
+ verifyZeroInteractions(methodDescription);
+ }
+
+ @Test
+ public void testFactory() throws Exception {
+ assertThat(MethodAttributeAppender.NoOp.INSTANCE.make(instrumentedType), is((MethodAttributeAppender) MethodAttributeAppender.NoOp.INSTANCE));
+ verifyZeroInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodAttributeAppender.NoOp.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/TypeAttributeAppenderCompoundTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/TypeAttributeAppenderCompoundTest.java
new file mode 100644
index 0000000..6f8906f
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/TypeAttributeAppenderCompoundTest.java
@@ -0,0 +1,37 @@
+package net.bytebuddy.implementation.attribute;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.util.Collections;
+import java.util.List;
+
+import static org.mockito.Mockito.*;
+
+public class TypeAttributeAppenderCompoundTest extends AbstractTypeAttributeAppenderTest {
+
+ @Mock
+ private TypeAttributeAppender first, second;
+
+ @Test
+ public void testApplication() throws Exception {
+ TypeAttributeAppender typeAttributeAppender = new TypeAttributeAppender.Compound(first, second);
+ typeAttributeAppender.apply(classVisitor, instrumentedType, annotationValueFilter);
+ verify(first).apply(classVisitor, instrumentedType, annotationValueFilter);
+ verifyNoMoreInteractions(first);
+ verify(second).apply(classVisitor, instrumentedType, annotationValueFilter);
+ verifyNoMoreInteractions(second);
+ verifyZeroInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeAttributeAppender.Compound.class).create(new ObjectPropertyAssertion.Creator<List<?>>() {
+ @Override
+ public List<?> create() {
+ return Collections.singletonList(mock(TypeAttributeAppender.class));
+ }
+ }).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/TypeAttributeAppenderExplicitTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/TypeAttributeAppenderExplicitTest.java
new file mode 100644
index 0000000..079d10b
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/TypeAttributeAppenderExplicitTest.java
@@ -0,0 +1,68 @@
+package net.bytebuddy.implementation.attribute;
+
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+import org.objectweb.asm.Type;
+
+import java.lang.annotation.Annotation;
+
+import static org.mockito.Mockito.*;
+
+public class TypeAttributeAppenderExplicitTest extends AbstractTypeAttributeAppenderTest {
+
+ @Test
+ public void testAnnotationNoRetention() throws Exception {
+ when(instrumentedType.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(instrumentedType.getInterfaces()).thenReturn(new TypeList.Generic.Empty());
+ when(instrumentedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Qux.Instance()));
+ new TypeAttributeAppender.Explicit(new AnnotationList.ForLoadedAnnotations(new Qux.Instance())).apply(classVisitor, instrumentedType, annotationValueFilter);
+ verifyNoMoreInteractions(classVisitor);
+ verifyZeroInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testAnnotationByteCodeRetention() throws Exception {
+ when(instrumentedType.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(instrumentedType.getInterfaces()).thenReturn(new TypeList.Generic.Empty());
+ when(instrumentedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Baz.Instance()));
+ new TypeAttributeAppender.Explicit(new AnnotationList.ForLoadedAnnotations(new Baz.Instance())).apply(classVisitor, instrumentedType, annotationValueFilter);
+ verify(classVisitor).visitAnnotation(Type.getDescriptor(Baz.class), true);
+ verifyNoMoreInteractions(classVisitor);
+ verifyZeroInteractions(instrumentedType);
+
+ }
+
+ @Test
+ public void testAnnotationClassFileRetention() throws Exception {
+ when(instrumentedType.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(instrumentedType.getInterfaces()).thenReturn(new TypeList.Generic.Empty());
+ when(instrumentedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new QuxBaz.Instance()));
+ new TypeAttributeAppender.Explicit(new AnnotationList.ForLoadedAnnotations(new QuxBaz.Instance())).apply(classVisitor, instrumentedType, annotationValueFilter);
+ verify(classVisitor).visitAnnotation(Type.getDescriptor(QuxBaz.class), false);
+ verifyNoMoreInteractions(classVisitor);
+ verifyZeroInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeAttributeAppender.Explicit.class).generate(new ObjectPropertyAssertion.Generator<Annotation>() {
+ @Override
+ public Class<? extends Annotation> generate() {
+ return SimpleAnnotation.class;
+ }
+ }).refine(new ObjectPropertyAssertion.Refinement<SimpleAnnotation>() {
+ @Override
+ public void apply(SimpleAnnotation mock) {
+ doReturn(SimpleAnnotation.class).when(mock).annotationType();
+ when(mock.value()).thenReturn("annotation" + System.identityHashCode(mock));
+ }
+ }).apply();
+ }
+
+ public @interface SimpleAnnotation {
+
+ String value();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/TypeAttributeAppenderForInstrumentedTypeDifferentiatingTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/TypeAttributeAppenderForInstrumentedTypeDifferentiatingTest.java
new file mode 100644
index 0000000..8e793a4
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/TypeAttributeAppenderForInstrumentedTypeDifferentiatingTest.java
@@ -0,0 +1,189 @@
+package net.bytebuddy.implementation.attribute;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.TypeReference;
+
+import java.util.Arrays;
+import java.util.Random;
+
+import static org.mockito.Mockito.*;
+
+public class TypeAttributeAppenderForInstrumentedTypeDifferentiatingTest extends AbstractTypeAttributeAppenderTest {
+
+ @Mock
+ private TypeDescription.Generic pseudoType;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ when(pseudoType.asGenericType()).thenReturn(pseudoType);
+ }
+
+ @Test
+ public void testAnnotationNoRetention() throws Exception {
+ when(instrumentedType.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(instrumentedType.getInterfaces()).thenReturn(new TypeList.Generic.Empty());
+ when(instrumentedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Baz.Instance(), new Qux.Instance()));
+ new TypeAttributeAppender.ForInstrumentedType.Differentiating(1, 0, 0).apply(classVisitor, instrumentedType, annotationValueFilter);
+ verifyZeroInteractions(classVisitor);
+ verify(instrumentedType).getDeclaredAnnotations();
+ verify(instrumentedType).getInterfaces();
+ verify(instrumentedType).getTypeVariables();
+ verifyNoMoreInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testAnnotationByteCodeRetention() throws Exception {
+ when(instrumentedType.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(instrumentedType.getInterfaces()).thenReturn(new TypeList.Generic.Empty());
+ when(instrumentedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new QuxBaz.Instance(), new Baz.Instance()));
+ new TypeAttributeAppender.ForInstrumentedType.Differentiating(1, 0, 0).apply(classVisitor, instrumentedType, annotationValueFilter);
+ verify(classVisitor).visitAnnotation(Type.getDescriptor(Baz.class), true);
+ verifyZeroInteractions(classVisitor);
+ verify(instrumentedType).getDeclaredAnnotations();
+ verify(instrumentedType).getInterfaces();
+ verify(instrumentedType).getTypeVariables();
+ verifyNoMoreInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testAnnotationClassFileRetention() throws Exception {
+ when(instrumentedType.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(instrumentedType.getInterfaces()).thenReturn(new TypeList.Generic.Empty());
+ when(instrumentedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Baz.Instance(), new QuxBaz.Instance()));
+ new TypeAttributeAppender.ForInstrumentedType.Differentiating(1, 0, 0).apply(classVisitor, instrumentedType, annotationValueFilter);
+ verify(classVisitor).visitAnnotation(Type.getDescriptor(QuxBaz.class), false);
+ verifyZeroInteractions(classVisitor);
+ verify(instrumentedType).getDeclaredAnnotations();
+ verify(instrumentedType).getInterfaces();
+ verify(instrumentedType).getTypeVariables();
+ verifyNoMoreInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testInterfaceTypeAnnotationNoRetention() throws Exception {
+ when(instrumentedType.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(instrumentedType.getInterfaces()).thenReturn(new TypeList.Generic.Explicit(pseudoType, simpleAnnotatedType));
+ when(simpleAnnotatedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Qux.Instance()));
+ when(instrumentedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ new TypeAttributeAppender.ForInstrumentedType.Differentiating(0, 0, 1).apply(classVisitor, instrumentedType, annotationValueFilter);
+ verifyZeroInteractions(classVisitor);
+ verify(instrumentedType).getDeclaredAnnotations();
+ verify(instrumentedType).getInterfaces();
+ verify(instrumentedType).getTypeVariables();
+ verifyNoMoreInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testInterfaceTypeAnnotationRuntimeRetention() throws Exception {
+ when(instrumentedType.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(instrumentedType.getInterfaces()).thenReturn(new TypeList.Generic.Explicit(pseudoType, simpleAnnotatedType));
+ when(simpleAnnotatedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Baz.Instance()));
+ when(instrumentedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ new TypeAttributeAppender.ForInstrumentedType.Differentiating(0, 0, 1).apply(classVisitor, instrumentedType, annotationValueFilter);
+ verify(classVisitor).visitTypeAnnotation(TypeReference.newSuperTypeReference(1).getValue(), null, Type.getDescriptor(Baz.class), true);
+ verifyNoMoreInteractions(classVisitor);
+ verify(instrumentedType).getDeclaredAnnotations();
+ verify(instrumentedType).getInterfaces();
+ verify(instrumentedType).getTypeVariables();
+ verifyNoMoreInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testInterfaceTypeAnnotations() throws Exception {
+ when(instrumentedType.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(instrumentedType.getInterfaces()).thenReturn(new TypeList.Generic.Explicit(pseudoType, simpleAnnotatedType));
+ when(simpleAnnotatedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new QuxBaz.Instance()));
+ when(instrumentedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ new TypeAttributeAppender.ForInstrumentedType.Differentiating(0, 0, 1).apply(classVisitor, instrumentedType, annotationValueFilter);
+ verify(classVisitor).visitTypeAnnotation(TypeReference.newSuperTypeReference(1).getValue(), null, Type.getDescriptor(QuxBaz.class), false);
+ verifyNoMoreInteractions(classVisitor);
+ verify(instrumentedType).getDeclaredAnnotations();
+ verify(instrumentedType).getInterfaces();
+ verify(instrumentedType).getTypeVariables();
+ verifyNoMoreInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testTypeVariableTypeAnnotationNoRetention() throws Exception {
+ when(instrumentedType.getTypeVariables()).thenReturn(new TypeList.Generic.Explicit(pseudoType, annotatedTypeVariable));
+ when(annotatedTypeVariable.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Qux.Instance()));
+ when(annotatedTypeVariableBound.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Qux.Instance()));
+ when(instrumentedType.getInterfaces()).thenReturn(new TypeList.Generic.Empty());
+ when(instrumentedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ new TypeAttributeAppender.ForInstrumentedType.Differentiating(0, 1, 0).apply(classVisitor, instrumentedType, annotationValueFilter);
+ verifyZeroInteractions(classVisitor);
+ verify(instrumentedType).getDeclaredAnnotations();
+ verify(instrumentedType).getInterfaces();
+ verify(instrumentedType).getTypeVariables();
+ verifyNoMoreInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testTypeVariableTypeAnnotationRuntimeRetention() throws Exception {
+ when(instrumentedType.getTypeVariables()).thenReturn(new TypeList.Generic.Explicit(pseudoType, annotatedTypeVariable));
+ when(annotatedTypeVariable.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Baz.Instance()));
+ when(annotatedTypeVariableBound.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Baz.Instance()));
+ when(instrumentedType.getInterfaces()).thenReturn(new TypeList.Generic.Empty());
+ when(instrumentedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ new TypeAttributeAppender.ForInstrumentedType.Differentiating(0, 1, 0).apply(classVisitor, instrumentedType, annotationValueFilter);
+ verify(classVisitor).visitTypeAnnotation(TypeReference.newTypeParameterReference(TypeReference.CLASS_TYPE_PARAMETER, 1).getValue(),
+ null,
+ Type.getDescriptor(Baz.class),
+ true);
+ verify(classVisitor).visitTypeAnnotation(TypeReference.newTypeParameterBoundReference(TypeReference.CLASS_TYPE_PARAMETER_BOUND, 1, 0).getValue(),
+ null,
+ Type.getDescriptor(Baz.class),
+ true);
+ verifyNoMoreInteractions(classVisitor);
+ verify(instrumentedType).getDeclaredAnnotations();
+ verify(instrumentedType).getInterfaces();
+ verify(instrumentedType).getTypeVariables();
+ verifyNoMoreInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testTypeVariableTypeAnnotations() throws Exception {
+ when(instrumentedType.getTypeVariables()).thenReturn(new TypeList.Generic.Explicit(pseudoType, annotatedTypeVariable));
+ when(annotatedTypeVariable.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new QuxBaz.Instance()));
+ when(annotatedTypeVariableBound.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new QuxBaz.Instance()));
+ when(instrumentedType.getInterfaces()).thenReturn(new TypeList.Generic.Empty());
+ when(instrumentedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ new TypeAttributeAppender.ForInstrumentedType.Differentiating(0, 1, 0).apply(classVisitor, instrumentedType, annotationValueFilter);
+ verify(classVisitor).visitTypeAnnotation(TypeReference.newTypeParameterReference(TypeReference.CLASS_TYPE_PARAMETER, 1).getValue(),
+ null,
+ Type.getDescriptor(QuxBaz.class),
+ false);
+ verify(classVisitor).visitTypeAnnotation(TypeReference.newTypeParameterBoundReference(TypeReference.CLASS_TYPE_PARAMETER_BOUND, 1, 0).getValue(),
+ null,
+ Type.getDescriptor(QuxBaz.class),
+ false);
+ verifyNoMoreInteractions(classVisitor);
+ verify(instrumentedType).getDeclaredAnnotations();
+ verify(instrumentedType).getInterfaces();
+ verify(instrumentedType).getTypeVariables();
+ verifyNoMoreInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeAttributeAppender.ForInstrumentedType.Differentiating.class).refine(new ObjectPropertyAssertion.Refinement<TypeDescription>() {
+ @Override
+ public void apply(TypeDescription mock) {
+ AnnotationDescription[] annotationDescription = new AnnotationDescription[new Random().nextInt(10000)];
+ when(mock.getDeclaredAnnotations()).thenReturn(new AnnotationList.Explicit(Arrays.asList(annotationDescription)));
+ when(mock.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(mock.getInterfaces()).thenReturn(new TypeList.Generic.Empty());
+ }
+ }).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/TypeAttributeAppenderForInstrumentedTypeTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/TypeAttributeAppenderForInstrumentedTypeTest.java
new file mode 100644
index 0000000..6eb1bc2
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/TypeAttributeAppenderForInstrumentedTypeTest.java
@@ -0,0 +1,223 @@
+package net.bytebuddy.implementation.attribute;
+
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.TypeReference;
+
+import static org.mockito.Mockito.*;
+
+public class TypeAttributeAppenderForInstrumentedTypeTest extends AbstractTypeAttributeAppenderTest {
+
+ @Test
+ public void testAnnotationNoRetention() throws Exception {
+ when(instrumentedType.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(instrumentedType.getInterfaces()).thenReturn(new TypeList.Generic.Empty());
+ when(instrumentedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Qux.Instance()));
+ TypeAttributeAppender.ForInstrumentedType.INSTANCE.apply(classVisitor, instrumentedType, annotationValueFilter);
+ verifyZeroInteractions(classVisitor);
+ verify(instrumentedType).getDeclaredAnnotations();
+ verify(instrumentedType).getSuperClass();
+ verify(instrumentedType).getInterfaces();
+ verify(instrumentedType).getTypeVariables();
+ verifyNoMoreInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testAnnotationByteCodeRetention() throws Exception {
+ when(instrumentedType.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(instrumentedType.getInterfaces()).thenReturn(new TypeList.Generic.Empty());
+ when(instrumentedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Baz.Instance()));
+ TypeAttributeAppender.ForInstrumentedType.INSTANCE.apply(classVisitor, instrumentedType, annotationValueFilter);
+ verify(classVisitor).visitAnnotation(Type.getDescriptor(Baz.class), true);
+ verifyZeroInteractions(classVisitor);
+ verify(instrumentedType).getDeclaredAnnotations();
+ verify(instrumentedType).getSuperClass();
+ verify(instrumentedType).getInterfaces();
+ verify(instrumentedType).getTypeVariables();
+ verifyNoMoreInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testAnnotationClassFileRetention() throws Exception {
+ when(instrumentedType.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(instrumentedType.getInterfaces()).thenReturn(new TypeList.Generic.Empty());
+ when(instrumentedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new QuxBaz.Instance()));
+ TypeAttributeAppender.ForInstrumentedType.INSTANCE.apply(classVisitor, instrumentedType, annotationValueFilter);
+ verify(classVisitor).visitAnnotation(Type.getDescriptor(QuxBaz.class), false);
+ verifyZeroInteractions(classVisitor);
+ verify(instrumentedType).getDeclaredAnnotations();
+ verify(instrumentedType).getSuperClass();
+ verify(instrumentedType).getInterfaces();
+ verify(instrumentedType).getTypeVariables();
+ verifyNoMoreInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testSuperClassTypeAnnotationNoRetention() throws Exception {
+ when(instrumentedType.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(instrumentedType.getInterfaces()).thenReturn(new TypeList.Generic.Empty());
+ when(simpleAnnotatedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Qux.Instance()));
+ when(instrumentedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(instrumentedType.getSuperClass()).thenReturn(simpleAnnotatedType);
+ TypeAttributeAppender.ForInstrumentedType.INSTANCE.apply(classVisitor, instrumentedType, annotationValueFilter);
+ verifyZeroInteractions(classVisitor);
+ verify(instrumentedType).getDeclaredAnnotations();
+ verify(instrumentedType).getSuperClass();
+ verify(instrumentedType).getInterfaces();
+ verify(instrumentedType).getTypeVariables();
+ verifyNoMoreInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testSuperClassTypeAnnotationByteCodeRetention() throws Exception {
+ when(instrumentedType.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(instrumentedType.getInterfaces()).thenReturn(new TypeList.Generic.Empty());
+ when(simpleAnnotatedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Baz.Instance()));
+ when(instrumentedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(instrumentedType.getSuperClass()).thenReturn(simpleAnnotatedType);
+ TypeAttributeAppender.ForInstrumentedType.INSTANCE.apply(classVisitor, instrumentedType, annotationValueFilter);
+ verify(classVisitor).visitTypeAnnotation(TypeReference.newSuperTypeReference(-1).getValue(), null, Type.getDescriptor(Baz.class), true);
+ verify(instrumentedType).getDeclaredAnnotations();
+ verify(instrumentedType).getSuperClass();
+ verify(instrumentedType).getInterfaces();
+ verify(instrumentedType).getTypeVariables();
+ verifyNoMoreInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testSuperClassTypeAnnotationClassFileRetention() throws Exception {
+ when(instrumentedType.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(instrumentedType.getInterfaces()).thenReturn(new TypeList.Generic.Empty());
+ when(simpleAnnotatedType.getDeclaredAnnotations())
+ .thenReturn(new AnnotationList.ForLoadedAnnotations(new QuxBaz.Instance()));
+ when(instrumentedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(instrumentedType.getSuperClass()).thenReturn(simpleAnnotatedType);
+ TypeAttributeAppender.ForInstrumentedType.INSTANCE.apply(classVisitor, instrumentedType, annotationValueFilter);
+ verify(classVisitor).visitTypeAnnotation(TypeReference.newSuperTypeReference(-1).getValue(), null, Type.getDescriptor(QuxBaz.class), false);
+ verifyNoMoreInteractions(classVisitor);
+ verify(instrumentedType).getDeclaredAnnotations();
+ verify(instrumentedType).getSuperClass();
+ verify(instrumentedType).getInterfaces();
+ verify(instrumentedType).getTypeVariables();
+ verifyNoMoreInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testInterfaceTypeAnnotationNoRetention() throws Exception {
+ when(instrumentedType.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(instrumentedType.getInterfaces()).thenReturn(new TypeList.Generic.Explicit(simpleAnnotatedType));
+ when(simpleAnnotatedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Qux.Instance()));
+ when(instrumentedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ TypeAttributeAppender.ForInstrumentedType.INSTANCE.apply(classVisitor, instrumentedType, annotationValueFilter);
+ verifyZeroInteractions(classVisitor);
+ verify(instrumentedType).getDeclaredAnnotations();
+ verify(instrumentedType).getSuperClass();
+ verify(instrumentedType).getInterfaces();
+ verify(instrumentedType).getTypeVariables();
+ verifyNoMoreInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testInterfaceTypeAnnotationRuntimeRetention() throws Exception {
+ when(instrumentedType.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(instrumentedType.getInterfaces()).thenReturn(new TypeList.Generic.Explicit(simpleAnnotatedType));
+ when(simpleAnnotatedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Baz.Instance()));
+ when(instrumentedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ TypeAttributeAppender.ForInstrumentedType.INSTANCE.apply(classVisitor, instrumentedType, annotationValueFilter);
+ verify(classVisitor).visitTypeAnnotation(TypeReference.newSuperTypeReference(0).getValue(), null, Type.getDescriptor(Baz.class), true);
+ verifyNoMoreInteractions(classVisitor);
+ verify(instrumentedType).getDeclaredAnnotations();
+ verify(instrumentedType).getSuperClass();
+ verify(instrumentedType).getInterfaces();
+ verify(instrumentedType).getTypeVariables();
+ verifyNoMoreInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testInterfaceTypeAnnotations() throws Exception {
+ when(instrumentedType.getTypeVariables()).thenReturn(new TypeList.Generic.Empty());
+ when(instrumentedType.getInterfaces()).thenReturn(new TypeList.Generic.Explicit(simpleAnnotatedType));
+ when(simpleAnnotatedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new QuxBaz.Instance()));
+ when(instrumentedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ TypeAttributeAppender.ForInstrumentedType.INSTANCE.apply(classVisitor, instrumentedType, annotationValueFilter);
+ verify(classVisitor).visitTypeAnnotation(TypeReference.newSuperTypeReference(0).getValue(), null, Type.getDescriptor(QuxBaz.class), false);
+ verifyNoMoreInteractions(classVisitor);
+ verify(instrumentedType).getDeclaredAnnotations();
+ verify(instrumentedType).getSuperClass();
+ verify(instrumentedType).getInterfaces();
+ verify(instrumentedType).getTypeVariables();
+ verifyNoMoreInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testTypeVariableTypeAnnotationNoRetention() throws Exception {
+ when(instrumentedType.getTypeVariables()).thenReturn(new TypeList.Generic.Explicit(annotatedTypeVariable));
+ when(annotatedTypeVariable.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Qux.Instance()));
+ when(annotatedTypeVariableBound.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Qux.Instance()));
+ when(instrumentedType.getInterfaces()).thenReturn(new TypeList.Generic.Empty());
+ when(instrumentedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ TypeAttributeAppender.ForInstrumentedType.INSTANCE.apply(classVisitor, instrumentedType, annotationValueFilter);
+ verifyZeroInteractions(classVisitor);
+ verify(instrumentedType).getDeclaredAnnotations();
+ verify(instrumentedType).getSuperClass();
+ verify(instrumentedType).getInterfaces();
+ verify(instrumentedType).getTypeVariables();
+ verifyNoMoreInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testTypeVariableTypeAnnotationRuntimeRetention() throws Exception {
+ when(instrumentedType.getTypeVariables()).thenReturn(new TypeList.Generic.Explicit(annotatedTypeVariable));
+ when(annotatedTypeVariable.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Baz.Instance()));
+ when(annotatedTypeVariableBound.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new Baz.Instance()));
+ when(instrumentedType.getInterfaces()).thenReturn(new TypeList.Generic.Empty());
+ when(instrumentedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ TypeAttributeAppender.ForInstrumentedType.INSTANCE.apply(classVisitor, instrumentedType, annotationValueFilter);
+ verify(classVisitor).visitTypeAnnotation(TypeReference.newTypeParameterReference(TypeReference.CLASS_TYPE_PARAMETER, 0).getValue(),
+ null,
+ Type.getDescriptor(Baz.class),
+ true);
+ verify(classVisitor).visitTypeAnnotation(TypeReference.newTypeParameterBoundReference(TypeReference.CLASS_TYPE_PARAMETER_BOUND, 0, 0).getValue(),
+ null,
+ Type.getDescriptor(Baz.class),
+ true);
+ verifyNoMoreInteractions(classVisitor);
+ verify(instrumentedType).getDeclaredAnnotations();
+ verify(instrumentedType).getSuperClass();
+ verify(instrumentedType).getInterfaces();
+ verify(instrumentedType).getTypeVariables();
+ verifyNoMoreInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testTypeVariableTypeAnnotations() throws Exception {
+ when(instrumentedType.getTypeVariables()).thenReturn(new TypeList.Generic.Explicit(annotatedTypeVariable));
+ when(annotatedTypeVariable.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new QuxBaz.Instance()));
+ when(annotatedTypeVariableBound.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(new QuxBaz.Instance()));
+ when(instrumentedType.getInterfaces()).thenReturn(new TypeList.Generic.Empty());
+ when(instrumentedType.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ TypeAttributeAppender.ForInstrumentedType.INSTANCE.apply(classVisitor, instrumentedType, annotationValueFilter);
+ verify(classVisitor).visitTypeAnnotation(TypeReference.newTypeParameterReference(TypeReference.CLASS_TYPE_PARAMETER, 0).getValue(),
+ null,
+ Type.getDescriptor(QuxBaz.class),
+ false);
+ verify(classVisitor).visitTypeAnnotation(TypeReference.newTypeParameterBoundReference(TypeReference.CLASS_TYPE_PARAMETER_BOUND, 0, 0).getValue(),
+ null,
+ Type.getDescriptor(QuxBaz.class),
+ false);
+ verifyNoMoreInteractions(classVisitor);
+ verify(instrumentedType).getDeclaredAnnotations();
+ verify(instrumentedType).getSuperClass();
+ verify(instrumentedType).getInterfaces();
+ verify(instrumentedType).getTypeVariables();
+ verifyNoMoreInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeAttributeAppender.ForInstrumentedType.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/TypeAttributeAppenderNoOpTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/TypeAttributeAppenderNoOpTest.java
new file mode 100644
index 0000000..8caa3e5
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/attribute/TypeAttributeAppenderNoOpTest.java
@@ -0,0 +1,21 @@
+package net.bytebuddy.implementation.attribute;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+public class TypeAttributeAppenderNoOpTest extends AbstractTypeAttributeAppenderTest {
+
+ @Test
+ public void testApplication() throws Exception {
+ TypeAttributeAppender.NoOp.INSTANCE.apply(classVisitor, instrumentedType, annotationValueFilter);
+ verifyZeroInteractions(classVisitor);
+ verifyZeroInteractions(instrumentedType);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeAttributeAppender.NoOp.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/auxiliary/AbstractMethodCallProxyTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/auxiliary/AbstractMethodCallProxyTest.java
new file mode 100644
index 0000000..f960c03
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/auxiliary/AbstractMethodCallProxyTest.java
@@ -0,0 +1,67 @@
+package net.bytebuddy.implementation.auxiliary;
+
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.MethodAccessorFactory;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.hamcrest.CoreMatchers;
+import org.junit.Rule;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.Opcodes;
+
+import java.util.concurrent.Callable;
+
+import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
+import static net.bytebuddy.matcher.ElementMatchers.not;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class AbstractMethodCallProxyTest {
+
+ protected static final String FOO = "foo";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private Implementation.SpecialMethodInvocation specialMethodInvocation;
+
+ @Mock
+ private MethodAccessorFactory methodAccessorFactory;
+
+ protected Class<?> proxyOnlyDeclaredMethodOf(Class<?> proxyTarget) throws Exception {
+ MethodDescription.InDefinedShape proxyMethod = new TypeDescription.ForLoadedType(proxyTarget)
+ .getDeclaredMethods().filter(not(isConstructor())).getOnly();
+ when(methodAccessorFactory.registerAccessorFor(specialMethodInvocation, MethodAccessorFactory.AccessType.DEFAULT)).thenReturn(proxyMethod);
+ String auxiliaryTypeName = getClass().getName() + "$" + proxyTarget.getSimpleName() + "$Proxy";
+ DynamicType dynamicType = new MethodCallProxy(specialMethodInvocation, false).make(auxiliaryTypeName,
+ ClassFileVersion.ofThisVm(),
+ methodAccessorFactory);
+ DynamicType.Unloaded<?> unloaded = (DynamicType.Unloaded<?>) dynamicType;
+ Class<?> auxiliaryType = unloaded.load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER).getLoaded();
+ assertThat(auxiliaryType.getName(), is(auxiliaryTypeName));
+ verify(methodAccessorFactory).registerAccessorFor(specialMethodInvocation, MethodAccessorFactory.AccessType.DEFAULT);
+ verifyNoMoreInteractions(methodAccessorFactory);
+ verifyZeroInteractions(specialMethodInvocation);
+ assertThat(auxiliaryType.getModifiers(), is(Opcodes.ACC_SYNTHETIC));
+ assertThat(Callable.class.isAssignableFrom(auxiliaryType), is(true));
+ assertThat(Runnable.class.isAssignableFrom(auxiliaryType), is(true));
+ assertThat(auxiliaryType.getDeclaredConstructors().length, is(1));
+ assertThat(auxiliaryType.getDeclaredMethods().length, is(2));
+ assertThat(auxiliaryType.getDeclaredFields().length, is(proxyMethod.getParameters().size() + (proxyMethod.isStatic() ? 0 : 1)));
+ int fieldIndex = 0;
+ if (!proxyMethod.isStatic()) {
+ assertThat(auxiliaryType.getDeclaredFields()[fieldIndex++].getType(), CoreMatchers.<Class<?>>is(proxyTarget));
+ }
+ for (Class<?> parameterType : proxyTarget.getDeclaredMethods()[0].getParameterTypes()) {
+ assertThat(auxiliaryType.getDeclaredFields()[fieldIndex++].getType(), CoreMatchers.<Class<?>>is(parameterType));
+ }
+ return auxiliaryType;
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/auxiliary/AuxiliaryTypeSignatureRelevantTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/auxiliary/AuxiliaryTypeSignatureRelevantTest.java
new file mode 100644
index 0000000..4ff0e23
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/auxiliary/AuxiliaryTypeSignatureRelevantTest.java
@@ -0,0 +1,30 @@
+package net.bytebuddy.implementation.auxiliary;
+
+import org.junit.Test;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Modifier;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class AuxiliaryTypeSignatureRelevantTest {
+
+ @Test
+ public void testClassRetention() throws Exception {
+ assertThat(AuxiliaryType.SignatureRelevant.class.getAnnotation(Retention.class).value(), is(RetentionPolicy.CLASS));
+ }
+
+ @Test
+ public void testTypeTarget() throws Exception {
+ assertThat(AuxiliaryType.SignatureRelevant.class.getAnnotation(Target.class).value(), is(new ElementType[]{ElementType.TYPE}));
+ }
+
+ @Test
+ public void testModifiers() throws Exception {
+ assertThat(Modifier.isPublic(AuxiliaryType.SignatureRelevant.class.getModifiers()), is(true));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/auxiliary/MethodCallProxyObjectPropertiesTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/auxiliary/MethodCallProxyObjectPropertiesTest.java
new file mode 100644
index 0000000..f1120e9
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/auxiliary/MethodCallProxyObjectPropertiesTest.java
@@ -0,0 +1,17 @@
+package net.bytebuddy.implementation.auxiliary;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+public class MethodCallProxyObjectPropertiesTest {
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodCallProxy.class).apply();
+ ObjectPropertyAssertion.of(MethodCallProxy.PrecomputedMethodGraph.class).apply();
+ ObjectPropertyAssertion.of(MethodCallProxy.AssignableSignatureCall.class).apply();
+ ObjectPropertyAssertion.of(MethodCallProxy.ConstructorCall.Appender.class).skipSynthetic().apply();
+ ObjectPropertyAssertion.of(MethodCallProxy.MethodCall.class).skipSynthetic().apply();
+ ObjectPropertyAssertion.of(MethodCallProxy.MethodCall.Appender.class).skipSynthetic().apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/auxiliary/MethodCallProxySingleArgumentTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/auxiliary/MethodCallProxySingleArgumentTest.java
new file mode 100644
index 0000000..75337f4
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/auxiliary/MethodCallProxySingleArgumentTest.java
@@ -0,0 +1,175 @@
+package net.bytebuddy.implementation.auxiliary;
+
+import net.bytebuddy.test.utility.CallTraceable;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.lang.reflect.Constructor;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.Callable;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class MethodCallProxySingleArgumentTest<T extends CallTraceable> extends AbstractMethodCallProxyTest {
+
+ private static final String STRING_VALUE = "foo";
+
+ private static final boolean BOOLEAN_VALUE = true;
+
+ private static final byte BYTE_VALUE = 42;
+
+ private static final short SHORT_VALUE = 42;
+
+ private static final char CHAR_VALUE = '@';
+
+ private static final int INT_VALUE = 42;
+
+ private static final long LONG_VALUE = 42L;
+
+ private static final float FLOAT_VALUE = 42f;
+
+ private static final double DOUBLE_VALUE = 42d;
+
+ private static final Object NULL_VALUE = null;
+
+ private final Object value;
+
+ private final Class<T> targetType;
+
+ private final Class<?> valueType;
+
+ public MethodCallProxySingleArgumentTest(Object value, Class<T> targetType, Class<?> valueType) {
+ this.value = value;
+ this.targetType = targetType;
+ this.valueType = valueType;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {STRING_VALUE, StringTarget.class, String.class},
+ {BOOLEAN_VALUE, BooleanTarget.class, boolean.class},
+ {BYTE_VALUE, ByteTarget.class, byte.class},
+ {SHORT_VALUE, ShortTarget.class, short.class},
+ {CHAR_VALUE, CharTarget.class, char.class},
+ {INT_VALUE, IntTarget.class, int.class},
+ {LONG_VALUE, LongTarget.class, long.class},
+ {FLOAT_VALUE, FloatTarget.class, float.class},
+ {DOUBLE_VALUE, DoubleTarget.class, double.class},
+ {NULL_VALUE, NullTarget.class, Void.class}
+ });
+ }
+
+ @Test
+ public void testRunMethod() throws Exception {
+ Class<?> auxiliaryType = proxyOnlyDeclaredMethodOf(targetType);
+ Constructor<?> constructor = auxiliaryType.getDeclaredConstructor(targetType, valueType);
+ constructor.setAccessible(true);
+ T proxiedInstance = targetType.getDeclaredConstructor().newInstance();
+ ((Runnable) constructor.newInstance(proxiedInstance, value)).run();
+ proxiedInstance.assertOnlyCall(FOO, value);
+ }
+
+ @Test
+ public void testCallMethod() throws Exception {
+ Class<?> auxiliaryType = proxyOnlyDeclaredMethodOf(targetType);
+ Constructor<?> constructor = auxiliaryType.getDeclaredConstructor(targetType, valueType);
+ constructor.setAccessible(true);
+ T proxiedInstance = targetType.getDeclaredConstructor().newInstance();
+ assertThat(((Callable<?>) constructor.newInstance(proxiedInstance, value)).call(), is(value));
+ proxiedInstance.assertOnlyCall(FOO, value);
+ }
+
+ @SuppressWarnings("unused")
+ public static class StringTarget extends CallTraceable {
+
+ public String foo(String s) {
+ register(FOO, s);
+ return s;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class BooleanTarget extends CallTraceable {
+
+ public boolean foo(boolean b) {
+ register(FOO, b);
+ return b;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ByteTarget extends CallTraceable {
+
+ public byte foo(byte b) {
+ register(FOO, b);
+ return b;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class ShortTarget extends CallTraceable {
+
+ public short foo(short s) {
+ register(FOO, s);
+ return s;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class CharTarget extends CallTraceable {
+
+ public char foo(char c) {
+ register(FOO, c);
+ return c;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IntTarget extends CallTraceable {
+
+ public int foo(int i) {
+ register(FOO, i);
+ return i;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class LongTarget extends CallTraceable {
+
+ public long foo(long l) {
+ register(FOO, l);
+ return l;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FloatTarget extends CallTraceable {
+
+ public float foo(float f) {
+ register(FOO, f);
+ return f;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class DoubleTarget extends CallTraceable {
+
+ public double foo(double d) {
+ register(FOO, d);
+ return d;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class NullTarget extends CallTraceable {
+
+ public void foo(Void v) {
+ register(FOO, v);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/auxiliary/MethodCallProxyTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/auxiliary/MethodCallProxyTest.java
new file mode 100644
index 0000000..552e87b
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/auxiliary/MethodCallProxyTest.java
@@ -0,0 +1,121 @@
+package net.bytebuddy.implementation.auxiliary;
+
+import net.bytebuddy.test.utility.CallTraceable;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Type;
+import java.util.concurrent.Callable;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodCallProxyTest extends AbstractMethodCallProxyTest {
+
+ private static final long LONG_VALUE = 42L;
+
+ private static final String String_VALUE = "BAR";
+
+ private static final int INT_VALUE = 21;
+
+ private static final boolean BOOLEAN_VALUE = true;
+
+ @Test
+ public void testNoParameterMethod() throws Exception {
+ Class<?> auxiliaryType = proxyOnlyDeclaredMethodOf(NoParameterMethod.class);
+ Constructor<?> constructor = auxiliaryType.getDeclaredConstructor(NoParameterMethod.class);
+ constructor.setAccessible(true);
+ NoParameterMethod runnableProxied = new NoParameterMethod();
+ ((Runnable) constructor.newInstance(runnableProxied)).run();
+ runnableProxied.assertOnlyCall(FOO, runnableProxied);
+ NoParameterMethod callableProxied = new NoParameterMethod();
+ assertThat(((Callable<?>) constructor.newInstance(callableProxied)).call(), nullValue());
+ callableProxied.assertOnlyCall(FOO, callableProxied);
+ }
+
+ @Test
+ public void testStaticMethod() throws Exception {
+ Class<?> auxiliaryType = proxyOnlyDeclaredMethodOf(StaticMethod.class);
+ Constructor<?> constructor = auxiliaryType.getDeclaredConstructor();
+ constructor.setAccessible(true);
+ ((Runnable) constructor.newInstance()).run();
+ StaticMethod.CALL_TRACEABLE.assertOnlyCall(FOO);
+ StaticMethod.CALL_TRACEABLE.reset();
+ assertThat(((Callable<?>) constructor.newInstance()).call(), nullValue());
+ StaticMethod.CALL_TRACEABLE.assertOnlyCall(FOO);
+ StaticMethod.CALL_TRACEABLE.reset();
+ }
+
+ @Test
+ public void testMultipleParameterMethod() throws Exception {
+ Class<?> auxiliaryType = proxyOnlyDeclaredMethodOf(MultipleParameterMethod.class);
+ Constructor<?> constructor = auxiliaryType.getDeclaredConstructor(MultipleParameterMethod.class,
+ long.class,
+ String.class,
+ int.class,
+ boolean.class);
+ constructor.setAccessible(true);
+ MultipleParameterMethod runnableProxied = new MultipleParameterMethod();
+ Object[] runnableArguments = new Object[]{runnableProxied, LONG_VALUE, String_VALUE, INT_VALUE, BOOLEAN_VALUE};
+ ((Runnable) constructor.newInstance(runnableArguments)).run();
+ runnableProxied.assertOnlyCall(FOO, runnableArguments);
+ MultipleParameterMethod callableProxied = new MultipleParameterMethod();
+ Object[] callableArguments = new Object[]{callableProxied, LONG_VALUE, String_VALUE, INT_VALUE, BOOLEAN_VALUE};
+ assertThat(((Callable<?>) constructor.newInstance(callableArguments)).call(), nullValue());
+ callableProxied.assertOnlyCall(FOO, callableArguments);
+ }
+
+ @Test
+ public void testNonGenericParameter() throws Exception {
+ Class<?> auxiliaryType = proxyOnlyDeclaredMethodOf(GenericType.class);
+ assertThat(auxiliaryType.getTypeParameters().length, is(0));
+ assertThat(auxiliaryType.getDeclaredMethod("call").getGenericReturnType(), is((Type) Object.class));
+ assertThat(auxiliaryType.getDeclaredFields()[1].getGenericType(), is((Type) Object.class));
+ assertThat(auxiliaryType.getDeclaredFields()[2].getGenericType(), is((Type) Number.class));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodCallProxy.class).apply();
+ ObjectPropertyAssertion.of(MethodCallProxy.AssignableSignatureCall.class).apply();
+ ObjectPropertyAssertion.of(MethodCallProxy.ConstructorCall.class).apply();
+ ObjectPropertyAssertion.of(MethodCallProxy.MethodCall.class).apply();
+ ObjectPropertyAssertion.of(MethodCallProxy.MethodCall.Appender.class).skipSynthetic().apply();
+ }
+
+ @SuppressWarnings("unused")
+ public static class NoParameterMethod extends CallTraceable {
+
+ public void foo() {
+ register(FOO, this);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class StaticMethod extends CallTraceable {
+
+ public static final CallTraceable CALL_TRACEABLE = new CallTraceable();
+
+ public static void foo() {
+ CALL_TRACEABLE.register(FOO);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class MultipleParameterMethod extends CallTraceable {
+
+ public void foo(long l, String s, int i, boolean b) {
+ register(FOO, this, l, s, i, b);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class GenericType<T, S extends Number> {
+
+ T foo(T t, S s) {
+ return t;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/auxiliary/TrivialTypeTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/auxiliary/TrivialTypeTest.java
new file mode 100644
index 0000000..f0dbc8b
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/auxiliary/TrivialTypeTest.java
@@ -0,0 +1,58 @@
+package net.bytebuddy.implementation.auxiliary;
+
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.implementation.MethodAccessorFactory;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.Opcodes;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class TrivialTypeTest {
+
+ private static final String FOO = "foo";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private ClassFileVersion classFileVersion;
+
+ @Mock
+ private MethodAccessorFactory methodAccessorFactory;
+
+ @Test
+ public void testPlain() throws Exception {
+ when(classFileVersion.getMinorMajorVersion()).thenReturn(ClassFileVersion.JAVA_V5.getMinorMajorVersion());
+ DynamicType dynamicType = TrivialType.PLAIN.make(FOO, classFileVersion, methodAccessorFactory);
+ assertThat(dynamicType.getTypeDescription().getName(), is(FOO));
+ assertThat(dynamicType.getTypeDescription().getModifiers(), is(Opcodes.ACC_SYNTHETIC));
+ assertThat(dynamicType.getTypeDescription().getDeclaredAnnotations().size(), is(0));
+ assertThat(dynamicType.getAuxiliaryTypes().size(), is(0));
+ assertThat(dynamicType.getLoadedTypeInitializers().get(dynamicType.getTypeDescription()).isAlive(), is(false));
+ }
+
+ @Test
+ public void testEager() throws Exception {
+ when(classFileVersion.getMinorMajorVersion()).thenReturn(ClassFileVersion.JAVA_V5.getMinorMajorVersion());
+ DynamicType dynamicType = TrivialType.SIGNATURE_RELEVANT.make(FOO, classFileVersion, methodAccessorFactory);
+ assertThat(dynamicType.getTypeDescription().getName(), is(FOO));
+ assertThat(dynamicType.getTypeDescription().getModifiers(), is(Opcodes.ACC_SYNTHETIC));
+ assertThat(dynamicType.getTypeDescription().getDeclaredAnnotations().size(), is(1));
+ assertThat(dynamicType.getTypeDescription().getDeclaredAnnotations().isAnnotationPresent(AuxiliaryType.SignatureRelevant.class), is(true));
+ assertThat(dynamicType.getAuxiliaryTypes().size(), is(0));
+ assertThat(dynamicType.getLoadedTypeInitializers().get(dynamicType.getTypeDescription()).isAlive(), is(false));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TrivialType.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/auxiliary/TypeProxyCreationTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/auxiliary/TypeProxyCreationTest.java
new file mode 100644
index 0000000..a5fbb5f
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/auxiliary/TypeProxyCreationTest.java
@@ -0,0 +1,361 @@
+package net.bytebuddy.implementation.auxiliary;
+
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.modifier.ModifierContributor;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.dynamic.scaffold.MethodGraph;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.MethodAccessorFactory;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.io.Serializable;
+import java.util.Collections;
+
+import static net.bytebuddy.matcher.ElementMatchers.*;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class TypeProxyCreationTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private Implementation.Target implementationTarget;
+
+ @Mock
+ private TypeProxy.InvocationFactory invocationFactory;
+
+ @Mock
+ private MethodAccessorFactory methodAccessorFactory;
+
+ @Mock
+ private Implementation.SpecialMethodInvocation specialMethodInvocation;
+
+ @Mock
+ private MethodDescription.InDefinedShape proxyMethod;
+
+ private TypeDescription foo;
+
+ private MethodList<?> fooMethods;
+
+ private int modifiers;
+
+ @Before
+ public void setUp() throws Exception {
+ for (ModifierContributor modifierContributor : AuxiliaryType.DEFAULT_TYPE_MODIFIER) {
+ modifiers = modifiers | modifierContributor.getMask();
+ }
+ foo = new TypeDescription.ForLoadedType(Foo.class);
+ fooMethods = MethodGraph.Compiler.DEFAULT.compile(foo)
+ .listNodes()
+ .asMethodList()
+ .filter(isVirtual().and(not(isFinal())).and(not(isDefaultFinalizer())));
+ when(proxyMethod.getParameters()).thenReturn(new ParameterList.Explicit.ForTypes(proxyMethod, foo, foo, foo));
+ when(proxyMethod.getDeclaringType()).thenReturn(foo);
+ when(proxyMethod.getInternalName()).thenReturn(FOO);
+ when(proxyMethod.getDescriptor()).thenReturn(FOO);
+ when(proxyMethod.getReturnType()).thenReturn(TypeDescription.Generic.OBJECT);
+ when(proxyMethod.asDefined()).thenReturn(proxyMethod);
+ }
+
+ @Test
+ public void testAllIllegal() throws Exception {
+ when(implementationTarget.getInstrumentedType()).thenReturn(foo);
+ when(invocationFactory.invoke(eq(implementationTarget), eq(foo), any(MethodDescription.class)))
+ .thenReturn(specialMethodInvocation);
+ TypeDescription dynamicType = new TypeProxy(foo,
+ implementationTarget,
+ invocationFactory,
+ true,
+ false)
+ .make(BAR, ClassFileVersion.ofThisVm(), methodAccessorFactory)
+ .getTypeDescription();
+ assertThat(dynamicType.getModifiers(), is(modifiers));
+ assertThat(dynamicType.getSuperClass().asErasure(), is(foo));
+ assertThat(dynamicType.getInterfaces(), is((TypeList.Generic) new TypeList.Generic.Empty()));
+ assertThat(dynamicType.getName(), is(BAR));
+ assertThat(dynamicType.getDeclaredMethods().size(), is(2));
+ assertThat(dynamicType.isAssignableTo(Serializable.class), is(false));
+ verifyZeroInteractions(methodAccessorFactory);
+ for (MethodDescription methodDescription : fooMethods) {
+ verify(invocationFactory).invoke(implementationTarget, foo, methodDescription);
+ }
+ verifyNoMoreInteractions(invocationFactory);
+ verify(specialMethodInvocation, times(fooMethods.size())).isValid();
+ verifyNoMoreInteractions(specialMethodInvocation);
+ }
+
+ @Test
+ public void testAllLegal() throws Exception {
+ when(implementationTarget.getInstrumentedType()).thenReturn(foo);
+ when(invocationFactory.invoke(eq(implementationTarget), eq(foo), any(MethodDescription.class)))
+ .thenReturn(specialMethodInvocation);
+ when(specialMethodInvocation.isValid()).thenReturn(true);
+ when(specialMethodInvocation.apply(any(MethodVisitor.class), any(Implementation.Context.class)))
+ .thenReturn(new StackManipulation.Size(0, 0));
+ when(methodAccessorFactory.registerAccessorFor(specialMethodInvocation, MethodAccessorFactory.AccessType.DEFAULT)).thenReturn(proxyMethod);
+ TypeDescription dynamicType = new TypeProxy(foo,
+ implementationTarget,
+ invocationFactory,
+ true,
+ false)
+ .make(BAR, ClassFileVersion.ofThisVm(), methodAccessorFactory)
+ .getTypeDescription();
+ assertThat(dynamicType.getModifiers(), is(modifiers));
+ assertThat(dynamicType.getSuperClass().asErasure(), is(foo));
+ assertThat(dynamicType.getInterfaces(), is((TypeList.Generic) new TypeList.Generic.Empty()));
+ assertThat(dynamicType.getName(), is(BAR));
+ assertThat(dynamicType.getDeclaredMethods().size(), is(2));
+ assertThat(dynamicType.isAssignableTo(Serializable.class), is(false));
+ verify(methodAccessorFactory, times(fooMethods.size())).registerAccessorFor(specialMethodInvocation, MethodAccessorFactory.AccessType.DEFAULT);
+ for (MethodDescription methodDescription : fooMethods) {
+ verify(invocationFactory).invoke(implementationTarget, foo, methodDescription);
+ }
+ verifyNoMoreInteractions(invocationFactory);
+ verify(specialMethodInvocation, times(fooMethods.size())).isValid();
+ verifyNoMoreInteractions(specialMethodInvocation);
+ }
+
+ @Test
+ public void testAllLegalSerializable() throws Exception {
+ when(implementationTarget.getInstrumentedType()).thenReturn(foo);
+ when(invocationFactory.invoke(eq(implementationTarget), eq(foo), any(MethodDescription.class)))
+ .thenReturn(specialMethodInvocation);
+ when(specialMethodInvocation.isValid()).thenReturn(true);
+ when(specialMethodInvocation.apply(any(MethodVisitor.class), any(Implementation.Context.class)))
+ .thenReturn(new StackManipulation.Size(0, 0));
+ when(methodAccessorFactory.registerAccessorFor(specialMethodInvocation, MethodAccessorFactory.AccessType.DEFAULT)).thenReturn(proxyMethod);
+ TypeDescription dynamicType = new TypeProxy(foo,
+ implementationTarget,
+ invocationFactory,
+ true,
+ true)
+ .make(BAR, ClassFileVersion.ofThisVm(), methodAccessorFactory)
+ .getTypeDescription();
+ assertThat(dynamicType.getModifiers(), is(modifiers));
+ assertThat(dynamicType.getSuperClass().asErasure(), is(foo));
+ assertThat(dynamicType.getInterfaces(), is((TypeList.Generic) new TypeList.Generic.ForLoadedTypes(Serializable.class)));
+ assertThat(dynamicType.getName(), is(BAR));
+ assertThat(dynamicType.getDeclaredMethods().size(), is(2));
+ assertThat(dynamicType.isAssignableTo(Serializable.class), is(true));
+ verify(methodAccessorFactory, times(fooMethods.size())).registerAccessorFor(specialMethodInvocation, MethodAccessorFactory.AccessType.DEFAULT);
+ for (MethodDescription methodDescription : fooMethods) {
+ verify(invocationFactory).invoke(implementationTarget, foo, methodDescription);
+ }
+ verifyNoMoreInteractions(invocationFactory);
+ verify(specialMethodInvocation, times(fooMethods.size())).isValid();
+ verifyNoMoreInteractions(specialMethodInvocation);
+ }
+
+ @Test
+ public void testAllLegalNotIgnoreFinalizer() throws Exception {
+ when(implementationTarget.getInstrumentedType()).thenReturn(foo);
+ when(invocationFactory.invoke(eq(implementationTarget), eq(foo), any(MethodDescription.class)))
+ .thenReturn(specialMethodInvocation);
+ when(specialMethodInvocation.isValid()).thenReturn(true);
+ when(specialMethodInvocation.apply(any(MethodVisitor.class), any(Implementation.Context.class)))
+ .thenReturn(new StackManipulation.Size(0, 0));
+ when(methodAccessorFactory.registerAccessorFor(specialMethodInvocation, MethodAccessorFactory.AccessType.DEFAULT)).thenReturn(proxyMethod);
+ TypeDescription dynamicType = new TypeProxy(foo,
+ implementationTarget,
+ invocationFactory,
+ false,
+ false)
+ .make(BAR, ClassFileVersion.ofThisVm(), methodAccessorFactory)
+ .getTypeDescription();
+ assertThat(dynamicType.getModifiers(), is(modifiers));
+ assertThat(dynamicType.getSuperClass().asErasure(), is(foo));
+ assertThat(dynamicType.getInterfaces(), is((TypeList.Generic) new TypeList.Generic.Empty()));
+ assertThat(dynamicType.getName(), is(BAR));
+ assertThat(dynamicType.getDeclaredMethods().size(), is(2));
+ assertThat(dynamicType.isAssignableTo(Serializable.class), is(false));
+ verify(methodAccessorFactory, times(fooMethods.size() + 1)).registerAccessorFor(specialMethodInvocation, MethodAccessorFactory.AccessType.DEFAULT);
+ for (MethodDescription methodDescription : fooMethods) {
+ verify(invocationFactory).invoke(implementationTarget, foo, methodDescription);
+ }
+ verify(invocationFactory).invoke(implementationTarget, foo,
+ new MethodDescription.ForLoadedMethod(Object.class.getDeclaredMethod("finalize")));
+ verifyNoMoreInteractions(invocationFactory);
+ verify(specialMethodInvocation, times(fooMethods.size() + 1)).isValid();
+ verifyNoMoreInteractions(specialMethodInvocation);
+ }
+
+ @Test
+ public void testForConstructorConstruction() throws Exception {
+ when(implementationTarget.getInstrumentedType()).thenReturn(foo);
+ when(invocationFactory.invoke(eq(implementationTarget), eq(foo), any(MethodDescription.class)))
+ .thenReturn(specialMethodInvocation);
+ when(specialMethodInvocation.isValid()).thenReturn(true);
+ when(specialMethodInvocation.apply(any(MethodVisitor.class), any(Implementation.Context.class)))
+ .thenReturn(new StackManipulation.Size(0, 0));
+ when(methodAccessorFactory.registerAccessorFor(specialMethodInvocation, MethodAccessorFactory.AccessType.DEFAULT)).thenReturn(proxyMethod);
+ StackManipulation stackManipulation = new TypeProxy.ForSuperMethodByConstructor(foo,
+ implementationTarget,
+ Collections.singletonList((TypeDescription) new TypeDescription.ForLoadedType(Void.class)),
+ true,
+ false);
+ MethodVisitor methodVisitor = mock(MethodVisitor.class);
+ Implementation.Context implementationContext = mock(Implementation.Context.class);
+ when(implementationContext.register(any(AuxiliaryType.class))).thenReturn(foo);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(1));
+ assertThat(size.getMaximalSize(), is(3));
+ verify(implementationContext).register(any(AuxiliaryType.class));
+ verifyNoMoreInteractions(implementationContext);
+ verify(methodVisitor).visitTypeInsn(Opcodes.NEW, Type.getInternalName(Foo.class));
+ verify(methodVisitor, times(2)).visitInsn(Opcodes.DUP);
+ verify(methodVisitor).visitInsn(Opcodes.ACONST_NULL);
+ verify(methodVisitor).visitMethodInsn(Opcodes.INVOKESPECIAL,
+ foo.getInternalName(),
+ MethodDescription.CONSTRUCTOR_INTERNAL_NAME,
+ foo.getDeclaredMethods().filter(isConstructor()).getOnly().getDescriptor(),
+ false);
+ verify(methodVisitor).visitFieldInsn(Opcodes.PUTFIELD,
+ foo.getInternalName(),
+ TypeProxy.INSTANCE_FIELD,
+ Type.getDescriptor(Void.class));
+ verify(methodVisitor).visitVarInsn(Opcodes.ALOAD, 0);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testForDefaultMethodConstruction() throws Exception {
+ when(implementationTarget.getInstrumentedType()).thenReturn(foo);
+ when(invocationFactory.invoke(eq(implementationTarget), eq(foo), any(MethodDescription.class)))
+ .thenReturn(specialMethodInvocation);
+ when(specialMethodInvocation.isValid()).thenReturn(true);
+ when(specialMethodInvocation.apply(any(MethodVisitor.class), any(Implementation.Context.class)))
+ .thenReturn(new StackManipulation.Size(0, 0));
+ when(methodAccessorFactory.registerAccessorFor(specialMethodInvocation, MethodAccessorFactory.AccessType.DEFAULT)).thenReturn(proxyMethod);
+ StackManipulation stackManipulation = new TypeProxy.ForDefaultMethod(foo,
+ implementationTarget,
+ false);
+ MethodVisitor methodVisitor = mock(MethodVisitor.class);
+ Implementation.Context implementationContext = mock(Implementation.Context.class);
+ when(implementationContext.register(any(AuxiliaryType.class))).thenReturn(foo);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(0));
+ assertThat(size.getMaximalSize(), is(2));
+ verify(implementationContext).register(any(AuxiliaryType.class));
+ verifyNoMoreInteractions(implementationContext);
+ verify(methodVisitor).visitTypeInsn(Opcodes.NEW, Type.getInternalName(Foo.class));
+ verify(methodVisitor, times(2)).visitInsn(Opcodes.DUP);
+ verify(methodVisitor).visitMethodInsn(Opcodes.INVOKESPECIAL,
+ foo.getInternalName(),
+ MethodDescription.CONSTRUCTOR_INTERNAL_NAME,
+ foo.getDeclaredMethods().filter(isConstructor()).getOnly().getDescriptor(),
+ false);
+ verify(methodVisitor).visitFieldInsn(Opcodes.PUTFIELD,
+ foo.getInternalName(),
+ TypeProxy.INSTANCE_FIELD,
+ Type.getDescriptor(Void.class));
+ verify(methodVisitor).visitVarInsn(Opcodes.ALOAD, 0);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testForReflectionFactoryConstruction() throws Exception {
+ when(implementationTarget.getInstrumentedType()).thenReturn(foo);
+ when(invocationFactory.invoke(eq(implementationTarget), eq(foo), any(MethodDescription.class)))
+ .thenReturn(specialMethodInvocation);
+ when(specialMethodInvocation.isValid()).thenReturn(true);
+ when(specialMethodInvocation.apply(any(MethodVisitor.class), any(Implementation.Context.class)))
+ .thenReturn(new StackManipulation.Size(0, 0));
+ when(methodAccessorFactory.registerAccessorFor(specialMethodInvocation, MethodAccessorFactory.AccessType.DEFAULT)).thenReturn(proxyMethod);
+ StackManipulation stackManipulation = new TypeProxy.ForSuperMethodByReflectionFactory(foo,
+ implementationTarget,
+ true,
+ false);
+ MethodVisitor methodVisitor = mock(MethodVisitor.class);
+ Implementation.Context implementationContext = mock(Implementation.Context.class);
+ when(implementationContext.register(any(AuxiliaryType.class)))
+ .thenReturn(new TypeDescription.ForLoadedType(FooProxyMake.class));
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(1));
+ assertThat(size.getMaximalSize(), is(3));
+ verify(implementationContext).register(any(AuxiliaryType.class));
+ verifyNoMoreInteractions(implementationContext);
+ verify(methodVisitor).visitMethodInsn(Opcodes.INVOKESTATIC,
+ Type.getInternalName(FooProxyMake.class),
+ TypeProxy.REFLECTION_METHOD,
+ Type.getMethodDescriptor(FooProxyMake.class.getDeclaredMethod("make")),
+ false);
+ verify(methodVisitor).visitInsn(Opcodes.DUP);
+ verify(methodVisitor).visitFieldInsn(Opcodes.PUTFIELD,
+ Type.getInternalName(FooProxyMake.class),
+ TypeProxy.INSTANCE_FIELD,
+ Type.getDescriptor(Void.class));
+ verify(methodVisitor).visitVarInsn(Opcodes.ALOAD, 0);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testImplementationIsValid() throws Exception {
+ assertThat(TypeProxy.AbstractMethodErrorThrow.INSTANCE.isValid(), is(true));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testAccessorIsValid() throws Exception {
+ TypeProxy typeProxy = new TypeProxy(mock(TypeDescription.class),
+ mock(Implementation.Target.class),
+ mock(TypeProxy.InvocationFactory.class),
+ false,
+ false);
+ TypeProxy.MethodCall methodCall = typeProxy.new MethodCall(mock(MethodAccessorFactory.class));
+ TypeDescription instrumentedType = mock(TypeDescription.class);
+ FieldList<FieldDescription.InDefinedShape> fieldList = mock(FieldList.class);
+ when(fieldList.filter(any(ElementMatcher.class))).thenReturn(fieldList);
+ when(fieldList.getOnly()).thenReturn(mock(FieldDescription.InDefinedShape.class));
+ when(instrumentedType.getDeclaredFields()).thenReturn(fieldList);
+ TypeProxy.MethodCall.Appender appender = methodCall.new Appender(instrumentedType);
+ Implementation.SpecialMethodInvocation specialMethodInvocation = mock(Implementation.SpecialMethodInvocation.class);
+ when(specialMethodInvocation.isValid()).thenReturn(true);
+ StackManipulation stackManipulation = appender.new AccessorMethodInvocation(mock(MethodDescription.class), specialMethodInvocation);
+ assertThat(stackManipulation.isValid(), is(true));
+ verify(specialMethodInvocation).isValid();
+ verifyNoMoreInteractions(specialMethodInvocation);
+ }
+
+ @SuppressWarnings("unused")
+ public static class Foo {
+
+ private Void target;
+
+ public Foo(Void argument) {
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FooProxyMake {
+
+ private Void target;
+
+ public static FooProxyMake make() {
+ return null;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/auxiliary/TypeProxyInvocationFactoryDefaultTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/auxiliary/TypeProxyInvocationFactoryDefaultTest.java
new file mode 100644
index 0000000..b445d8e
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/auxiliary/TypeProxyInvocationFactoryDefaultTest.java
@@ -0,0 +1,59 @@
+package net.bytebuddy.implementation.auxiliary;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class TypeProxyInvocationFactoryDefaultTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private Implementation.Target implementationTarget;
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Mock
+ private MethodDescription methodDescription;
+
+ @Mock
+ private MethodDescription.SignatureToken token;
+
+ @Mock
+ private Implementation.SpecialMethodInvocation specialMethodInvocation;
+
+ @Before
+ public void setUp() throws Exception {
+ when(methodDescription.asSignatureToken()).thenReturn(token);
+ }
+
+ @Test
+ public void testSuperMethod() throws Exception {
+ when(implementationTarget.invokeDominant(token)).thenReturn(specialMethodInvocation);
+ assertThat(TypeProxy.InvocationFactory.Default.SUPER_METHOD.invoke(implementationTarget, typeDescription, methodDescription),
+ is(specialMethodInvocation));
+ verify(implementationTarget).invokeDominant(token);
+ verifyNoMoreInteractions(implementationTarget);
+ }
+
+ @Test
+ public void testDefaultMethod() throws Exception {
+ when(implementationTarget.invokeDefault(token, typeDescription)).thenReturn(specialMethodInvocation);
+ assertThat(TypeProxy.InvocationFactory.Default.DEFAULT_METHOD.invoke(implementationTarget, typeDescription, methodDescription),
+ is(specialMethodInvocation));
+ verify(implementationTarget).invokeDefault(token, typeDescription);
+ verifyNoMoreInteractions(implementationTarget);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/auxiliary/TypeProxyObjectPropertiesTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/auxiliary/TypeProxyObjectPropertiesTest.java
new file mode 100644
index 0000000..d4614e3
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/auxiliary/TypeProxyObjectPropertiesTest.java
@@ -0,0 +1,48 @@
+package net.bytebuddy.implementation.auxiliary;
+
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import static org.mockito.Mockito.when;
+
+public class TypeProxyObjectPropertiesTest {
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeProxy.class).apply();
+ ObjectPropertyAssertion.of(TypeProxy.AbstractMethodErrorThrow.class).apply();
+ ObjectPropertyAssertion.of(TypeProxy.SilentConstruction.class).apply();
+ ObjectPropertyAssertion.of(TypeProxy.MethodCall.class).skipSynthetic().apply();
+ ObjectPropertyAssertion.of(TypeProxy.MethodCall.Appender.class).refine(new ObjectPropertyAssertion.Refinement<TypeDescription>() {
+ @Override
+ public void apply(TypeDescription mock) {
+ FieldDescription.InDefinedShape fieldDescription = Mockito.mock(FieldDescription.InDefinedShape.class);
+ when(fieldDescription.getActualName()).thenReturn(TypeProxy.INSTANCE_FIELD);
+ when(mock.getDeclaredFields()).thenReturn(new FieldList.Explicit<FieldDescription.InDefinedShape>(fieldDescription));
+ }
+ }).skipSynthetic().apply();
+ ObjectPropertyAssertion.of(TypeProxy.MethodCall.Appender.AccessorMethodInvocation.class).skipSynthetic().apply();
+ ObjectPropertyAssertion.of(TypeProxy.SilentConstruction.Appender.class).skipSynthetic().apply();
+ ObjectPropertyAssertion.of(TypeProxy.InvocationFactory.Default.class).apply();
+ }
+
+ @Test
+ public void testConstructorObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeProxy.ForSuperMethodByConstructor.class).apply();
+ }
+
+ @Test
+ public void testReflectionFactoryObjectPropertiesFactoryEqualsHashCode() throws Exception {
+ ObjectPropertyAssertion.of(TypeProxy.ForSuperMethodByReflectionFactory.class).apply();
+ }
+
+ @Test
+ public void testDefaultMethodObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeProxy.ForDefaultMethod.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/AbstractAmbiguityResolverTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/AbstractAmbiguityResolverTest.java
new file mode 100644
index 0000000..129bce5
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/AbstractAmbiguityResolverTest.java
@@ -0,0 +1,31 @@
+package net.bytebuddy.implementation.bind;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.mockito.Mockito.when;
+
+public abstract class AbstractAmbiguityResolverTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ protected MethodDescription source;
+
+ @Mock
+ protected MethodDescription leftMethod, rightMethod;
+
+ @Mock
+ protected MethodDelegationBinder.MethodBinding left, right;
+
+ @Before
+ public void setUp() throws Exception {
+ when(left.getTarget()).thenReturn(leftMethod);
+ when(right.getTarget()).thenReturn(rightMethod);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/AbstractArgumentTypeResolverTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/AbstractArgumentTypeResolverTest.java
new file mode 100644
index 0000000..011ce54
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/AbstractArgumentTypeResolverTest.java
@@ -0,0 +1,80 @@
+package net.bytebuddy.implementation.bind;
+
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.TypeDescription;
+import org.junit.Before;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.*;
+
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.when;
+
+public class AbstractArgumentTypeResolverTest extends AbstractAmbiguityResolverTest {
+
+ @Mock
+ protected ParameterList<?> sourceParameterList, leftParameterList, rightParameterList;
+
+ @Mock
+ protected TypeDescription sourceType;
+
+ @Mock
+ protected TypeDescription.Generic genericSourceType;
+
+ @Mock
+ private ParameterDescription sourceParameter;
+
+ protected static ArgumentMatcher<? super ArgumentTypeResolver.ParameterIndexToken> describesArgument(final int... index) {
+ return new ArgumentMatcher<ArgumentTypeResolver.ParameterIndexToken>() {
+ @Override
+ public boolean matches(ArgumentTypeResolver.ParameterIndexToken parameterIndexToken) {
+ for (int anIndex : index) {
+ if (parameterIndexToken.equals(new ArgumentTypeResolver.ParameterIndexToken(anIndex))) {
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+ }
+
+ @Override
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ super.setUp();
+ when(source.getParameters()).thenReturn((ParameterList) sourceParameterList);
+ when(sourceParameterList.get(anyInt())).thenReturn(sourceParameter);
+ when(sourceParameter.getType()).thenReturn(genericSourceType);
+ when(genericSourceType.asErasure()).thenReturn(sourceType);
+ when(leftMethod.getParameters()).thenReturn((ParameterList) leftParameterList);
+ when(rightMethod.getParameters()).thenReturn((ParameterList) rightParameterList);
+ }
+
+ protected static class TokenAnswer implements Answer<Integer> {
+
+ private final Map<ArgumentTypeResolver.ParameterIndexToken, Integer> indexMapping;
+
+ protected TokenAnswer(int[][] mapping) {
+ Map<ArgumentTypeResolver.ParameterIndexToken, Integer> indexMapping = new HashMap<ArgumentTypeResolver.ParameterIndexToken, Integer>();
+ for (int[] entry : mapping) {
+ assert entry.length == 2;
+ assert entry[0] >= 0;
+ assert entry[1] >= 0;
+ Object override = indexMapping.put(new ArgumentTypeResolver.ParameterIndexToken(entry[0]), entry[1]);
+ assert override == null;
+ }
+ this.indexMapping = Collections.unmodifiableMap(indexMapping);
+ }
+
+ @Override
+ public Integer answer(InvocationOnMock invocation) throws Throwable {
+ assert invocation.getArguments().length == 1;
+ return indexMapping.get((ArgumentTypeResolver.ParameterIndexToken) invocation.getArguments()[0]);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/ArgumentTypeResolverPrimitiveTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/ArgumentTypeResolverPrimitiveTest.java
new file mode 100644
index 0000000..032c6ed
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/ArgumentTypeResolverPrimitiveTest.java
@@ -0,0 +1,143 @@
+package net.bytebuddy.implementation.bind;
+
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.Mock;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.AdditionalMatchers.not;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class ArgumentTypeResolverPrimitiveTest extends AbstractArgumentTypeResolverTest {
+
+ private final Class<?> firstType;
+
+ private final Class<?> secondType;
+
+ @Mock
+ private TypeDescription.Generic firstPrimitive, secondPrimitive;
+
+ @Mock
+ private TypeDescription firstRawPrimitive, secondRawPrimitive;
+
+ public ArgumentTypeResolverPrimitiveTest(Class<?> firstType, Class<?> secondType) {
+ this.firstType = firstType;
+ this.secondType = secondType;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {boolean.class, byte.class},
+ {boolean.class, short.class},
+ {boolean.class, char.class},
+ {boolean.class, int.class},
+ {boolean.class, long.class},
+ {boolean.class, float.class},
+ {boolean.class, double.class},
+
+ {byte.class, short.class},
+ {byte.class, char.class},
+ {byte.class, int.class},
+ {byte.class, long.class},
+ {byte.class, float.class},
+ {byte.class, double.class},
+
+ {short.class, char.class},
+ {short.class, int.class},
+ {short.class, long.class},
+ {short.class, float.class},
+ {short.class, double.class},
+
+ {char.class, long.class},
+ {char.class, float.class},
+ {char.class, double.class},
+
+ {int.class, char.class},
+ {int.class, long.class},
+ {int.class, float.class},
+ {int.class, double.class},
+
+ {long.class, float.class},
+ {long.class, double.class},
+
+ {float.class, double.class},
+ });
+ }
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ when(firstPrimitive.asErasure()).thenReturn(firstRawPrimitive);
+ when(secondPrimitive.asErasure()).thenReturn(secondRawPrimitive);
+ when(sourceType.isPrimitive()).thenReturn(true);
+ when(firstRawPrimitive.isPrimitive()).thenReturn(true);
+ when(firstRawPrimitive.represents(firstType)).thenReturn(true);
+ when(secondRawPrimitive.isPrimitive()).thenReturn(true);
+ when(secondRawPrimitive.represents(secondType)).thenReturn(true);
+ }
+
+ @Test
+ public void testLeftDominance() throws Exception {
+ testDominance(firstPrimitive, secondPrimitive, MethodDelegationBinder.AmbiguityResolver.Resolution.LEFT);
+ }
+
+ @Test
+ public void testRightDominance() throws Exception {
+ testDominance(secondPrimitive, firstPrimitive, MethodDelegationBinder.AmbiguityResolver.Resolution.RIGHT);
+ }
+
+ @Test
+ public void testLeftNonDominance() throws Exception {
+ testDominance(secondPrimitive, firstPrimitive, MethodDelegationBinder.AmbiguityResolver.Resolution.RIGHT);
+ }
+
+ @Test
+ public void testRightNonDominance() throws Exception {
+ testDominance(firstPrimitive, secondPrimitive, MethodDelegationBinder.AmbiguityResolver.Resolution.LEFT);
+ }
+
+ @Test
+ public void testNonDominance() throws Exception {
+ testDominance(firstPrimitive, firstPrimitive, MethodDelegationBinder.AmbiguityResolver.Resolution.AMBIGUOUS);
+ }
+
+ private void testDominance(TypeDescription.Generic leftPrimitive,
+ TypeDescription.Generic rightPrimitive,
+ MethodDelegationBinder.AmbiguityResolver.Resolution expected) throws Exception {
+ when(sourceParameterList.size()).thenReturn(2);
+ when(sourceType.isPrimitive()).thenReturn(true);
+ ParameterDescription leftParameter = mock(ParameterDescription.class);
+ when(leftParameter.getType()).thenReturn(leftPrimitive);
+ when(leftParameterList.get(0)).thenReturn(leftParameter);
+ when(left.getTargetParameterIndex(any(ArgumentTypeResolver.ParameterIndexToken.class)))
+ .thenAnswer(new TokenAnswer(new int[][]{{0, 0}}));
+ ParameterDescription rightParameter = mock(ParameterDescription.class);
+ when(rightParameter.getType()).thenReturn(rightPrimitive);
+ when(rightParameterList.get(0)).thenReturn(rightParameter);
+ when(right.getTargetParameterIndex(any(ArgumentTypeResolver.ParameterIndexToken.class)))
+ .thenAnswer(new TokenAnswer(new int[][]{{0, 0}}));
+ MethodDelegationBinder.AmbiguityResolver.Resolution resolution =
+ ArgumentTypeResolver.INSTANCE.resolve(source, left, right);
+ assertThat(resolution, is(expected));
+ verify(source, atLeast(1)).getParameters();
+ verify(leftMethod, atLeast(1)).getParameters();
+ verify(rightMethod, atLeast(1)).getParameters();
+ verify(left, atLeast(1)).getTargetParameterIndex(argThat(describesArgument(0)));
+ verify(left, atLeast(1)).getTargetParameterIndex(argThat(describesArgument(1)));
+ verify(left, never()).getTargetParameterIndex(not(argThat(describesArgument(0, 1))));
+ verify(right, atLeast(1)).getTargetParameterIndex(argThat(describesArgument(0)));
+ verify(right, atLeast(1)).getTargetParameterIndex(argThat(describesArgument(1)));
+ verify(right, never()).getTargetParameterIndex(not(argThat(describesArgument(0, 1))));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/ArgumentTypeResolverReferenceTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/ArgumentTypeResolverReferenceTest.java
new file mode 100644
index 0000000..25c6f43
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/ArgumentTypeResolverReferenceTest.java
@@ -0,0 +1,244 @@
+package net.bytebuddy.implementation.bind;
+
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.AdditionalMatchers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.AdditionalMatchers.not;
+import static org.mockito.Mockito.*;
+
+public class ArgumentTypeResolverReferenceTest extends AbstractArgumentTypeResolverTest {
+
+ @Mock
+ private TypeDescription weakTargetType, dominantTargetType;
+
+ @Mock
+ private TypeDescription.Generic genericWeakTargetType, genericDominantTargetType;
+
+ @Mock
+ private ParameterDescription weakTargetParameter, dominantTargetParameter;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ when(weakTargetType.isAssignableFrom(dominantTargetType)).thenReturn(true);
+ when(weakTargetType.isAssignableFrom(weakTargetType)).thenReturn(true);
+ when(weakTargetType.isAssignableTo(weakTargetType)).thenReturn(true);
+ when(dominantTargetType.isAssignableTo(weakTargetType)).thenReturn(true);
+ when(weakTargetParameter.getType()).thenReturn(genericWeakTargetType);
+ when(dominantTargetParameter.getType()).thenReturn(genericDominantTargetType);
+ when(genericWeakTargetType.asErasure()).thenReturn(weakTargetType);
+ when(genericDominantTargetType.asErasure()).thenReturn(dominantTargetType);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMethodWithoutArguments() throws Exception {
+ when(source.getParameters()).thenReturn((ParameterList) sourceParameterList);
+ MethodDelegationBinder.AmbiguityResolver.Resolution resolution =
+ ArgumentTypeResolver.INSTANCE.resolve(source, left, right);
+ assertThat(resolution.isUnresolved(), is(true));
+ verify(source, atLeast(1)).getParameters();
+ verifyZeroInteractions(left);
+ verifyZeroInteractions(right);
+ }
+
+ @Test
+ public void testMethodWithoutMappedArguments() throws Exception {
+ when(sourceParameterList.size()).thenReturn(1);
+ when(left.getTargetParameterIndex(argThat(describesArgument(0)))).thenReturn(null);
+ when(right.getTargetParameterIndex(argThat(describesArgument(0)))).thenReturn(null);
+ MethodDelegationBinder.AmbiguityResolver.Resolution resolution =
+ ArgumentTypeResolver.INSTANCE.resolve(source, left, right);
+ assertThat(resolution, is(MethodDelegationBinder.AmbiguityResolver.Resolution.AMBIGUOUS));
+ verify(source, atLeast(1)).getParameters();
+ verify(left, atLeast(1)).getTargetParameterIndex(argThat(describesArgument(0)));
+ verify(left, never()).getTargetParameterIndex(not(argThat(describesArgument(0))));
+ verify(right, atLeast(1)).getTargetParameterIndex(argThat(describesArgument(0)));
+ verify(right, never()).getTargetParameterIndex(not(argThat(describesArgument(0))));
+ }
+
+ @Test
+ public void testMethodWithoutSeveralUnmappedArguments() throws Exception {
+ when(sourceParameterList.size()).thenReturn(3);
+ when(left.getTargetParameterIndex(any())).thenReturn(null);
+ when(right.getTargetParameterIndex(any())).thenReturn(null);
+ MethodDelegationBinder.AmbiguityResolver.Resolution resolution =
+ ArgumentTypeResolver.INSTANCE.resolve(source, left, right);
+ assertThat(resolution, is(MethodDelegationBinder.AmbiguityResolver.Resolution.AMBIGUOUS));
+ verify(source, atLeast(1)).getParameters();
+ verify(left, atLeast(1)).getTargetParameterIndex(argThat(describesArgument(0)));
+ verify(left, atLeast(1)).getTargetParameterIndex(argThat(describesArgument(1)));
+ verify(left, atLeast(1)).getTargetParameterIndex(argThat(describesArgument(2)));
+ verify(left, never()).getTargetParameterIndex(not(argThat(describesArgument(0, 1, 2))));
+ verify(right, atLeast(1)).getTargetParameterIndex(argThat(describesArgument(0)));
+ verify(right, atLeast(1)).getTargetParameterIndex(argThat(describesArgument(1)));
+ verify(right, atLeast(1)).getTargetParameterIndex(argThat(describesArgument(2)));
+ verify(right, never()).getTargetParameterIndex(not(argThat(describesArgument(0, 1, 2))));
+ }
+
+ @Test
+ public void testLeftMethodDominantByType() throws Exception {
+ when(sourceParameterList.size()).thenReturn(1);
+ when(leftParameterList.get(0)).thenReturn(dominantTargetParameter);
+ when(left.getTargetParameterIndex(any())).thenAnswer(new TokenAnswer(new int[][]{{0, 0}}));
+ when(rightParameterList.get(1)).thenReturn(weakTargetParameter);
+ when(right.getTargetParameterIndex(any())).thenAnswer(new TokenAnswer(new int[][]{{0, 1}}));
+ MethodDelegationBinder.AmbiguityResolver.Resolution resolution =
+ ArgumentTypeResolver.INSTANCE.resolve(source, left, right);
+ assertThat(resolution, is(MethodDelegationBinder.AmbiguityResolver.Resolution.LEFT));
+ verify(source, atLeast(1)).getParameters();
+ verify(left, atLeast(1)).getTargetParameterIndex(argThat(describesArgument(0)));
+ verify(left, never()).getTargetParameterIndex(not(argThat(describesArgument(0))));
+ verify(right, atLeast(1)).getTargetParameterIndex(argThat(describesArgument(0)));
+ verify(right, never()).getTargetParameterIndex(not(argThat(describesArgument(0))));
+ }
+
+ @Test
+ public void testRightMethodDominantByType() throws Exception {
+ when(sourceParameterList.size()).thenReturn(1);
+ when(leftParameterList.get(0)).thenReturn(weakTargetParameter);
+ when(left.getTargetParameterIndex(any())).thenAnswer(new TokenAnswer(new int[][]{{0, 0}}));
+ when(rightParameterList.get(1)).thenReturn(dominantTargetParameter);
+ when(right.getTargetParameterIndex(any())).thenAnswer(new TokenAnswer(new int[][]{{0, 1}}));
+ MethodDelegationBinder.AmbiguityResolver.Resolution resolution =
+ ArgumentTypeResolver.INSTANCE.resolve(source, left, right);
+ assertThat(resolution, is(MethodDelegationBinder.AmbiguityResolver.Resolution.RIGHT));
+ verify(source, atLeast(1)).getParameters();
+ verify(left, atLeast(1)).getTargetParameterIndex(argThat(describesArgument(0)));
+ verify(left, never()).getTargetParameterIndex(not(argThat(describesArgument(0))));
+ verify(right, atLeast(1)).getTargetParameterIndex(argThat(describesArgument(0)));
+ verify(right, never()).getTargetParameterIndex(not(argThat(describesArgument(0))));
+ }
+
+ @Test
+ public void testAmbiguousByCrossAssignableType() throws Exception {
+ when(sourceParameterList.size()).thenReturn(1);
+ when(leftParameterList.get(0)).thenReturn(weakTargetParameter);
+ when(left.getTargetParameterIndex(any(ArgumentTypeResolver.ParameterIndexToken.class)))
+ .thenAnswer(new TokenAnswer(new int[][]{{0, 0}}));
+ when(rightParameterList.get(0)).thenReturn(weakTargetParameter);
+ when(right.getTargetParameterIndex(any(ArgumentTypeResolver.ParameterIndexToken.class)))
+ .thenAnswer(new TokenAnswer(new int[][]{{0, 0}}));
+ MethodDelegationBinder.AmbiguityResolver.Resolution resolution =
+ ArgumentTypeResolver.INSTANCE.resolve(source, left, right);
+ assertThat(resolution, is(MethodDelegationBinder.AmbiguityResolver.Resolution.AMBIGUOUS));
+ verify(source, atLeast(1)).getParameters();
+ verify(leftMethod, atLeast(1)).getParameters();
+ verify(rightMethod, atLeast(1)).getParameters();
+ verify(left, atLeast(1)).getTargetParameterIndex(argThat(describesArgument(0)));
+ verify(left, never()).getTargetParameterIndex(not(argThat(describesArgument(0))));
+ verify(right, atLeast(1)).getTargetParameterIndex(argThat(describesArgument(0)));
+ verify(right, never()).getTargetParameterIndex(not(argThat(describesArgument(0))));
+ }
+
+ @Test
+ public void testAmbiguousByNonCrossAssignableType() throws Exception {
+ when(sourceParameterList.size()).thenReturn(1);
+ when(leftParameterList.get(0)).thenReturn(dominantTargetParameter);
+ when(left.getTargetParameterIndex(any(ArgumentTypeResolver.ParameterIndexToken.class)))
+ .thenAnswer(new TokenAnswer(new int[][]{{0, 0}}));
+ when(rightParameterList.get(0)).thenReturn(dominantTargetParameter);
+ when(right.getTargetParameterIndex(any(ArgumentTypeResolver.ParameterIndexToken.class)))
+ .thenAnswer(new TokenAnswer(new int[][]{{0, 0}}));
+ MethodDelegationBinder.AmbiguityResolver.Resolution resolution =
+ ArgumentTypeResolver.INSTANCE.resolve(source, left, right);
+ assertThat(resolution, is(MethodDelegationBinder.AmbiguityResolver.Resolution.AMBIGUOUS));
+ verify(source, atLeast(1)).getParameters();
+ verify(leftMethod, atLeast(1)).getParameters();
+ verify(rightMethod, atLeast(1)).getParameters();
+ verify(left, atLeast(1)).getTargetParameterIndex(argThat(describesArgument(0)));
+ verify(left, never()).getTargetParameterIndex(not(argThat(describesArgument(0))));
+ verify(right, atLeast(1)).getTargetParameterIndex(argThat(describesArgument(0)));
+ verify(right, never()).getTargetParameterIndex(not(argThat(describesArgument(0))));
+ }
+
+ @Test
+ public void testAmbiguousByDifferentIndexedAssignableType() throws Exception {
+ when(sourceParameterList.size()).thenReturn(2);
+ when(leftParameterList.get(0)).thenReturn(dominantTargetParameter);
+ when(leftParameterList.get(1)).thenReturn(weakTargetParameter);
+ when(left.getTargetParameterIndex(any(ArgumentTypeResolver.ParameterIndexToken.class)))
+ .thenAnswer(new TokenAnswer(new int[][]{{0, 0}, {1, 1}}));
+ when(rightParameterList.get(0)).thenReturn(weakTargetParameter);
+ when(rightParameterList.get(1)).thenReturn(dominantTargetParameter);
+ when(right.getTargetParameterIndex(any(ArgumentTypeResolver.ParameterIndexToken.class)))
+ .thenAnswer(new TokenAnswer(new int[][]{{0, 0}, {1, 1}}));
+ MethodDelegationBinder.AmbiguityResolver.Resolution resolution =
+ ArgumentTypeResolver.INSTANCE.resolve(source, left, right);
+ assertThat(resolution, is(MethodDelegationBinder.AmbiguityResolver.Resolution.AMBIGUOUS));
+ verify(source, atLeast(1)).getParameters();
+ verify(leftMethod, atLeast(1)).getParameters();
+ verify(rightMethod, atLeast(1)).getParameters();
+ verify(left, atLeast(1)).getTargetParameterIndex(argThat(describesArgument(0)));
+ verify(left, atLeast(1)).getTargetParameterIndex(argThat(describesArgument(1)));
+ verify(left, never()).getTargetParameterIndex(not(argThat(describesArgument(0, 1))));
+ verify(right, atLeast(1)).getTargetParameterIndex(argThat(describesArgument(0)));
+ verify(right, atLeast(1)).getTargetParameterIndex(argThat(describesArgument(1)));
+ verify(right, never()).getTargetParameterIndex(not(argThat(describesArgument(0, 1))));
+ }
+
+ @Test
+ public void testLeftMethodDominantByScore() throws Exception {
+ when(sourceParameterList.size()).thenReturn(2);
+ when(leftParameterList.get(0)).thenReturn(dominantTargetParameter);
+ when(leftParameterList.get(1)).thenReturn(weakTargetParameter);
+ when(left.getTargetParameterIndex(any(ArgumentTypeResolver.ParameterIndexToken.class)))
+ .thenAnswer(new TokenAnswer(new int[][]{{0, 0}, {1, 1}}));
+ when(rightParameterList.get(0)).thenReturn(dominantTargetParameter);
+ when(right.getTargetParameterIndex(any(ArgumentTypeResolver.ParameterIndexToken.class)))
+ .thenAnswer(new TokenAnswer(new int[][]{{0, 0}}));
+ MethodDelegationBinder.AmbiguityResolver.Resolution resolution =
+ ArgumentTypeResolver.INSTANCE.resolve(source, left, right);
+ assertThat(resolution, is(MethodDelegationBinder.AmbiguityResolver.Resolution.LEFT));
+ verify(source, atLeast(1)).getParameters();
+ verify(leftMethod, atLeast(1)).getParameters();
+ verify(rightMethod, atLeast(1)).getParameters();
+ verify(left, atLeast(1)).getTargetParameterIndex(argThat(describesArgument(0)));
+ verify(left, atLeast(1)).getTargetParameterIndex(argThat(describesArgument(1)));
+ verify(left, never()).getTargetParameterIndex(not(argThat(describesArgument(0, 1))));
+ verify(right, atLeast(1)).getTargetParameterIndex(argThat(describesArgument(0)));
+ verify(right, atLeast(1)).getTargetParameterIndex(argThat(describesArgument(1)));
+ verify(right, never()).getTargetParameterIndex(not(argThat(describesArgument(0, 1))));
+ }
+
+ @Test
+ public void testRightMethodDominantByScore() throws Exception {
+ when(sourceParameterList.size()).thenReturn(2);
+ when(leftParameterList.get(0)).thenReturn(dominantTargetParameter);
+ when(left.getTargetParameterIndex(any(ArgumentTypeResolver.ParameterIndexToken.class)))
+ .thenAnswer(new TokenAnswer(new int[][]{{0, 0}}));
+ when(rightParameterList.get(0)).thenReturn(dominantTargetParameter);
+ when(rightParameterList.get(1)).thenReturn(dominantTargetParameter);
+ when(right.getTargetParameterIndex(any(ArgumentTypeResolver.ParameterIndexToken.class)))
+ .thenAnswer(new TokenAnswer(new int[][]{{0, 0}, {1, 1}}));
+ MethodDelegationBinder.AmbiguityResolver.Resolution resolution =
+ ArgumentTypeResolver.INSTANCE.resolve(source, left, right);
+ assertThat(resolution, is(MethodDelegationBinder.AmbiguityResolver.Resolution.RIGHT));
+ verify(source, atLeast(1)).getParameters();
+ verify(leftMethod, atLeast(1)).getParameters();
+ verify(rightMethod, atLeast(1)).getParameters();
+ verify(left, atLeast(1)).getTargetParameterIndex(argThat(describesArgument(0)));
+ verify(left, atLeast(1)).getTargetParameterIndex(argThat(describesArgument(1)));
+ verify(left, never()).getTargetParameterIndex(not(argThat((describesArgument(0, 1)))));
+ verify(right, atLeast(1)).getTargetParameterIndex(argThat(describesArgument(0)));
+ verify(right, atLeast(1)).getTargetParameterIndex(argThat(describesArgument(1)));
+ verify(right, never()).getTargetParameterIndex(not(argThat(describesArgument(0, 1))));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ArgumentTypeResolver.class).apply();
+ ObjectPropertyAssertion.of(ArgumentTypeResolver.PrimitiveTypePrecedence.class).apply();
+ ObjectPropertyAssertion.of(ArgumentTypeResolver.ParameterIndexToken.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/DeclaringTypeResolverTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/DeclaringTypeResolverTest.java
new file mode 100644
index 0000000..f4e8067
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/DeclaringTypeResolverTest.java
@@ -0,0 +1,74 @@
+package net.bytebuddy.implementation.bind;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class DeclaringTypeResolverTest extends AbstractAmbiguityResolverTest {
+
+ @Mock
+ private TypeDescription leftType, rightType;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ when(leftMethod.getDeclaringType()).thenReturn(leftType);
+ when(rightMethod.getDeclaringType()).thenReturn(rightType);
+ when(leftType.asErasure()).thenReturn(leftType);
+ when(rightType.asErasure()).thenReturn(rightType);
+ }
+
+ @Test
+ public void testEquals() throws Exception {
+ when(leftType.isAssignableFrom(rightType)).thenReturn(true);
+ assertThat(DeclaringTypeResolver.INSTANCE.resolve(source, left, left), is(MethodDelegationBinder.AmbiguityResolver.Resolution.AMBIGUOUS));
+ verify(leftType, times(2)).asErasure();
+ verifyNoMoreInteractions(leftType);
+ }
+
+ @Test
+ public void testLeftAssignable() throws Exception {
+ when(leftType.isAssignableFrom(rightType)).thenReturn(true);
+ assertThat(DeclaringTypeResolver.INSTANCE.resolve(source, left, right), is(MethodDelegationBinder.AmbiguityResolver.Resolution.RIGHT));
+ verify(leftType).isAssignableFrom(rightType);
+ verify(leftType).asErasure();
+ verifyNoMoreInteractions(leftType);
+ verify(rightType).asErasure();
+ verifyNoMoreInteractions(rightType);
+ }
+
+ @Test
+ public void testRightAssignable() throws Exception {
+ when(leftType.isAssignableTo(rightType)).thenReturn(true);
+ assertThat(DeclaringTypeResolver.INSTANCE.resolve(source, left, right), is(MethodDelegationBinder.AmbiguityResolver.Resolution.LEFT));
+ verify(leftType).isAssignableFrom(rightType);
+ verify(leftType).isAssignableTo(rightType);
+ verify(leftType).asErasure();
+ verifyNoMoreInteractions(leftType);
+ verify(rightType).asErasure();
+ verifyNoMoreInteractions(rightType);
+ }
+
+ @Test
+ public void testNonAssignable() throws Exception {
+ assertThat(DeclaringTypeResolver.INSTANCE.resolve(source, left, right), is(MethodDelegationBinder.AmbiguityResolver.Resolution.AMBIGUOUS));
+ verify(leftType).isAssignableFrom(rightType);
+ verify(leftType).isAssignableTo(rightType);
+ verify(leftType).asErasure();
+ verifyNoMoreInteractions(leftType);
+ verify(rightType).asErasure();
+ verifyNoMoreInteractions(rightType);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(DeclaringTypeResolver.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/MethodBindingAmbiguityResolutionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/MethodBindingAmbiguityResolutionTest.java
new file mode 100644
index 0000000..718f8c3
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/MethodBindingAmbiguityResolutionTest.java
@@ -0,0 +1,46 @@
+package net.bytebuddy.implementation.bind;
+
+import org.junit.Test;
+import org.omg.CORBA.UNKNOWN;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MethodBindingAmbiguityResolutionTest {
+
+ private static void testConflictMerge(MethodDelegationBinder.AmbiguityResolver.Resolution first,
+ MethodDelegationBinder.AmbiguityResolver.Resolution second) {
+ assertThat(first.merge(second), is(MethodDelegationBinder.AmbiguityResolver.Resolution.AMBIGUOUS));
+ }
+
+ private static void testSelfMerge(MethodDelegationBinder.AmbiguityResolver.Resolution resolution) {
+ assertThat(resolution.merge(resolution), is(resolution));
+ }
+
+ private static void testUnknownMerge(MethodDelegationBinder.AmbiguityResolver.Resolution resolution) {
+ assertThat(MethodDelegationBinder.AmbiguityResolver.Resolution.UNKNOWN.merge(resolution), is(resolution));
+ assertThat(resolution.merge(MethodDelegationBinder.AmbiguityResolver.Resolution.UNKNOWN), is(resolution));
+ }
+
+ @Test
+ public void testUnknownMerge() throws Exception {
+ testUnknownMerge(MethodDelegationBinder.AmbiguityResolver.Resolution.LEFT);
+ testUnknownMerge(MethodDelegationBinder.AmbiguityResolver.Resolution.RIGHT);
+ testUnknownMerge(MethodDelegationBinder.AmbiguityResolver.Resolution.UNKNOWN);
+ }
+
+ @Test
+ public void testSelfMerge() throws Exception {
+ testSelfMerge(MethodDelegationBinder.AmbiguityResolver.Resolution.LEFT);
+ testSelfMerge(MethodDelegationBinder.AmbiguityResolver.Resolution.RIGHT);
+ testSelfMerge(MethodDelegationBinder.AmbiguityResolver.Resolution.UNKNOWN);
+ }
+
+ @Test
+ public void testConflictMerge() throws Exception {
+ testConflictMerge(MethodDelegationBinder.AmbiguityResolver.Resolution.LEFT, MethodDelegationBinder.AmbiguityResolver.Resolution.RIGHT);
+ testConflictMerge(MethodDelegationBinder.AmbiguityResolver.Resolution.LEFT, MethodDelegationBinder.AmbiguityResolver.Resolution.AMBIGUOUS);
+ testConflictMerge(MethodDelegationBinder.AmbiguityResolver.Resolution.AMBIGUOUS, MethodDelegationBinder.AmbiguityResolver.Resolution.LEFT);
+ testConflictMerge(MethodDelegationBinder.AmbiguityResolver.Resolution.RIGHT, MethodDelegationBinder.AmbiguityResolver.Resolution.LEFT);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/MethodBindingBuilderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/MethodBindingBuilderTest.java
new file mode 100644
index 0000000..d7cea08
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/MethodBindingBuilderTest.java
@@ -0,0 +1,189 @@
+package net.bytebuddy.implementation.bind;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.objectweb.asm.MethodVisitor;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class MethodBindingBuilderTest {
+
+ private static final String FOO = "foo";
+
+ private static final String BAR = "bar";
+
+ private static final String BAZ = "baz";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodDescription methodDescription;
+
+ @Mock
+ private ParameterList<?> methodParameterList;
+
+ @Mock
+ private TargetMethodAnnotationDrivenBinder.MethodInvoker methodInvoker;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private TypeDescription.Generic returnType;
+
+ @Mock(answer = Answers.RETURNS_MOCKS)
+ private StackManipulation legalStackManipulation, illegalStackManipulation;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ when(methodDescription.getParameters()).thenReturn((ParameterList) methodParameterList);
+ when(methodDescription.isStatic()).thenReturn(false);
+ TypeDescription declaringType = mock(TypeDescription.class);
+ when(methodDescription.getDeclaringType()).thenReturn(declaringType);
+ when(declaringType.getInternalName()).thenReturn(FOO);
+ when(declaringType.isInterface()).thenReturn(false);
+ when(methodDescription.getInternalName()).thenReturn(BAR);
+ when(methodDescription.getDescriptor()).thenReturn(BAZ);
+ when(methodDescription.getStackSize()).thenReturn(0);
+ when(methodDescription.getReturnType()).thenReturn(returnType);
+ when(returnType.getStackSize()).thenReturn(StackSize.ZERO);
+ when(legalStackManipulation.isValid()).thenReturn(true);
+ when(illegalStackManipulation.isValid()).thenReturn(false);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testIllegalReturnTypeBinding() throws Exception {
+ when(methodInvoker.invoke(any(MethodDescription.class))).thenReturn(legalStackManipulation);
+ MethodDelegationBinder.MethodBinding.Builder builder = new MethodDelegationBinder.MethodBinding.Builder(methodInvoker, methodDescription);
+ MethodDelegationBinder.MethodBinding methodBinding = builder.build(illegalStackManipulation);
+ assertThat(methodBinding.isValid(), is(false));
+ assertThat(methodBinding.getTarget(), is(methodDescription));
+ }
+
+ @Test
+ public void testLegalReturnTypeBinding() throws Exception {
+ when(methodInvoker.invoke(any(MethodDescription.class))).thenReturn(legalStackManipulation);
+ MethodDelegationBinder.MethodBinding.Builder builder = new MethodDelegationBinder.MethodBinding.Builder(methodInvoker, methodDescription);
+ MethodDelegationBinder.MethodBinding methodBinding = builder.build(legalStackManipulation);
+ assertThat(methodBinding.isValid(), is(true));
+ assertThat(methodBinding.getTarget(), is(methodDescription));
+ methodBinding.apply(methodVisitor, implementationContext);
+ verify(legalStackManipulation, times(2)).apply(methodVisitor, implementationContext);
+ verifyZeroInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testIllegalParameterTypeBinding() throws Exception {
+ when(methodInvoker.invoke(any(MethodDescription.class))).thenReturn(legalStackManipulation);
+ when(methodParameterList.size()).thenReturn(2);
+ MethodDelegationBinder.MethodBinding.Builder builder = new MethodDelegationBinder.MethodBinding.Builder(methodInvoker, methodDescription);
+ assertThat(builder.append(MethodDelegationBinder.ParameterBinding.Unique.of(legalStackManipulation, new Object())), is(true));
+ assertThat(builder.append(MethodDelegationBinder.ParameterBinding.Unique.of(illegalStackManipulation, new Object())), is(true));
+ MethodDelegationBinder.MethodBinding methodBinding = builder.build(legalStackManipulation);
+ assertThat(methodBinding.isValid(), is(false));
+ assertThat(methodBinding.getTarget(), is(methodDescription));
+ }
+
+ @Test
+ public void testLegalParameterTypeBinding() throws Exception {
+ when(methodInvoker.invoke(any(MethodDescription.class))).thenReturn(legalStackManipulation);
+ when(methodParameterList.size()).thenReturn(2);
+ MethodDelegationBinder.MethodBinding.Builder builder = new MethodDelegationBinder.MethodBinding.Builder(methodInvoker, methodDescription);
+ assertThat(builder.append(MethodDelegationBinder.ParameterBinding.Unique.of(legalStackManipulation, new Object())), is(true));
+ assertThat(builder.append(MethodDelegationBinder.ParameterBinding.Unique.of(legalStackManipulation, new Object())), is(true));
+ MethodDelegationBinder.MethodBinding methodBinding = builder.build(legalStackManipulation);
+ assertThat(methodBinding.isValid(), is(true));
+ assertThat(methodBinding.getTarget(), is(methodDescription));
+ methodBinding.apply(methodVisitor, implementationContext);
+ verify(legalStackManipulation, times(4)).apply(methodVisitor, implementationContext);
+ verifyZeroInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testUniqueIdentification() throws Exception {
+ when(methodInvoker.invoke(any(MethodDescription.class))).thenReturn(legalStackManipulation);
+ when(methodParameterList.size()).thenReturn(2);
+ MethodDelegationBinder.MethodBinding.Builder builder = new MethodDelegationBinder.MethodBinding.Builder(methodInvoker, methodDescription);
+ assertThat(builder.append(MethodDelegationBinder.ParameterBinding.Unique.of(legalStackManipulation, new Key(FOO))), is(true));
+ assertThat(builder.append(MethodDelegationBinder.ParameterBinding.Unique.of(legalStackManipulation, new Key(BAR))), is(true));
+ MethodDelegationBinder.MethodBinding methodBinding = builder.build(legalStackManipulation);
+ assertThat(methodBinding.getTargetParameterIndex(new Key(FOO)), is(0));
+ assertThat(methodBinding.getTargetParameterIndex(new Key(BAR)), is(1));
+ assertThat(methodBinding.isValid(), is(true));
+ assertThat(methodBinding.getTarget(), is(methodDescription));
+ }
+
+ @Test
+ public void testNonUniqueIdentification() throws Exception {
+ when(methodInvoker.invoke(any(MethodDescription.class))).thenReturn(legalStackManipulation);
+ MethodDelegationBinder.MethodBinding.Builder builder = new MethodDelegationBinder.MethodBinding.Builder(methodInvoker, methodDescription);
+ assertThat(builder.append(MethodDelegationBinder.ParameterBinding.Unique.of(legalStackManipulation, new Key(FOO))), is(true));
+ assertThat(builder.append(MethodDelegationBinder.ParameterBinding.Unique.of(legalStackManipulation, new Key(FOO))), is(false));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testParameterNumberInequality() throws Exception {
+ when(methodParameterList.size()).thenReturn(1);
+ new MethodDelegationBinder.MethodBinding.Builder(methodInvoker, methodDescription).build(legalStackManipulation);
+ }
+
+ @Test
+ public void testBuildHashCodeEquals() throws Exception {
+ when(methodInvoker.invoke(any(MethodDescription.class))).thenReturn(legalStackManipulation);
+ MethodDelegationBinder.MethodBinding.Builder builder = new MethodDelegationBinder.MethodBinding.Builder(methodInvoker, methodDescription);
+ MethodDelegationBinder.MethodBinding methodBinding = builder.build(legalStackManipulation);
+ MethodDelegationBinder.MethodBinding equalMethodBinding = builder.build(legalStackManipulation);
+ assertThat(methodBinding.hashCode(), is(equalMethodBinding.hashCode()));
+ assertThat(methodBinding, is(equalMethodBinding));
+ MethodDelegationBinder.MethodBinding unequalMethodBinding = builder.build(mock(StackManipulation.class));
+ assertThat(methodBinding.hashCode(), not(unequalMethodBinding.hashCode()));
+ assertThat(methodBinding, not(unequalMethodBinding));
+ }
+
+ private static class Key {
+
+ private final String identifier;
+
+ private Key(String identifier) {
+ this.identifier = identifier;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && identifier.equals(((Key) other).identifier);
+
+ }
+
+ @Override
+ public int hashCode() {
+ return identifier.hashCode();
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/MethodDelegationBinderAmbiguityResolverChainTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/MethodDelegationBinderAmbiguityResolverChainTest.java
new file mode 100644
index 0000000..5a61bb3
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/MethodDelegationBinderAmbiguityResolverChainTest.java
@@ -0,0 +1,58 @@
+package net.bytebuddy.implementation.bind;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.util.Collections;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class MethodDelegationBinderAmbiguityResolverChainTest extends AbstractAmbiguityResolverTest {
+
+ @Mock
+ private MethodDelegationBinder.AmbiguityResolver first, second, third;
+
+ private MethodDelegationBinder.AmbiguityResolver chain;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ chain = new MethodDelegationBinder.AmbiguityResolver.Compound(first, second);
+ }
+
+ @Test
+ public void testFirstResolves() throws Exception {
+ when(first.resolve(source, left, right)).thenReturn(MethodDelegationBinder.AmbiguityResolver.Resolution.LEFT);
+ assertThat(chain.resolve(source, left, right), is(MethodDelegationBinder.AmbiguityResolver.Resolution.LEFT));
+ verify(first).resolve(source, left, right);
+ verifyNoMoreInteractions(first);
+ verifyZeroInteractions(second);
+ }
+
+ @Test
+ public void testSecondResolves() throws Exception {
+ when(first.resolve(source, left, right)).thenReturn(MethodDelegationBinder.AmbiguityResolver.Resolution.AMBIGUOUS);
+ when(second.resolve(source, left, right)).thenReturn(MethodDelegationBinder.AmbiguityResolver.Resolution.RIGHT);
+ assertThat(chain.resolve(source, left, right), is(MethodDelegationBinder.AmbiguityResolver.Resolution.RIGHT));
+ verify(first).resolve(source, left, right);
+ verifyNoMoreInteractions(first);
+ verify(second).resolve(source, left, right);
+ verifyNoMoreInteractions(second);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodDelegationBinder.AmbiguityResolver.Compound.class).create(new ObjectPropertyAssertion.Creator<List<?>>() {
+ @Override
+ public List<?> create() {
+ return Collections.singletonList(mock(MethodDelegationBinder.AmbiguityResolver.class));
+ }
+ }).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/MethodDelegationBinderAmbiguityResolverDirectionalTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/MethodDelegationBinderAmbiguityResolverDirectionalTest.java
new file mode 100644
index 0000000..1338a4f
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/MethodDelegationBinderAmbiguityResolverDirectionalTest.java
@@ -0,0 +1,50 @@
+package net.bytebuddy.implementation.bind;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+public class MethodDelegationBinderAmbiguityResolverDirectionalTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodDescription source;
+
+ @Mock
+ private MethodDelegationBinder.MethodBinding left, right;
+
+ @After
+ public void tearDown() throws Exception {
+ verifyZeroInteractions(source);
+ verifyZeroInteractions(left);
+ verifyZeroInteractions(right);
+ }
+
+ @Test
+ public void testLeft() throws Exception {
+ assertThat(MethodDelegationBinder.AmbiguityResolver.Directional.LEFT.resolve(source, left, right),
+ is(MethodDelegationBinder.AmbiguityResolver.Resolution.LEFT));
+ }
+
+ @Test
+ public void testRight() throws Exception {
+ assertThat(MethodDelegationBinder.AmbiguityResolver.Directional.RIGHT.resolve(source, left, right),
+ is(MethodDelegationBinder.AmbiguityResolver.Resolution.RIGHT));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodDelegationBinder.AmbiguityResolver.Directional.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/MethodDelegationBinderAmbiguityResolverNoOpTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/MethodDelegationBinderAmbiguityResolverNoOpTest.java
new file mode 100644
index 0000000..67c4149
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/MethodDelegationBinderAmbiguityResolverNoOpTest.java
@@ -0,0 +1,19 @@
+package net.bytebuddy.implementation.bind;
+
+import net.bytebuddy.description.method.MethodDescription;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class MethodDelegationBinderAmbiguityResolverNoOpTest {
+
+ @Test
+ public void testResolution() throws Exception {
+ assertThat(MethodDelegationBinder.AmbiguityResolver.NoOp.INSTANCE.resolve(mock(MethodDescription.class),
+ mock(MethodDelegationBinder.MethodBinding.class),
+ mock(MethodDelegationBinder.MethodBinding.class)),
+ is(MethodDelegationBinder.AmbiguityResolver.Resolution.UNKNOWN));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/MethodDelegationBinderProcessorTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/MethodDelegationBinderProcessorTest.java
new file mode 100644
index 0000000..20c86d2
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/MethodDelegationBinderProcessorTest.java
@@ -0,0 +1,146 @@
+package net.bytebuddy.implementation.bind;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.Arrays;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class MethodDelegationBinderProcessorTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodDescription source;
+
+ @Mock
+ private MethodDelegationBinder.AmbiguityResolver ambiguityResolver;
+
+ @Mock
+ private MethodDelegationBinder.MethodBinding boundDelegation, unboundDelegation, dominantBoundDelegation;
+
+ @Mock
+ private MethodDelegationBinder.Record boundRecord, unboundRecord, dominantBoundRecord;
+
+ @Mock
+ private MethodDelegationBinder.TerminationHandler terminationHandler;
+
+ @Mock
+ private MethodDelegationBinder.MethodInvoker methodInvoker;
+
+ @Mock
+ private Implementation.Target implementationTarget;
+
+ @Mock
+ private Assigner assigner;
+
+ @Before
+ public void setUp() throws Exception {
+ when(boundDelegation.isValid()).thenReturn(true);
+ when(unboundDelegation.isValid()).thenReturn(false);
+ when(dominantBoundDelegation.isValid()).thenReturn(true);
+ when(boundRecord.bind(implementationTarget, source, terminationHandler, methodInvoker, assigner)).thenReturn(boundDelegation);
+ when(unboundRecord.bind(implementationTarget, source, terminationHandler, methodInvoker, assigner)).thenReturn(unboundDelegation);
+ when(dominantBoundRecord.bind(implementationTarget, source, terminationHandler, methodInvoker, assigner)).thenReturn(dominantBoundDelegation);
+ when(ambiguityResolver.resolve(source, dominantBoundDelegation, boundDelegation)).thenReturn(MethodDelegationBinder.AmbiguityResolver.Resolution.LEFT);
+ when(ambiguityResolver.resolve(source, boundDelegation, dominantBoundDelegation)).thenReturn(MethodDelegationBinder.AmbiguityResolver.Resolution.RIGHT);
+ when(ambiguityResolver.resolve(source, boundDelegation, boundDelegation)).thenReturn(MethodDelegationBinder.AmbiguityResolver.Resolution.AMBIGUOUS);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNoBindableTarget() throws Exception {
+ MethodDelegationBinder.Processor processor = new MethodDelegationBinder.Processor(Arrays.asList(unboundRecord, unboundRecord, unboundRecord), ambiguityResolver);
+ processor.bind(implementationTarget, source, terminationHandler, methodInvoker, assigner);
+ }
+
+ @Test
+ public void testOneBindableTarget() throws Exception {
+ MethodDelegationBinder.Processor processor = new MethodDelegationBinder.Processor(Arrays.asList(unboundRecord, boundRecord, unboundRecord), ambiguityResolver);
+ MethodDelegationBinder.MethodBinding result = processor.bind(implementationTarget, source, terminationHandler, methodInvoker, assigner);
+ assertThat(result, is(boundDelegation));
+ verify(boundRecord, times(1)).bind(implementationTarget, source, terminationHandler, methodInvoker, assigner);
+ verify(boundDelegation, atLeast(1)).isValid();
+ verify(unboundRecord, times(2)).bind(implementationTarget, source, terminationHandler, methodInvoker, assigner);
+ verify(unboundDelegation, atLeast(2)).isValid();
+ verifyZeroInteractions(ambiguityResolver);
+ }
+
+ @Test
+ public void testTwoBindableTargetsWithDominant() throws Exception {
+ MethodDelegationBinder.Processor processor = new MethodDelegationBinder.Processor(Arrays.asList(unboundRecord, boundRecord, dominantBoundRecord), ambiguityResolver);
+ MethodDelegationBinder.MethodBinding result = processor.bind(implementationTarget, source, terminationHandler, methodInvoker, assigner);
+ assertThat(result, is(dominantBoundDelegation));
+ verify(unboundRecord, times(1)).bind(implementationTarget, source, terminationHandler, methodInvoker, assigner);
+ verify(unboundDelegation, atLeast(1)).isValid();
+ verify(boundRecord, times(1)).bind(implementationTarget, source, terminationHandler, methodInvoker, assigner);
+ verify(boundDelegation, atLeast(1)).isValid();
+ verify(dominantBoundRecord, times(1)).bind(implementationTarget, source, terminationHandler, methodInvoker, assigner);
+ verify(dominantBoundDelegation, atLeast(1)).isValid();
+ verify(ambiguityResolver).resolve(source, boundDelegation, dominantBoundDelegation);
+ verifyNoMoreInteractions(ambiguityResolver);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testTwoBindableTargetsWithoutDominant() throws Exception {
+ MethodDelegationBinder.Processor processor = new MethodDelegationBinder.Processor(Arrays.asList(unboundRecord, boundRecord, boundRecord), ambiguityResolver);
+ processor.bind(implementationTarget, source, terminationHandler, methodInvoker, assigner);
+ }
+
+ @Test
+ public void testThreeBindableTargetsDominantBindableFirst() throws Exception {
+ MethodDelegationBinder.Processor processor = new MethodDelegationBinder.Processor(Arrays.asList(dominantBoundRecord, boundRecord, boundRecord), ambiguityResolver);
+ MethodDelegationBinder.MethodBinding result = processor.bind(implementationTarget, source, terminationHandler, methodInvoker, assigner);
+ assertThat(result, is(dominantBoundDelegation));
+ verify(boundRecord, times(2)).bind(implementationTarget, source, terminationHandler, methodInvoker, assigner);
+ verify(boundDelegation, atLeast(2)).isValid();
+ verify(dominantBoundRecord, times(1)).bind(implementationTarget, source, terminationHandler, methodInvoker, assigner);
+ verify(dominantBoundDelegation, atLeast(1)).isValid();
+ verify(ambiguityResolver, times(2)).resolve(source, dominantBoundDelegation, boundDelegation);
+ verifyNoMoreInteractions(ambiguityResolver);
+ }
+
+ @Test
+ public void testThreeBindableTargetsDominantBindableMid() throws Exception {
+ MethodDelegationBinder.Processor processor = new MethodDelegationBinder.Processor(Arrays.asList(boundRecord, dominantBoundRecord, boundRecord), ambiguityResolver);
+ MethodDelegationBinder.MethodBinding result = processor.bind(implementationTarget, source, terminationHandler, methodInvoker, assigner);
+ assertThat(result, is(dominantBoundDelegation));
+ verify(boundRecord, times(2)).bind(implementationTarget, source, terminationHandler, methodInvoker, assigner);
+ verify(boundDelegation, atLeast(2)).isValid();
+ verify(dominantBoundRecord, times(1)).bind(implementationTarget, source, terminationHandler, methodInvoker, assigner);
+ verify(dominantBoundDelegation, atLeast(1)).isValid();
+ verify(ambiguityResolver).resolve(source, boundDelegation, dominantBoundDelegation);
+ verify(ambiguityResolver).resolve(source, dominantBoundDelegation, boundDelegation);
+ verifyNoMoreInteractions(ambiguityResolver);
+ }
+
+ @Test
+ public void testThreeBindableTargetsDominantBindableLast() throws Exception {
+ MethodDelegationBinder.Processor processor = new MethodDelegationBinder.Processor(Arrays.asList(boundRecord, boundRecord, dominantBoundRecord), ambiguityResolver);
+ MethodDelegationBinder.MethodBinding result = processor.bind(implementationTarget, source, terminationHandler, methodInvoker, assigner);
+ assertThat(result, is(dominantBoundDelegation));
+ verify(boundRecord, times(2)).bind(implementationTarget, source, terminationHandler, methodInvoker, assigner);
+ verify(boundDelegation, atLeast(2)).isValid();
+ verify(dominantBoundRecord, times(1)).bind(implementationTarget, source, terminationHandler, methodInvoker, assigner);
+ verify(dominantBoundDelegation, atLeast(1)).isValid();
+ verify(ambiguityResolver).resolve(source, boundDelegation, boundDelegation);
+ verify(ambiguityResolver, times(2)).resolve(source, boundDelegation, dominantBoundDelegation);
+ verifyNoMoreInteractions(ambiguityResolver);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodDelegationBinder.Processor.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/MethodDelegationBinderTerminationHandlerTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/MethodDelegationBinderTerminationHandlerTest.java
new file mode 100644
index 0000000..d6dbbaa
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/MethodDelegationBinderTerminationHandlerTest.java
@@ -0,0 +1,70 @@
+package net.bytebuddy.implementation.bind;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.bytecode.Removal;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.member.MethodReturn;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class MethodDelegationBinderTerminationHandlerTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private Assigner assigner;
+
+ @Mock
+ private MethodDescription source, target;
+
+ @Mock
+ private TypeDescription sourceType, targetType;
+
+ @Mock
+ private TypeDescription.Generic genericSourceType, genericTargetType;
+
+ @Mock
+ private StackManipulation stackManipulation;
+
+ @Test
+ public void testDropping() throws Exception {
+ when(target.getReturnType()).thenReturn(genericSourceType);
+ when(genericSourceType.getStackSize()).thenReturn(StackSize.SINGLE);
+ StackManipulation stackManipulation = MethodDelegationBinder.TerminationHandler.Default.DROPPING.resolve(assigner, Assigner.Typing.STATIC, source, target);
+ assertThat(stackManipulation, is((StackManipulation) Removal.SINGLE));
+ verify(genericSourceType).getStackSize();
+ verifyNoMoreInteractions(genericSourceType);
+ }
+
+ @Test
+ public void testReturning() throws Exception {
+ when(source.getReturnType()).thenReturn(genericSourceType);
+ when(target.getReturnType()).thenReturn(genericTargetType);
+ when(genericSourceType.asErasure()).thenReturn(sourceType);
+ when(genericTargetType.asErasure()).thenReturn(targetType);
+ when(assigner.assign(genericTargetType, genericSourceType, Assigner.Typing.STATIC)).thenReturn(stackManipulation);
+ StackManipulation stackManipulation = MethodDelegationBinder.TerminationHandler.Default.RETURNING.resolve(assigner,
+ Assigner.Typing.STATIC,
+ source,
+ target);
+ assertThat(stackManipulation, is((StackManipulation) new StackManipulation.Compound(this.stackManipulation, MethodReturn.REFERENCE)));
+ verify(assigner).assign(genericTargetType, genericSourceType, Assigner.Typing.STATIC);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodDelegationBinder.TerminationHandler.Default.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/MethodDelegationBinderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/MethodDelegationBinderTest.java
new file mode 100644
index 0000000..8751bd9
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/MethodDelegationBinderTest.java
@@ -0,0 +1,76 @@
+package net.bytebuddy.implementation.bind;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+import org.objectweb.asm.MethodVisitor;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class MethodDelegationBinderTest {
+
+ @Test
+ public void testIllegalBindingInInvalid() throws Exception {
+ assertThat(MethodDelegationBinder.MethodBinding.Illegal.INSTANCE.isValid(), is(false));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIllegalBindingParameterIndexThrowsException() throws Exception {
+ MethodDelegationBinder.MethodBinding.Illegal.INSTANCE.getTargetParameterIndex(mock(Object.class));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIllegalBindingApplicationThrowsException() throws Exception {
+ MethodDelegationBinder.MethodBinding.Illegal.INSTANCE.apply(mock(MethodVisitor.class), mock(Implementation.Context.class));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIllegalBindingTargetThrowsException() throws Exception {
+ MethodDelegationBinder.MethodBinding.Illegal.INSTANCE.getTarget();
+ }
+
+ @Test
+ public void testIgnored() throws Exception {
+ assertThat(MethodDelegationBinder.Record.Illegal.INSTANCE.bind(mock(Implementation.Target.class),
+ mock(MethodDescription.class),
+ mock(MethodDelegationBinder.TerminationHandler.class),
+ mock(MethodDelegationBinder.MethodInvoker.class),
+ mock(Assigner.class)).isValid(), is(false));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodDelegationBinder.Record.Illegal.class).apply();
+ ObjectPropertyAssertion.of(MethodDelegationBinder.MethodInvoker.Simple.class).apply();
+ ObjectPropertyAssertion.of(MethodDelegationBinder.MethodInvoker.Virtual.class).apply();
+ ObjectPropertyAssertion.of(MethodDelegationBinder.ParameterBinding.Illegal.class).apply();
+ ObjectPropertyAssertion.of(MethodDelegationBinder.ParameterBinding.Anonymous.class).apply();
+ ObjectPropertyAssertion.of(MethodDelegationBinder.ParameterBinding.Unique.class).apply();
+ ObjectPropertyAssertion.of(MethodDelegationBinder.MethodBinding.Illegal.class).apply();
+ ObjectPropertyAssertion.of(MethodDelegationBinder.MethodBinding.Builder.Build.class).create(new ObjectPropertyAssertion.Creator<Map<?, ?>>() {
+ @Override
+ public Map<?, ?> create() {
+ return Collections.singletonMap(new Object(), new Object());
+ }
+ }).create(new ObjectPropertyAssertion.Creator<List<?>>() {
+ @Override
+ public List<?> create() {
+ return Collections.singletonList(new Object());
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(MethodDelegationBinder.AmbiguityResolver.Resolution.class).apply();
+ ObjectPropertyAssertion.of(MethodDelegationBinder.AmbiguityResolver.NoOp.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/MethodDelegationBindingParameterBindingTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/MethodDelegationBindingParameterBindingTest.java
new file mode 100644
index 0000000..f4b34b5
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/MethodDelegationBindingParameterBindingTest.java
@@ -0,0 +1,82 @@
+package net.bytebuddy.implementation.bind;
+
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.MethodVisitor;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class MethodDelegationBindingParameterBindingTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @Mock
+ private StackManipulation stackManipulation;
+
+ @Mock
+ private StackManipulation.Size size;
+
+ @Before
+ public void setUp() throws Exception {
+ when(stackManipulation.apply(methodVisitor, implementationContext)).thenReturn(size);
+ when(stackManipulation.isValid()).thenReturn(true);
+ }
+
+ @Test
+ public void testIllegalBindingIsInvalid() throws Exception {
+ assertThat(MethodDelegationBinder.MethodBinding.Illegal.INSTANCE.isValid(), is(false));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIllegalBindingCannotExtractToken() throws Exception {
+ MethodDelegationBinder.ParameterBinding.Illegal.INSTANCE.getIdentificationToken();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIllegalBindingIsNotApplicable() throws Exception {
+ MethodDelegationBinder.ParameterBinding.Illegal.INSTANCE.apply(methodVisitor, implementationContext);
+ }
+
+ @Test
+ public void testAnonymousToken() throws Exception {
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = new MethodDelegationBinder.ParameterBinding.Anonymous(stackManipulation);
+ assertThat(parameterBinding.isValid(), is(true));
+ assertThat(parameterBinding.getIdentificationToken(), notNullValue());
+ assertThat(parameterBinding.apply(methodVisitor, implementationContext), is(size));
+ verify(stackManipulation).isValid();
+ verify(stackManipulation).apply(methodVisitor, implementationContext);
+ verifyNoMoreInteractions(stackManipulation);
+ verifyZeroInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testUniqueToken() throws Exception {
+ Object token = new Object();
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = new MethodDelegationBinder.ParameterBinding.Unique<Object>(stackManipulation, token);
+ assertThat(parameterBinding.isValid(), is(true));
+ assertThat(parameterBinding.getIdentificationToken(), is(token));
+ assertThat(parameterBinding.apply(methodVisitor, implementationContext), is(size));
+ verify(stackManipulation).isValid();
+ verify(stackManipulation).apply(methodVisitor, implementationContext);
+ verifyNoMoreInteractions(stackManipulation);
+ verifyZeroInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/MethodNameEqualityResolverTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/MethodNameEqualityResolverTest.java
new file mode 100644
index 0000000..8e4cbf8
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/MethodNameEqualityResolverTest.java
@@ -0,0 +1,56 @@
+package net.bytebuddy.implementation.bind;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class MethodNameEqualityResolverTest extends AbstractAmbiguityResolverTest {
+
+ private static final String FOO = "foo";
+
+ private static final String BAR = "bar";
+
+ @Test
+ public void testBothEqual() throws Exception {
+ test(FOO, FOO, FOO, MethodDelegationBinder.AmbiguityResolver.Resolution.AMBIGUOUS);
+ }
+
+ @Test
+ public void testLeftEqual() throws Exception {
+ test(FOO, BAR, FOO, MethodDelegationBinder.AmbiguityResolver.Resolution.LEFT);
+ }
+
+ @Test
+ public void testRightEqual() throws Exception {
+ test(BAR, FOO, FOO, MethodDelegationBinder.AmbiguityResolver.Resolution.RIGHT);
+ }
+
+ @Test
+ public void testNoneEqual() throws Exception {
+ test(BAR, BAR, FOO, MethodDelegationBinder.AmbiguityResolver.Resolution.AMBIGUOUS);
+ }
+
+ private void test(String leftName,
+ String rightName,
+ String resultName,
+ MethodDelegationBinder.AmbiguityResolver.Resolution expected) throws Exception {
+ when(leftMethod.getName()).thenReturn(leftName);
+ when(rightMethod.getName()).thenReturn(rightName);
+ when(source.getName()).thenReturn(resultName);
+ MethodDelegationBinder.AmbiguityResolver.Resolution resolution =
+ MethodNameEqualityResolver.INSTANCE.resolve(source, left, right);
+ assertThat(resolution, is(expected));
+ verify(left, atLeast(1)).getTarget();
+ verify(leftMethod, atLeast(1)).getName();
+ verify(right, atLeast(1)).getTarget();
+ verify(rightMethod, atLeast(1)).getName();
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodNameEqualityResolver.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/ParameterLengthResolverTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/ParameterLengthResolverTest.java
new file mode 100644
index 0000000..2ed2bfd
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/ParameterLengthResolverTest.java
@@ -0,0 +1,51 @@
+package net.bytebuddy.implementation.bind;
+
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class ParameterLengthResolverTest extends AbstractAmbiguityResolverTest {
+
+ @Mock
+ private ParameterList<?> leftList, rightList;
+
+ @Override
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ super.setUp();
+ when(leftMethod.getParameters()).thenReturn((ParameterList) leftList);
+ when(rightMethod.getParameters()).thenReturn((ParameterList) rightList);
+ }
+
+ @Test
+ public void testAmbiguous() throws Exception {
+ assertThat(ParameterLengthResolver.INSTANCE.resolve(source, left, right),
+ is(MethodDelegationBinder.AmbiguityResolver.Resolution.AMBIGUOUS));
+ }
+
+ @Test
+ public void testLeft() throws Exception {
+ when(leftList.size()).thenReturn(1);
+ assertThat(ParameterLengthResolver.INSTANCE.resolve(source, left, right),
+ is(MethodDelegationBinder.AmbiguityResolver.Resolution.LEFT));
+ }
+
+ @Test
+ public void testRight() throws Exception {
+ when(rightList.size()).thenReturn(1);
+ assertThat(ParameterLengthResolver.INSTANCE.resolve(source, left, right),
+ is(MethodDelegationBinder.AmbiguityResolver.Resolution.RIGHT));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ParameterLengthResolver.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/ParameterMethodBindingTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/ParameterMethodBindingTest.java
new file mode 100644
index 0000000..d9c2e20
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/ParameterMethodBindingTest.java
@@ -0,0 +1,71 @@
+package net.bytebuddy.implementation.bind;
+
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.test.utility.MockitoRule;
+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 org.objectweb.asm.MethodVisitor;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class ParameterMethodBindingTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private StackManipulation stackManipulation;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @Before
+ public void setUp() throws Exception {
+ when(stackManipulation.isValid()).thenReturn(true);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ verifyZeroInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testIllegal() throws Exception {
+ assertThat(MethodDelegationBinder.ParameterBinding.Illegal.INSTANCE.isValid(), is(false));
+ }
+
+ @Test
+ public void testAnonymous() throws Exception {
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding =
+ new MethodDelegationBinder.ParameterBinding.Anonymous(stackManipulation);
+ assertThat(parameterBinding.getIdentificationToken(), notNullValue());
+ assertThat(parameterBinding.isValid(), is(true));
+ parameterBinding.apply(methodVisitor, implementationContext);
+ verify(stackManipulation).apply(methodVisitor, implementationContext);
+ verify(stackManipulation).isValid();
+ }
+
+ @Test
+ public void testIdentified() throws Exception {
+ Object identificationToken = new Object();
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding =
+ MethodDelegationBinder.ParameterBinding.Unique.of(stackManipulation, identificationToken);
+ assertThat(parameterBinding.getIdentificationToken(), notNullValue());
+ assertThat(parameterBinding.isValid(), is(true));
+ parameterBinding.apply(methodVisitor, implementationContext);
+ verify(stackManipulation).apply(methodVisitor, implementationContext);
+ verify(stackManipulation).isValid();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/AbstractAnnotationBinderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/AbstractAnnotationBinderTest.java
new file mode 100644
index 0000000..c23b7cc
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/AbstractAnnotationBinderTest.java
@@ -0,0 +1,86 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+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.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.lang.annotation.Annotation;
+import java.util.Collections;
+import java.util.Iterator;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public abstract class AbstractAnnotationBinderTest<T extends Annotation> extends AbstractAnnotationTest<T> {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ protected AnnotationDescription.Loadable<T> annotationDescription;
+
+ protected T annotation;
+
+ @Mock
+ protected MethodDescription.InDefinedShape source;
+
+ @Mock
+ protected ParameterDescription target;
+
+ @Mock
+ protected Implementation.Target implementationTarget;
+
+ @Mock
+ protected TypeDescription instrumentedType, sourceDeclaringType, targetDeclaringType;
+
+ @Mock
+ protected Assigner assigner;
+
+ @Mock
+ protected StackManipulation stackManipulation;
+
+ protected AbstractAnnotationBinderTest(Class<T> annotationType) {
+ super(annotationType);
+ }
+
+ protected abstract TargetMethodAnnotationDrivenBinder.ParameterBinder<T> getSimpleBinder();
+
+ @Test
+ public void testHandledType() throws Exception {
+ assertThat(getSimpleBinder().getHandledType(), CoreMatchers.<Class<?>>is(annotationType));
+ }
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ when(sourceDeclaringType.asErasure()).thenReturn(sourceDeclaringType);
+ when(targetDeclaringType.asErasure()).thenReturn(targetDeclaringType);
+ when(source.getDeclaringType()).thenReturn(sourceDeclaringType);
+ annotation = mock(annotationType);
+ doReturn(annotationType).when(annotation).annotationType();
+ annotationDescription = AnnotationDescription.ForLoadedAnnotation.of(annotation);
+ when(assigner.assign(any(TypeDescription.Generic.class), any(TypeDescription.Generic.class), any(Assigner.Typing.class))).thenReturn(stackManipulation);
+ when(implementationTarget.getInstrumentedType()).thenReturn(instrumentedType);
+ when(implementationTarget.getOriginType()).thenReturn(instrumentedType);
+ when(instrumentedType.asErasure()).thenReturn(instrumentedType);
+ when(instrumentedType.iterator()).then(new Answer<Iterator<TypeDefinition>>() {
+ @Override
+ public Iterator<TypeDefinition> answer(InvocationOnMock invocationOnMock) throws Throwable {
+ return Collections.<TypeDefinition>singleton(instrumentedType).iterator();
+ }
+ });
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/AbstractAnnotationTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/AbstractAnnotationTest.java
new file mode 100644
index 0000000..10fec0e
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/AbstractAnnotationTest.java
@@ -0,0 +1,25 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import org.junit.Test;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public abstract class AbstractAnnotationTest<T extends Annotation> {
+
+ protected final Class<T> annotationType;
+
+ protected AbstractAnnotationTest(Class<T> annotationType) {
+ this.annotationType = annotationType;
+ }
+
+ @Test
+ public void testAnnotationVisibility() throws Exception {
+ assertThat(annotationType.isAnnotationPresent(Retention.class), is(true));
+ assertThat(annotationType.getAnnotation(Retention.class).value(), is(RetentionPolicy.RUNTIME));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/AllArgumentsBinderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/AllArgumentsBinderTest.java
new file mode 100644
index 0000000..f87be61
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/AllArgumentsBinderTest.java
@@ -0,0 +1,169 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class AllArgumentsBinderTest extends AbstractAnnotationBinderTest<AllArguments> {
+
+ private static final String FOO = "foo";
+
+ @Mock
+ private TypeDescription.Generic firstSourceType, secondSourceType, genericInstrumentedType;
+
+ @Mock
+ private TypeDescription rawTargetType, rawComponentType;
+
+ @Mock
+ private TypeDescription.Generic targetType, componentType;
+
+ public AllArgumentsBinderTest() {
+ super(AllArguments.class);
+ }
+
+ @Override
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ super.setUp();
+ when(firstSourceType.getStackSize()).thenReturn(StackSize.SINGLE);
+ when(secondSourceType.getStackSize()).thenReturn(StackSize.SINGLE);
+ when(componentType.asErasure()).thenReturn(rawComponentType);
+ when(targetType.getComponentType()).thenReturn(componentType);
+ when(targetType.asErasure()).thenReturn(rawTargetType);
+ when(firstSourceType.asGenericType()).thenReturn(firstSourceType);
+ when(firstSourceType.accept(any(TypeDescription.Generic.Visitor.class))).thenReturn(firstSourceType);
+ when(secondSourceType.asGenericType()).thenReturn(secondSourceType);
+ when(secondSourceType.accept(any(TypeDescription.Generic.Visitor.class))).thenReturn(secondSourceType);
+ }
+
+ @Override
+ protected TargetMethodAnnotationDrivenBinder.ParameterBinder<AllArguments> getSimpleBinder() {
+ return AllArguments.Binder.INSTANCE;
+ }
+
+ @Test
+ public void testLegalStrictBindingRuntimeType() throws Exception {
+ when(target.getIndex()).thenReturn(1);
+ testLegalStrictBinding(Assigner.Typing.STATIC);
+ }
+
+ @Test
+ public void testLegalStrictBindingNoRuntimeType() throws Exception {
+ when(target.getIndex()).thenReturn(1);
+ RuntimeType runtimeType = mock(RuntimeType.class);
+ doReturn(RuntimeType.class).when(runtimeType).annotationType();
+ when(target.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(runtimeType));
+ testLegalStrictBinding(Assigner.Typing.DYNAMIC);
+ }
+
+ private void testLegalStrictBinding(Assigner.Typing typing) throws Exception {
+ when(annotation.value()).thenReturn(AllArguments.Assignment.STRICT);
+ when(stackManipulation.isValid()).thenReturn(true);
+ when(source.getParameters()).thenReturn(new ParameterList.Explicit.ForTypes(source, firstSourceType, secondSourceType));
+ when(source.isStatic()).thenReturn(false);
+ when(targetType.isArray()).thenReturn(true);
+ when(targetType.getComponentType()).thenReturn(componentType);
+ when(componentType.getStackSize()).thenReturn(StackSize.SINGLE);
+ when(target.getType()).thenReturn(targetType);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = AllArguments.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, typing);
+ assertThat(parameterBinding.isValid(), is(true));
+ verify(source, atLeast(1)).getParameters();
+ verify(source, atLeast(1)).isStatic();
+ verify(target, atLeast(1)).getType();
+ verify(target, never()).getDeclaredAnnotations();
+ verify(assigner).assign(firstSourceType, componentType, typing);
+ verify(assigner).assign(secondSourceType, componentType, typing);
+ verifyNoMoreInteractions(assigner);
+ }
+
+ @Test
+ public void testIllegalBinding() throws Exception {
+ when(target.getIndex()).thenReturn(1);
+ when(annotation.value()).thenReturn(AllArguments.Assignment.STRICT);
+ when(stackManipulation.isValid()).thenReturn(false);
+ when(source.getParameters()).thenReturn(new ParameterList.Explicit.ForTypes(source, firstSourceType, secondSourceType));
+ when(source.isStatic()).thenReturn(false);
+ when(targetType.isArray()).thenReturn(true);
+ when(targetType.getComponentType()).thenReturn(componentType);
+ when(componentType.getStackSize()).thenReturn(StackSize.SINGLE);
+ when(target.getType()).thenReturn(targetType);
+ when(target.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = AllArguments.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ assertThat(parameterBinding.isValid(), is(false));
+ verify(source, atLeast(1)).getParameters();
+ verify(source, atLeast(1)).isStatic();
+ verify(target, atLeast(1)).getType();
+ verify(target, never()).getDeclaredAnnotations();
+ verify(assigner).assign(firstSourceType, componentType, Assigner.Typing.STATIC);
+ verifyNoMoreInteractions(assigner);
+ }
+
+ @Test
+ public void testLegalSlackBinding() throws Exception {
+ when(target.getIndex()).thenReturn(1);
+ when(annotation.value()).thenReturn(AllArguments.Assignment.SLACK);
+ when(stackManipulation.isValid()).thenReturn(false);
+ when(source.getParameters()).thenReturn(new ParameterList.Explicit.ForTypes(source, firstSourceType, secondSourceType));
+ when(source.isStatic()).thenReturn(false);
+ when(targetType.isArray()).thenReturn(true);
+ when(targetType.getComponentType()).thenReturn(componentType);
+ when(componentType.getStackSize()).thenReturn(StackSize.SINGLE);
+ when(target.getType()).thenReturn(targetType);
+ when(target.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(rawComponentType.getInternalName()).thenReturn(FOO);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = AllArguments.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ MethodVisitor methodVisitor = mock(MethodVisitor.class);
+ Implementation.Context implementationContext = mock(Implementation.Context.class);
+ StackManipulation.Size size = parameterBinding.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(1));
+ assertThat(size.getMaximalSize(), is(1));
+ verify(methodVisitor).visitInsn(Opcodes.ICONST_0);
+ verify(methodVisitor).visitTypeInsn(Opcodes.ANEWARRAY, FOO);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ assertThat(parameterBinding.isValid(), is(true));
+ verify(source, atLeast(1)).getParameters();
+ verify(source, atLeast(1)).isStatic();
+ verify(target, atLeast(1)).getType();
+ verify(target, never()).getDeclaredAnnotations();
+ verify(assigner).assign(firstSourceType, componentType, Assigner.Typing.STATIC);
+ verify(assigner).assign(secondSourceType, componentType, Assigner.Typing.STATIC);
+ verifyNoMoreInteractions(assigner);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonArrayTypeBinding() throws Exception {
+ when(target.getIndex()).thenReturn(0);
+ TypeDescription.Generic targetType = mock(TypeDescription.Generic.class);
+ TypeDescription rawTargetType = mock(TypeDescription.class);
+ when(targetType.asErasure()).thenReturn(rawTargetType);
+ when(targetType.isArray()).thenReturn(false);
+ when(target.getType()).thenReturn(targetType);
+ AllArguments.Binder.INSTANCE.bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(AllArguments.Assignment.class).apply();
+ ObjectPropertyAssertion.of(AllArguments.Binder.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/ArgumentBinderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/ArgumentBinderTest.java
new file mode 100644
index 0000000..654d82c
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/ArgumentBinderTest.java
@@ -0,0 +1,143 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.bind.ArgumentTypeResolver;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class ArgumentBinderTest extends AbstractAnnotationBinderTest<Argument> {
+
+ @Mock
+ private TypeDescription sourceType, targetType;
+
+ @Mock
+ private TypeDescription.Generic genericSourceType, genericTargetType;
+
+ public ArgumentBinderTest() {
+ super(Argument.class);
+ }
+
+ @Override
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ super.setUp();
+ when(genericSourceType.asErasure()).thenReturn(sourceType);
+ when(sourceType.asGenericType()).thenReturn(genericSourceType);
+ when(genericSourceType.accept(any(TypeDescription.Generic.Visitor.class))).thenReturn(genericSourceType);
+ when(genericTargetType.asErasure()).thenReturn(targetType);
+ when(targetType.asGenericType()).thenReturn(genericTargetType);
+ when(genericTargetType.accept(any(TypeDescription.Generic.Visitor.class))).thenReturn(targetType);
+ }
+
+ @Override
+ protected TargetMethodAnnotationDrivenBinder.ParameterBinder<Argument> getSimpleBinder() {
+ return Argument.Binder.INSTANCE;
+ }
+
+ @Test
+ public void testLegalBindingNoRuntimeTypeUnique() throws Exception {
+ assertBinding(Assigner.Typing.STATIC, Argument.BindingMechanic.UNIQUE);
+ }
+
+ @Test
+ public void testLegalBindingRuntimeTypeUnique() throws Exception {
+ assertBinding(Assigner.Typing.DYNAMIC, Argument.BindingMechanic.UNIQUE);
+ }
+
+ @Test
+ public void testLegalBindingNoRuntimeTypeAnonymous() throws Exception {
+ assertBinding(Assigner.Typing.STATIC, Argument.BindingMechanic.ANONYMOUS);
+ }
+
+ @Test
+ public void testLegalBindingRuntimeTypeAnonymous() throws Exception {
+ assertBinding(Assigner.Typing.DYNAMIC, Argument.BindingMechanic.ANONYMOUS);
+ }
+
+ private void assertBinding(Assigner.Typing typing, Argument.BindingMechanic bindingMechanic) throws Exception {
+ final int sourceIndex = 2;
+ when(stackManipulation.isValid()).thenReturn(true);
+ when(annotation.value()).thenReturn(sourceIndex);
+ when(annotation.bindingMechanic()).thenReturn(bindingMechanic);
+ List<TypeDescription> typeDescriptions = new ArrayList<TypeDescription>(sourceIndex + 1);
+ for (int i = 0; i < sourceIndex; i++) {
+ TypeDescription typeDescription = mock(TypeDescription.class);
+ when(typeDescription.getStackSize()).thenReturn(StackSize.ZERO);
+ typeDescriptions.add(i, typeDescription);
+ }
+ when(sourceType.getStackSize()).thenReturn(StackSize.ZERO);
+ typeDescriptions.add(sourceIndex, sourceType);
+ when(source.getParameters()).thenReturn(new ParameterList.Explicit.ForTypes(source, typeDescriptions));
+ when(source.isStatic()).thenReturn(false);
+ when(target.getType()).thenReturn(genericTargetType);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = Argument.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, typing);
+ assertThat(parameterBinding.isValid(), is(true));
+ Object expectedToken = new ArgumentTypeResolver.ParameterIndexToken(sourceIndex);
+ if (bindingMechanic == Argument.BindingMechanic.UNIQUE) {
+ assertThat(parameterBinding.getIdentificationToken(), is(expectedToken));
+ assertThat(parameterBinding.getIdentificationToken().hashCode(), is(expectedToken.hashCode()));
+ } else {
+ assertThat(parameterBinding.getIdentificationToken(), not(expectedToken));
+ assertThat(parameterBinding.getIdentificationToken().hashCode(), not(expectedToken.hashCode()));
+ }
+ verify(annotation, atLeast(1)).value();
+ verify(source, atLeast(1)).getParameters();
+ verify(target, atLeast(1)).getType();
+ verify(target, never()).getDeclaredAnnotations();
+ verify(assigner).assign(genericSourceType, genericTargetType, typing);
+ verifyNoMoreInteractions(assigner);
+ }
+
+ @Test
+ public void testIllegalBinding() throws Exception {
+ final int sourceIndex = 0, targetIndex = 0;
+ when(annotation.value()).thenReturn(sourceIndex);
+ when(target.getIndex()).thenReturn(targetIndex);
+ when(source.getParameters()).thenReturn(new ParameterList.Empty<ParameterDescription.InDefinedShape>());
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = Argument.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ assertThat(parameterBinding.isValid(), is(false));
+ verify(annotation, atLeast(1)).value();
+ verify(source, atLeast(1)).getParameters();
+ verifyZeroInteractions(assigner);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNegativeAnnotationValue() throws Exception {
+ when(annotation.value()).thenReturn(-1);
+ Argument.Binder.INSTANCE.bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(Argument.Binder.class).apply();
+ ObjectPropertyAssertion.of(Argument.BindingMechanic.class).apply();
+ }
+
+ @SuppressWarnings("unused")
+ private static class Carrier {
+
+ private void method(@Argument(0) Void parameter) {
+ /* do nothing */
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/BindingPriorityResolverTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/BindingPriorityResolverTest.java
new file mode 100644
index 0000000..47c4cde
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/BindingPriorityResolverTest.java
@@ -0,0 +1,78 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class BindingPriorityResolverTest extends AbstractAnnotationTest<BindingPriority> {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodDescription source, leftMethod, rightMethod;
+
+ @Mock
+ private MethodDelegationBinder.MethodBinding left, right;
+
+ @Mock
+ private AnnotationList leftAnnotations, rightAnnotations;
+
+ @Mock
+ private BindingPriority highPriority, lowPriority;
+
+ public BindingPriorityResolverTest() {
+ super(BindingPriority.class);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(left.getTarget()).thenReturn(leftMethod);
+ when(right.getTarget()).thenReturn(rightMethod);
+ when(leftMethod.getDeclaredAnnotations()).thenReturn(leftAnnotations);
+ when(rightMethod.getDeclaredAnnotations()).thenReturn(rightAnnotations);
+ when(highPriority.value()).thenReturn(BindingPriority.DEFAULT * 3);
+ when(lowPriority.value()).thenReturn(BindingPriority.DEFAULT * 2);
+ }
+
+ @Test
+ public void testNoPriorities() throws Exception {
+ assertThat(BindingPriority.Resolver.INSTANCE.resolve(source, left, right),
+ is(MethodDelegationBinder.AmbiguityResolver.Resolution.AMBIGUOUS));
+ }
+
+ @Test
+ public void testLeftPrioritized() throws Exception {
+ AnnotationDescription.Loadable<BindingPriority> highPriority = AnnotationDescription.ForLoadedAnnotation.of(this.highPriority);
+ when(leftAnnotations.ofType(BindingPriority.class)).thenReturn(highPriority);
+ AnnotationDescription.Loadable<BindingPriority> lowPriority = AnnotationDescription.ForLoadedAnnotation.of(this.lowPriority);
+ when(rightAnnotations.ofType(BindingPriority.class)).thenReturn(lowPriority);
+ assertThat(BindingPriority.Resolver.INSTANCE.resolve(source, left, right), is(MethodDelegationBinder.AmbiguityResolver.Resolution.LEFT));
+ }
+
+ @Test
+ public void testRightPrioritized() throws Exception {
+ AnnotationDescription.Loadable<BindingPriority> lowPriority = AnnotationDescription.ForLoadedAnnotation.of(this.lowPriority);
+ when(leftAnnotations.ofType(BindingPriority.class)).thenReturn(lowPriority);
+ AnnotationDescription.Loadable<BindingPriority> highPriority = AnnotationDescription.ForLoadedAnnotation.of(this.highPriority);
+ when(rightAnnotations.ofType(BindingPriority.class)).thenReturn(highPriority);
+ assertThat(BindingPriority.Resolver.INSTANCE.resolve(source, left, right), is(MethodDelegationBinder.AmbiguityResolver.Resolution.RIGHT));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(BindingPriority.Resolver.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/DefaultBinderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/DefaultBinderTest.java
new file mode 100644
index 0000000..db3faaa
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/DefaultBinderTest.java
@@ -0,0 +1,99 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+public class DefaultBinderTest extends AbstractAnnotationBinderTest<Default> {
+
+ @Mock
+ private TypeDescription targetType;
+
+ @Mock
+ private TypeDescription.Generic typeDescription;
+
+ @Mock
+ private TypeList.Generic interfaces;
+
+ @Mock
+ private TypeList rawInterfaces;
+
+ public DefaultBinderTest() {
+ super(Default.class);
+ }
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ when(target.getType()).thenReturn(typeDescription);
+ when(typeDescription.asErasure()).thenReturn(targetType);
+ when(instrumentedType.getInterfaces()).thenReturn(interfaces);
+ when(interfaces.asErasures()).thenReturn(rawInterfaces);
+ }
+
+ @Override
+ protected TargetMethodAnnotationDrivenBinder.ParameterBinder<Default> getSimpleBinder() {
+ return Default.Binder.INSTANCE;
+ }
+
+ @Test
+ public void testAssignableBinding() throws Exception {
+ doReturn(void.class).when(annotation).proxyType();
+ when(targetType.isInterface()).thenReturn(true);
+ when(stackManipulation.isValid()).thenReturn(true);
+ when(rawInterfaces.contains(targetType)).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = Default.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ assertThat(parameterBinding.isValid(), is(true));
+ }
+
+ @Test
+ public void testIllegalBindingNonDeclaredInterface() throws Exception {
+ doReturn(void.class).when(annotation).proxyType();
+ when(targetType.isInterface()).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = Default.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ assertThat(parameterBinding.isValid(), is(false));
+ }
+
+ @Test
+ public void testIllegalBindingStatic() throws Exception {
+ doReturn(void.class).when(annotation).proxyType();
+ when(targetType.isInterface()).thenReturn(true);
+ when(source.isStatic()).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = Default.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ assertThat(parameterBinding.isValid(), is(false));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonInterfaceProxyType() throws Exception {
+ doReturn(void.class).when(annotation).proxyType();
+ when(targetType.isInterface()).thenReturn(false);
+ Default.Binder.INSTANCE.bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonInterfaceExplicitType() throws Exception {
+ doReturn(Void.class).when(annotation).proxyType();
+ Default.Binder.INSTANCE.bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(Default.Binder.class).apply();
+ ObjectPropertyAssertion.of(Default.Binder.TypeLocator.ForParameterType.class).apply();
+ ObjectPropertyAssertion.of(Default.Binder.TypeLocator.ForType.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/DefaultCallBinderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/DefaultCallBinderTest.java
new file mode 100644
index 0000000..dd8e033
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/DefaultCallBinderTest.java
@@ -0,0 +1,141 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.io.Serializable;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class DefaultCallBinderTest extends AbstractAnnotationBinderTest<DefaultCall> {
+
+ private static final Class<?> NON_INTERFACE_TYPE = Object.class, INTERFACE_TYPE = Serializable.class, VOID_TYPE = void.class;
+
+ @Mock
+ private TypeDescription targetParameterType, firstInterface, secondInterface;
+
+ @Mock
+ private TypeDescription.Generic genericTargetParameterType, firstGenericInterface, secondGenericInterface;
+
+ @Mock
+ private MethodDescription.SignatureToken token;
+
+ @Mock
+ private Implementation.SpecialMethodInvocation specialMethodInvocation;
+
+ public DefaultCallBinderTest() {
+ super(DefaultCall.class);
+ }
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ when(target.getType()).thenReturn(genericTargetParameterType);
+ when(genericTargetParameterType.asErasure()).thenReturn(targetParameterType);
+ when(implementationTarget.invokeDefault(token)).thenReturn(specialMethodInvocation);
+ when(implementationTarget.invokeDefault(eq(token), any(TypeDescription.class))).thenReturn(specialMethodInvocation);
+ when(firstGenericInterface.asErasure()).thenReturn(firstInterface);
+ when(secondGenericInterface.asErasure()).thenReturn(secondInterface);
+ when(firstInterface.asGenericType()).thenReturn(firstGenericInterface);
+ when(secondInterface.asGenericType()).thenReturn(secondGenericInterface);
+ }
+
+ @Override
+ protected TargetMethodAnnotationDrivenBinder.ParameterBinder<DefaultCall> getSimpleBinder() {
+ return DefaultCall.Binder.INSTANCE;
+ }
+
+ @Test
+ public void testImplicitLookupIsUnique() throws Exception {
+ when(targetParameterType.represents(any(Class.class))).thenReturn(true);
+ when(specialMethodInvocation.isValid()).thenReturn(true, false);
+ doReturn(VOID_TYPE).when(annotation).targetType();
+ when(source.asSignatureToken()).thenReturn(token);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = DefaultCall.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ assertThat(parameterBinding.isValid(), is(true));
+ verify(implementationTarget).invokeDefault(token);
+ verifyNoMoreInteractions(implementationTarget);
+ }
+
+ @Test
+ public void testImplicitLookupIsAmbiguousNullFallback() throws Exception {
+ when(targetParameterType.represents(any(Class.class))).thenReturn(true);
+ when(specialMethodInvocation.isValid()).thenReturn(true, false);
+ doReturn(VOID_TYPE).when(annotation).targetType();
+ when(source.asSignatureToken()).thenReturn(token);
+ when(source.isSpecializableFor(firstInterface)).thenReturn(true);
+ when(source.isSpecializableFor(secondInterface)).thenReturn(true);
+ when(instrumentedType.getInterfaces()).thenReturn(new TypeList.Generic.Explicit(firstInterface, secondInterface));
+ when(annotation.nullIfImpossible()).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = DefaultCall.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ assertThat(parameterBinding.isValid(), is(true));
+ verify(implementationTarget).invokeDefault(token);
+ verifyNoMoreInteractions(implementationTarget);
+ }
+
+ @Test
+ public void testExplicitLookup() throws Exception {
+ when(targetParameterType.represents(any(Class.class))).thenReturn(true);
+ when(specialMethodInvocation.isValid()).thenReturn(true);
+ doReturn(INTERFACE_TYPE).when(annotation).targetType();
+ when(source.asSignatureToken()).thenReturn(token);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = DefaultCall.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ assertThat(parameterBinding.isValid(), is(true));
+ verify(implementationTarget).invokeDefault(token, new TypeDescription.ForLoadedType(INTERFACE_TYPE));
+ verifyNoMoreInteractions(implementationTarget);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonInterfaceTarget() throws Exception {
+ when(targetParameterType.represents(any(Class.class))).thenReturn(true);
+ doReturn(NON_INTERFACE_TYPE).when(annotation).targetType();
+ DefaultCall.Binder.INSTANCE.bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIllegalAnnotatedValue() throws Exception {
+ DefaultCall.Binder.INSTANCE.bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ }
+
+ @Test
+ public void testConstructorIsNotInvokeable() throws Exception {
+ when(targetParameterType.represents(any(Class.class))).thenReturn(true);
+ when(source.isConstructor()).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = DefaultCall.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ verifyZeroInteractions(implementationTarget);
+ assertThat(parameterBinding.isValid(), is(false));
+ }
+
+ @Test
+ public void testConstructorNullFallback() throws Exception {
+ when(targetParameterType.represents(any(Class.class))).thenReturn(true);
+ when(source.isConstructor()).thenReturn(true);
+ when(annotation.nullIfImpossible()).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = DefaultCall.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ verifyZeroInteractions(implementationTarget);
+ assertThat(parameterBinding.isValid(), is(true));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(DefaultCall.Binder.class).apply();
+ ObjectPropertyAssertion.of(DefaultCall.Binder.DefaultMethodLocator.Implicit.class).apply();
+ ObjectPropertyAssertion.of(DefaultCall.Binder.DefaultMethodLocator.Explicit.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/DefaultMethodBinderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/DefaultMethodBinderTest.java
new file mode 100644
index 0000000..04a379a
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/DefaultMethodBinderTest.java
@@ -0,0 +1,184 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.lang.reflect.Method;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class DefaultMethodBinderTest extends AbstractAnnotationBinderTest<DefaultMethod> {
+
+ public DefaultMethodBinderTest() {
+ super(DefaultMethod.class);
+ }
+
+ @Mock
+ private TypeDescription targetType, interfaceType;
+
+ @Mock
+ private TypeDescription.Generic genericTargetType, genericInterfaceType;
+
+ @Mock
+ private MethodDescription.SignatureToken token;
+
+ @Mock
+ private Implementation.SpecialMethodInvocation specialMethodInvocation;
+
+ @Override
+ protected TargetMethodAnnotationDrivenBinder.ParameterBinder<DefaultMethod> getSimpleBinder() {
+ return DefaultMethod.Binder.INSTANCE;
+ }
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ when(target.getType()).thenReturn(genericTargetType);
+ when(instrumentedType.getInterfaces()).thenReturn(new TypeList.Generic.Explicit(genericInterfaceType));
+ when(genericInterfaceType.asGenericType()).thenReturn(genericInterfaceType);
+ when(genericInterfaceType.asErasure()).thenReturn(interfaceType);
+ when(genericTargetType.asErasure()).thenReturn(targetType);
+ when(source.asSignatureToken()).thenReturn(token);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testBindNoMethodParameter() throws Exception {
+ DefaultMethod.Binder.INSTANCE.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testBind() throws Exception {
+ when(targetType.isAssignableFrom(Method.class)).thenReturn(true);
+ when(source.isMethod()).thenReturn(true);
+ when(implementationTarget.invokeDefault(token)).thenReturn(specialMethodInvocation);
+ when(specialMethodInvocation.isValid()).thenReturn(true);
+ when(annotation.targetType()).thenReturn((Class) void.class);
+ MethodDelegationBinder.ParameterBinding<?> binding = DefaultMethod.Binder.INSTANCE.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(true));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testBindNoInterface() throws Exception {
+ when(targetType.isAssignableFrom(Method.class)).thenReturn(true);
+ when(source.isMethod()).thenReturn(true);
+ when(instrumentedType.getInterfaces()).thenReturn(new TypeList.Generic.Empty());
+ when(implementationTarget.invokeDefault(token)).thenReturn(specialMethodInvocation);
+ when(annotation.targetType()).thenReturn((Class) void.class);
+ MethodDelegationBinder.ParameterBinding<?> binding = DefaultMethod.Binder.INSTANCE.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(false));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testBindExplicit() throws Exception {
+ when(targetType.isAssignableFrom(Method.class)).thenReturn(true);
+ when(source.isMethod()).thenReturn(true);
+ when(implementationTarget.invokeDefault(token, new TypeDescription.ForLoadedType(Runnable.class))).thenReturn(specialMethodInvocation);
+ when(specialMethodInvocation.isValid()).thenReturn(true);
+ when(annotation.targetType()).thenReturn((Class) Runnable.class);
+ when(instrumentedType.getInterfaces()).thenReturn(new TypeList.Generic.Explicit(genericInterfaceType, genericInterfaceType));
+ MethodDelegationBinder.ParameterBinding<?> binding = DefaultMethod.Binder.INSTANCE.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(true));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ @SuppressWarnings("unchecked")
+ public void testBindExplicitNoInterface() throws Exception {
+ when(targetType.isAssignableFrom(Method.class)).thenReturn(true);
+ when(source.isMethod()).thenReturn(true);
+ when(annotation.targetType()).thenReturn((Class) Void.class);
+ DefaultMethod.Binder.INSTANCE.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testBindIllegalFallback() throws Exception {
+ when(targetType.isAssignableFrom(Method.class)).thenReturn(true);
+ when(source.isMethod()).thenReturn(true);
+ when(annotation.nullIfImpossible()).thenReturn(true);
+ when(implementationTarget.invokeDefault(token)).thenReturn(specialMethodInvocation);
+ when(specialMethodInvocation.isValid()).thenReturn(false);
+ when(annotation.targetType()).thenReturn((Class) void.class);
+ MethodDelegationBinder.ParameterBinding<?> binding = DefaultMethod.Binder.INSTANCE.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(true));
+ }
+
+ @Test
+ public void testNoMethod() throws Exception {
+ when(targetType.isAssignableFrom(Method.class)).thenReturn(true);
+ when(source.isMethod()).thenReturn(false);
+ when(annotation.nullIfImpossible()).thenReturn(false);
+ MethodDelegationBinder.ParameterBinding<?> binding = DefaultMethod.Binder.INSTANCE.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(false));
+ }
+
+ @Test
+ public void testNoMethodFallback() throws Exception {
+ when(targetType.isAssignableFrom(Method.class)).thenReturn(true);
+ when(source.isMethod()).thenReturn(false);
+ when(annotation.nullIfImpossible()).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> binding = DefaultMethod.Binder.INSTANCE.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(true));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(DefaultMethod.Binder.class).apply();
+ ObjectPropertyAssertion.of(DefaultMethod.Binder.MethodLocator.ForExplicitType.class).apply();
+ ObjectPropertyAssertion.of(DefaultMethod.Binder.MethodLocator.ForImplicitType.class).apply();
+ ObjectPropertyAssertion.of(DefaultMethod.Binder.DelegationMethod.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/EmptyBinderObjectPropertiesTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/EmptyBinderObjectPropertiesTest.java
new file mode 100644
index 0000000..1137a1a
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/EmptyBinderObjectPropertiesTest.java
@@ -0,0 +1,12 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+public class EmptyBinderObjectPropertiesTest {
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(Empty.Binder.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/EmptyBinderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/EmptyBinderTest.java
new file mode 100644
index 0000000..42ebcce
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/EmptyBinderTest.java
@@ -0,0 +1,78 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.Mock;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class EmptyBinderTest extends AbstractAnnotationBinderTest<Empty> {
+
+ private final TypeDescription.Generic typeDescription;
+
+ private final int opcode;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ public EmptyBinderTest(Class<?> type, int opcode) {
+ super(Empty.class);
+ typeDescription = new TypeDescription.Generic.OfNonGenericType.ForLoadedType(type);
+ this.opcode = opcode;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {boolean.class, Opcodes.ICONST_0},
+ {byte.class, Opcodes.ICONST_0},
+ {short.class, Opcodes.ICONST_0},
+ {char.class, Opcodes.ICONST_0},
+ {int.class, Opcodes.ICONST_0},
+ {long.class, Opcodes.LCONST_0},
+ {float.class, Opcodes.FCONST_0},
+ {double.class, Opcodes.DCONST_0},
+ {Object.class, Opcodes.ACONST_NULL}
+ });
+ }
+
+ @Override
+ protected TargetMethodAnnotationDrivenBinder.ParameterBinder<Empty> getSimpleBinder() {
+ return Empty.Binder.INSTANCE;
+ }
+
+ @Test
+ public void testEmptyValue() throws Exception {
+ when(target.getType()).thenReturn(typeDescription);
+ TargetMethodAnnotationDrivenBinder.ParameterBinding<?> binding = Empty.Binder
+ .INSTANCE.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(true));
+ StackManipulation.Size size = binding.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(typeDescription.getStackSize().getSize()));
+ assertThat(size.getMaximalSize(), is(typeDescription.getStackSize().getSize()));
+ verify(methodVisitor).visitInsn(opcode);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/FieldProxyBinderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/FieldProxyBinderTest.java
new file mode 100644
index 0000000..e55da44
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/FieldProxyBinderTest.java
@@ -0,0 +1,345 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.MethodAccessorFactory;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class FieldProxyBinderTest extends AbstractAnnotationBinderTest<FieldProxy> {
+
+ private static final String FOO = "foo";
+
+ @Mock
+ private MethodDescription.InDefinedShape getterMethod, setterMethod;
+
+ @Mock
+ private TypeDescription setterType, getterType, fieldType;
+
+ @Mock
+ private TypeDescription.Generic genericSetterType, genericGetterType, genericFieldType;
+
+ @Mock
+ private FieldDescription.InDefinedShape fieldDescription;
+
+ public FieldProxyBinderTest() {
+ super(FieldProxy.class);
+ }
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ when(getterMethod.getDeclaringType()).thenReturn(getterType);
+ when(setterMethod.getDeclaringType()).thenReturn(setterType);
+ when(instrumentedType.getDeclaredFields()).thenReturn(new FieldList.Explicit<FieldDescription.InDefinedShape>(fieldDescription));
+ when(fieldDescription.getType()).thenReturn(genericFieldType);
+ when(genericFieldType.getSort()).thenReturn(TypeDefinition.Sort.NON_GENERIC);
+ when(genericFieldType.getStackSize()).thenReturn(StackSize.ZERO);
+ when(genericFieldType.asErasure()).thenReturn(fieldType);
+ when(fieldType.getSort()).thenReturn(TypeDefinition.Sort.NON_GENERIC);
+ when(fieldType.asErasure()).thenReturn(fieldType);
+ when(fieldType.getInternalName()).thenReturn(FOO);
+ when(genericSetterType.asErasure()).thenReturn(setterType);
+ when(genericGetterType.asErasure()).thenReturn(getterType);
+ }
+
+ @Override
+ protected TargetMethodAnnotationDrivenBinder.ParameterBinder<FieldProxy> getSimpleBinder() {
+ return new FieldProxy.Binder(getterMethod, setterMethod);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testFieldOfArrayThrowsException() throws Exception {
+ doReturn(Object[].class).when(annotation).declaringType();
+ new FieldProxy.Binder(getterMethod, setterMethod).bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testFieldOfPrimitiveThrowsException() throws Exception {
+ doReturn(int.class).when(annotation).declaringType();
+ new FieldProxy.Binder(getterMethod, setterMethod).bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIllegalType() throws Exception {
+ doReturn(Foo.class).when(annotation).declaringType();
+ when(annotation.value()).thenReturn(FOO);
+ TypeDescription targetType = mock(TypeDescription.class);
+ TypeDescription.Generic genericTargetType = mock(TypeDescription.Generic.class);
+ when(genericTargetType.asErasure()).thenReturn(targetType);
+ when(target.getType()).thenReturn(genericTargetType);
+ when(instrumentedType.isAssignableTo(new TypeDescription.ForLoadedType(Foo.class))).thenReturn(true);
+ new FieldProxy.Binder(getterMethod, setterMethod).bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ }
+
+ @Test
+ public void testGetterForImplicitNamedFieldInHierarchy() throws Exception {
+ when(target.getType()).thenReturn(genericGetterType);
+ doReturn(void.class).when(annotation).declaringType();
+ when(annotation.value()).thenReturn(FieldProxy.Binder.BEAN_PROPERTY);
+ when(fieldDescription.getActualName()).thenReturn(FOO);
+ when(source.getReturnType()).thenReturn(genericFieldType);
+ when(source.getParameters()).thenReturn(new ParameterList.Empty<ParameterDescription.InDefinedShape>());
+ when(source.getName()).thenReturn("getFoo");
+ when(source.getActualName()).thenReturn("getFoo");
+ when(source.getInternalName()).thenReturn("getFoo");
+ when(fieldDescription.isVisibleTo(instrumentedType)).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> binding = new FieldProxy.Binder(getterMethod, setterMethod).bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(true));
+ }
+
+ @Test
+ public void testGetterForExplicitNamedFieldInHierarchy() throws Exception {
+ when(target.getType()).thenReturn(genericGetterType);
+ doReturn(void.class).when(annotation).declaringType();
+ when(annotation.value()).thenReturn(FOO);
+ when(fieldDescription.getActualName()).thenReturn(FOO);
+ when(source.getReturnType()).thenReturn(genericFieldType);
+ when(source.getParameters()).thenReturn(new ParameterList.Empty<ParameterDescription.InDefinedShape>());
+ when(source.getName()).thenReturn("getFoo");
+ when(source.getInternalName()).thenReturn("getFoo");
+ when(fieldDescription.isVisibleTo(instrumentedType)).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> binding = new FieldProxy.Binder(getterMethod, setterMethod).bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(true));
+ }
+
+ @Test
+ public void testGetterForImplicitNamedFieldInNamedType() throws Exception {
+ when(target.getType()).thenReturn(genericGetterType);
+ doReturn(Foo.class).when(annotation).declaringType();
+ when(instrumentedType.isAssignableTo(new TypeDescription.ForLoadedType(Foo.class))).thenReturn(true);
+ when(annotation.value()).thenReturn(FieldProxy.Binder.BEAN_PROPERTY);
+ when(fieldDescription.getInternalName()).thenReturn(FOO);
+ when(source.getReturnType()).thenReturn(genericFieldType);
+ when(source.getParameters()).thenReturn(new ParameterList.Empty<ParameterDescription.InDefinedShape>());
+ when(source.getName()).thenReturn("getFoo");
+ when(source.getActualName()).thenReturn("getFoo");
+ when(source.getInternalName()).thenReturn("getFoo");
+ when(fieldDescription.isVisibleTo(instrumentedType)).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> binding = new FieldProxy.Binder(getterMethod, setterMethod).bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(true));
+ }
+
+ @Test
+ public void testGetterForExplicitNamedFieldInNamedType() throws Exception {
+ when(target.getType()).thenReturn(genericGetterType);
+ doReturn(Foo.class).when(annotation).declaringType();
+ when(instrumentedType.isAssignableTo(new TypeDescription.ForLoadedType(Foo.class))).thenReturn(true);
+ when(annotation.value()).thenReturn(FOO);
+ when(fieldDescription.getInternalName()).thenReturn(FOO);
+ when(source.getReturnType()).thenReturn(genericFieldType);
+ when(source.getParameters()).thenReturn(new ParameterList.Empty<ParameterDescription.InDefinedShape>());
+ when(source.getName()).thenReturn("getFoo");
+ when(source.getInternalName()).thenReturn("getFoo");
+ when(fieldDescription.isVisibleTo(instrumentedType)).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> binding = new FieldProxy.Binder(getterMethod, setterMethod).bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(true));
+ }
+
+ @Test
+ public void testSetterForImplicitNamedFieldInHierarchy() throws Exception {
+ when(target.getType()).thenReturn(genericSetterType);
+ doReturn(void.class).when(annotation).declaringType();
+ when(annotation.value()).thenReturn(FieldProxy.Binder.BEAN_PROPERTY);
+ when(fieldDescription.getActualName()).thenReturn(FOO);
+ when(source.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(source.getParameters()).thenReturn(new ParameterList.Explicit.ForTypes(source, fieldType));
+ when(source.getActualName()).thenReturn("setFoo");
+ when(source.getInternalName()).thenReturn("setFoo");
+ when(fieldDescription.isVisibleTo(instrumentedType)).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> binding = new FieldProxy.Binder(getterMethod, setterMethod).bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(true));
+ }
+
+ @Test
+ public void testSetterForExplicitNamedFieldInHierarchy() throws Exception {
+ when(target.getType()).thenReturn(genericSetterType);
+ doReturn(void.class).when(annotation).declaringType();
+ when(annotation.value()).thenReturn(FOO);
+ when(fieldDescription.getActualName()).thenReturn(FOO);
+ when(source.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(source.getParameters()).thenReturn(new ParameterList.Explicit.ForTypes(source, fieldType));
+ when(source.getName()).thenReturn("setFoo");
+ when(source.getInternalName()).thenReturn("setFoo");
+ when(fieldDescription.isVisibleTo(instrumentedType)).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> binding = new FieldProxy.Binder(getterMethod, setterMethod).bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(true));
+ }
+
+ @Test
+ public void testSetterForImplicitNamedFieldInNamedType() throws Exception {
+ when(target.getType()).thenReturn(genericSetterType);
+ doReturn(Foo.class).when(annotation).declaringType();
+ when(instrumentedType.isAssignableTo(new TypeDescription.ForLoadedType(Foo.class))).thenReturn(true);
+ when(annotation.value()).thenReturn(FieldProxy.Binder.BEAN_PROPERTY);
+ when(fieldDescription.getActualName()).thenReturn(FOO);
+ when(source.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(source.getParameters()).thenReturn(new ParameterList.Explicit.ForTypes(source, fieldType));
+ when(source.getName()).thenReturn("setFoo");
+ when(source.getActualName()).thenReturn("setFoo");
+ when(source.getInternalName()).thenReturn("setFoo");
+ when(fieldDescription.isVisibleTo(instrumentedType)).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> binding = new FieldProxy.Binder(getterMethod, setterMethod).bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(true));
+ }
+
+ @Test
+ public void testSetterForExplicitNamedFieldInNamedType() throws Exception {
+ when(target.getType()).thenReturn(genericSetterType);
+ doReturn(Foo.class).when(annotation).declaringType();
+ when(instrumentedType.isAssignableTo(new TypeDescription.ForLoadedType(Foo.class))).thenReturn(true);
+ when(annotation.value()).thenReturn(FOO);
+ when(fieldDescription.getActualName()).thenReturn(FOO);
+ when(source.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(source.getParameters()).thenReturn(new ParameterList.Explicit.ForTypes(source, fieldType));
+ when(source.getName()).thenReturn("setFoo");
+ when(source.getInternalName()).thenReturn("setFoo");
+ when(fieldDescription.isVisibleTo(instrumentedType)).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> binding = new FieldProxy.Binder(getterMethod, setterMethod).bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(true));
+ }
+
+ @Test
+ public void testDefiningTypeNotAssignable() throws Exception {
+ when(target.getType()).thenReturn(genericSetterType);
+ doReturn(Foo.class).when(annotation).declaringType();
+ when(instrumentedType.isAssignableTo(new TypeDescription.ForLoadedType(Foo.class))).thenReturn(false);
+ MethodDelegationBinder.ParameterBinding<?> binding = new FieldProxy.Binder(getterMethod, setterMethod).bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(false));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testDefiningTypePrimitive() throws Exception {
+ when(target.getType()).thenReturn(genericSetterType);
+ doReturn(int.class).when(annotation).declaringType();
+ new FieldProxy.Binder(getterMethod, setterMethod).bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUnresolvedResolverNoProxyType() throws Exception {
+ FieldProxy.Binder.FieldResolver.Unresolved.INSTANCE.getProxyType();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testUnresolvedResolverNoApplication() throws Exception {
+ FieldProxy.Binder.FieldResolver.Unresolved.INSTANCE.apply(mock(DynamicType.Builder.class),
+ mock(FieldDescription.class),
+ mock(Assigner.class),
+ mock(MethodAccessorFactory.class));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(FieldProxy.Binder.class).apply();
+ ObjectPropertyAssertion.of(FieldProxy.Binder.StaticFieldConstructor.class).apply();
+ ObjectPropertyAssertion.of(FieldProxy.Binder.InstanceFieldConstructor.class).apply();
+ ObjectPropertyAssertion.of(FieldProxy.Binder.InstanceFieldConstructor.Appender.class).refine(new ObjectPropertyAssertion.Refinement<Implementation.Target>() {
+ @Override
+ @SuppressWarnings("unchecked")
+ public void apply(Implementation.Target mock) {
+ TypeDescription typeDescription = mock(TypeDescription.class);
+ when(mock.getInstrumentedType()).thenReturn(typeDescription);
+ FieldList fieldList = mock(FieldList.class);
+ FieldList filteredFieldList = mock(FieldList.class);
+ when(typeDescription.getDeclaredFields()).thenReturn(fieldList);
+ when(fieldList.filter(any(ElementMatcher.class))).thenReturn(filteredFieldList);
+ when(filteredFieldList.getOnly()).thenReturn(mock(FieldDescription.class));
+ }
+ }).skipSynthetic().apply();
+ ObjectPropertyAssertion.of(FieldProxy.Binder.FieldResolver.ForGetter.class).apply();
+ ObjectPropertyAssertion.of(FieldProxy.Binder.FieldResolver.ForSetter.class).apply();
+ ObjectPropertyAssertion.of(FieldProxy.Binder.FieldResolver.ForGetterSetterPair.class).apply();
+ ObjectPropertyAssertion.of(FieldProxy.Binder.FieldResolver.Unresolved.class).apply();
+ ObjectPropertyAssertion.of(FieldProxy.Binder.FieldResolver.Factory.Simplex.class).apply();
+ ObjectPropertyAssertion.of(FieldProxy.Binder.FieldResolver.Factory.Duplex.class).apply();
+ ObjectPropertyAssertion.of(FieldProxy.Binder.AccessorProxy.class).apply();
+ ObjectPropertyAssertion.of(FieldProxy.Binder.FieldGetter.class).apply();
+ ObjectPropertyAssertion.of(FieldProxy.Binder.FieldGetter.Appender.class).refine(new ObjectPropertyAssertion.Refinement<Implementation.Target>() {
+ @Override
+ public void apply(Implementation.Target mock) {
+ when(mock.getInstrumentedType()).thenReturn(mock(TypeDescription.class));
+ }
+ }).skipSynthetic().apply();
+ ObjectPropertyAssertion.of(FieldProxy.Binder.FieldSetter.class).apply();
+ ObjectPropertyAssertion.of(FieldProxy.Binder.FieldSetter.Appender.class).refine(new ObjectPropertyAssertion.Refinement<Implementation.Target>() {
+ @Override
+ public void apply(Implementation.Target mock) {
+ when(mock.getInstrumentedType()).thenReturn(mock(TypeDescription.class));
+ }
+ }).skipSynthetic().apply();
+ ObjectPropertyAssertion.of(FieldProxy.Binder.AccessorProxy.class).apply();
+ }
+
+ public static class Foo {
+
+ public Foo foo;
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/FieldValueBinderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/FieldValueBinderTest.java
new file mode 100644
index 0000000..ee5c5a5
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/FieldValueBinderTest.java
@@ -0,0 +1,351 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+public class FieldValueBinderTest extends AbstractAnnotationBinderTest<FieldValue> {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ @Mock
+ private FieldDescription.InDefinedShape fieldDescription;
+
+ @Mock
+ private TypeDescription.Generic fieldType, targetType;
+
+ @Mock
+ private TypeDescription rawFieldType;
+
+ public FieldValueBinderTest() {
+ super(FieldValue.class);
+ }
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ when(fieldDescription.asDefined()).thenReturn(fieldDescription);
+ when(fieldDescription.getType()).thenReturn(fieldType);
+ when(target.getType()).thenReturn(targetType);
+ when(fieldType.asErasure()).thenReturn(rawFieldType);
+ }
+
+ @Override
+ protected TargetMethodAnnotationDrivenBinder.ParameterBinder<FieldValue> getSimpleBinder() {
+ return FieldValue.Binder.INSTANCE;
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testFieldOfArrayThrowsException() throws Exception {
+ doReturn(Object[].class).when(annotation).declaringType();
+ FieldValue.Binder.INSTANCE.bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testFieldOfPrimitiveThrowsException() throws Exception {
+ doReturn(int.class).when(annotation).declaringType();
+ FieldValue.Binder.INSTANCE.bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ }
+
+ @Test
+ public void testLegalAssignment() throws Exception {
+ doReturn(void.class).when(annotation).declaringType();
+ when(annotation.value()).thenReturn(FOO);
+ when(instrumentedType.getDeclaredFields()).thenReturn(new FieldList.Explicit<FieldDescription.InDefinedShape>(fieldDescription));
+ when(fieldDescription.getActualName()).thenReturn(FOO);
+ when(fieldDescription.isVisibleTo(instrumentedType)).thenReturn(true);
+ when(target.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(stackManipulation.isValid()).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> binding = FieldValue.Binder.INSTANCE.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(true));
+ }
+
+ @Test
+ public void testIllegalAssignmentNonAssignable() throws Exception {
+ doReturn(void.class).when(annotation).declaringType();
+ when(annotation.value()).thenReturn(FOO);
+ when(instrumentedType.getDeclaredFields()).thenReturn(new FieldList.Explicit<FieldDescription.InDefinedShape>(fieldDescription));
+ when(fieldDescription.getActualName()).thenReturn(FOO);
+ when(fieldDescription.isVisibleTo(instrumentedType)).thenReturn(true);
+ when(target.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(stackManipulation.isValid()).thenReturn(false);
+ MethodDelegationBinder.ParameterBinding<?> binding = FieldValue.Binder.INSTANCE.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(false));
+ }
+
+ @Test
+ public void testIllegalAssignmentStaticMethod() throws Exception {
+ doReturn(void.class).when(annotation).declaringType();
+ when(annotation.value()).thenReturn(FOO);
+ when(instrumentedType.getDeclaredFields()).thenReturn(new FieldList.Explicit<FieldDescription.InDefinedShape>(fieldDescription));
+ when(fieldDescription.getActualName()).thenReturn(FOO);
+ when(fieldDescription.isVisibleTo(instrumentedType)).thenReturn(true);
+ when(target.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(stackManipulation.isValid()).thenReturn(true);
+ when(source.isStatic()).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> binding = FieldValue.Binder.INSTANCE.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(false));
+ }
+
+ @Test
+ public void testLegalAssignmentStaticMethodStaticField() throws Exception {
+ doReturn(void.class).when(annotation).declaringType();
+ when(annotation.value()).thenReturn(FOO);
+ when(instrumentedType.getDeclaredFields()).thenReturn(new FieldList.Explicit<FieldDescription.InDefinedShape>(fieldDescription));
+ when(fieldDescription.getActualName()).thenReturn(FOO);
+ when(fieldDescription.isVisibleTo(instrumentedType)).thenReturn(true);
+ when(target.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(stackManipulation.isValid()).thenReturn(true);
+ when(source.isStatic()).thenReturn(true);
+ when(fieldDescription.isStatic()).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> binding = FieldValue.Binder.INSTANCE.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(true));
+ }
+
+ @Test
+ public void testIllegalAssignmentNoField() throws Exception {
+ doReturn(void.class).when(annotation).declaringType();
+ when(annotation.value()).thenReturn(FOO);
+ when(instrumentedType.getDeclaredFields()).thenReturn(new FieldList.Empty<FieldDescription.InDefinedShape>());
+ MethodDelegationBinder.ParameterBinding<?> binding = FieldValue.Binder.INSTANCE.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(false));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testIllegalAssignmentNonVisible() throws Exception {
+ doReturn(void.class).when(annotation).declaringType();
+ when(annotation.value()).thenReturn(FOO);
+ when(instrumentedType.getDeclaredFields()).thenReturn((FieldList) new FieldList.Explicit<FieldDescription>(fieldDescription));
+ when(fieldDescription.getActualName()).thenReturn(FOO);
+ when(fieldDescription.isVisibleTo(instrumentedType)).thenReturn(false);
+ when(target.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(stackManipulation.isValid()).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> binding = FieldValue.Binder.INSTANCE.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(false));
+ }
+
+ @Test
+ public void testLegalAssignmentExplicitType() throws Exception {
+ doReturn(Foo.class).when(annotation).declaringType();
+ when(annotation.value()).thenReturn(FOO);
+ when(target.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(stackManipulation.isValid()).thenReturn(true);
+ when(instrumentedType.isAssignableTo(new TypeDescription.ForLoadedType(Foo.class))).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> binding = FieldValue.Binder.INSTANCE.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(true));
+ }
+
+ @Test
+ public void testIllegalAssignmentExplicitTypeNonAssignable() throws Exception {
+ doReturn(Foo.class).when(annotation).declaringType();
+ when(annotation.value()).thenReturn(FOO);
+ when(target.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(stackManipulation.isValid()).thenReturn(true);
+ when(instrumentedType.isAssignableTo(new TypeDescription.ForLoadedType(Foo.class))).thenReturn(false);
+ MethodDelegationBinder.ParameterBinding<?> binding = FieldValue.Binder.INSTANCE.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(false));
+ }
+
+ @Test
+ public void testIllegalAssignmentExplicitTypeNonAssignableFieldType() throws Exception {
+ doReturn(Foo.class).when(annotation).declaringType();
+ when(annotation.value()).thenReturn(FOO);
+ when(target.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(stackManipulation.isValid()).thenReturn(true);
+ when(instrumentedType.isAssignableTo(new TypeDescription.ForLoadedType(Foo.class))).thenReturn(false);
+ MethodDelegationBinder.ParameterBinding<?> binding = FieldValue.Binder.INSTANCE.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(false));
+ }
+
+ @Test
+ public void testIllegalAssignmentExplicitTypeStaticMethod() throws Exception {
+ doReturn(Foo.class).when(annotation).declaringType();
+ when(annotation.value()).thenReturn(FOO);
+ when(target.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(source.isStatic()).thenReturn(true);
+ when(stackManipulation.isValid()).thenReturn(true);
+ when(instrumentedType.isAssignableTo(new TypeDescription.ForLoadedType(Foo.class))).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> binding = FieldValue.Binder.INSTANCE.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(false));
+ }
+
+ @Test
+ public void testLegalAssignmentExplicitTypeStaticMethodStaticField() throws Exception {
+ doReturn(FooStatic.class).when(annotation).declaringType();
+ when(annotation.value()).thenReturn(FOO);
+ when(target.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(source.isStatic()).thenReturn(true);
+ when(stackManipulation.isValid()).thenReturn(true);
+ when(instrumentedType.isAssignableTo(new TypeDescription.ForLoadedType(FooStatic.class))).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> binding = FieldValue.Binder.INSTANCE.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(true));
+ }
+
+ @Test
+ public void testIllegalAssignmentExplicitTypeNoField() throws Exception {
+ doReturn(Foo.class).when(annotation).declaringType();
+ when(annotation.value()).thenReturn(BAR);
+ when(target.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(stackManipulation.isValid()).thenReturn(true);
+ when(instrumentedType.isAssignableTo(new TypeDescription.ForLoadedType(Foo.class))).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> binding = FieldValue.Binder.INSTANCE.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(false));
+ }
+
+ @Test
+ public void testGetterNameDiscovery() throws Exception {
+ doReturn(void.class).when(annotation).declaringType();
+ when(annotation.value()).thenReturn(FieldValue.Binder.Delegate.BEAN_PROPERTY);
+ when(instrumentedType.getDeclaredFields()).thenReturn(new FieldList.Explicit<FieldDescription.InDefinedShape>(fieldDescription));
+ when(fieldDescription.getActualName()).thenReturn(FOO);
+ when(fieldDescription.isVisibleTo(instrumentedType)).thenReturn(true);
+ when(target.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(stackManipulation.isValid()).thenReturn(true);
+ when(source.getInternalName()).thenReturn("getFoo");
+ when(source.getActualName()).thenReturn("getFoo");
+ when(source.getReturnType()).thenReturn(TypeDescription.Generic.OBJECT);
+ when(source.getParameters()).thenReturn(new ParameterList.Empty<ParameterDescription.InDefinedShape>());
+ MethodDelegationBinder.ParameterBinding<?> binding = FieldValue.Binder.INSTANCE.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(true));
+ }
+
+ @Test
+ public void testGetterNameDiscoveryBoolean() throws Exception {
+ doReturn(void.class).when(annotation).declaringType();
+ when(annotation.value()).thenReturn(FieldValue.Binder.Delegate.BEAN_PROPERTY);
+ when(instrumentedType.getDeclaredFields()).thenReturn(new FieldList.Explicit<FieldDescription.InDefinedShape>(fieldDescription));
+ when(fieldDescription.getActualName()).thenReturn(FOO);
+ when(fieldDescription.isVisibleTo(instrumentedType)).thenReturn(true);
+ when(target.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(stackManipulation.isValid()).thenReturn(true);
+ when(source.getInternalName()).thenReturn("isFoo");
+ when(source.getActualName()).thenReturn("isFoo");
+ when(source.getReturnType()).thenReturn(new TypeDescription.Generic.OfNonGenericType.ForLoadedType(boolean.class));
+ when(source.getParameters()).thenReturn(new ParameterList.Empty<ParameterDescription.InDefinedShape>());
+ MethodDelegationBinder.ParameterBinding<?> binding = FieldValue.Binder.INSTANCE.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(true));
+ }
+
+ @Test
+ public void testSetterNameDiscovery() throws Exception {
+ doReturn(void.class).when(annotation).declaringType();
+ when(annotation.value()).thenReturn(FieldValue.Binder.Delegate.BEAN_PROPERTY);
+ when(instrumentedType.getDeclaredFields()).thenReturn(new FieldList.Explicit<FieldDescription.InDefinedShape>(fieldDescription));
+ when(fieldDescription.getActualName()).thenReturn(FOO);
+ when(fieldDescription.isVisibleTo(instrumentedType)).thenReturn(true);
+ when(target.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(stackManipulation.isValid()).thenReturn(true);
+ when(source.getInternalName()).thenReturn("setFoo");
+ when(source.getActualName()).thenReturn("setFoo");
+ when(source.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(source.getParameters()).thenReturn(new ParameterList.Explicit.ForTypes(source, TypeDescription.Generic.OBJECT));
+ MethodDelegationBinder.ParameterBinding<?> binding = FieldValue.Binder.INSTANCE.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(true));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(FieldValue.Binder.class).apply();
+ }
+
+ public static class Foo {
+
+ public String foo;
+ }
+
+ public static class FooStatic {
+
+ public static String foo;
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/IgnoreForBindingVerifierTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/IgnoreForBindingVerifierTest.java
new file mode 100644
index 0000000..577cf9a
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/IgnoreForBindingVerifierTest.java
@@ -0,0 +1,72 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.Opcodes;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.*;
+
+public class IgnoreForBindingVerifierTest extends AbstractAnnotationTest<IgnoreForBinding> {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodDescription methodDescription;
+
+ @Mock
+ private AnnotationList annotationList;
+
+ public IgnoreForBindingVerifierTest() {
+ super(IgnoreForBinding.class);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(methodDescription.getDeclaredAnnotations()).thenReturn(annotationList);
+ }
+
+ @Test
+ public void testIsPresent() throws Exception {
+ when(annotationList.isAnnotationPresent(IgnoreForBinding.class)).thenReturn(true);
+ assertThat(IgnoreForBinding.Verifier.check(methodDescription), is(true));
+ verify(methodDescription).getDeclaredAnnotations();
+ verifyNoMoreInteractions(methodDescription);
+ verify(annotationList).isAnnotationPresent(IgnoreForBinding.class);
+ verifyNoMoreInteractions(annotationList);
+ }
+
+ @Test
+ public void testIsNotPresent() throws Exception {
+ assertThat(IgnoreForBinding.Verifier.check(methodDescription), is(false));
+ verify(methodDescription).getDeclaredAnnotations();
+ verifyNoMoreInteractions(methodDescription);
+ verify(annotationList).isAnnotationPresent(IgnoreForBinding.class);
+ verifyNoMoreInteractions(annotationList);
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testInstantiation() throws Exception {
+ Constructor<?> constructor = IgnoreForBinding.Verifier.class.getDeclaredConstructor();
+ assertThat(constructor.getModifiers(), is(Opcodes.ACC_PRIVATE));
+ constructor.setAccessible(true);
+ try {
+ constructor.newInstance();
+ fail();
+ } catch (InvocationTargetException exception) {
+ throw (UnsupportedOperationException) exception.getCause();
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/MorphBinderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/MorphBinderTest.java
new file mode 100644
index 0000000..e6c34b1
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/MorphBinderTest.java
@@ -0,0 +1,186 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+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 MorphBinderTest extends AbstractAnnotationBinderTest<Morph> {
+
+ @Mock
+ private MethodDescription morphMethod;
+
+ @Mock
+ private MethodDescription.SignatureToken morphToken;
+
+ @Mock
+ private TypeDescription morphType, defaultType;
+
+ @Mock
+ private TypeDescription.Generic genericMorphType;
+
+ @Mock
+ private MethodDescription.SignatureToken sourceToken;
+
+ @Mock
+ private Implementation.SpecialMethodInvocation specialMethodInvocation;
+
+ public MorphBinderTest() {
+ super(Morph.class);
+ }
+
+ @Override
+ protected TargetMethodAnnotationDrivenBinder.ParameterBinder<Morph> getSimpleBinder() {
+ return new Morph.Binder(morphMethod);
+ }
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ when(genericMorphType.asErasure()).thenReturn(morphType);
+ when(defaultType.asErasure()).thenReturn(defaultType);
+ when(source.asSignatureToken()).thenReturn(sourceToken);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIllegalType() throws Exception {
+ when(target.getType()).thenReturn(genericMorphType);
+ when(morphMethod.getDeclaringType()).thenReturn(mock(TypeDescription.class));
+ new Morph.Binder(morphMethod).bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ }
+
+ @Test
+ public void testSuperMethodCallInvalid() throws Exception {
+ when(target.getType()).thenReturn(genericMorphType);
+ when(morphMethod.getDeclaringType()).thenReturn(morphType);
+ doReturn(void.class).when(annotation).defaultTarget();
+ when(implementationTarget.invokeSuper(sourceToken)).thenReturn(specialMethodInvocation);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = new Morph.Binder(morphMethod)
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ assertThat(parameterBinding.isValid(), is(false));
+ verify(specialMethodInvocation).isValid();
+ }
+
+ @Test
+ public void testSuperMethodCallValid() throws Exception {
+ when(target.getType()).thenReturn(genericMorphType);
+ when(morphMethod.getDeclaringType()).thenReturn(morphType);
+ doReturn(void.class).when(annotation).defaultTarget();
+ when(implementationTarget.invokeSuper(sourceToken)).thenReturn(specialMethodInvocation);
+ when(specialMethodInvocation.isValid()).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = new Morph.Binder(morphMethod)
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ assertThat(parameterBinding.isValid(), is(true));
+ verify(specialMethodInvocation).isValid();
+ }
+
+ @Test
+ public void testDefaultMethodCallImplicitInvalid() throws Exception {
+ when(source.asSignatureToken()).thenReturn(morphToken);
+ when(target.getType()).thenReturn(genericMorphType);
+ when(morphMethod.getDeclaringType()).thenReturn(morphType);
+ when(annotation.defaultMethod()).thenReturn(true);
+ doReturn(void.class).when(annotation).defaultTarget();
+ when(implementationTarget.invokeDefault(morphToken)).thenReturn(specialMethodInvocation);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = new Morph.Binder(morphMethod)
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ assertThat(parameterBinding.isValid(), is(false));
+ verify(specialMethodInvocation).isValid();
+ }
+
+ @Test
+ public void testDefaultMethodCallImplicitValid() throws Exception {
+ when(source.asSignatureToken()).thenReturn(morphToken);
+ when(target.getType()).thenReturn(genericMorphType);
+ when(morphMethod.getDeclaringType()).thenReturn(morphType);
+ when(annotation.defaultMethod()).thenReturn(true);
+ doReturn(void.class).when(annotation).defaultTarget();
+ when(implementationTarget.invokeDefault(morphToken)).thenReturn(specialMethodInvocation);
+ when(specialMethodInvocation.isValid()).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = new Morph.Binder(morphMethod)
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ assertThat(parameterBinding.isValid(), is(true));
+ verify(specialMethodInvocation).isValid();
+ }
+
+ @Test
+ public void testDefaultMethodCallExplicitInvalid() throws Exception {
+ when(source.asSignatureToken()).thenReturn(morphToken);
+ when(instrumentedType.getInterfaces()).thenReturn(new TypeList.Generic.ForLoadedTypes(Foo.class));
+ when(target.getType()).thenReturn(genericMorphType);
+ when(morphMethod.getDeclaringType()).thenReturn(morphType);
+ when(annotation.defaultMethod()).thenReturn(true);
+ doReturn(Foo.class).when(annotation).defaultTarget();
+ when(implementationTarget.invokeDefault(morphToken, new TypeDescription.ForLoadedType(Foo.class)))
+ .thenReturn(specialMethodInvocation);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = new Morph.Binder(morphMethod)
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ assertThat(parameterBinding.isValid(), is(false));
+ verify(specialMethodInvocation).isValid();
+ }
+
+ @Test
+ public void testDefaultMethodCallExplicitValid() throws Exception {
+ when(source.asSignatureToken()).thenReturn(morphToken);
+ when(instrumentedType.getInterfaces()).thenReturn(new TypeList.Generic.ForLoadedTypes(Foo.class));
+ when(target.getType()).thenReturn(genericMorphType);
+ when(morphMethod.getDeclaringType()).thenReturn(morphType);
+ when(annotation.defaultMethod()).thenReturn(true);
+ doReturn(Foo.class).when(annotation).defaultTarget();
+ when(implementationTarget.invokeDefault(morphToken, new TypeDescription.ForLoadedType(Foo.class)))
+ .thenReturn(specialMethodInvocation);
+ when(specialMethodInvocation.isValid()).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = new Morph.Binder(morphMethod)
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ assertThat(parameterBinding.isValid(), is(true));
+ verify(specialMethodInvocation).isValid();
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(Morph.Binder.class).apply();
+ ObjectPropertyAssertion.of(Morph.Binder.RedirectionProxy.class).apply();
+ ObjectPropertyAssertion.of(Morph.Binder.RedirectionProxy.MethodCall.class).apply();
+ ObjectPropertyAssertion.of(Morph.Binder.RedirectionProxy.MethodCall.Appender.class).refine(new ObjectPropertyAssertion.Refinement<Implementation.Target>() {
+ @Override
+ public void apply(Implementation.Target mock) {
+ when(mock.getInstrumentedType()).thenReturn(mock(TypeDescription.class));
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(Morph.Binder.RedirectionProxy.StaticFieldConstructor.class).apply();
+ ObjectPropertyAssertion.of(Morph.Binder.RedirectionProxy.InstanceFieldConstructor.class).apply();
+ ObjectPropertyAssertion.of(Morph.Binder.RedirectionProxy.InstanceFieldConstructor.Appender.class).refine(new ObjectPropertyAssertion.Refinement<Implementation.Target>() {
+ @Override
+ public void apply(Implementation.Target mock) {
+ TypeDescription typeDescription = mock(TypeDescription.class);
+ FieldList<?> fieldList = mock(FieldList.class);
+ FieldList<?> filteredFieldList = mock(FieldList.class);
+ when(fieldList.filter(named(Morph.Binder.RedirectionProxy.FIELD_NAME))).thenReturn((FieldList) filteredFieldList);
+ when(filteredFieldList.getOnly()).thenReturn(mock(FieldDescription.class));
+ when(typeDescription.getDeclaredFields()).thenReturn((FieldList) fieldList);
+ when(mock.getInstrumentedType()).thenReturn(typeDescription);
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(Morph.Binder.DefaultMethodLocator.Implicit.class).apply();
+ ObjectPropertyAssertion.of(Morph.Binder.DefaultMethodLocator.Explicit.class).apply();
+ }
+
+ private interface Foo {
+
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/OriginBinderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/OriginBinderTest.java
new file mode 100644
index 0000000..faafa43
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/OriginBinderTest.java
@@ -0,0 +1,165 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.utility.JavaType;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+import org.mockito.Mock;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class OriginBinderTest extends AbstractAnnotationBinderTest<Origin> {
+
+ private static final String FOO = "foo";
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ @Mock
+ private TypeDescription targetType;
+
+ @Mock
+ private TypeDescription.Generic genericTargetType;
+
+ @Mock
+ private MethodDescription.InDefinedShape methodDescription;
+
+ public OriginBinderTest() {
+ super(Origin.class);
+ }
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ when(target.getType()).thenReturn(genericTargetType);
+ when(targetType.asErasure()).thenReturn(targetType);
+ when(genericTargetType.asErasure()).thenReturn(targetType);
+ when(source.asDefined()).thenReturn(methodDescription);
+ }
+
+ @Override
+ protected TargetMethodAnnotationDrivenBinder.ParameterBinder<Origin> getSimpleBinder() {
+ return Origin.Binder.INSTANCE;
+ }
+
+ @Test
+ public void testClassBinding() throws Exception {
+ when(targetType.getInternalName()).thenReturn(FOO);
+ when(targetType.represents(Class.class)).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = Origin.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ assertThat(parameterBinding.isValid(), is(true));
+ verify(implementationTarget).getOriginType();
+ }
+
+ @Test
+ public void testMethodBinding() throws Exception {
+ when(targetType.getInternalName()).thenReturn(FOO);
+ when(targetType.represents(Method.class)).thenReturn(true);
+ when(source.isMethod()).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = Origin.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ assertThat(parameterBinding.isValid(), is(true));
+ }
+
+ @Test
+ public void testMethodBindingForNonMethod() throws Exception {
+ when(targetType.getInternalName()).thenReturn(FOO);
+ when(targetType.represents(Method.class)).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = Origin.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ assertThat(parameterBinding.isValid(), is(false));
+ }
+
+ @Test
+ public void testConstructorBinding() throws Exception {
+ when(targetType.getInternalName()).thenReturn(FOO);
+ when(targetType.represents(Constructor.class)).thenReturn(true);
+ when(source.isConstructor()).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = Origin.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ assertThat(parameterBinding.isValid(), is(true));
+ }
+
+ @Test
+ public void testConstructorBindingForNonConstructor() throws Exception {
+ when(targetType.getInternalName()).thenReturn(FOO);
+ when(targetType.represents(Constructor.class)).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = Origin.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ assertThat(parameterBinding.isValid(), is(false));
+ }
+
+ @Test
+ public void testStringBinding() throws Exception {
+ when(targetType.getInternalName()).thenReturn(FOO);
+ when(targetType.represents(String.class)).thenReturn(true);
+ when(targetType.getSort()).thenReturn(TypeDefinition.Sort.NON_GENERIC);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = Origin.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ assertThat(parameterBinding.isValid(), is(true));
+ }
+
+ @Test
+ public void testModifierBinding() throws Exception {
+ when(targetType.getInternalName()).thenReturn(FOO);
+ when(targetType.represents(int.class)).thenReturn(true);
+ when(targetType.getSort()).thenReturn(TypeDefinition.Sort.NON_GENERIC);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = Origin.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ assertThat(parameterBinding.isValid(), is(true));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(7)
+ public void testMethodHandleBinding() throws Exception {
+ when(genericTargetType.asErasure()).thenReturn(new TypeDescription.ForLoadedType(JavaType.METHOD_HANDLE.load()));
+ when(methodDescription.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(methodDescription.getParameters()).thenReturn(new ParameterList.Empty<ParameterDescription.InDefinedShape>());
+ TypeDescription typeDescription = mock(TypeDescription.class);
+ when(typeDescription.asErasure()).thenReturn(typeDescription);
+ when(methodDescription.getDeclaringType()).thenReturn(typeDescription);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = Origin.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ assertThat(parameterBinding.isValid(), is(true));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(7)
+ public void testMethodTypeBinding() throws Exception {
+ when(genericTargetType.asErasure()).thenReturn(new TypeDescription.ForLoadedType(JavaType.METHOD_TYPE.load()));
+ when(methodDescription.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ when(methodDescription.getParameters()).thenReturn(new ParameterList.Empty<ParameterDescription.InDefinedShape>());
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = Origin.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ assertThat(parameterBinding.isValid(), is(true));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIllegalBinding() throws Exception {
+ when(targetType.getInternalName()).thenReturn(FOO);
+ when(targetType.getSort()).thenReturn(TypeDefinition.Sort.NON_GENERIC);
+ Origin.Binder.INSTANCE.bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(Origin.Binder.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/PipeBinderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/PipeBinderTest.java
new file mode 100644
index 0000000..3ee559f
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/PipeBinderTest.java
@@ -0,0 +1,94 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class PipeBinderTest extends AbstractAnnotationBinderTest<Pipe> {
+
+ private TargetMethodAnnotationDrivenBinder.ParameterBinder<Pipe> binder;
+
+ @Mock
+ private MethodDescription targetMethod;
+
+ @Mock
+ private TypeDescription targetMethodType;
+
+ @Mock
+ private TypeDescription.Generic genericTargetMethodType;
+
+ public PipeBinderTest() {
+ super(Pipe.class);
+ }
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ when(targetMethod.getDeclaringType()).thenReturn(targetMethodType);
+ when(genericTargetMethodType.asErasure()).thenReturn(targetMethodType);
+ binder = new Pipe.Binder(targetMethod);
+ }
+
+ @Override
+ protected TargetMethodAnnotationDrivenBinder.ParameterBinder<Pipe> getSimpleBinder() {
+ return binder;
+ }
+
+ @Test
+ public void testParameterBinding() throws Exception {
+ when(target.getType()).thenReturn(genericTargetMethodType);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = binder.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(parameterBinding.isValid(), is(true));
+ }
+
+ @Test
+ public void testCannotPipeStaticMethod() throws Exception {
+ when(target.getType()).thenReturn(genericTargetMethodType);
+ when(source.isStatic()).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = binder.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(parameterBinding.isValid(), is(false));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testParameterBindingOnIllegalTargetTypeThrowsException() throws Exception {
+ TypeDescription.Generic targetType = mock(TypeDescription.Generic.class);
+ TypeDescription rawTargetType = mock(TypeDescription.class);
+ when(targetType.asErasure()).thenReturn(rawTargetType);
+ when(target.getType()).thenReturn(targetType);
+ binder.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(Pipe.Binder.class).apply();
+ ObjectPropertyAssertion.of(Pipe.Binder.Redirection.class).apply();
+ ObjectPropertyAssertion.of(Pipe.Binder.Redirection.MethodCall.class).skipSynthetic().apply();
+ ObjectPropertyAssertion.of(Pipe.Binder.Redirection.ConstructorCall.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/RuntimeTypeVerifierTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/RuntimeTypeVerifierTest.java
new file mode 100644
index 0000000..8e70a55
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/RuntimeTypeVerifierTest.java
@@ -0,0 +1,70 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.description.annotation.AnnotationSource;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.Opcodes;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.*;
+
+public class RuntimeTypeVerifierTest extends AbstractAnnotationTest<RuntimeType> {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private AnnotationSource annotationSource;
+
+ @Mock
+ private RuntimeType runtimeType;
+
+ public RuntimeTypeVerifierTest() {
+ super(RuntimeType.class);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ doReturn(RuntimeType.class).when(runtimeType).annotationType();
+ }
+
+ @Test
+ public void testCheckElementValid() throws Exception {
+ when(annotationSource.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations(runtimeType));
+ assertThat(RuntimeType.Verifier.check(annotationSource), is(Assigner.Typing.DYNAMIC));
+ verify(annotationSource).getDeclaredAnnotations();
+ verifyNoMoreInteractions(annotationSource);
+ }
+
+ @Test
+ public void testCheckElementInvalid() throws Exception {
+ when(annotationSource.getDeclaredAnnotations()).thenReturn(new AnnotationList.ForLoadedAnnotations());
+ assertThat(RuntimeType.Verifier.check(annotationSource), is(Assigner.Typing.STATIC));
+ verify(annotationSource).getDeclaredAnnotations();
+ verifyNoMoreInteractions(annotationSource);
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testInstantiation() throws Exception {
+ Constructor<?> constructor = RuntimeType.Verifier.class.getDeclaredConstructor();
+ assertThat(constructor.getModifiers(), is(Opcodes.ACC_PRIVATE));
+ constructor.setAccessible(true);
+ try {
+ constructor.newInstance();
+ fail();
+ } catch (InvocationTargetException exception) {
+ throw (UnsupportedOperationException) exception.getCause();
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/StubValueBinderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/StubValueBinderTest.java
new file mode 100644
index 0000000..4e53d2c
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/StubValueBinderTest.java
@@ -0,0 +1,86 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class StubValueBinderTest extends AbstractAnnotationBinderTest<StubValue> {
+
+ @Mock
+ private TypeDescription type;
+
+ @Mock
+ private TypeDescription.Generic genericType;
+
+ public StubValueBinderTest() {
+ super(StubValue.class);
+ }
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ when(genericType.asErasure()).thenReturn(type);
+ }
+
+ @Override
+ protected TargetMethodAnnotationDrivenBinder.ParameterBinder<StubValue> getSimpleBinder() {
+ return StubValue.Binder.INSTANCE;
+ }
+
+ @Test
+ public void testVoidReturnType() throws Exception {
+ when(target.getType()).thenReturn(TypeDescription.Generic.OBJECT);
+ when(source.getReturnType()).thenReturn(TypeDescription.Generic.VOID);
+ assertThat(StubValue.Binder.INSTANCE.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC).isValid(), is(true));
+ }
+
+ @Test
+ public void testNonVoidAssignableReturnType() throws Exception {
+ when(target.getType()).thenReturn(TypeDescription.Generic.OBJECT);
+ when(source.getReturnType()).thenReturn(genericType);
+ when(stackManipulation.isValid()).thenReturn(true);
+ assertThat(StubValue.Binder.INSTANCE.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC).isValid(), is(true));
+ }
+
+ @Test
+ public void testNonVoidNonAssignableReturnType() throws Exception {
+ when(target.getType()).thenReturn(TypeDescription.Generic.OBJECT);
+ when(source.getReturnType()).thenReturn(TypeDescription.Generic.OBJECT);
+ when(stackManipulation.isValid()).thenReturn(false);
+ assertThat(StubValue.Binder.INSTANCE.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC).isValid(), is(false));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIllegalParameter() throws Exception {
+ when(target.getType()).thenReturn(genericType);
+ StubValue.Binder.INSTANCE.bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(StubValue.Binder.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/SuperBinderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/SuperBinderTest.java
new file mode 100644
index 0000000..9029b8b
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/SuperBinderTest.java
@@ -0,0 +1,120 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class SuperBinderTest extends AbstractAnnotationBinderTest<Super> {
+
+ @Mock
+ private TypeDescription targetType;
+
+ @Mock
+ private TypeDescription.Generic genericTargetType;
+
+ @Mock
+ private Super.Instantiation instantiation;
+
+ public SuperBinderTest() {
+ super(Super.class);
+ }
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ when(target.getType()).thenReturn(genericTargetType);
+ when(genericTargetType.asErasure()).thenReturn(targetType);
+ when(annotation.strategy()).thenReturn(instantiation);
+ when(instantiation.proxyFor(targetType, implementationTarget, annotationDescription)).thenReturn(stackManipulation);
+ when(annotation.constructorParameters()).thenReturn(new Class<?>[0]);
+ when(targetType.asErasure()).thenReturn(targetType);
+ }
+
+ @Override
+ protected TargetMethodAnnotationDrivenBinder.ParameterBinder<Super> getSimpleBinder() {
+ return Super.Binder.INSTANCE;
+ }
+
+ @Test
+ public void testAssignableBinding() throws Exception {
+ doReturn(void.class).when(annotation).proxyType();
+ when(stackManipulation.isValid()).thenReturn(true);
+ when(instrumentedType.isAssignableTo(targetType)).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = Super.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ assertThat(parameterBinding.isValid(), is(true));
+ verify(instantiation).proxyFor(targetType, implementationTarget, annotationDescription);
+ }
+
+ @Test
+ public void testIllegalBindingForNonAssignableType() throws Exception {
+ doReturn(void.class).when(annotation).proxyType();
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = Super.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ assertThat(parameterBinding.isValid(), is(false));
+ }
+
+ @Test
+ public void testIllegalBindingStaticMethod() throws Exception {
+ doReturn(void.class).when(annotation).proxyType();
+ when(source.isStatic()).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = Super.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ assertThat(parameterBinding.isValid(), is(false));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testPrimitiveParameterType() throws Exception {
+ when(genericTargetType.isPrimitive()).thenReturn(true);
+ Super.Binder.INSTANCE.bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testArrayParameterType() throws Exception {
+ when(genericTargetType.isArray()).thenReturn(true);
+ Super.Binder.INSTANCE.bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testPrimitiveProxyType() throws Exception {
+ doReturn(int.class).when(annotation).proxyType();
+ Super.Binder.INSTANCE.bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testArrayProxyType() throws Exception {
+ doReturn(Object[].class).when(annotation).proxyType();
+ Super.Binder.INSTANCE.bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNonAssignableType() throws Exception {
+ doReturn(Void.class).when(annotation).proxyType();
+ Super.Binder.INSTANCE.bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testFinalProxyType() throws Exception {
+ doReturn(void.class).when(annotation).proxyType();
+ when(targetType.isFinal()).thenReturn(true);
+ Super.Binder.INSTANCE.bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(Super.Binder.class).apply();
+ ObjectPropertyAssertion.of(Super.Instantiation.class).apply();
+ ObjectPropertyAssertion.of(Super.Binder.TypeLocator.ForInstrumentedType.class).apply();
+ ObjectPropertyAssertion.of(Super.Binder.TypeLocator.ForParameterType.class).apply();
+ ObjectPropertyAssertion.of(Super.Binder.TypeLocator.ForType.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/SuperCallBinderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/SuperCallBinderTest.java
new file mode 100644
index 0000000..d198639
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/SuperCallBinderTest.java
@@ -0,0 +1,114 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class SuperCallBinderTest extends AbstractAnnotationBinderTest<SuperCall> {
+
+ @Mock
+ private TypeDescription targetParameterType;
+
+ @Mock
+ private TypeDescription.Generic genericTargetParameterType;
+
+ @Mock
+ private Implementation.SpecialMethodInvocation specialMethodInvocation;
+
+ @Mock
+ private MethodDescription.SignatureToken sourceToken;
+
+ public SuperCallBinderTest() {
+ super(SuperCall.class);
+ }
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ when(target.getType()).thenReturn(genericTargetParameterType);
+ when(genericTargetParameterType.asErasure()).thenReturn(targetParameterType);
+ when(source.asSignatureToken()).thenReturn(sourceToken);
+ when(implementationTarget.invokeSuper(sourceToken)).thenReturn(specialMethodInvocation);
+ }
+
+ @Override
+ protected TargetMethodAnnotationDrivenBinder.ParameterBinder<SuperCall> getSimpleBinder() {
+ return SuperCall.Binder.INSTANCE;
+ }
+
+ @Test
+ public void testValidSuperMethodCall() throws Exception {
+ when(targetParameterType.represents(any(Class.class))).thenReturn(true);
+ when(specialMethodInvocation.isValid()).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = SuperCall.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ verify(implementationTarget).invokeSuper(sourceToken);
+ verifyNoMoreInteractions(implementationTarget);
+ assertThat(parameterBinding.isValid(), is(true));
+ }
+
+ @Test
+ public void testInvalidSuperMethodCall() throws Exception {
+ when(targetParameterType.represents(any(Class.class))).thenReturn(true);
+ when(specialMethodInvocation.isValid()).thenReturn(false);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = SuperCall.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ verify(implementationTarget).invokeSuper(sourceToken);
+ verifyNoMoreInteractions(implementationTarget);
+ assertThat(parameterBinding.isValid(), is(false));
+ }
+
+ @Test
+ public void testInvalidSuperMethodCallNullFallback() throws Exception {
+ when(targetParameterType.represents(any(Class.class))).thenReturn(true);
+ when(specialMethodInvocation.isValid()).thenReturn(false);
+ when(annotation.nullIfImpossible()).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = SuperCall.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ verify(implementationTarget).invokeSuper(sourceToken);
+ verifyNoMoreInteractions(implementationTarget);
+ assertThat(parameterBinding.isValid(), is(true));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testWrongTypeThrowsException() throws Exception {
+ SuperCall.Binder.INSTANCE.bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ }
+
+ @Test
+ public void testConstructorIsNotInvokeable() throws Exception {
+ when(targetParameterType.represents(any(Class.class))).thenReturn(true);
+ when(source.isConstructor()).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = SuperCall.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ verifyZeroInteractions(implementationTarget);
+ assertThat(parameterBinding.isValid(), is(false));
+ }
+
+ @Test
+ public void testConstructorNullFallback() throws Exception {
+ when(targetParameterType.represents(any(Class.class))).thenReturn(true);
+ when(source.isConstructor()).thenReturn(true);
+ when(annotation.nullIfImpossible()).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = SuperCall.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ verifyZeroInteractions(implementationTarget);
+ assertThat(parameterBinding.isValid(), is(true));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(SuperCall.Binder.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/SuperMethodBinderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/SuperMethodBinderTest.java
new file mode 100644
index 0000000..5abbf0f
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/SuperMethodBinderTest.java
@@ -0,0 +1,151 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.lang.reflect.Method;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class SuperMethodBinderTest extends AbstractAnnotationBinderTest<SuperMethod> {
+
+ public SuperMethodBinderTest() {
+ super(SuperMethod.class);
+ }
+
+ @Mock
+ private TypeDescription targetType;
+
+ @Mock
+ private TypeDescription.Generic genericTargetType;
+
+ @Mock
+ private MethodDescription.SignatureToken token;
+
+ @Mock
+ private Implementation.SpecialMethodInvocation specialMethodInvocation;
+
+ @Override
+ protected TargetMethodAnnotationDrivenBinder.ParameterBinder<SuperMethod> getSimpleBinder() {
+ return SuperMethod.Binder.INSTANCE;
+ }
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ when(target.getType()).thenReturn(genericTargetType);
+ when(genericTargetType.asErasure()).thenReturn(targetType);
+ when(source.asSignatureToken()).thenReturn(token);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testBindNoMethodParameter() throws Exception {
+ SuperMethod.Binder.INSTANCE.bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ }
+
+ @Test
+ public void testBind() throws Exception {
+ when(targetType.isAssignableFrom(Method.class)).thenReturn(true);
+ when(source.isMethod()).thenReturn(true);
+ when(implementationTarget.invokeSuper(token)).thenReturn(specialMethodInvocation);
+ when(specialMethodInvocation.isValid()).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> binding = SuperMethod.Binder.INSTANCE.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(true));
+ }
+
+ @Test
+ public void testBindDefaultFallback() throws Exception {
+ when(targetType.isAssignableFrom(Method.class)).thenReturn(true);
+ when(source.isMethod()).thenReturn(true);
+ when(annotation.fallbackToDefault()).thenReturn(true);
+ when(implementationTarget.invokeDominant(token)).thenReturn(specialMethodInvocation);
+ when(specialMethodInvocation.isValid()).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> binding = SuperMethod.Binder.INSTANCE.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(true));
+ }
+
+ @Test
+ public void testBindIllegal() throws Exception {
+ when(targetType.isAssignableFrom(Method.class)).thenReturn(true);
+ when(source.isMethod()).thenReturn(true);
+ when(implementationTarget.invokeSuper(token)).thenReturn(specialMethodInvocation);
+ when(specialMethodInvocation.isValid()).thenReturn(false);
+ MethodDelegationBinder.ParameterBinding<?> binding = SuperMethod.Binder.INSTANCE.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(false));
+ }
+
+ @Test
+ public void testBindIllegalFallback() throws Exception {
+ when(targetType.isAssignableFrom(Method.class)).thenReturn(true);
+ when(source.isMethod()).thenReturn(true);
+ when(annotation.nullIfImpossible()).thenReturn(true);
+ when(implementationTarget.invokeSuper(token)).thenReturn(specialMethodInvocation);
+ when(specialMethodInvocation.isValid()).thenReturn(false);
+ MethodDelegationBinder.ParameterBinding<?> binding = SuperMethod.Binder.INSTANCE.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(true));
+ }
+
+ @Test
+ public void testNoMethod() throws Exception {
+ when(targetType.isAssignableFrom(Method.class)).thenReturn(true);
+ when(source.isMethod()).thenReturn(false);
+ when(annotation.nullIfImpossible()).thenReturn(false);
+ MethodDelegationBinder.ParameterBinding<?> binding = SuperMethod.Binder.INSTANCE.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(false));
+ }
+
+ @Test
+ public void testNoMethodFallback() throws Exception {
+ when(targetType.isAssignableFrom(Method.class)).thenReturn(true);
+ when(source.isMethod()).thenReturn(false);
+ when(annotation.nullIfImpossible()).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> binding = SuperMethod.Binder.INSTANCE.bind(annotationDescription,
+ source,
+ target,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ assertThat(binding.isValid(), is(true));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(SuperMethod.Binder.class).apply();
+ ObjectPropertyAssertion.of(SuperMethod.Binder.DelegationMethod.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/TargetMethodAnnotationDrivenBinderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/TargetMethodAnnotationDrivenBinderTest.java
new file mode 100644
index 0000000..0d1761b
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/TargetMethodAnnotationDrivenBinderTest.java
@@ -0,0 +1,413 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bind.ArgumentTypeResolver;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+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 org.objectweb.asm.MethodVisitor;
+
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.*;
+
+public class TargetMethodAnnotationDrivenBinderTest {
+
+ private static final String FOO = "foo", BAR = "bar", BAZ = "baz";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TargetMethodAnnotationDrivenBinder.ParameterBinder<?> firstParameterBinder, secondParameterBinder;
+
+ @Mock
+ private TargetMethodAnnotationDrivenBinder.TerminationHandler terminationHandler;
+
+ @Mock
+ private Assigner assigner;
+
+ private Assigner.Typing typing = Assigner.Typing.STATIC;
+
+ @Mock
+ private StackManipulation assignmentBinding, methodInvocation, termination;
+
+ @Mock
+ private TargetMethodAnnotationDrivenBinder.MethodInvoker methodInvoker;
+
+ @Mock
+ private Implementation.Target implementationTarget;
+
+ @Mock
+ private MethodDescription sourceMethod, targetMethod;
+
+ @Mock
+ private TypeDescription.Generic sourceTypeDescription, targetTypeDescription;
+
+ @Mock
+ private TypeDescription instrumentedType;
+
+ @Mock
+ private AnnotationDescription.ForLoadedAnnotation<FirstPseudoAnnotation> firstPseudoAnnotation;
+
+ @Mock
+ private AnnotationDescription.ForLoadedAnnotation<SecondPseudoAnnotation> secondPseudoAnnotation;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @Mock
+ private ParameterDescription firstParameter, secondParameter;
+
+ @SuppressWarnings("unchecked")
+ private static MethodDelegationBinder.ParameterBinding<?> prepareArgumentBinder(TargetMethodAnnotationDrivenBinder.ParameterBinder<?> parameterBinder,
+ Class<? extends Annotation> annotationType,
+ Object identificationToken) {
+ doReturn(annotationType).when(parameterBinder).getHandledType();
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = mock(MethodDelegationBinder.ParameterBinding.class);
+ when(parameterBinding.isValid()).thenReturn(true);
+ when(parameterBinding.apply(any(MethodVisitor.class), any(Implementation.Context.class))).thenReturn(new StackManipulation.Size(0, 0));
+ when(parameterBinding.getIdentificationToken()).thenReturn(identificationToken);
+ when(((TargetMethodAnnotationDrivenBinder.ParameterBinder) parameterBinder).bind(any(AnnotationDescription.Loadable.class),
+ any(MethodDescription.class),
+ any(ParameterDescription.class),
+ any(Implementation.Target.class),
+ any(Assigner.class),
+ any(Assigner.Typing.class)))
+ .thenReturn(parameterBinding);
+ return parameterBinding;
+ }
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ when(assignmentBinding.apply(any(MethodVisitor.class), any(Implementation.Context.class)))
+ .thenReturn(new StackManipulation.Size(0, 0));
+ when(assigner.assign(any(TypeDescription.Generic.class), any(TypeDescription.Generic.class), any(Assigner.Typing.class)))
+ .thenReturn(assignmentBinding);
+ when(methodInvoker.invoke(any(MethodDescription.class))).thenReturn(methodInvocation);
+ when(methodInvocation.apply(any(MethodVisitor.class), any(Implementation.Context.class)))
+ .thenReturn(new StackManipulation.Size(0, 0));
+ when(assignmentBinding.apply(any(MethodVisitor.class), any(Implementation.Context.class)))
+ .thenReturn(new StackManipulation.Size(0, 0));
+ TypeDescription declaringType = mock(TypeDescription.class);
+ when(declaringType.getInternalName()).thenReturn(FOO);
+ when(declaringType.isInterface()).thenReturn(false);
+ when(targetMethod.getInternalName()).thenReturn(BAR);
+ when(targetMethod.isStatic()).thenReturn(true);
+ when(targetMethod.getDeclaringType()).thenReturn(declaringType);
+ when(targetMethod.getDescriptor()).thenReturn(BAZ);
+ when(firstParameter.getDeclaringMethod()).thenReturn(targetMethod);
+ when(firstParameter.getIndex()).thenReturn(0);
+ when(secondParameter.getDeclaringMethod()).thenReturn(targetMethod);
+ when(secondParameter.getIndex()).thenReturn(1);
+ when(targetMethod.getParameters())
+ .thenReturn((ParameterList) new ParameterList.Explicit<ParameterDescription>(firstParameter, secondParameter));
+ when(firstPseudoAnnotation.getAnnotationType())
+ .thenReturn(new TypeDescription.ForLoadedType(FirstPseudoAnnotation.class));
+ when(firstPseudoAnnotation.prepare(FirstPseudoAnnotation.class)).thenReturn(firstPseudoAnnotation);
+ when(secondPseudoAnnotation.getAnnotationType())
+ .thenReturn(new TypeDescription.ForLoadedType(SecondPseudoAnnotation.class));
+ when(secondPseudoAnnotation.prepare(SecondPseudoAnnotation.class)).thenReturn(secondPseudoAnnotation);
+ when(sourceTypeDescription.getStackSize()).thenReturn(StackSize.ZERO);
+ when(targetTypeDescription.getStackSize()).thenReturn(StackSize.ZERO);
+ when(sourceMethod.getReturnType()).thenReturn(sourceTypeDescription);
+ when(targetMethod.getReturnType()).thenReturn(targetTypeDescription);
+ when(terminationHandler.resolve(assigner, typing, sourceMethod, targetMethod)).thenReturn(termination);
+ when(termination.apply(any(MethodVisitor.class), any(Implementation.Context.class))).thenReturn(new StackManipulation.Size(0, 0));
+ when(implementationTarget.getInstrumentedType()).thenReturn(instrumentedType);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testConflictingBinderBinding() throws Exception {
+ doReturn(FirstPseudoAnnotation.class).when(firstParameterBinder).getHandledType();
+ doReturn(FirstPseudoAnnotation.class).when(secondParameterBinder).getHandledType();
+ TargetMethodAnnotationDrivenBinder.of(Arrays.<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>>asList(firstParameterBinder, secondParameterBinder));
+ }
+
+ @Test
+ public void testIgnoreForBindingAnnotation() throws Exception {
+ when(targetMethod.isAccessibleTo(instrumentedType)).thenReturn(true);
+ AnnotationDescription ignoreForBinding = mock(AnnotationDescription.class);
+ when(ignoreForBinding.getAnnotationType()).thenReturn(new TypeDescription.ForLoadedType(IgnoreForBinding.class));
+ when(targetMethod.getDeclaredAnnotations()).thenReturn(new AnnotationList.Explicit(Collections.singletonList(ignoreForBinding)));
+ when(termination.isValid()).thenReturn(true);
+ MethodDelegationBinder methodDelegationBinder = TargetMethodAnnotationDrivenBinder.of(Collections.<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>>emptyList());
+ assertThat(methodDelegationBinder.compile(targetMethod).bind(implementationTarget,
+ sourceMethod,
+ terminationHandler,
+ methodInvoker,
+ assigner).isValid(), is(false));
+ verifyZeroInteractions(assigner);
+ verifyZeroInteractions(implementationTarget);
+ verifyZeroInteractions(sourceMethod);
+ }
+
+ @Test
+ public void testNonAccessible() throws Exception {
+ when(targetMethod.isAccessibleTo(instrumentedType)).thenReturn(false);
+ when(assignmentBinding.isValid()).thenReturn(true);
+ when(methodInvocation.isValid()).thenReturn(true);
+ when(termination.isValid()).thenReturn(true);
+ when(targetMethod.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(firstParameter.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(secondParameter.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ MethodDelegationBinder methodDelegationBinder = TargetMethodAnnotationDrivenBinder.of(Collections.<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>>emptyList());
+ assertThat(methodDelegationBinder.compile(targetMethod).bind(implementationTarget,
+ sourceMethod,
+ terminationHandler,
+ methodInvoker,
+ assigner).isValid(), is(false));
+ verifyZeroInteractions(terminationHandler);
+ verifyZeroInteractions(assigner);
+ verifyZeroInteractions(methodInvoker);
+ }
+
+ @Test
+ public void testTerminationBinderMismatch() throws Exception {
+ when(targetMethod.isAccessibleTo(instrumentedType)).thenReturn(true);
+ when(assignmentBinding.isValid()).thenReturn(false);
+ when(methodInvocation.isValid()).thenReturn(true);
+ when(termination.isValid()).thenReturn(false);
+ when(targetMethod.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(firstParameter.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(secondParameter.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ MethodDelegationBinder methodDelegationBinder = TargetMethodAnnotationDrivenBinder.of(Collections.<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>>emptyList());
+ assertThat(methodDelegationBinder.compile(targetMethod).bind(implementationTarget,
+ sourceMethod,
+ terminationHandler,
+ methodInvoker,
+ assigner).isValid(), is(false));
+ verify(terminationHandler).resolve(assigner, typing, sourceMethod, targetMethod);
+ verifyNoMoreInteractions(terminationHandler);
+ verifyZeroInteractions(assigner);
+ verifyZeroInteractions(methodInvoker);
+ }
+
+ @Test
+ public void testDoNotBindOnIllegalMethodInvocation() throws Exception {
+ when(targetMethod.isAccessibleTo(instrumentedType)).thenReturn(true);
+ when(assignmentBinding.isValid()).thenReturn(true);
+ when(methodInvocation.isValid()).thenReturn(false);
+ when(termination.isValid()).thenReturn(true);
+ when(targetMethod.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(firstParameter.getDeclaredAnnotations()).thenReturn(new AnnotationList.Explicit(Collections.singletonList(firstPseudoAnnotation)));
+ when(secondParameter.getDeclaredAnnotations()).thenReturn(new AnnotationList.Explicit(Collections.singletonList(secondPseudoAnnotation)));
+ MethodDelegationBinder.ParameterBinding<?> firstBinding = prepareArgumentBinder(
+ firstParameterBinder,
+ FirstPseudoAnnotation.class,
+ new Key(FOO));
+ MethodDelegationBinder.ParameterBinding<?> secondBinding = prepareArgumentBinder(
+ secondParameterBinder,
+ SecondPseudoAnnotation.class,
+ new Key(BAR));
+ MethodDelegationBinder methodDelegationBinder = TargetMethodAnnotationDrivenBinder.of(Arrays.<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>>asList(firstParameterBinder, secondParameterBinder));
+ MethodDelegationBinder.MethodBinding methodBinding = methodDelegationBinder.compile(targetMethod).bind(implementationTarget,
+ sourceMethod,
+ terminationHandler,
+ methodInvoker,
+ assigner);
+ assertThat(methodBinding.isValid(), is(false));
+ verify(firstBinding).isValid();
+ verify(secondBinding).isValid();
+ verify(termination).isValid();
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testBindingByDefault() throws Exception {
+ when(targetMethod.isAccessibleTo(instrumentedType)).thenReturn(true);
+ when(assignmentBinding.isValid()).thenReturn(true);
+ when(methodInvocation.isValid()).thenReturn(true);
+ when(termination.isValid()).thenReturn(true);
+ when(targetMethod.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(firstParameter.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(secondParameter.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(firstParameter.getType()).thenReturn(TypeDescription.Generic.OBJECT);
+ when(secondParameter.getType()).thenReturn(TypeDescription.Generic.OBJECT);
+ when(sourceMethod.getParameters()).thenReturn(new ParameterList.Explicit(firstParameter, secondParameter));
+ MethodDelegationBinder methodDelegationBinder = TargetMethodAnnotationDrivenBinder.of(Collections.<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>>emptyList());
+ MethodDelegationBinder.MethodBinding methodBinding = methodDelegationBinder.compile(targetMethod).bind(implementationTarget,
+ sourceMethod,
+ terminationHandler,
+ methodInvoker,
+ assigner);
+ assertThat(methodBinding.isValid(), is(true));
+ assertThat(methodBinding.getTarget(), is(targetMethod));
+ assertThat(methodBinding.getTargetParameterIndex(new ArgumentTypeResolver.ParameterIndexToken(0)), is(0));
+ assertThat(methodBinding.getTargetParameterIndex(new ArgumentTypeResolver.ParameterIndexToken(1)), is(1));
+ StackManipulation.Size size = methodBinding.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(2));
+ assertThat(size.getMaximalSize(), is(2));
+ verify(firstParameter, atLeast(1)).getDeclaredAnnotations();
+ verify(secondParameter, atLeast(1)).getDeclaredAnnotations();
+ verify(targetMethod, atLeast(1)).getDeclaredAnnotations();
+ verify(terminationHandler).resolve(assigner, typing, sourceMethod, targetMethod);
+ verifyNoMoreInteractions(terminationHandler);
+ verify(methodInvoker).invoke(targetMethod);
+ verifyNoMoreInteractions(methodInvoker);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testBindingByParameterAnnotations() throws Exception {
+ when(targetMethod.isAccessibleTo(instrumentedType)).thenReturn(true);
+ when(assignmentBinding.isValid()).thenReturn(true);
+ when(methodInvocation.isValid()).thenReturn(true);
+ when(termination.isValid()).thenReturn(true);
+ when(targetMethod.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(firstParameter.getDeclaredAnnotations()).thenReturn(new AnnotationList.Explicit(Collections.singletonList(secondPseudoAnnotation)));
+ when(secondParameter.getDeclaredAnnotations()).thenReturn(new AnnotationList.Explicit(Collections.singletonList(firstPseudoAnnotation)));
+ MethodDelegationBinder.ParameterBinding<?> firstBinding = prepareArgumentBinder(
+ firstParameterBinder,
+ FirstPseudoAnnotation.class,
+ new Key(FOO));
+ MethodDelegationBinder.ParameterBinding<?> secondBinding = prepareArgumentBinder(
+ secondParameterBinder,
+ SecondPseudoAnnotation.class,
+ new Key(BAR));
+ MethodDelegationBinder methodDelegationBinder = TargetMethodAnnotationDrivenBinder.of(Arrays.<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>>asList(firstParameterBinder, secondParameterBinder));
+ MethodDelegationBinder.MethodBinding methodBinding = methodDelegationBinder.compile(targetMethod).bind(implementationTarget,
+ sourceMethod,
+ terminationHandler,
+ methodInvoker,
+ assigner);
+ assertThat(methodBinding.isValid(), is(true));
+ assertThat(methodBinding.getTarget(), is(targetMethod));
+ assertThat(methodBinding.getTargetParameterIndex(new Key(FOO)), is(1));
+ assertThat(methodBinding.getTargetParameterIndex(new Key(BAR)), is(0));
+ StackManipulation.Size size = methodBinding.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(0));
+ assertThat(size.getMaximalSize(), is(0));
+ verifyZeroInteractions(methodVisitor);
+ verify(targetMethod, atLeast(1)).getDeclaredAnnotations();
+ verify(firstParameter, atLeast(1)).getDeclaredAnnotations();
+ verify(secondParameter, atLeast(1)).getDeclaredAnnotations();
+ verifyNoMoreInteractions(assigner);
+ verify(terminationHandler).resolve(assigner, typing, sourceMethod, targetMethod);
+ verifyNoMoreInteractions(terminationHandler);
+ verify(methodInvoker).invoke(targetMethod);
+ verifyNoMoreInteractions(methodInvoker);
+ verify(firstParameterBinder, atLeast(1)).getHandledType();
+ verify((TargetMethodAnnotationDrivenBinder.ParameterBinder) firstParameterBinder).bind(firstPseudoAnnotation,
+ sourceMethod,
+ secondParameter,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ verifyNoMoreInteractions(firstParameterBinder);
+ verify(secondParameterBinder, atLeast(1)).getHandledType();
+ verify((TargetMethodAnnotationDrivenBinder.ParameterBinder) secondParameterBinder).bind(secondPseudoAnnotation,
+ sourceMethod,
+ firstParameter,
+ implementationTarget,
+ assigner,
+ Assigner.Typing.STATIC);
+ verifyNoMoreInteractions(secondParameterBinder);
+ verify(firstBinding, atLeast(1)).isValid();
+ verify(firstBinding).getIdentificationToken();
+ verify(secondBinding, atLeast(1)).isValid();
+ verify(secondBinding).getIdentificationToken();
+ }
+
+ @Test
+ public void testAnnotation() throws Exception {
+ Argument argument = new TargetMethodAnnotationDrivenBinder.DelegationProcessor.Handler.Unbound.DefaultArgument(0);
+ Argument sample = (Argument) Sample.class.getDeclaredMethod(FOO, Object.class).getParameterAnnotations()[0][0];
+ assertThat(argument.toString(), is(sample.toString()));
+ assertThat(argument.hashCode(), is(sample.hashCode()));
+ assertThat(argument, is(sample));
+ assertThat(argument, is(argument));
+ assertThat(argument, not(equalTo(null)));
+ assertThat(argument, not(new Object()));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TargetMethodAnnotationDrivenBinder.class).refine(new ObjectPropertyAssertion.Refinement<TargetMethodAnnotationDrivenBinder.ParameterBinder>() {
+ @Override
+ public void apply(TargetMethodAnnotationDrivenBinder.ParameterBinder mock) {
+ when(mock.getHandledType()).thenReturn(Annotation.class);
+ }
+ }).refine(new ObjectPropertyAssertion.Refinement<TypeDescription>() {
+ @Override
+ public void apply(TypeDescription mock) {
+ when(mock.getStackSize()).thenReturn(StackSize.ZERO);
+ }
+ }).create(new ObjectPropertyAssertion.Creator<List<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>>>() {
+ @Override
+ public List<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>> create() {
+ TargetMethodAnnotationDrivenBinder.ParameterBinder<?> parameterBinder = mock(TargetMethodAnnotationDrivenBinder.ParameterBinder.class);
+ doReturn(Annotation.class).when(parameterBinder).getHandledType();
+ return Collections.<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>>singletonList(parameterBinder);
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(TargetMethodAnnotationDrivenBinder.Record.class).apply();
+ ObjectPropertyAssertion.of(TargetMethodAnnotationDrivenBinder.TerminationHandler.class).apply();
+ ObjectPropertyAssertion.of(TargetMethodAnnotationDrivenBinder.DelegationProcessor.class).apply();
+ ObjectPropertyAssertion.of(TargetMethodAnnotationDrivenBinder.DelegationProcessor.Handler.Bound.class).apply();
+ ObjectPropertyAssertion.of(TargetMethodAnnotationDrivenBinder.DelegationProcessor.Handler.Unbound.class).apply();
+ }
+
+ private interface Sample {
+
+ void foo(@Argument(0) Object foo);
+ }
+
+ private @interface FirstPseudoAnnotation {
+
+ }
+
+ private @interface SecondPseudoAnnotation {
+
+ }
+
+ private static class Key {
+
+ private final String value;
+
+ private Key(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return this == other || !(other == null || getClass() != other.getClass())
+ && value.equals(((Key) other).value);
+ }
+
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/TargetMethodAnnotationDriverBinderParameterBinderForFixedValueOfConstantOtherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/TargetMethodAnnotationDriverBinderParameterBinderForFixedValueOfConstantOtherTest.java
new file mode 100644
index 0000000..2a4e6c3
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/TargetMethodAnnotationDriverBinderParameterBinderForFixedValueOfConstantOtherTest.java
@@ -0,0 +1,182 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.MethodDelegation;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.utility.JavaConstant;
+import net.bytebuddy.utility.JavaType;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Iterator;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class TargetMethodAnnotationDriverBinderParameterBinderForFixedValueOfConstantOtherTest {
+
+ private static final String FOO = "foo";
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ @Test
+ public void testTypeDescription() throws Exception {
+ assertThat(new ByteBuddy()
+ .subclass(Foo.class)
+ .method(named(FOO))
+ .intercept(MethodDelegation.withDefaultConfiguration()
+ .withBinders(TargetMethodAnnotationDrivenBinder.ParameterBinder.ForFixedValue.OfConstant.of(Bar.class, TypeDescription.OBJECT))
+ .to(Foo.class))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance()
+ .foo(), is((Object) Object.class));
+ }
+
+ @Test
+ public void testNull() throws Exception {
+ assertThat(new ByteBuddy()
+ .subclass(Foo.class)
+ .method(named(FOO))
+ .intercept(MethodDelegation.withDefaultConfiguration()
+ .withBinders(TargetMethodAnnotationDrivenBinder.ParameterBinder.ForFixedValue.OfConstant.of(Bar.class, null))
+ .to(Foo.class))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance()
+ .foo(), nullValue(Object.class));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(7)
+ public void testMethodHandleLoaded() throws Exception {
+ Method publicLookup = Class.forName("java.lang.invoke.MethodHandles").getDeclaredMethod("publicLookup");
+ Object lookup = publicLookup.invoke(null);
+ Method unreflected = Class.forName("java.lang.invoke.MethodHandles$Lookup").getDeclaredMethod("unreflect", Method.class);
+ Object methodHandleLoaded = unreflected.invoke(lookup, Foo.class.getDeclaredMethod(FOO));
+ assertThat(JavaConstant.MethodHandle.ofLoaded(new ByteBuddy()
+ .subclass(Foo.class)
+ .method(named(FOO))
+ .intercept(MethodDelegation.withDefaultConfiguration()
+ .withBinders(TargetMethodAnnotationDrivenBinder.ParameterBinder.ForFixedValue.OfConstant.of(Bar.class, methodHandleLoaded))
+ .to(Foo.class))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance()
+ .foo()), is(JavaConstant.MethodHandle.ofLoaded(methodHandleLoaded)));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(7)
+ public void testMethodHandle() throws Exception {
+ Method publicLookup = Class.forName("java.lang.invoke.MethodHandles").getDeclaredMethod("publicLookup");
+ Object lookup = publicLookup.invoke(null);
+ Method unreflected = Class.forName("java.lang.invoke.MethodHandles$Lookup").getDeclaredMethod("unreflect", Method.class);
+ Object methodHandleLoaded = unreflected.invoke(lookup, Foo.class.getDeclaredMethod(FOO));
+ assertThat(JavaConstant.MethodHandle.ofLoaded(new ByteBuddy()
+ .subclass(Foo.class)
+ .method(named(FOO))
+ .intercept(MethodDelegation.withDefaultConfiguration()
+ .withBinders(TargetMethodAnnotationDrivenBinder.ParameterBinder.ForFixedValue.OfConstant.of(Bar.class, JavaConstant.MethodHandle.ofLoaded(methodHandleLoaded)))
+ .to(Foo.class))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance()
+ .foo()), is(JavaConstant.MethodHandle.ofLoaded(methodHandleLoaded)));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(7)
+ public void testMethodTypeLoaded() throws Exception {
+ Object loadedMethodType = JavaType.METHOD_TYPE.load().getDeclaredMethod("methodType", Class.class, Class[].class)
+ .invoke(null, void.class, new Class<?>[]{Object.class});
+ assertThat(JavaConstant.MethodType.ofLoaded(new ByteBuddy()
+ .subclass(Foo.class)
+ .method(named(FOO))
+ .intercept(MethodDelegation.withDefaultConfiguration()
+ .withBinders(TargetMethodAnnotationDrivenBinder.ParameterBinder.ForFixedValue.OfConstant.of(Bar.class, loadedMethodType))
+ .to(Foo.class))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance()
+ .foo()), is(JavaConstant.MethodType.ofLoaded(loadedMethodType)));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(7)
+ public void testMethodType() throws Exception {
+ Object loadedMethodType = JavaType.METHOD_TYPE.load().getDeclaredMethod("methodType", Class.class, Class[].class)
+ .invoke(null, void.class, new Class<?>[]{Object.class});
+ assertThat(JavaConstant.MethodType.ofLoaded(new ByteBuddy()
+ .subclass(Foo.class)
+ .method(named(FOO))
+ .intercept(MethodDelegation.withDefaultConfiguration()
+ .withBinders(TargetMethodAnnotationDrivenBinder.ParameterBinder.ForFixedValue.OfConstant.of(Bar.class, JavaConstant.MethodType.ofLoaded(loadedMethodType)))
+ .to(Foo.class))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance()
+ .foo()), is(JavaConstant.MethodType.ofLoaded(loadedMethodType)));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIllegalArgument() throws Exception {
+ new ByteBuddy()
+ .subclass(Foo.class)
+ .method(named(FOO))
+ .intercept(MethodDelegation.withDefaultConfiguration()
+ .withBinders(TargetMethodAnnotationDrivenBinder.ParameterBinder.ForFixedValue.OfConstant.of(Bar.class, new Object()))
+ .to(Foo.class))
+ .make();
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ final Iterator<Class<?>> iterator = Arrays.<Class<?>>asList(Object.class, String.class, int.class, float.class).iterator();
+ ObjectPropertyAssertion.of(TargetMethodAnnotationDrivenBinder.ParameterBinder.ForFixedValue.OfConstant.class).create(new ObjectPropertyAssertion.Creator<Class<?>>() {
+ @Override
+ public Class<?> create() {
+ return iterator.next();
+ }
+ }).apply();
+ }
+
+ public static class Foo {
+
+ public static Object intercept(@Bar Object value) {
+ return value;
+ }
+
+ public Object foo() {
+ throw new AssertionError();
+ }
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface Bar {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/TargetMethodAnnotationDriverBinderParameterBinderForFixedValueOfConstantTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/TargetMethodAnnotationDriverBinderParameterBinderForFixedValueOfConstantTest.java
new file mode 100644
index 0000000..80e5303
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/TargetMethodAnnotationDriverBinderParameterBinderForFixedValueOfConstantTest.java
@@ -0,0 +1,79 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.MethodDelegation;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Collection;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class TargetMethodAnnotationDriverBinderParameterBinderForFixedValueOfConstantTest {
+
+ private static final String FOO = "foo";
+
+ private static final byte NUMERIC_VALUE = 42;
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {true},
+ {NUMERIC_VALUE},
+ {(short) NUMERIC_VALUE},
+ {(char) NUMERIC_VALUE},
+ {(int) NUMERIC_VALUE},
+ {(long) NUMERIC_VALUE},
+ {(float) NUMERIC_VALUE},
+ {(double) NUMERIC_VALUE},
+ {FOO},
+ {Object.class}
+ });
+ }
+
+ private final Object value;
+
+ public TargetMethodAnnotationDriverBinderParameterBinderForFixedValueOfConstantTest(Object value) {
+ this.value = value;
+ }
+
+ @Test
+ public void testConstant() throws Exception {
+ assertThat(new ByteBuddy()
+ .subclass(Foo.class)
+ .method(named(FOO))
+ .intercept(MethodDelegation.withDefaultConfiguration()
+ .withBinders(TargetMethodAnnotationDrivenBinder.ParameterBinder.ForFixedValue.OfConstant.of(Bar.class, value))
+ .to(Foo.class))
+ .make()
+ .load(Foo.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded()
+ .getDeclaredConstructor()
+ .newInstance()
+ .foo(), is(value));
+ }
+
+ public static class Foo {
+
+ public static Object intercept(@Bar Object value) {
+ return value;
+ }
+
+ public Object foo() {
+ throw new AssertionError();
+ }
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface Bar {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/ThisBinderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/ThisBinderTest.java
new file mode 100644
index 0000000..ba50a94
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/ThisBinderTest.java
@@ -0,0 +1,124 @@
+package net.bytebuddy.implementation.bind.annotation;
+
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.bind.MethodDelegationBinder;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class ThisBinderTest extends AbstractAnnotationBinderTest<This> {
+
+ @Mock
+ private TypeDescription.Generic parameterType, genericInstrumentedType;
+
+ public ThisBinderTest() {
+ super(This.class);
+ }
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ when(stackManipulation.isValid()).thenReturn(true);
+ when(instrumentedType.asGenericType()).thenReturn(genericInstrumentedType);
+ }
+
+ @Override
+ protected TargetMethodAnnotationDrivenBinder.ParameterBinder<This> getSimpleBinder() {
+ return This.Binder.INSTANCE;
+ }
+
+ @Test
+ public void testLegalBinding() throws Exception {
+ when(stackManipulation.isValid()).thenReturn(true);
+ when(target.getType()).thenReturn(parameterType);
+ when(target.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = This.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ assertThat(parameterBinding.isValid(), is(true));
+ verify(assigner).assign(genericInstrumentedType, parameterType, Assigner.Typing.STATIC);
+ verifyNoMoreInteractions(assigner);
+ verify(target, atLeast(1)).getType();
+ verify(target, never()).getDeclaredAnnotations();
+ }
+
+ @Test
+ public void testLegalBindingRuntimeType() throws Exception {
+ when(stackManipulation.isValid()).thenReturn(true);
+ when(target.getType()).thenReturn(parameterType);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = This.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.DYNAMIC);
+ assertThat(parameterBinding.isValid(), is(true));
+ verify(assigner).assign(genericInstrumentedType, parameterType, Assigner.Typing.DYNAMIC);
+ verifyNoMoreInteractions(assigner);
+ verify(target, atLeast(1)).getType();
+ verify(target, never()).getDeclaredAnnotations();
+ }
+
+ @Test
+ public void testIllegalBinding() throws Exception {
+ when(stackManipulation.isValid()).thenReturn(false);
+ when(target.getType()).thenReturn(parameterType);
+ when(target.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ when(assigner.assign(any(TypeDescription.Generic.class), any(TypeDescription.Generic.class), any(Assigner.Typing.class)))
+ .thenReturn(StackManipulation.Illegal.INSTANCE);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = This.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ assertThat(parameterBinding.isValid(), is(false));
+ verify(assigner).assign(genericInstrumentedType, parameterType, Assigner.Typing.STATIC);
+ verifyNoMoreInteractions(assigner);
+ verify(target, atLeast(1)).getType();
+ verify(target, never()).getDeclaredAnnotations();
+ }
+
+ @Test
+ public void testOptionalBinding() throws Exception {
+ when(stackManipulation.isValid()).thenReturn(true);
+ when(annotation.optional()).thenReturn(true);
+ when(source.isStatic()).thenReturn(true);
+ when(target.getType()).thenReturn(parameterType);
+ when(target.getDeclaredAnnotations()).thenReturn(new AnnotationList.Empty());
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = This.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ assertThat(parameterBinding.isValid(), is(true));
+ verify(annotation).optional();
+ verify(source, atLeast(1)).isStatic();
+ verifyZeroInteractions(assigner);
+ }
+
+ @Test
+ public void testStaticMethodIllegal() throws Exception {
+ when(target.getType()).thenReturn(parameterType);
+ when(source.isStatic()).thenReturn(true);
+ MethodDelegationBinder.ParameterBinding<?> parameterBinding = This.Binder.INSTANCE
+ .bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ assertThat(parameterBinding.isValid(), is(false));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testPrimitiveType() throws Exception {
+ when(parameterType.isPrimitive()).thenReturn(true);
+ when(target.getType()).thenReturn(parameterType);
+ This.Binder.INSTANCE.bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testArrayType() throws Exception {
+ when(parameterType.isArray()).thenReturn(true);
+ when(target.getType()).thenReturn(parameterType);
+ This.Binder.INSTANCE.bind(annotationDescription, source, target, implementationTarget, assigner, Assigner.Typing.STATIC);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(This.Binder.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/AdditionObjectPropertiesTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/AdditionObjectPropertiesTest.java
new file mode 100644
index 0000000..070d073
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/AdditionObjectPropertiesTest.java
@@ -0,0 +1,12 @@
+package net.bytebuddy.implementation.bytecode;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+public class AdditionObjectPropertiesTest {
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(Addition.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/AdditionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/AdditionTest.java
new file mode 100644
index 0000000..59bb1ed
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/AdditionTest.java
@@ -0,0 +1,64 @@
+package net.bytebuddy.implementation.bytecode;
+
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.test.utility.MockitoRule;
+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.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class AdditionTest {
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {Addition.INTEGER, StackSize.SINGLE, Opcodes.IADD},
+ {Addition.LONG, StackSize.DOUBLE, Opcodes.LADD},
+ {Addition.FLOAT, StackSize.SINGLE, Opcodes.FADD},
+ {Addition.DOUBLE, StackSize.DOUBLE, Opcodes.DADD},
+ });
+ }
+
+ private final StackManipulation stackManipulation;
+
+ private final StackSize stackSize;
+
+ private final int opcodes;
+
+ public AdditionTest(StackManipulation stackManipulation, StackSize stackSize, int opcodes) {
+ this.stackManipulation = stackManipulation;
+ this.stackSize = stackSize;
+ this.opcodes = opcodes;
+ }
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @Test
+ public void testAddition() throws Exception {
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getMaximalSize(), is(0));
+ assertThat(size.getSizeImpact(), is(-stackSize.getSize()));
+ verify(methodVisitor).visitInsn(opcodes);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ }
+}
\ No newline at end of file
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/ByteCodeAppenderCompoundTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/ByteCodeAppenderCompoundTest.java
new file mode 100644
index 0000000..9859bdf
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/ByteCodeAppenderCompoundTest.java
@@ -0,0 +1,67 @@
+package net.bytebuddy.implementation.bytecode;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.MethodVisitor;
+
+import java.util.Collections;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class ByteCodeAppenderCompoundTest {
+
+ private static final int MINIMUM = 3, MAXIMUM = 5;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private ByteCodeAppender first, second;
+
+ @Mock
+ private MethodDescription methodDescription;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ private ByteCodeAppender compound;
+
+ @Before
+ public void setUp() throws Exception {
+ compound = new ByteCodeAppender.Compound(first, second);
+ }
+
+ @Test
+ public void testApplication() throws Exception {
+ when(first.apply(methodVisitor, implementationContext, methodDescription)).thenReturn(new ByteCodeAppender.Size(MINIMUM, MAXIMUM));
+ when(second.apply(methodVisitor, implementationContext, methodDescription)).thenReturn(new ByteCodeAppender.Size(MAXIMUM, MINIMUM));
+ ByteCodeAppender.Size size = compound.apply(methodVisitor, implementationContext, methodDescription);
+ assertThat(size.getLocalVariableSize(), is(MAXIMUM));
+ assertThat(size.getOperandStackSize(), is(MAXIMUM));
+ verifyZeroInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ByteCodeAppender.Compound.class).create(new ObjectPropertyAssertion.Creator<List<?>>() {
+ @Override
+ public List<?> create() {
+ return Collections.singletonList(mock(ByteCodeAppender.class));
+ }
+ }).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/ByteCodeAppenderSimpleTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/ByteCodeAppenderSimpleTest.java
new file mode 100644
index 0000000..b3be0a8
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/ByteCodeAppenderSimpleTest.java
@@ -0,0 +1,69 @@
+package net.bytebuddy.implementation.bytecode;
+
+import net.bytebuddy.agent.builder.AgentBuilder;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.MethodVisitor;
+
+import java.util.Collections;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class ByteCodeAppenderSimpleTest {
+
+ private static final int STACK_SIZE = 42;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private StackManipulation first, second;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @Mock
+ private MethodDescription methodDescription;
+
+ @Before
+ public void setUp() throws Exception {
+ when(first.apply(methodVisitor, implementationContext)).thenReturn(new StackManipulation.Size(0, 0));
+ when(second.apply(methodVisitor, implementationContext)).thenReturn(new StackManipulation.Size(0, 0));
+ when(methodDescription.getStackSize()).thenReturn(STACK_SIZE);
+ }
+
+ @Test
+ public void testApplication() throws Exception {
+ ByteCodeAppender byteCodeAppender = new ByteCodeAppender.Simple(first, second);
+ ByteCodeAppender.Size size = byteCodeAppender.apply(methodVisitor, implementationContext, methodDescription);
+ assertThat(size.getLocalVariableSize(), is(STACK_SIZE));
+ assertThat(size.getOperandStackSize(), is(0));
+ verify(first).apply(methodVisitor, implementationContext);
+ verifyNoMoreInteractions(first);
+ verify(second).apply(methodVisitor, implementationContext);
+ verifyNoMoreInteractions(second);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ByteCodeAppender.Simple.class).create(new ObjectPropertyAssertion.Creator<List<?>>() {
+ @Override
+ public List<?> create() {
+ return Collections.singletonList(mock(StackManipulation.class));
+ }
+ }).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/ByteCodeAppenderSizeTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/ByteCodeAppenderSizeTest.java
new file mode 100644
index 0000000..64668cb
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/ByteCodeAppenderSizeTest.java
@@ -0,0 +1,28 @@
+package net.bytebuddy.implementation.bytecode;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class ByteCodeAppenderSizeTest {
+
+ private static final int LOWER = 3, BIGGER = 5;
+
+ @Test
+ public void testMerge() throws Exception {
+ ByteCodeAppender.Size left = new ByteCodeAppender.Size(LOWER, BIGGER);
+ ByteCodeAppender.Size right = new ByteCodeAppender.Size(BIGGER, LOWER);
+ ByteCodeAppender.Size mergedLeft = left.merge(right);
+ ByteCodeAppender.Size mergedRight = right.merge(left);
+ assertThat(mergedLeft, is(mergedRight));
+ assertThat(mergedLeft.getOperandStackSize(), is(BIGGER));
+ assertThat(mergedLeft.getLocalVariableSize(), is(BIGGER));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ByteCodeAppender.Size.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/DuplicationOtherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/DuplicationOtherTest.java
new file mode 100644
index 0000000..e17c5c9
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/DuplicationOtherTest.java
@@ -0,0 +1,36 @@
+package net.bytebuddy.implementation.bytecode;
+
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class DuplicationOtherTest {
+
+ @Test(expected = IllegalStateException.class)
+ public void testZeroFlip() throws Exception {
+ Duplication.ZERO.flipOver(mock(TypeDefinition.class));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSingleToZeroFlip() throws Exception {
+ TypeDefinition typeDefinition = mock(TypeDefinition.class);
+ when(typeDefinition.getStackSize()).thenReturn(StackSize.ZERO);
+ Duplication.SINGLE.flipOver(typeDefinition);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testDoubleToZeroFlip() throws Exception {
+ TypeDefinition typeDefinition = mock(TypeDefinition.class);
+ when(typeDefinition.getStackSize()).thenReturn(StackSize.ZERO);
+ Duplication.DOUBLE.flipOver(typeDefinition);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(Duplication.class).apply();
+ ObjectPropertyAssertion.of(Duplication.WithFlip.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/DuplicationTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/DuplicationTest.java
new file mode 100644
index 0000000..6d819c7
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/DuplicationTest.java
@@ -0,0 +1,80 @@
+package net.bytebuddy.implementation.bytecode;
+
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.After;
+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.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class DuplicationTest {
+
+ private final StackSize stackSize;
+
+ private final int opcode;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDefinition typeDefinition;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ public DuplicationTest(StackSize stackSize, int opcode) {
+ this.stackSize = stackSize;
+ this.opcode = opcode;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {StackSize.ZERO, Opcodes.NOP},
+ {StackSize.SINGLE, Opcodes.DUP},
+ {StackSize.DOUBLE, Opcodes.DUP2}
+ });
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(typeDefinition.getStackSize()).thenReturn(stackSize);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testDuplication() throws Exception {
+ StackManipulation stackManipulation = Duplication.of(typeDefinition);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(stackSize.getSize()));
+ assertThat(size.getMaximalSize(), is(stackSize.getSize()));
+ if (stackSize != StackSize.ZERO) {
+ verify(methodVisitor).visitInsn(opcode);
+ }
+ verifyNoMoreInteractions(methodVisitor);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/DuplicationWithFlipTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/DuplicationWithFlipTest.java
new file mode 100644
index 0000000..0ce07a9
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/DuplicationWithFlipTest.java
@@ -0,0 +1,75 @@
+package net.bytebuddy.implementation.bytecode;
+
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.test.utility.MockitoRule;
+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.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class DuplicationWithFlipTest {
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {StackSize.SINGLE, StackSize.SINGLE, Opcodes.DUP_X1},
+ {StackSize.SINGLE, StackSize.DOUBLE, Opcodes.DUP_X2},
+ {StackSize.DOUBLE, StackSize.SINGLE, Opcodes.DUP2_X1},
+ {StackSize.DOUBLE, StackSize.DOUBLE, Opcodes.DUP2_X2}
+ });
+ }
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDefinition top, second;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ private final StackSize topSize, secondSize;
+
+ private final int opcode;
+
+ public DuplicationWithFlipTest(StackSize topSize, StackSize secondSize, int opcode) {
+ this.topSize = topSize;
+ this.secondSize = secondSize;
+ this.opcode = opcode;
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(top.getStackSize()).thenReturn(topSize);
+ when(second.getStackSize()).thenReturn(secondSize);
+ }
+
+ @Test
+ public void testFlip() throws Exception {
+ StackManipulation stackManipulation = Duplication.of(top).flipOver(second);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getMaximalSize(), is(topSize.getSize()));
+ assertThat(size.getSizeImpact(), is(topSize.getSize()));
+ verify(methodVisitor).visitInsn(opcode);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/RemovalObjectPropertiesTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/RemovalObjectPropertiesTest.java
new file mode 100644
index 0000000..d011a36
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/RemovalObjectPropertiesTest.java
@@ -0,0 +1,12 @@
+package net.bytebuddy.implementation.bytecode;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+public class RemovalObjectPropertiesTest {
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(Removal.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/RemovalTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/RemovalTest.java
new file mode 100644
index 0000000..328a750
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/RemovalTest.java
@@ -0,0 +1,80 @@
+package net.bytebuddy.implementation.bytecode;
+
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.After;
+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.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class RemovalTest {
+
+ private final StackSize stackSize;
+
+ private final int opcode;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDefinition typeDefinition;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ public RemovalTest(StackSize stackSize, int opcode) {
+ this.stackSize = stackSize;
+ this.opcode = opcode;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {StackSize.ZERO, Opcodes.NOP},
+ {StackSize.SINGLE, Opcodes.POP},
+ {StackSize.DOUBLE, Opcodes.POP2}
+ });
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(typeDefinition.getStackSize()).thenReturn(stackSize);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testDuplication() throws Exception {
+ StackManipulation stackManipulation = Removal.of(typeDefinition);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(-stackSize.getSize()));
+ assertThat(size.getMaximalSize(), is(0));
+ if (stackSize != StackSize.ZERO) {
+ verify(methodVisitor).visitInsn(opcode);
+ }
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/StackManipulationCompoundTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/StackManipulationCompoundTest.java
new file mode 100644
index 0000000..3a7030c
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/StackManipulationCompoundTest.java
@@ -0,0 +1,75 @@
+package net.bytebuddy.implementation.bytecode;
+
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.MethodVisitor;
+
+import java.util.Collections;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class StackManipulationCompoundTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private StackManipulation first, second;
+
+ @Test
+ public void testIsValid() throws Exception {
+ when(first.isValid()).thenReturn(true);
+ when(second.isValid()).thenReturn(true);
+ assertThat(new StackManipulation.Compound(first, second).isValid(), is(true));
+ verify(first).isValid();
+ verifyNoMoreInteractions(first);
+ verify(second).isValid();
+ verifyNoMoreInteractions(second);
+ }
+
+ @Test
+ public void testIsInvalid() throws Exception {
+ when(first.isValid()).thenReturn(true);
+ when(second.isValid()).thenReturn(false);
+ assertThat(new StackManipulation.Compound(first, second).isValid(), is(false));
+ verify(first).isValid();
+ verifyNoMoreInteractions(first);
+ verify(second).isValid();
+ verifyNoMoreInteractions(second);
+ }
+
+ @Test
+ public void testApplication() throws Exception {
+ MethodVisitor methodVisitor = mock(MethodVisitor.class);
+ Implementation.Context implementationContext = mock(Implementation.Context.class);
+ when(first.apply(methodVisitor, implementationContext)).thenReturn(new StackManipulation.Size(2, 3));
+ when(second.apply(methodVisitor, implementationContext)).thenReturn(new StackManipulation.Size(2, 3));
+ StackManipulation.Size size = new StackManipulation.Compound(first, second).apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(4));
+ assertThat(size.getMaximalSize(), is(5));
+ verify(first).apply(methodVisitor, implementationContext);
+ verifyNoMoreInteractions(first);
+ verify(second).apply(methodVisitor, implementationContext);
+ verifyNoMoreInteractions(second);
+ verifyZeroInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(StackManipulation.Compound.class).create(new ObjectPropertyAssertion.Creator<List<?>>() {
+ @Override
+ public List<?> create() {
+ return Collections.singletonList(mock(StackManipulation.class));
+ }
+ }).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/StackManipulationSizeTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/StackManipulationSizeTest.java
new file mode 100644
index 0000000..47d84ce
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/StackManipulationSizeTest.java
@@ -0,0 +1,42 @@
+package net.bytebuddy.implementation.bytecode;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class StackManipulationSizeTest {
+
+ @Test
+ public void testSizeGrowth() throws Exception {
+ StackManipulation.Size first = new StackManipulation.Size(2, 5);
+ StackManipulation.Size second = new StackManipulation.Size(1, 1);
+ StackManipulation.Size merged = first.aggregate(second);
+ assertThat(merged.getSizeImpact(), is(3));
+ assertThat(merged.getMaximalSize(), is(5));
+ }
+
+ @Test
+ public void testSizeReduction() throws Exception {
+ StackManipulation.Size first = new StackManipulation.Size(-3, 0);
+ StackManipulation.Size second = new StackManipulation.Size(-2, 0);
+ StackManipulation.Size merged = first.aggregate(second);
+ assertThat(merged.getSizeImpact(), is(-5));
+ assertThat(merged.getMaximalSize(), is(0));
+ }
+
+ @Test
+ public void testSizeGrowthAndReduction() throws Exception {
+ StackManipulation.Size first = new StackManipulation.Size(3, 4);
+ StackManipulation.Size second = new StackManipulation.Size(-5, 1);
+ StackManipulation.Size merged = first.aggregate(second);
+ assertThat(merged.getSizeImpact(), is(-2));
+ assertThat(merged.getMaximalSize(), is(4));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(StackManipulation.Size.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/StackManipulationTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/StackManipulationTest.java
new file mode 100644
index 0000000..f7ddb87
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/StackManipulationTest.java
@@ -0,0 +1,61 @@
+package net.bytebuddy.implementation.bytecode;
+
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.MethodVisitor;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+public class StackManipulationTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @After
+ public void tearDown() throws Exception {
+ verifyZeroInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testLegalIsValid() throws Exception {
+ assertThat(StackManipulation.Trivial.INSTANCE.isValid(), is(true));
+ }
+
+ @Test
+ public void testIllegalIsNotValid() throws Exception {
+ assertThat(StackManipulation.Illegal.INSTANCE.isValid(), is(false));
+ }
+
+ @Test
+ public void testLegalIsApplicable() throws Exception {
+ StackManipulation.Size size = StackManipulation.Trivial.INSTANCE.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(0));
+ assertThat(size.getMaximalSize(), is(0));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIllegalIsNotApplicable() throws Exception {
+ StackManipulation.Illegal.INSTANCE.apply(methodVisitor, implementationContext);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(StackManipulation.Trivial.class).apply();
+ ObjectPropertyAssertion.of(StackManipulation.Illegal.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/StackSizeMaximumTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/StackSizeMaximumTest.java
new file mode 100644
index 0000000..41bdcb2
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/StackSizeMaximumTest.java
@@ -0,0 +1,43 @@
+package net.bytebuddy.implementation.bytecode;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class StackSizeMaximumTest {
+
+ private final StackSize first, second, expected;
+
+ public StackSizeMaximumTest(StackSize first, StackSize second, StackSize expected) {
+ this.first = first;
+ this.second = second;
+ this.expected = expected;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {StackSize.DOUBLE, StackSize.ZERO, StackSize.DOUBLE},
+ {StackSize.DOUBLE, StackSize.SINGLE, StackSize.DOUBLE},
+ {StackSize.DOUBLE, StackSize.DOUBLE, StackSize.DOUBLE},
+ {StackSize.SINGLE, StackSize.DOUBLE, StackSize.DOUBLE},
+ {StackSize.SINGLE, StackSize.SINGLE, StackSize.SINGLE},
+ {StackSize.SINGLE, StackSize.ZERO, StackSize.SINGLE},
+ {StackSize.ZERO, StackSize.DOUBLE, StackSize.DOUBLE},
+ {StackSize.ZERO, StackSize.SINGLE, StackSize.SINGLE},
+ {StackSize.ZERO, StackSize.ZERO, StackSize.ZERO},
+ });
+ }
+
+ @Test
+ public void testMaximum() throws Exception {
+ assertThat(first.maximum(second), is(expected));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/StackSizeObjectPropertiesTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/StackSizeObjectPropertiesTest.java
new file mode 100644
index 0000000..2330502
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/StackSizeObjectPropertiesTest.java
@@ -0,0 +1,12 @@
+package net.bytebuddy.implementation.bytecode;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+public class StackSizeObjectPropertiesTest {
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(StackSize.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/StackSizeTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/StackSizeTest.java
new file mode 100644
index 0000000..d58f5a2
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/StackSizeTest.java
@@ -0,0 +1,58 @@
+package net.bytebuddy.implementation.bytecode;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class StackSizeTest {
+
+ private final Class<?> type;
+
+ private final int size;
+
+ private final StackSize stackSize;
+
+ public StackSizeTest(Class<?> type, int size, StackSize stackSize) {
+ this.type = type;
+ this.size = size;
+ this.stackSize = stackSize;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {void.class, 0, StackSize.ZERO},
+ {boolean.class, 1, StackSize.SINGLE},
+ {byte.class, 1, StackSize.SINGLE},
+ {short.class, 1, StackSize.SINGLE},
+ {int.class, 1, StackSize.SINGLE},
+ {char.class, 1, StackSize.SINGLE},
+ {float.class, 1, StackSize.SINGLE},
+ {long.class, 2, StackSize.DOUBLE},
+ {double.class, 2, StackSize.DOUBLE},
+ {Object.class, 1, StackSize.SINGLE},
+ });
+ }
+
+ @Test
+ public void testStackSize() throws Exception {
+ assertThat(StackSize.of(type), is(stackSize));
+ }
+
+ @Test
+ public void testStackSizeValue() throws Exception {
+ assertThat(StackSize.of(type).getSize(), is(size));
+ }
+
+ @Test
+ public void testStackSizeResolution() throws Exception {
+ assertThat(StackSize.of(size), is(stackSize));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/ThrowTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/ThrowTest.java
new file mode 100644
index 0000000..8996180
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/ThrowTest.java
@@ -0,0 +1,47 @@
+package net.bytebuddy.implementation.bytecode;
+
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class ThrowTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @Test
+ public void testApplication() throws Exception {
+ StackManipulation.Size size = Throw.INSTANCE.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(-1));
+ assertThat(size.getMaximalSize(), is(0));
+ verify(methodVisitor).visitInsn(Opcodes.ATHROW);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testValidity() throws Exception {
+ assertThat(Throw.INSTANCE.isValid(), is(true));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(Throw.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/TypeCreationTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/TypeCreationTest.java
new file mode 100644
index 0000000..2251a65
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/TypeCreationTest.java
@@ -0,0 +1,74 @@
+package net.bytebuddy.implementation.bytecode;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class TypeCreationTest {
+
+ private static final String FOO = "foo";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @Before
+ public void setUp() throws Exception {
+ when(typeDescription.getInternalName()).thenReturn(FOO);
+ }
+
+ @Test
+ public void testTypeCreation() throws Exception {
+ StackManipulation stackManipulation = TypeCreation.of(typeDescription);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(1));
+ assertThat(size.getMaximalSize(), is(1));
+ verify(methodVisitor).visitTypeInsn(Opcodes.NEW, FOO);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testTypeCreationArray() throws Exception {
+ when(typeDescription.isArray()).thenReturn(true);
+ TypeCreation.of(typeDescription);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testTypeCreationPrimitive() throws Exception {
+ when(typeDescription.isPrimitive()).thenReturn(true);
+ TypeCreation.of(typeDescription);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testTypeCreationAbstract() throws Exception {
+ when(typeDescription.isAbstract()).thenReturn(true);
+ TypeCreation.of(typeDescription);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeCreation.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/AssignerEqualTypesOnlyObjectPropertiesTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/AssignerEqualTypesOnlyObjectPropertiesTest.java
new file mode 100644
index 0000000..c4e999b
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/AssignerEqualTypesOnlyObjectPropertiesTest.java
@@ -0,0 +1,12 @@
+package net.bytebuddy.implementation.bytecode.assign;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+public class AssignerEqualTypesOnlyObjectPropertiesTest {
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(Assigner.EqualTypesOnly.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/AssignerEqualTypesOnlyTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/AssignerEqualTypesOnlyTest.java
new file mode 100644
index 0000000..87b0aab
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/AssignerEqualTypesOnlyTest.java
@@ -0,0 +1,103 @@
+package net.bytebuddy.implementation.bytecode.assign;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.After;
+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.MethodVisitor;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class AssignerEqualTypesOnlyTest {
+
+ private final boolean dynamicallyTyped;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription.Generic first, second;
+
+ @Mock
+ private TypeDescription firstRaw, secondRaw;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ public AssignerEqualTypesOnlyTest(boolean dynamicallyTyped) {
+ this.dynamicallyTyped = dynamicallyTyped;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[]{false}, new Object[]{true});
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(first.asErasure()).thenReturn(firstRaw);
+ when(second.asErasure()).thenReturn(secondRaw);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ verifyZeroInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testAssignmentGenericEqual() throws Exception {
+ StackManipulation stackManipulation = Assigner.EqualTypesOnly.GENERIC.assign(first, first, Assigner.Typing.of(dynamicallyTyped));
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(0));
+ assertThat(size.getMaximalSize(), is(0));
+ verifyZeroInteractions(first);
+ }
+
+ @Test
+ public void testAssignmentGenericNotEqual() throws Exception {
+ StackManipulation stackManipulation = Assigner.EqualTypesOnly.GENERIC.assign(first, second, Assigner.Typing.of(dynamicallyTyped));
+ assertThat(stackManipulation.isValid(), is(false));
+ verifyZeroInteractions(first);
+ verifyZeroInteractions(second);
+ }
+
+ @Test
+ public void testAssignmentErausreEqual() throws Exception {
+ StackManipulation stackManipulation = Assigner.EqualTypesOnly.ERASURE.assign(first, first, Assigner.Typing.of(dynamicallyTyped));
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(0));
+ assertThat(size.getMaximalSize(), is(0));
+ verify(first, times(2)).asErasure();
+ verifyNoMoreInteractions(first);
+ }
+
+ @Test
+ public void testAssignmentErasureNotEqual() throws Exception {
+ StackManipulation stackManipulation = Assigner.EqualTypesOnly.ERASURE.assign(first, second, Assigner.Typing.of(dynamicallyTyped));
+ assertThat(stackManipulation.isValid(), is(false));
+ verify(first).asErasure();
+ verifyNoMoreInteractions(first);
+ verify(second).asErasure();
+ verifyNoMoreInteractions(second);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/AssignerRefusingObjectPropertiesTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/AssignerRefusingObjectPropertiesTest.java
new file mode 100644
index 0000000..a9a3820
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/AssignerRefusingObjectPropertiesTest.java
@@ -0,0 +1,12 @@
+package net.bytebuddy.implementation.bytecode.assign;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+public class AssignerRefusingObjectPropertiesTest {
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(Assigner.Refusing.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/AssignerRefusingTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/AssignerRefusingTest.java
new file mode 100644
index 0000000..c123390
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/AssignerRefusingTest.java
@@ -0,0 +1,66 @@
+package net.bytebuddy.implementation.bytecode.assign;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.After;
+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.MethodVisitor;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+ at RunWith(Parameterized.class)
+public class AssignerRefusingTest {
+
+ private final boolean dynamicallyTyped;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription.Generic first, second;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ public AssignerRefusingTest(boolean dynamicallyTyped) {
+ this.dynamicallyTyped = dynamicallyTyped;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[]{false}, new Object[]{true});
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ verifyZeroInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testAssignmentEqual() throws Exception {
+ StackManipulation stackManipulation = Assigner.Refusing.INSTANCE.assign(first, first, Assigner.Typing.of(dynamicallyTyped));
+ assertThat(stackManipulation.isValid(), is(false));
+ }
+
+ @Test
+ public void testAssignmentNotEqual() throws Exception {
+ StackManipulation stackManipulation = Assigner.Refusing.INSTANCE.assign(first, second, Assigner.Typing.of(dynamicallyTyped));
+ assertThat(stackManipulation.isValid(), is(false));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/AssignerTypingTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/AssignerTypingTest.java
new file mode 100644
index 0000000..a7586fa
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/AssignerTypingTest.java
@@ -0,0 +1,27 @@
+package net.bytebuddy.implementation.bytecode.assign;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class AssignerTypingTest {
+
+ @Test
+ public void testStatic() throws Exception {
+ assertThat(Assigner.Typing.of(false), is(Assigner.Typing.STATIC));
+ assertThat(Assigner.Typing.STATIC.isDynamic(), is(false));
+ }
+
+ @Test
+ public void testDynamic() throws Exception {
+ assertThat(Assigner.Typing.of(true), is(Assigner.Typing.DYNAMIC));
+ assertThat(Assigner.Typing.DYNAMIC.isDynamic(), is(true));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(Assigner.Typing.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/TypeCastingTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/TypeCastingTest.java
new file mode 100644
index 0000000..e832913
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/TypeCastingTest.java
@@ -0,0 +1,63 @@
+package net.bytebuddy.implementation.bytecode.assign;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Random;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class TypeCastingTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ @Rule
+ public TestRule mockitoTest = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @Test
+ public void testCasting() throws Exception {
+ when(typeDescription.getInternalName()).thenReturn(FOO);
+ StackManipulation.Size size = new TypeCasting(typeDescription).apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(0));
+ assertThat(size.getMaximalSize(), is(0));
+ verify(methodVisitor).visitTypeInsn(Opcodes.CHECKCAST, FOO);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testPrimitiveCastingThrowsException() throws Exception {
+ when(typeDescription.isPrimitive()).thenReturn(true);
+ TypeCasting.to(typeDescription);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeCasting.class).refine(new ObjectPropertyAssertion.Refinement<TypeDescription>() {
+ @Override
+ public void apply(TypeDescription mock) {
+ when((mock).getInternalName()).thenReturn(FOO + new Random().nextInt());
+ }
+ }).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveBoxingDelegateObjectPropertiesTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveBoxingDelegateObjectPropertiesTest.java
new file mode 100644
index 0000000..f2f74c7
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveBoxingDelegateObjectPropertiesTest.java
@@ -0,0 +1,12 @@
+package net.bytebuddy.implementation.bytecode.assign.primitive;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+public class PrimitiveBoxingDelegateObjectPropertiesTest {
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(PrimitiveBoxingDelegate.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveBoxingDelegateTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveBoxingDelegateTest.java
new file mode 100644
index 0000000..77ed3c5
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveBoxingDelegateTest.java
@@ -0,0 +1,125 @@
+package net.bytebuddy.implementation.bytecode.assign.primitive;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.After;
+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.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class PrimitiveBoxingDelegateTest {
+
+ private static final String VALUE_OF = "valueOf";
+
+ private final Class<?> primitiveType;
+
+ private final TypeDescription primitiveTypeDescription;
+
+ private final TypeDescription referenceTypeDescription;
+
+ private final String boxingMethodDescriptor;
+
+ private final int sizeChange;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription.Generic targetType;
+
+ @Mock
+ private Assigner chainedAssigner;
+
+ @Mock
+ private StackManipulation stackManipulation;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ public PrimitiveBoxingDelegateTest(Class<?> primitiveType,
+ Class<?> referenceType,
+ String boxingMethodDescriptor,
+ int sizeChange) {
+ this.primitiveType = primitiveType;
+ primitiveTypeDescription = mock(TypeDescription.class);
+ when(primitiveTypeDescription.represents(primitiveType)).thenReturn(true);
+ referenceTypeDescription = new TypeDescription.ForLoadedType(referenceType);
+ this.boxingMethodDescriptor = boxingMethodDescriptor;
+ this.sizeChange = sizeChange;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {boolean.class, Boolean.class, "(Z)Ljava/lang/Boolean;", 0},
+ {byte.class, Byte.class, "(B)Ljava/lang/Byte;", 0},
+ {short.class, Short.class, "(S)Ljava/lang/Short;", 0},
+ {char.class, Character.class, "(C)Ljava/lang/Character;", 0},
+ {int.class, Integer.class, "(I)Ljava/lang/Integer;", 0},
+ {long.class, Long.class, "(J)Ljava/lang/Long;", -1},
+ {float.class, Float.class, "(F)Ljava/lang/Float;", 0},
+ {double.class, Double.class, "(D)Ljava/lang/Double;", -1},
+ });
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(chainedAssigner.assign(any(TypeDescription.Generic.class), any(TypeDescription.Generic.class), any(Assigner.Typing.class)))
+ .thenReturn(stackManipulation);
+ when(stackManipulation.isValid())
+ .thenReturn(true);
+ when(stackManipulation.apply(any(MethodVisitor.class), any(Implementation.Context.class)))
+ .thenReturn(StackSize.ZERO.toIncreasingSize());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ verifyZeroInteractions(targetType);
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testBoxing() throws Exception {
+ StackManipulation boxingStackManipulation = PrimitiveBoxingDelegate.forPrimitive(primitiveTypeDescription)
+ .assignBoxedTo(targetType, chainedAssigner, Assigner.Typing.STATIC);
+ assertThat(boxingStackManipulation.isValid(), is(true));
+ StackManipulation.Size size = boxingStackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(sizeChange));
+ assertThat(size.getMaximalSize(), is(0));
+ verify(primitiveTypeDescription).represents(primitiveType);
+ verify(primitiveTypeDescription, atLeast(1)).represents(any(Class.class));
+ verifyNoMoreInteractions(primitiveTypeDescription);
+ verify(chainedAssigner).assign(referenceTypeDescription.asGenericType(), targetType, Assigner.Typing.STATIC);
+ verifyNoMoreInteractions(chainedAssigner);
+ verify(methodVisitor).visitMethodInsn(Opcodes.INVOKESTATIC,
+ referenceTypeDescription.getInternalName(),
+ VALUE_OF,
+ boxingMethodDescriptor,
+ false);
+ verifyNoMoreInteractions(methodVisitor);
+ verify(stackManipulation, atLeast(1)).isValid();
+ verify(stackManipulation).apply(methodVisitor, implementationContext);
+ verifyNoMoreInteractions(stackManipulation);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveBoxingDelegateTestWithReferenceTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveBoxingDelegateTestWithReferenceTest.java
new file mode 100644
index 0000000..9861192
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveBoxingDelegateTestWithReferenceTest.java
@@ -0,0 +1,12 @@
+package net.bytebuddy.implementation.bytecode.assign.primitive;
+
+import net.bytebuddy.description.type.TypeDescription;
+import org.junit.Test;
+
+public class PrimitiveBoxingDelegateTestWithReferenceTest {
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testThrowsException() throws Exception {
+ PrimitiveBoxingDelegate.forPrimitive(TypeDescription.OBJECT);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveTypeAwareAssignerBoxingTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveTypeAwareAssignerBoxingTest.java
new file mode 100644
index 0000000..562476f
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveTypeAwareAssignerBoxingTest.java
@@ -0,0 +1,94 @@
+package net.bytebuddy.implementation.bytecode.assign.primitive;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.test.utility.MockitoRule;
+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 java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class PrimitiveTypeAwareAssignerBoxingTest {
+
+ private final Class<?> sourceType;
+
+ private final Class<?> targetType;
+
+ private final boolean assignable;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription.Generic sourceTypeDescription, targetTypeDescription;
+
+ @Mock
+ private Assigner chainedAssigner;
+
+ @Mock
+ private StackManipulation chainedStackManipulation;
+
+ private Assigner primitiveAssigner;
+
+ public PrimitiveTypeAwareAssignerBoxingTest(Class<?> sourceType,
+ Class<?> targetType,
+ boolean assignable) {
+ this.sourceType = sourceType;
+ this.targetType = targetType;
+ this.assignable = assignable;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {boolean.class, Boolean.class, true},
+ {byte.class, Byte.class, true},
+ {short.class, Short.class, true},
+ {char.class, Character.class, true},
+ {int.class, Integer.class, true},
+ {long.class, Long.class, true},
+ {float.class, Float.class, true},
+ {double.class, Double.class, true}
+ });
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(sourceTypeDescription.represents(sourceType)).thenReturn(true);
+ when(sourceTypeDescription.isPrimitive()).thenReturn(true);
+ when(targetTypeDescription.represents(targetType)).thenReturn(true);
+ when(targetTypeDescription.isPrimitive()).thenReturn(false);
+ when(chainedStackManipulation.isValid()).thenReturn(true);
+ when(chainedAssigner.assign(any(TypeDescription.Generic.class), any(TypeDescription.Generic.class), any(Assigner.Typing.class)))
+ .thenReturn(chainedStackManipulation);
+ primitiveAssigner = new PrimitiveTypeAwareAssigner(chainedAssigner);
+ }
+
+ @Test
+ public void testBoxingAssignment() {
+ StackManipulation stackManipulation = primitiveAssigner.assign(sourceTypeDescription, targetTypeDescription, Assigner.Typing.STATIC);
+ assertThat(stackManipulation.isValid(), is(assignable));
+ verify(chainedStackManipulation).isValid();
+ verifyNoMoreInteractions(chainedStackManipulation);
+ verify(sourceTypeDescription, atLeast(0)).represents(any(Class.class));
+ verify(sourceTypeDescription).represents(sourceType);
+ verify(sourceTypeDescription, atLeast(1)).isPrimitive();
+ verifyNoMoreInteractions(sourceTypeDescription);
+ verify(targetTypeDescription, atLeast(1)).isPrimitive();
+ verifyNoMoreInteractions(targetTypeDescription);
+ verify(chainedAssigner).assign(new TypeDescription.Generic.OfNonGenericType.ForLoadedType(targetType), targetTypeDescription, Assigner.Typing.STATIC);
+ verifyNoMoreInteractions(chainedAssigner);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveTypeAwareAssignerImplicitUnboxingTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveTypeAwareAssignerImplicitUnboxingTest.java
new file mode 100644
index 0000000..8774236
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveTypeAwareAssignerImplicitUnboxingTest.java
@@ -0,0 +1,100 @@
+package net.bytebuddy.implementation.bytecode.assign.primitive;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.test.utility.MockitoRule;
+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 java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class PrimitiveTypeAwareAssignerImplicitUnboxingTest {
+
+ private final Class<?> sourceType;
+
+ private final Class<?> wrapperType;
+
+ private final Class<?> targetType;
+
+ private final boolean assignable;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription.Generic source, target;
+
+ @Mock
+ private Assigner chainedAssigner;
+
+ @Mock
+ private StackManipulation chainedStackManipulation;
+
+ private Assigner primitiveAssigner;
+
+ public PrimitiveTypeAwareAssignerImplicitUnboxingTest(Class<?> sourceType,
+ Class<?> wrapperType,
+ Class<?> targetType,
+ boolean assignable) {
+ this.sourceType = sourceType;
+ this.wrapperType = wrapperType;
+ this.targetType = targetType;
+ this.assignable = assignable;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {Object.class, Boolean.class, boolean.class, true},
+ {Object.class, Byte.class, byte.class, true},
+ {Object.class, Short.class, short.class, true},
+ {Object.class, Character.class, char.class, true},
+ {Object.class, Integer.class, int.class, true},
+ {Object.class, Long.class, long.class, true},
+ {Object.class, Float.class, float.class, true},
+ {Object.class, Double.class, double.class, true}
+ });
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(source.represents(sourceType)).thenReturn(true);
+ when(source.isPrimitive()).thenReturn(false);
+ when(source.asGenericType()).thenReturn(source);
+ when(target.represents(targetType)).thenReturn(true);
+ when(target.isPrimitive()).thenReturn(true);
+ when(chainedStackManipulation.isValid()).thenReturn(true);
+ when(chainedAssigner.assign(any(TypeDescription.Generic.class), any(TypeDescription.Generic.class), any(Assigner.Typing.class)))
+ .thenReturn(chainedStackManipulation);
+ primitiveAssigner = new PrimitiveTypeAwareAssigner(chainedAssigner);
+ }
+
+ @Test
+ public void testImplicitUnboxingAssignment() {
+ StackManipulation stackManipulation = primitiveAssigner.assign(source, target, Assigner.Typing.DYNAMIC);
+ assertThat(stackManipulation.isValid(), is(assignable));
+ verify(chainedStackManipulation).isValid();
+ verifyNoMoreInteractions(chainedStackManipulation);
+ verify(source, atLeast(0)).represents(any(Class.class));
+ verify(source, atLeast(1)).isPrimitive();
+ verify(source).asGenericType();
+ verifyNoMoreInteractions(source);
+ verify(target, atLeast(0)).represents(any(Class.class));
+ verify(target, atLeast(1)).isPrimitive();
+ verifyNoMoreInteractions(target);
+ verify(chainedAssigner).assign(source, new TypeDescription.Generic.OfNonGenericType.ForLoadedType(wrapperType), Assigner.Typing.DYNAMIC);
+ verifyNoMoreInteractions(chainedAssigner);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveTypeAwareAssignerObjectPropertiesTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveTypeAwareAssignerObjectPropertiesTest.java
new file mode 100644
index 0000000..7fde0a7
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveTypeAwareAssignerObjectPropertiesTest.java
@@ -0,0 +1,12 @@
+package net.bytebuddy.implementation.bytecode.assign.primitive;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+public class PrimitiveTypeAwareAssignerObjectPropertiesTest {
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(PrimitiveTypeAwareAssigner.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveTypeAwareAssignerPrimitiveTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveTypeAwareAssignerPrimitiveTest.java
new file mode 100644
index 0000000..4615e33
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveTypeAwareAssignerPrimitiveTest.java
@@ -0,0 +1,150 @@
+package net.bytebuddy.implementation.bytecode.assign.primitive;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.test.utility.MockitoRule;
+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 java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class PrimitiveTypeAwareAssignerPrimitiveTest {
+
+ private final Class<?> sourceType;
+
+ private final Class<?> targetType;
+
+ private final boolean assignable;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription.Generic sourceTypeDescription, targetTypeDescription;
+
+ @Mock
+ private Assigner chainedAssigner;
+
+ private Assigner primitiveAssigner;
+
+ public PrimitiveTypeAwareAssignerPrimitiveTest(Class<?> sourceType,
+ Class<?> targetType,
+ boolean assignable) {
+ this.sourceType = sourceType;
+ this.targetType = targetType;
+ this.assignable = assignable;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {boolean.class, boolean.class, true},
+ {boolean.class, byte.class, false},
+ {boolean.class, short.class, false},
+ {boolean.class, char.class, false},
+ {boolean.class, int.class, false},
+ {boolean.class, long.class, false},
+ {boolean.class, float.class, false},
+ {boolean.class, double.class, false},
+
+ {byte.class, boolean.class, false},
+ {byte.class, byte.class, true},
+ {byte.class, short.class, true},
+ {byte.class, char.class, false},
+ {byte.class, int.class, true},
+ {byte.class, long.class, true},
+ {byte.class, float.class, true},
+ {byte.class, double.class, true},
+
+ {short.class, boolean.class, false},
+ {short.class, byte.class, false},
+ {short.class, short.class, true},
+ {short.class, char.class, false},
+ {short.class, int.class, true},
+ {short.class, long.class, true},
+ {short.class, float.class, true},
+ {short.class, double.class, true},
+
+ {char.class, boolean.class, false},
+ {char.class, byte.class, false},
+ {char.class, short.class, false},
+ {char.class, char.class, true},
+ {char.class, int.class, true},
+ {char.class, long.class, true},
+ {char.class, float.class, true},
+ {char.class, double.class, true},
+
+ {int.class, boolean.class, false},
+ {int.class, byte.class, false},
+ {int.class, short.class, false},
+ {int.class, char.class, false},
+ {int.class, int.class, true},
+ {int.class, long.class, true},
+ {int.class, float.class, true},
+ {int.class, double.class, true},
+
+ {long.class, boolean.class, false},
+ {long.class, byte.class, false},
+ {long.class, short.class, false},
+ {long.class, char.class, false},
+ {long.class, int.class, false},
+ {long.class, long.class, true},
+ {long.class, float.class, true},
+ {long.class, double.class, true},
+
+ {float.class, boolean.class, false},
+ {float.class, byte.class, false},
+ {float.class, short.class, false},
+ {float.class, char.class, false},
+ {float.class, int.class, false},
+ {float.class, long.class, false},
+ {float.class, float.class, true},
+ {float.class, double.class, true},
+
+ {double.class, boolean.class, false},
+ {double.class, byte.class, false},
+ {double.class, short.class, false},
+ {double.class, char.class, false},
+ {double.class, int.class, false},
+ {double.class, long.class, false},
+ {double.class, float.class, false},
+ {double.class, double.class, true},
+ });
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(sourceTypeDescription.represents(sourceType)).thenReturn(true);
+ when(sourceTypeDescription.isPrimitive()).thenReturn(true);
+ when(targetTypeDescription.represents(targetType)).thenReturn(true);
+ when(targetTypeDescription.isPrimitive()).thenReturn(true);
+ primitiveAssigner = new PrimitiveTypeAwareAssigner(chainedAssigner);
+ }
+
+ @Test
+ public void testPrimitiveToPrimitiveAssignment() throws Exception {
+ StackManipulation stackManipulation = primitiveAssigner.assign(sourceTypeDescription, targetTypeDescription, Assigner.Typing.STATIC);
+ assertThat(stackManipulation.isValid(), is(assignable));
+ verify(sourceTypeDescription, atLeast(0)).represents(any(Class.class));
+ verify(sourceTypeDescription).represents(sourceType);
+ verify(sourceTypeDescription, atLeast(1)).isPrimitive();
+ verifyNoMoreInteractions(sourceTypeDescription);
+ verify(targetTypeDescription, atLeast(0)).represents(any(Class.class));
+ verify(targetTypeDescription).represents(targetType);
+ verify(targetTypeDescription, atLeast(1)).isPrimitive();
+ verifyNoMoreInteractions(targetTypeDescription);
+ verifyZeroInteractions(chainedAssigner);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveTypeAwareAssignerUnboxingTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveTypeAwareAssignerUnboxingTest.java
new file mode 100644
index 0000000..f14d9ad
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveTypeAwareAssignerUnboxingTest.java
@@ -0,0 +1,87 @@
+package net.bytebuddy.implementation.bytecode.assign.primitive;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.test.utility.MockitoRule;
+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 java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class PrimitiveTypeAwareAssignerUnboxingTest {
+
+ private final Class<?> sourceType;
+
+ private final Class<?> targetType;
+
+ private final boolean assignable;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription.Generic sourceTypeDescription, targetTypeDescription;
+
+ @Mock
+ private Assigner chainedAssigner;
+
+ private Assigner primitiveAssigner;
+
+ public PrimitiveTypeAwareAssignerUnboxingTest(Class<?> sourceType,
+ Class<?> targetType,
+ boolean assignable) {
+ this.sourceType = sourceType;
+ this.targetType = targetType;
+ this.assignable = assignable;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {Boolean.class, boolean.class, true},
+ {Byte.class, byte.class, true},
+ {Short.class, short.class, true},
+ {Character.class, char.class, true},
+ {Integer.class, int.class, true},
+ {Long.class, long.class, true},
+ {Float.class, float.class, true},
+ {Double.class, double.class, true}
+ });
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(sourceTypeDescription.represents(sourceType)).thenReturn(true);
+ when(sourceTypeDescription.isPrimitive()).thenReturn(false);
+ when(targetTypeDescription.represents(targetType)).thenReturn(true);
+ when(targetTypeDescription.isPrimitive()).thenReturn(true);
+ primitiveAssigner = new PrimitiveTypeAwareAssigner(chainedAssigner);
+ }
+
+ @Test
+ public void testUnboxingAssignment() throws Exception {
+ StackManipulation stackManipulation = primitiveAssigner.assign(sourceTypeDescription, targetTypeDescription, Assigner.Typing.STATIC);
+ assertThat(stackManipulation.isValid(), is(assignable));
+ verify(sourceTypeDescription, atLeast(0)).represents(any(Class.class));
+ verify(sourceTypeDescription).represents(sourceType);
+ verify(sourceTypeDescription, atLeast(1)).isPrimitive();
+ verifyNoMoreInteractions(sourceTypeDescription);
+ verify(targetTypeDescription, atLeast(0)).represents(any(Class.class));
+ verify(targetTypeDescription).represents(targetType);
+ verify(targetTypeDescription, atLeast(1)).isPrimitive();
+ verifyNoMoreInteractions(targetTypeDescription);
+ verifyZeroInteractions(chainedAssigner);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveUnboxingDelegateDirectTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveUnboxingDelegateDirectTest.java
new file mode 100644
index 0000000..af04a69
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveUnboxingDelegateDirectTest.java
@@ -0,0 +1,148 @@
+package net.bytebuddy.implementation.bytecode.assign.primitive;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.After;
+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.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class PrimitiveUnboxingDelegateDirectTest {
+
+ private final Class<?> primitiveType;
+
+ private final Class<?> wrapperType;
+
+ private final String unboxingMethodName;
+
+ private final String unboxingMethodDescriptor;
+
+ private final int sizeChange;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription.Generic primitiveTypeDescription, wrapperTypeDescription;
+
+ @Mock
+ private TypeDescription rawPrimitiveTypeDescription, rawWrapperTypeDescription;
+
+ @Mock
+ private Assigner chainedAssigner;
+
+ @Mock
+ private StackManipulation stackManipulation;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ public PrimitiveUnboxingDelegateDirectTest(Class<?> primitiveType,
+ Class<?> wrapperType,
+ String unboxingMethodName,
+ String unboxingMethodDescriptor,
+ int sizeChange) {
+ this.primitiveType = primitiveType;
+ this.wrapperType = wrapperType;
+ this.unboxingMethodName = unboxingMethodName;
+ this.unboxingMethodDescriptor = unboxingMethodDescriptor;
+ this.sizeChange = sizeChange;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {boolean.class, Boolean.class, "booleanValue", "()Z", 0},
+ {byte.class, Byte.class, "byteValue", "()B", 0},
+ {short.class, Short.class, "shortValue", "()S", 0},
+ {char.class, Character.class, "charValue", "()C", 0},
+ {int.class, Integer.class, "intValue", "()I", 0},
+ {long.class, Long.class, "longValue", "()J", 1},
+ {float.class, Float.class, "floatValue", "()F", 0},
+ {double.class, Double.class, "doubleValue", "()D", 1},
+ });
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(primitiveTypeDescription.isPrimitive()).thenReturn(true);
+ when(primitiveTypeDescription.represents(primitiveType)).thenReturn(true);
+ when(primitiveTypeDescription.asErasure()).thenReturn(rawPrimitiveTypeDescription);
+ when(rawPrimitiveTypeDescription.getInternalName()).thenReturn(Type.getInternalName(primitiveType));
+ when(wrapperTypeDescription.isPrimitive()).thenReturn(false);
+ when(wrapperTypeDescription.represents(wrapperType)).thenReturn(true);
+ when(wrapperTypeDescription.asErasure()).thenReturn(rawWrapperTypeDescription);
+ when(rawWrapperTypeDescription.getInternalName()).thenReturn(Type.getInternalName(wrapperType));
+ when(chainedAssigner.assign(any(TypeDescription.Generic.class), any(TypeDescription.Generic.class), any(Assigner.Typing.class))).thenReturn(stackManipulation);
+ when(stackManipulation.isValid()).thenReturn(true);
+ when(stackManipulation.apply(any(MethodVisitor.class), any(Implementation.Context.class))).thenReturn(StackSize.ZERO.toIncreasingSize());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testTrivialBoxing() throws Exception {
+ StackManipulation stackManipulation = PrimitiveUnboxingDelegate.forReferenceType(wrapperTypeDescription)
+ .assignUnboxedTo(primitiveTypeDescription, chainedAssigner, Assigner.Typing.STATIC);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(sizeChange));
+ assertThat(size.getMaximalSize(), is(sizeChange));
+ verify(methodVisitor).visitMethodInsn(Opcodes.INVOKEVIRTUAL,
+ Type.getInternalName(wrapperType),
+ unboxingMethodName,
+ unboxingMethodDescriptor,
+ false);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(chainedAssigner);
+ verifyZeroInteractions(this.stackManipulation);
+ }
+
+ @Test
+ public void testImplicitBoxing() throws Exception {
+ TypeDescription.Generic referenceTypeDescription = mock(TypeDescription.Generic.class);
+ when(referenceTypeDescription.asGenericType()).thenReturn(referenceTypeDescription);
+ StackManipulation primitiveStackManipulation = PrimitiveUnboxingDelegate.forReferenceType(referenceTypeDescription)
+ .assignUnboxedTo(primitiveTypeDescription, chainedAssigner, Assigner.Typing.DYNAMIC);
+ assertThat(primitiveStackManipulation.isValid(), is(true));
+ StackManipulation.Size size = primitiveStackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(sizeChange));
+ assertThat(size.getMaximalSize(), is(sizeChange));
+ verify(methodVisitor).visitMethodInsn(Opcodes.INVOKEVIRTUAL,
+ Type.getInternalName(wrapperType),
+ unboxingMethodName,
+ unboxingMethodDescriptor,
+ false);
+ verifyNoMoreInteractions(methodVisitor);
+ verify(chainedAssigner).assign(referenceTypeDescription, new TypeDescription.Generic.OfNonGenericType.ForLoadedType(wrapperType), Assigner.Typing.DYNAMIC);
+ verifyNoMoreInteractions(chainedAssigner);
+ verify(stackManipulation, atLeast(1)).isValid();
+ verify(stackManipulation).apply(methodVisitor, implementationContext);
+ verifyNoMoreInteractions(stackManipulation);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveUnboxingDelegateOtherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveUnboxingDelegateOtherTest.java
new file mode 100644
index 0000000..aef0d1a
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveUnboxingDelegateOtherTest.java
@@ -0,0 +1,25 @@
+package net.bytebuddy.implementation.bytecode.assign.primitive;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+public class PrimitiveUnboxingDelegateOtherTest {
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testIllegalSourceTypeThrowsException() throws Exception {
+ PrimitiveUnboxingDelegate.forReferenceType(new TypeDescription.Generic.OfNonGenericType.ForLoadedType(int.class));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testVoidIllegal() throws Exception {
+ PrimitiveUnboxingDelegate.forPrimitive(TypeDescription.Generic.VOID);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(PrimitiveUnboxingDelegate.class).apply();
+ ObjectPropertyAssertion.of(PrimitiveUnboxingDelegate.ImplicitlyTypedUnboxingResponsible.class).apply();
+ ObjectPropertyAssertion.of(PrimitiveUnboxingDelegate.ExplicitlyTypedUnboxingResponsible.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveUnboxingDelegateWideningTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveUnboxingDelegateWideningTest.java
new file mode 100644
index 0000000..1ed57ce
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveUnboxingDelegateWideningTest.java
@@ -0,0 +1,119 @@
+package net.bytebuddy.implementation.bytecode.assign.primitive;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.After;
+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.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class PrimitiveUnboxingDelegateWideningTest {
+
+ private final Class<?> primitiveType;
+
+ private final Class<?> referenceType;
+
+ private final String unboxingMethodName;
+
+ private final String unboxingMethodDescriptor;
+
+ private final int wideningOpcode;
+
+ private final int sizeChange;
+
+ private final int interimMaximum;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription.Generic referenceTypeDescription, primitiveTypeDescription;
+
+ @Mock
+ private Assigner chainedAssigner;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ public PrimitiveUnboxingDelegateWideningTest(Class<?> referenceType,
+ Class<?> primitiveType,
+ String unboxingMethodName,
+ String unboxingMethodDescriptor,
+ int wideningOpcode,
+ int sizeChange,
+ int interimMaximum) {
+ this.primitiveType = primitiveType;
+ this.referenceType = referenceType;
+ this.unboxingMethodName = unboxingMethodName;
+ this.unboxingMethodDescriptor = unboxingMethodDescriptor;
+ this.wideningOpcode = wideningOpcode;
+ this.sizeChange = sizeChange;
+ this.interimMaximum = interimMaximum;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {Short.class, long.class, "shortValue", "()S", Opcodes.I2L, 1, 1},
+ {Short.class, float.class, "shortValue", "()S", Opcodes.I2F, 0, 0},
+ {Short.class, double.class, "shortValue", "()S", Opcodes.I2D, 1, 1},
+ {Integer.class, long.class, "intValue", "()I", Opcodes.I2L, 1, 1},
+ {Integer.class, float.class, "intValue", "()I", Opcodes.I2F, 0, 0},
+ {Integer.class, double.class, "intValue", "()I", Opcodes.I2D, 1, 1},
+ {Long.class, float.class, "longValue", "()J", Opcodes.L2F, 0, 1},
+ {Long.class, double.class, "longValue", "()J", Opcodes.L2D, 1, 1},
+ {Float.class, double.class, "floatValue", "()F", Opcodes.F2D, 1, 1},
+ });
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(referenceTypeDescription.represents(referenceType)).thenReturn(true);
+ when(primitiveTypeDescription.isPrimitive()).thenReturn(true);
+ when(primitiveTypeDescription.represents(primitiveType)).thenReturn(true);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ verifyZeroInteractions(chainedAssigner);
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testTrivialBoxing() throws Exception {
+ StackManipulation stackManipulation = PrimitiveUnboxingDelegate.forReferenceType(referenceTypeDescription)
+ .assignUnboxedTo(primitiveTypeDescription, chainedAssigner, Assigner.Typing.STATIC);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(sizeChange));
+ assertThat(size.getMaximalSize(), is(interimMaximum));
+ verify(methodVisitor).visitMethodInsn(Opcodes.INVOKEVIRTUAL,
+ Type.getInternalName(referenceType),
+ unboxingMethodName,
+ unboxingMethodDescriptor,
+ false);
+ verify(methodVisitor).visitInsn(wideningOpcode);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveWideningDelegateIllegalTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveWideningDelegateIllegalTest.java
new file mode 100644
index 0000000..f45d01c
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveWideningDelegateIllegalTest.java
@@ -0,0 +1,102 @@
+package net.bytebuddy.implementation.bytecode.assign.primitive;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.After;
+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.MethodVisitor;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class PrimitiveWideningDelegateIllegalTest {
+
+ private final TypeDescription sourceTypeDescription;
+
+ private final TypeDescription targetTypeDescription;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ public PrimitiveWideningDelegateIllegalTest(Class<?> sourceType, Class<?> targetType) {
+ sourceTypeDescription = mock(TypeDescription.class);
+ when(sourceTypeDescription.isPrimitive()).thenReturn(true);
+ when(sourceTypeDescription.represents(sourceType)).thenReturn(true);
+ targetTypeDescription = mock(TypeDescription.class);
+ when(targetTypeDescription.isPrimitive()).thenReturn(true);
+ when(targetTypeDescription.represents(targetType)).thenReturn(true);
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {boolean.class, byte.class},
+ {boolean.class, short.class},
+ {boolean.class, char.class},
+ {boolean.class, int.class},
+ {boolean.class, long.class},
+ {boolean.class, float.class},
+ {boolean.class, double.class},
+ {byte.class, boolean.class},
+ {byte.class, char.class},
+ {short.class, boolean.class},
+ {short.class, byte.class},
+ {short.class, char.class},
+ {char.class, boolean.class},
+ {char.class, byte.class},
+ {char.class, short.class},
+ {int.class, boolean.class},
+ {int.class, byte.class},
+ {int.class, short.class},
+ {long.class, boolean.class},
+ {long.class, byte.class},
+ {long.class, short.class},
+ {long.class, char.class},
+ {long.class, int.class},
+ {float.class, boolean.class},
+ {float.class, byte.class},
+ {float.class, short.class},
+ {float.class, char.class},
+ {float.class, int.class},
+ {float.class, long.class},
+ {double.class, boolean.class},
+ {double.class, byte.class},
+ {double.class, short.class},
+ {double.class, char.class},
+ {double.class, int.class},
+ {double.class, long.class},
+ {double.class, float.class},
+ });
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ verifyZeroInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIllegalBoolean() throws Exception {
+ StackManipulation stackManipulation = PrimitiveWideningDelegate.forPrimitive(sourceTypeDescription).widenTo(targetTypeDescription);
+ assertThat(stackManipulation.isValid(), is(false));
+ stackManipulation.apply(methodVisitor, implementationContext);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveWideningDelegateNontrivialTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveWideningDelegateNontrivialTest.java
new file mode 100644
index 0000000..c4fc304
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveWideningDelegateNontrivialTest.java
@@ -0,0 +1,100 @@
+package net.bytebuddy.implementation.bytecode.assign.primitive;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.After;
+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.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class PrimitiveWideningDelegateNontrivialTest {
+
+ private final Class<?> sourceType;
+
+ private final Class<?> targetType;
+
+ private final int sizeChange;
+
+ private final int opcode;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription sourceTypeDescription, targetTypeDescription;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ public PrimitiveWideningDelegateNontrivialTest(Class<?> sourceType,
+ Class<?> targetType,
+ int sizeChange,
+ int opcode) {
+ this.sourceType = sourceType;
+ this.targetType = targetType;
+ this.sizeChange = sizeChange;
+ this.opcode = opcode;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {byte.class, long.class, 1, Opcodes.I2L},
+ {byte.class, float.class, 0, Opcodes.I2F},
+ {byte.class, double.class, 1, Opcodes.I2L},
+ {short.class, long.class, 1, Opcodes.I2L},
+ {short.class, float.class, 0, Opcodes.I2F},
+ {short.class, double.class, 1, Opcodes.I2D},
+ {char.class, long.class, 1, Opcodes.I2L},
+ {char.class, float.class, 0, Opcodes.I2F},
+ {char.class, double.class, 1, Opcodes.I2D},
+ {int.class, long.class, 1, Opcodes.I2L},
+ {int.class, float.class, 0, Opcodes.I2F},
+ {int.class, double.class, 1, Opcodes.I2D},
+ {long.class, float.class, -1, Opcodes.L2F},
+ {long.class, double.class, 0, Opcodes.L2D},
+ {float.class, double.class, 1, Opcodes.F2D}
+ });
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(sourceTypeDescription.represents(sourceType)).thenReturn(true);
+ when(targetTypeDescription.represents(targetType)).thenReturn(true);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testWideningConversion() throws Exception {
+ StackManipulation stackManipulation = PrimitiveWideningDelegate.forPrimitive(sourceTypeDescription).widenTo(targetTypeDescription);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(sizeChange));
+ assertThat(size.getMaximalSize(), is(Math.max(0, sizeChange)));
+ verify(methodVisitor).visitInsn(opcode);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveWideningDelegateOtherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveWideningDelegateOtherTest.java
new file mode 100644
index 0000000..4de5a33
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveWideningDelegateOtherTest.java
@@ -0,0 +1,24 @@
+package net.bytebuddy.implementation.bytecode.assign.primitive;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+public class PrimitiveWideningDelegateOtherTest {
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testIllegalSourceTypeThrowsException() throws Exception {
+ PrimitiveWideningDelegate.forPrimitive(TypeDescription.OBJECT);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testIllegalTargetTypeThrowsException() throws Exception {
+ PrimitiveWideningDelegate.forPrimitive(new TypeDescription.ForLoadedType(int.class)).widenTo(TypeDescription.OBJECT);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(PrimitiveWideningDelegate.class).apply();
+ ObjectPropertyAssertion.of(PrimitiveWideningDelegate.WideningStackManipulation.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveWideningDelegateTrivialTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveWideningDelegateTrivialTest.java
new file mode 100644
index 0000000..ebabc25
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/PrimitiveWideningDelegateTrivialTest.java
@@ -0,0 +1,88 @@
+package net.bytebuddy.implementation.bytecode.assign.primitive;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.After;
+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.MethodVisitor;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class PrimitiveWideningDelegateTrivialTest {
+
+ private final Class<?> sourceType;
+
+ private final Class<?> targetType;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription sourceTypeDescription, targetTypeDescription;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ public PrimitiveWideningDelegateTrivialTest(Class<?> sourceType, Class<?> targetType) {
+ this.sourceType = sourceType;
+ this.targetType = targetType;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {boolean.class, boolean.class},
+ {byte.class, byte.class},
+ {byte.class, short.class},
+ {byte.class, int.class},
+ {short.class, short.class},
+ {short.class, int.class},
+ {char.class, int.class},
+ {char.class, char.class},
+ {int.class, int.class},
+ {long.class, long.class},
+ {float.class, float.class},
+ {double.class, double.class}
+ });
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(sourceTypeDescription.represents(sourceType)).thenReturn(true);
+ when(targetTypeDescription.represents(targetType)).thenReturn(true);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ verifyZeroInteractions(implementationContext);
+ verifyZeroInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testNoOpAssignment() throws Exception {
+ StackManipulation stackManipulation = PrimitiveWideningDelegate.forPrimitive(sourceTypeDescription).widenTo(targetTypeDescription);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(0));
+ assertThat(size.getMaximalSize(), is(0));
+ verify(sourceTypeDescription, atLeast(1)).represents(sourceType);
+ verify(targetTypeDescription, atLeast(1)).represents(sourceType);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/VoidAwareAssignerNonVoidToVoidTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/VoidAwareAssignerNonVoidToVoidTest.java
new file mode 100644
index 0000000..270e7d7
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/VoidAwareAssignerNonVoidToVoidTest.java
@@ -0,0 +1,111 @@
+package net.bytebuddy.implementation.bytecode.assign.primitive;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.After;
+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.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class VoidAwareAssignerNonVoidToVoidTest {
+
+ private final Class<?> sourceType;
+
+ private final int opcode;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription.Generic source, target;
+
+ @Mock
+ private TypeDescription rawSource;
+
+ @Mock
+ private Assigner chainedAssigner;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ public VoidAwareAssignerNonVoidToVoidTest(Class<?> sourceType, int opcode) {
+ this.sourceType = sourceType;
+ this.opcode = opcode;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {byte.class, Opcodes.POP},
+ {short.class, Opcodes.POP},
+ {char.class, Opcodes.POP},
+ {int.class, Opcodes.POP},
+ {long.class, Opcodes.POP2},
+ {float.class, Opcodes.POP},
+ {double.class, Opcodes.POP2},
+ {Object.class, Opcodes.POP}
+ });
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(source.represents(sourceType)).thenReturn(true);
+ when(source.getStackSize()).thenReturn(StackSize.of(sourceType));
+ when(source.asErasure()).thenReturn(rawSource);
+ when(rawSource.represents(sourceType)).thenReturn(true);
+ when(rawSource.asErasure()).thenReturn(rawSource);
+ if (sourceType.isPrimitive()) {
+ when(source.isPrimitive()).thenReturn(true);
+ }
+ when(target.represents(void.class)).thenReturn(true);
+ when(target.isPrimitive()).thenReturn(true);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ verifyZeroInteractions(chainedAssigner);
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testAssignDefaultValue() throws Exception {
+ testAssignDefaultValue(true);
+ }
+
+ @Test
+ public void testAssignNoDefaultValue() throws Exception {
+ testAssignDefaultValue(false);
+ }
+
+ private void testAssignDefaultValue(boolean dynamicallyTyped) throws Exception {
+ Assigner voidAwareAssigner = new VoidAwareAssigner(chainedAssigner);
+ StackManipulation stackManipulation = voidAwareAssigner.assign(source, target, Assigner.Typing.of(dynamicallyTyped));
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(-1 * StackSize.of(sourceType).getSize()));
+ assertThat(size.getMaximalSize(), is(0));
+ verify(methodVisitor).visitInsn(opcode);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/VoidAwareAssignerTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/VoidAwareAssignerTest.java
new file mode 100644
index 0000000..a1f5a22
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/VoidAwareAssignerTest.java
@@ -0,0 +1,77 @@
+package net.bytebuddy.implementation.bytecode.assign.primitive;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.MethodVisitor;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class VoidAwareAssignerTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription.Generic sourceTypeDescription, targetTypeDescription;
+
+ @Mock
+ private Assigner chainedAssigner;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @After
+ public void tearDown() throws Exception {
+ verifyZeroInteractions(implementationContext);
+ verifyZeroInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testAssignVoidToVoid() throws Exception {
+ when(sourceTypeDescription.represents(void.class)).thenReturn(true);
+ when(targetTypeDescription.represents(void.class)).thenReturn(true);
+ Assigner voidAwareAssigner = new VoidAwareAssigner(chainedAssigner);
+ StackManipulation stackManipulation = voidAwareAssigner.assign(sourceTypeDescription, targetTypeDescription, Assigner.Typing.STATIC);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(0));
+ assertThat(size.getMaximalSize(), is(0));
+ verifyZeroInteractions(chainedAssigner);
+ }
+
+ @Test
+ public void testAssignNonVoidToNonVoid() throws Exception {
+ Assigner voidAwareAssigner = new VoidAwareAssigner(chainedAssigner);
+ StackManipulation chainedStackManipulation = mock(StackManipulation.class);
+ when(chainedAssigner.assign(sourceTypeDescription, targetTypeDescription, Assigner.Typing.STATIC)).thenReturn(chainedStackManipulation);
+ StackManipulation stackManipulation = voidAwareAssigner.assign(sourceTypeDescription, targetTypeDescription, Assigner.Typing.STATIC);
+ assertThat(stackManipulation, is(chainedStackManipulation));
+ verify(chainedAssigner).assign(sourceTypeDescription, targetTypeDescription, Assigner.Typing.STATIC);
+ verifyNoMoreInteractions(chainedAssigner);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(VoidAwareAssigner.class).apply();
+ }
+
+ @Test
+ public void testValueRemoval() throws Exception {
+
+
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/VoidAwareAssignerVoidToNonVoidTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/VoidAwareAssignerVoidToNonVoidTest.java
new file mode 100644
index 0000000..9c3caf9
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/primitive/VoidAwareAssignerVoidToNonVoidTest.java
@@ -0,0 +1,103 @@
+package net.bytebuddy.implementation.bytecode.assign.primitive;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.After;
+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.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class VoidAwareAssignerVoidToNonVoidTest {
+
+ private final Class<?> targetType;
+
+ private final int opcode;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription.Generic source, target;
+
+ @Mock
+ private Assigner chainedAssigner;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ public VoidAwareAssignerVoidToNonVoidTest(Class<?> targetType, int opcode) {
+ this.targetType = targetType;
+ this.opcode = opcode;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {byte.class, Opcodes.ICONST_0},
+ {short.class, Opcodes.ICONST_0},
+ {char.class, Opcodes.ICONST_0},
+ {int.class, Opcodes.ICONST_0},
+ {long.class, Opcodes.LCONST_0},
+ {float.class, Opcodes.FCONST_0},
+ {double.class, Opcodes.DCONST_0},
+ {Object.class, Opcodes.ACONST_NULL}
+ });
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(source.represents(void.class)).thenReturn(true);
+ when(source.isPrimitive()).thenReturn(true);
+ when(target.represents(targetType)).thenReturn(true);
+ if (targetType.isPrimitive()) {
+ when(target.isPrimitive()).thenReturn(true);
+ }
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ verifyZeroInteractions(chainedAssigner);
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testAssignDefaultValue() throws Exception {
+ Assigner voidAwareAssigner = new VoidAwareAssigner(chainedAssigner);
+ StackManipulation stackManipulation = voidAwareAssigner.assign(source, target, Assigner.Typing.DYNAMIC);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(StackSize.of(targetType).getSize()));
+ assertThat(size.getMaximalSize(), is(StackSize.of(targetType).getSize()));
+ verify(methodVisitor).visitInsn(opcode);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAssignNoDefaultValue() throws Exception {
+ Assigner voidAwareAssigner = new VoidAwareAssigner(chainedAssigner);
+ StackManipulation stackManipulation = voidAwareAssigner.assign(source, target, Assigner.Typing.STATIC);
+ assertThat(stackManipulation.isValid(), is(false));
+ stackManipulation.apply(methodVisitor, implementationContext);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/reference/ReferenceTypeAwareAssignerTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/reference/ReferenceTypeAwareAssignerTest.java
new file mode 100644
index 0000000..e91ef3c
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/assign/reference/ReferenceTypeAwareAssignerTest.java
@@ -0,0 +1,130 @@
+package net.bytebuddy.implementation.bytecode.assign.reference;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+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 org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class ReferenceTypeAwareAssignerTest {
+
+ private static final String FOO = "foo";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private TypeDescription.Generic source, target;
+
+ @Mock
+ private TypeDescription rawSource, rawTarget;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @Before
+ public void setUp() throws Exception {
+ when(source.asErasure()).thenReturn(rawSource);
+ when(target.asErasure()).thenReturn(rawTarget);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testMutualAssignable() throws Exception {
+ defineAssignability(true, true);
+ StackManipulation stackManipulation = ReferenceTypeAwareAssigner.INSTANCE.assign(source, target, Assigner.Typing.STATIC);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(0));
+ assertThat(size.getMaximalSize(), is(0));
+ verifyZeroInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testSourceToTargetAssignable() throws Exception {
+ defineAssignability(true, false);
+ StackManipulation stackManipulation = ReferenceTypeAwareAssigner.INSTANCE.assign(source, target, Assigner.Typing.STATIC);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(0));
+ assertThat(size.getMaximalSize(), is(0));
+ verifyZeroInteractions(methodVisitor);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testTargetToSourceAssignable() throws Exception {
+ defineAssignability(false, true);
+ StackManipulation stackManipulation = ReferenceTypeAwareAssigner.INSTANCE.assign(source, target, Assigner.Typing.STATIC);
+ assertThat(stackManipulation.isValid(), is(false));
+ stackManipulation.apply(methodVisitor, implementationContext);
+ }
+
+ @Test
+ public void testTargetToSourceAssignableRuntimeType() throws Exception {
+ defineAssignability(false, false);
+ when(rawTarget.getInternalName()).thenReturn(FOO);
+ StackManipulation stackManipulation = ReferenceTypeAwareAssigner.INSTANCE.assign(source, target, Assigner.Typing.DYNAMIC);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(0));
+ assertThat(size.getMaximalSize(), is(0));
+ verify(methodVisitor).visitTypeInsn(Opcodes.CHECKCAST, FOO);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testPrimitiveAssignabilityWhenEqual() throws Exception {
+ TypeDescription.Generic primitiveType = new TypeDescription.Generic.OfNonGenericType.ForLoadedType(int.class); // Note: cannot mock equals
+ StackManipulation stackManipulation = ReferenceTypeAwareAssigner.INSTANCE.assign(primitiveType, primitiveType, Assigner.Typing.DYNAMIC);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(0));
+ assertThat(size.getMaximalSize(), is(0));
+ verifyZeroInteractions(methodVisitor);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testPrimitiveAssignabilityWhenNotEqual() throws Exception {
+ TypeDescription.Generic primitiveType = new TypeDescription.Generic.OfNonGenericType.ForLoadedType(int.class); // Note: cannot mock equals
+ TypeDescription.Generic otherPrimitiveType = new TypeDescription.Generic.OfNonGenericType.ForLoadedType(long.class); // Note: cannot mock equals
+ StackManipulation stackManipulation = ReferenceTypeAwareAssigner.INSTANCE.assign(primitiveType, otherPrimitiveType, Assigner.Typing.DYNAMIC);
+ assertThat(stackManipulation.isValid(), is(false));
+ stackManipulation.apply(methodVisitor, implementationContext);
+ }
+
+ private void defineAssignability(boolean sourceToTarget, boolean targetToSource) {
+ if (sourceToTarget) {
+ when(rawSource.isAssignableTo(rawTarget)).thenReturn(true);
+ when(rawTarget.isAssignableFrom(rawSource)).thenReturn(true);
+ }
+ if (targetToSource) {
+ when(rawTarget.isAssignableTo(rawSource)).thenReturn(true);
+ when(rawSource.isAssignableFrom(rawTarget)).thenReturn(true);
+ }
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ReferenceTypeAwareAssigner.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/collection/AbstractArrayFactoryTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/collection/AbstractArrayFactoryTest.java
new file mode 100644
index 0000000..1ee4bee
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/collection/AbstractArrayFactoryTest.java
@@ -0,0 +1,82 @@
+package net.bytebuddy.implementation.bytecode.collection;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public abstract class AbstractArrayFactoryTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription.Generic componentType;
+
+ @Mock
+ private TypeDescription rawComponentType;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private StackManipulation stackManipulation;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @Before
+ public void setUp() throws Exception {
+ when(stackManipulation.isValid()).thenReturn(true);
+ when(componentType.asErasure()).thenReturn(rawComponentType);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ verifyZeroInteractions(implementationContext);
+ }
+
+ protected void testCreationUsing(Class<?> componentType, int storageOpcode) throws Exception {
+ defineComponentType(componentType);
+ CollectionFactory arrayFactory = ArrayFactory.forType(this.componentType);
+ StackManipulation arrayStackManipulation = arrayFactory.withValues(Collections.singletonList(stackManipulation));
+ assertThat(arrayStackManipulation.isValid(), is(true));
+ verify(stackManipulation, atLeast(1)).isValid();
+ StackManipulation.Size size = arrayStackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(1));
+ assertThat(size.getMaximalSize(), is(3 + StackSize.of(componentType).toIncreasingSize().getSizeImpact()));
+ verify(methodVisitor).visitInsn(Opcodes.ICONST_1);
+ verifyArrayCreation(methodVisitor);
+ verify(methodVisitor).visitInsn(Opcodes.DUP);
+ verify(methodVisitor).visitInsn(Opcodes.ICONST_0);
+ verify(stackManipulation).apply(methodVisitor, implementationContext);
+ verify(methodVisitor).visitInsn(storageOpcode);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyNoMoreInteractions(stackManipulation);
+ }
+
+ protected abstract void verifyArrayCreation(MethodVisitor methodVisitor);
+
+ private void defineComponentType(Class<?> type) {
+ when(componentType.isPrimitive()).thenReturn(type.isPrimitive());
+ when(componentType.represents(type)).thenReturn(true);
+ when(rawComponentType.getInternalName()).thenReturn(Type.getInternalName(type));
+ when(componentType.getStackSize()).thenReturn(StackSize.of(type));
+ when(stackManipulation.apply(any(MethodVisitor.class), any(Implementation.Context.class))).thenReturn(StackSize.of(type).toIncreasingSize());
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/collection/ArrayAccessOtherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/collection/ArrayAccessOtherTest.java
new file mode 100644
index 0000000..b2541d0
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/collection/ArrayAccessOtherTest.java
@@ -0,0 +1,39 @@
+package net.bytebuddy.implementation.bytecode.collection;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.bytecode.Duplication;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.constant.IntegerConstant;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class ArrayAccessOtherTest {
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testVoidThrowsException() throws Exception {
+ ArrayAccess.of(TypeDescription.VOID);
+ }
+
+ @Test
+ public void testForEach() throws Exception {
+ StackManipulation stackManipulation = mock(StackManipulation.class);
+ assertThat(ArrayAccess.REFERENCE.forEach(Collections.singletonList(stackManipulation)),
+ is((StackManipulation) new StackManipulation.Compound(new StackManipulation.Compound(Duplication.SINGLE,
+ IntegerConstant.forValue(0),
+ ArrayAccess.REFERENCE.new Loader(),
+ stackManipulation))));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ArrayAccess.class).apply();
+ ObjectPropertyAssertion.of(ArrayAccess.Loader.class).apply();
+ ObjectPropertyAssertion.of(ArrayAccess.Putter.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/collection/ArrayAccessTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/collection/ArrayAccessTest.java
new file mode 100644
index 0000000..21760c4
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/collection/ArrayAccessTest.java
@@ -0,0 +1,83 @@
+package net.bytebuddy.implementation.bytecode.collection;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.test.utility.MockitoRule;
+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.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class ArrayAccessTest {
+
+ private final TypeDescription typeDescription;
+
+ private final int loadOpcode, storeOpcode;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ public ArrayAccessTest(Class<?> type, int loadOpcode, int storeOpcode) {
+ typeDescription = new TypeDescription.ForLoadedType(type);
+ this.loadOpcode = loadOpcode;
+ this.storeOpcode = storeOpcode;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {boolean.class, Opcodes.BALOAD, Opcodes.BASTORE},
+ {byte.class, Opcodes.BALOAD, Opcodes.BASTORE},
+ {short.class, Opcodes.SALOAD, Opcodes.SASTORE},
+ {char.class, Opcodes.CALOAD, Opcodes.CASTORE},
+ {int.class, Opcodes.IALOAD, Opcodes.IASTORE},
+ {long.class, Opcodes.LALOAD, Opcodes.LASTORE},
+ {float.class, Opcodes.FALOAD, Opcodes.FASTORE},
+ {double.class, Opcodes.DALOAD, Opcodes.DASTORE},
+ {Object.class, Opcodes.AALOAD, Opcodes.AASTORE},
+ });
+ }
+
+ @Test
+ public void testLoad() throws Exception {
+ ArrayAccess arrayAccess = ArrayAccess.of(typeDescription);
+ assertThat(arrayAccess.load().isValid(), is(true));
+ StackManipulation.Size size = arrayAccess.load().apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(typeDescription.getStackSize().getSize() - 2));
+ assertThat(size.getMaximalSize(), is(typeDescription.getStackSize().getSize()));
+ verify(methodVisitor).visitInsn(loadOpcode);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testStore() throws Exception {
+ ArrayAccess arrayAccess = ArrayAccess.of(typeDescription);
+ assertThat(arrayAccess.store().isValid(), is(true));
+ StackManipulation.Size size = arrayAccess.store().apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(-(typeDescription.getStackSize().getSize() + 2)));
+ assertThat(size.getMaximalSize(), is(0));
+ verify(methodVisitor).visitInsn(storeOpcode);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/collection/ArrayFactoryObjectPropertiesTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/collection/ArrayFactoryObjectPropertiesTest.java
new file mode 100644
index 0000000..01728b8
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/collection/ArrayFactoryObjectPropertiesTest.java
@@ -0,0 +1,56 @@
+package net.bytebuddy.implementation.bytecode.collection;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class ArrayFactoryObjectPropertiesTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private StackManipulation stackManipulation;
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testVoidIsIllegal() throws Exception {
+ ArrayFactory.forType(TypeDescription.Generic.VOID);
+ }
+
+ @Test
+ public void testIllegalArrayStackManipulation() throws Exception {
+ assertThat(ArrayFactory.forType(TypeDescription.Generic.OBJECT)
+ .new ArrayStackManipulation(Collections.<StackManipulation>singletonList(StackManipulation.Illegal.INSTANCE))
+ .isValid(), is(false));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ArrayFactory.class).refine(new ObjectPropertyAssertion.Refinement<TypeDescription.Generic>() {
+ @Override
+ public void apply(TypeDescription.Generic mock) {
+ when(mock.getStackSize()).thenReturn(StackSize.ZERO);
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(ArrayFactory.ArrayCreator.ForPrimitiveType.class).apply();
+ ObjectPropertyAssertion.of(ArrayFactory.ArrayCreator.ForReferenceType.class).refine(new ObjectPropertyAssertion.Refinement<TypeDescription>() {
+ @Override
+ public void apply(TypeDescription mock) {
+ when(mock.getInternalName()).thenReturn("" + System.identityHashCode(mock));
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(ArrayFactory.ArrayStackManipulation.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/collection/ArrayFactoryPrimitiveTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/collection/ArrayFactoryPrimitiveTest.java
new file mode 100644
index 0000000..821e520
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/collection/ArrayFactoryPrimitiveTest.java
@@ -0,0 +1,52 @@
+package net.bytebuddy.implementation.bytecode.collection;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.mockito.Mockito.verify;
+
+ at RunWith(Parameterized.class)
+public class ArrayFactoryPrimitiveTest extends AbstractArrayFactoryTest {
+
+ private final Class<?> primitiveType;
+
+ private final int createOpcode;
+
+ private final int storeOpcode;
+
+ public ArrayFactoryPrimitiveTest(Class<?> primitiveType, int createOpcode, int storeOpcode) {
+ this.primitiveType = primitiveType;
+ this.createOpcode = createOpcode;
+ this.storeOpcode = storeOpcode;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {boolean.class, Opcodes.T_BOOLEAN, Opcodes.BASTORE},
+ {byte.class, Opcodes.T_BYTE, Opcodes.BASTORE},
+ {short.class, Opcodes.T_SHORT, Opcodes.SASTORE},
+ {char.class, Opcodes.T_CHAR, Opcodes.CASTORE},
+ {int.class, Opcodes.T_INT, Opcodes.IASTORE},
+ {long.class, Opcodes.T_LONG, Opcodes.LASTORE},
+ {float.class, Opcodes.T_FLOAT, Opcodes.FASTORE},
+ {double.class, Opcodes.T_DOUBLE, Opcodes.DASTORE},
+ });
+ }
+
+ @Test
+ public void testArrayCreation() throws Exception {
+ testCreationUsing(primitiveType, storeOpcode);
+ }
+
+ @Override
+ protected void verifyArrayCreation(MethodVisitor methodVisitor) {
+ verify(methodVisitor).visitIntInsn(Opcodes.NEWARRAY, createOpcode);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/collection/ArrayFactoryReferenceTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/collection/ArrayFactoryReferenceTest.java
new file mode 100644
index 0000000..d499e27
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/collection/ArrayFactoryReferenceTest.java
@@ -0,0 +1,45 @@
+package net.bytebuddy.implementation.bytecode.collection;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.mockito.Mockito.verify;
+
+ at RunWith(Parameterized.class)
+public class ArrayFactoryReferenceTest extends AbstractArrayFactoryTest {
+
+ private final Class<?> type;
+
+ private final String internalTypeName;
+
+ public ArrayFactoryReferenceTest(Class<?> type) {
+ this.type = type;
+ internalTypeName = Type.getInternalName(type);
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {Object.class},
+ {Object[].class},
+ {String.class},
+ });
+ }
+
+ @Test
+ public void testArrayCreation() throws Exception {
+ testCreationUsing(type, Opcodes.AASTORE);
+ }
+
+ @Override
+ protected void verifyArrayCreation(MethodVisitor methodVisitor) {
+ verify(methodVisitor).visitTypeInsn(Opcodes.ANEWARRAY, internalTypeName);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/ClassConstantPrimitiveTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/ClassConstantPrimitiveTest.java
new file mode 100644
index 0000000..fab3550
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/ClassConstantPrimitiveTest.java
@@ -0,0 +1,68 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.test.utility.MockitoRule;
+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.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class ClassConstantPrimitiveTest {
+
+ private final TypeDescription primitiveType, wrapperType;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ public ClassConstantPrimitiveTest(Class<?> primitiveType, Class<?> wrapperType) {
+ this.primitiveType = new TypeDescription.ForLoadedType(primitiveType);
+ this.wrapperType = new TypeDescription.ForLoadedType(wrapperType);
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {void.class, Void.class},
+ {boolean.class, Boolean.class},
+ {byte.class, Byte.class},
+ {short.class, Short.class},
+ {char.class, Character.class},
+ {int.class, Integer.class},
+ {long.class, Long.class},
+ {float.class, Float.class},
+ {double.class, Double.class}
+ });
+ }
+
+ @Test
+ public void testClassConstant() throws Exception {
+ StackManipulation stackManipulation = ClassConstant.of(primitiveType);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(1));
+ assertThat(size.getMaximalSize(), is(1));
+ verify(methodVisitor).visitFieldInsn(Opcodes.GETSTATIC, wrapperType.getInternalName(), "TYPE", "Ljava/lang/Class;");
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/ClassConstantReferenceTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/ClassConstantReferenceTest.java
new file mode 100644
index 0000000..6472ab8
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/ClassConstantReferenceTest.java
@@ -0,0 +1,115 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class ClassConstantReferenceTest {
+
+ private static final String FOO = "Lfoo;";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription typeDescription, instrumentedType;
+
+ @Mock
+ private ClassFileVersion classFileVersion;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @Before
+ public void setUp() throws Exception {
+ when(implementationContext.getInstrumentedType()).thenReturn(instrumentedType);
+ when(implementationContext.getClassFileVersion()).thenReturn(classFileVersion);
+ }
+
+ @Test
+ public void testClassConstantModernVisible() throws Exception {
+ when(typeDescription.isVisibleTo(instrumentedType)).thenReturn(true);
+ when(classFileVersion.isAtLeast(ClassFileVersion.JAVA_V5)).thenReturn(true);
+ when(typeDescription.getDescriptor()).thenReturn(FOO);
+ StackManipulation stackManipulation = ClassConstant.of(typeDescription);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(1));
+ assertThat(size.getMaximalSize(), is(1));
+ verify(typeDescription).getDescriptor();
+ verify(typeDescription).isVisibleTo(instrumentedType);
+ verify(typeDescription, times(9)).represents(any(Class.class));
+ verifyNoMoreInteractions(typeDescription);
+ verify(methodVisitor).visitLdcInsn(Type.getType(FOO));
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testClassConstantModernInvisible() throws Exception {
+ when(typeDescription.isVisibleTo(instrumentedType)).thenReturn(false);
+ when(classFileVersion.isAtLeast(ClassFileVersion.JAVA_V5)).thenReturn(true);
+ when(typeDescription.getName()).thenReturn(FOO);
+ StackManipulation stackManipulation = ClassConstant.of(typeDescription);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(1));
+ assertThat(size.getMaximalSize(), is(1));
+ verify(typeDescription).getName();
+ verify(typeDescription).isVisibleTo(instrumentedType);
+ verify(typeDescription, times(9)).represents(any(Class.class));
+ verifyNoMoreInteractions(typeDescription);
+ verify(methodVisitor).visitLdcInsn(FOO);
+ verify(methodVisitor).visitMethodInsn(Opcodes.INVOKESTATIC,
+ Type.getInternalName(Class.class),
+ "forName",
+ Type.getMethodDescriptor(Type.getType(Class.class), Type.getType(String.class)),
+ false);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testClassConstantLegacy() throws Exception {
+ when(typeDescription.isVisibleTo(instrumentedType)).thenReturn(true);
+ when(classFileVersion.isAtLeast(ClassFileVersion.JAVA_V5)).thenReturn(false);
+ when(typeDescription.getName()).thenReturn(FOO);
+ StackManipulation stackManipulation = ClassConstant.of(typeDescription);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(1));
+ assertThat(size.getMaximalSize(), is(1));
+ verify(typeDescription).getName();
+ verify(typeDescription, times(9)).represents(any(Class.class));
+ verifyNoMoreInteractions(typeDescription);
+ verify(methodVisitor).visitLdcInsn(FOO);
+ verify(methodVisitor).visitMethodInsn(Opcodes.INVOKESTATIC,
+ Type.getInternalName(Class.class),
+ "forName",
+ Type.getMethodDescriptor(Type.getType(Class.class), Type.getType(String.class)),
+ false);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ClassConstant.class).apply();
+ ObjectPropertyAssertion.of(ClassConstant.ForReferenceType.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/DefaultValueObjectPropertiesTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/DefaultValueObjectPropertiesTest.java
new file mode 100644
index 0000000..00e4e6f
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/DefaultValueObjectPropertiesTest.java
@@ -0,0 +1,12 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+public class DefaultValueObjectPropertiesTest {
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(DefaultValue.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/DefaultValueTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/DefaultValueTest.java
new file mode 100644
index 0000000..9339f96
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/DefaultValueTest.java
@@ -0,0 +1,91 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.After;
+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.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class DefaultValueTest {
+
+ private final Class<?> type;
+
+ private final int opcode;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ public DefaultValueTest(Class<?> type, int opcode) {
+ this.type = type;
+ this.opcode = opcode;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {void.class, -1},
+ {boolean.class, Opcodes.ICONST_0},
+ {byte.class, Opcodes.ICONST_0},
+ {short.class, Opcodes.ICONST_0},
+ {char.class, Opcodes.ICONST_0},
+ {int.class, Opcodes.ICONST_0},
+ {long.class, Opcodes.LCONST_0},
+ {float.class, Opcodes.FCONST_0},
+ {double.class, Opcodes.DCONST_0},
+ {Object.class, Opcodes.ACONST_NULL}
+ });
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(typeDescription.isPrimitive()).thenReturn(type.isPrimitive());
+ when(typeDescription.represents(type)).thenReturn(true);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testDefaultValue() throws Exception {
+ StackManipulation stackManipulation = DefaultValue.of(typeDescription);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(StackSize.of(type).getSize()));
+ assertThat(size.getMaximalSize(), is(StackSize.of(type).getSize()));
+ if (opcode == -1) {
+ verifyZeroInteractions(methodVisitor);
+ } else {
+ verify(methodVisitor).visitInsn(opcode);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/DoubleConstantObjectPropertiesTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/DoubleConstantObjectPropertiesTest.java
new file mode 100644
index 0000000..a1f5127
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/DoubleConstantObjectPropertiesTest.java
@@ -0,0 +1,13 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+public class DoubleConstantObjectPropertiesTest {
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(DoubleConstant.class).apply();
+ ObjectPropertyAssertion.of(DoubleConstant.ConstantPool.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/DoubleConstantOpcodeTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/DoubleConstantOpcodeTest.java
new file mode 100644
index 0000000..2592806
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/DoubleConstantOpcodeTest.java
@@ -0,0 +1,66 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.After;
+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.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class DoubleConstantOpcodeTest {
+
+ private final double value;
+
+ private final int opcode;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ public DoubleConstantOpcodeTest(double value, int opcode) {
+ this.value = value;
+ this.opcode = opcode;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {0d, Opcodes.DCONST_0},
+ {1d, Opcodes.DCONST_1}
+ });
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testConstant() throws Exception {
+ StackManipulation.Size size = DoubleConstant.forValue(value).apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(2));
+ assertThat(size.getMaximalSize(), is(2));
+ verify(methodVisitor).visitInsn(opcode);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/DoubleConstantTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/DoubleConstantTest.java
new file mode 100644
index 0000000..137d7d5
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/DoubleConstantTest.java
@@ -0,0 +1,72 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+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.MethodVisitor;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class DoubleConstantTest {
+
+ private final double value;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ public DoubleConstantTest(double value) {
+ this.value = value;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {Double.MIN_VALUE},
+ {Float.MIN_VALUE},
+ {-100d},
+ {-2d},
+ {0.5d},
+ {6d},
+ {7d},
+ {100d},
+ {Float.MAX_VALUE},
+ {Double.MAX_VALUE},
+ });
+ }
+
+ @Test
+ public void testBiPush() throws Exception {
+ StackManipulation doubleConstant = DoubleConstant.forValue(value);
+ assertThat(doubleConstant.isValid(), is(true));
+ StackManipulation.Size size = doubleConstant.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(2));
+ assertThat(size.getMaximalSize(), is(2));
+ verify(methodVisitor).visitLdcInsn(value);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(DoubleConstant.ConstantPool.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/FieldConstantTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/FieldConstantTest.java
new file mode 100644
index 0000000..1c165ac
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/FieldConstantTest.java
@@ -0,0 +1,158 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.lang.reflect.Field;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class FieldConstantTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux", BAZ = "baz";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private FieldDescription.InDefinedShape fieldDescription, cacheField;
+
+ @Mock
+ private TypeDescription declaringType, cacheDeclaringType, cacheFieldType, instrumentedType;
+
+ @Mock
+ private TypeDescription.Generic genericCacheFieldType;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private ClassFileVersion classFileVersion;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @Before
+ public void setUp() throws Exception {
+ when(declaringType.getInternalName()).thenReturn(FOO);
+ when(fieldDescription.getInternalName()).thenReturn(BAR);
+ when(fieldDescription.getDeclaringType()).thenReturn(declaringType);
+ when(declaringType.getDescriptor()).thenReturn("L" + QUX + ";");
+ when(implementationContext.cache(new FieldConstant(fieldDescription), new TypeDescription.ForLoadedType(Field.class)))
+ .thenReturn(cacheField);
+ when(cacheField.getDeclaringType()).thenReturn(cacheDeclaringType);
+ when(cacheField.isStatic()).thenReturn(true);
+ when(declaringType.getName()).thenReturn(BAZ);
+ when(cacheDeclaringType.getInternalName()).thenReturn(BAZ);
+ when(cacheField.getName()).thenReturn(FOO + BAR);
+ when(cacheField.getType()).thenReturn(genericCacheFieldType);
+ when(genericCacheFieldType.asErasure()).thenReturn(cacheFieldType);
+ when(genericCacheFieldType.getStackSize()).thenReturn(StackSize.SINGLE);
+ when(cacheField.getInternalName()).thenReturn(FOO + BAR);
+ when(cacheField.getDescriptor()).thenReturn(QUX + BAZ);
+ when(implementationContext.getClassFileVersion()).thenReturn(classFileVersion);
+ when(implementationContext.getInstrumentedType()).thenReturn(instrumentedType);
+ }
+
+ @Test
+ public void testConstantCreationModernVisible() throws Exception {
+ when(classFileVersion.isAtLeast(ClassFileVersion.JAVA_V5)).thenReturn(true);
+ when(declaringType.isVisibleTo(instrumentedType)).thenReturn(true);
+ StackManipulation stackManipulation = new FieldConstant(fieldDescription);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(1));
+ assertThat(size.getMaximalSize(), is(2));
+ verify(methodVisitor).visitLdcInsn(Type.getObjectType(QUX));
+ verify(methodVisitor).visitLdcInsn(BAR);
+ verify(methodVisitor).visitMethodInsn(Opcodes.INVOKEVIRTUAL,
+ "java/lang/Class",
+ "getDeclaredField",
+ "(Ljava/lang/String;)Ljava/lang/reflect/Field;",
+ false);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testConstantCreationModernInvisible() throws Exception {
+ when(classFileVersion.isAtLeast(ClassFileVersion.JAVA_V5)).thenReturn(true);
+ when(declaringType.isVisibleTo(instrumentedType)).thenReturn(false);
+ StackManipulation stackManipulation = new FieldConstant(fieldDescription);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(1));
+ assertThat(size.getMaximalSize(), is(2));
+ verify(methodVisitor).visitLdcInsn(BAZ);
+ verify(methodVisitor).visitMethodInsn(Opcodes.INVOKESTATIC,
+ Type.getInternalName(Class.class),
+ "forName",
+ Type.getMethodDescriptor(Type.getType(Class.class), Type.getType(String.class)),
+ false);
+ verify(methodVisitor).visitLdcInsn(BAR);
+ verify(methodVisitor).visitMethodInsn(Opcodes.INVOKEVIRTUAL,
+ "java/lang/Class",
+ "getDeclaredField",
+ "(Ljava/lang/String;)Ljava/lang/reflect/Field;",
+ false);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testConstantCreationLegacy() throws Exception {
+ when(classFileVersion.isAtLeast(ClassFileVersion.JAVA_V5)).thenReturn(false);
+ when(declaringType.isVisibleTo(instrumentedType)).thenReturn(true);
+ StackManipulation stackManipulation = new FieldConstant(fieldDescription);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(1));
+ assertThat(size.getMaximalSize(), is(2));
+ verify(methodVisitor).visitLdcInsn(BAZ);
+ verify(methodVisitor).visitMethodInsn(Opcodes.INVOKESTATIC,
+ Type.getInternalName(Class.class),
+ "forName",
+ Type.getMethodDescriptor(Type.getType(Class.class), Type.getType(String.class)),
+ false);
+ verify(methodVisitor).visitLdcInsn(BAR);
+ verify(methodVisitor).visitMethodInsn(Opcodes.INVOKEVIRTUAL,
+ "java/lang/Class",
+ "getDeclaredField",
+ "(Ljava/lang/String;)Ljava/lang/reflect/Field;",
+ false);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testCached() throws Exception {
+ StackManipulation stackManipulation = new FieldConstant(fieldDescription).cached();
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(1));
+ assertThat(size.getMaximalSize(), is(1));
+ verify(implementationContext).cache(new FieldConstant(fieldDescription), new TypeDescription.ForLoadedType(Field.class));
+ verifyNoMoreInteractions(implementationContext);
+ verify(methodVisitor).visitFieldInsn(Opcodes.GETSTATIC, BAZ, FOO + BAR, QUX + BAZ);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(FieldConstant.class).apply();
+ ObjectPropertyAssertion.of(FieldConstant.Cached.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/FloatConstantObjectPropertiesTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/FloatConstantObjectPropertiesTest.java
new file mode 100644
index 0000000..37683a5
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/FloatConstantObjectPropertiesTest.java
@@ -0,0 +1,13 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+public class FloatConstantObjectPropertiesTest {
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(FloatConstant.class).apply();
+ ObjectPropertyAssertion.of(FloatConstant.ConstantPool.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/FloatConstantOpcodeTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/FloatConstantOpcodeTest.java
new file mode 100644
index 0000000..f15a97e
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/FloatConstantOpcodeTest.java
@@ -0,0 +1,66 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.After;
+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.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class FloatConstantOpcodeTest {
+
+ private final float value;
+
+ private final int opcode;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ public FloatConstantOpcodeTest(float value, int opcode) {
+ this.value = value;
+ this.opcode = opcode;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {0f, Opcodes.FCONST_0},
+ {1f, Opcodes.FCONST_1},
+ {2f, Opcodes.FCONST_2}
+ });
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testConstant() throws Exception {
+ StackManipulation.Size size = FloatConstant.forValue(value).apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(1));
+ assertThat(size.getMaximalSize(), is(1));
+ verify(methodVisitor).visitInsn(opcode);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/FloatConstantTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/FloatConstantTest.java
new file mode 100644
index 0000000..68a6b1e
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/FloatConstantTest.java
@@ -0,0 +1,70 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+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.MethodVisitor;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class FloatConstantTest {
+
+ private final float value;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ public FloatConstantTest(float value) {
+ this.value = value;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {Float.MIN_VALUE},
+ {-100f},
+ {-2f},
+ {0.5f},
+ {6f},
+ {7f},
+ {100f},
+ {Float.MAX_VALUE},
+ });
+ }
+
+ @Test
+ public void testBiPush() throws Exception {
+ StackManipulation floatConstant = FloatConstant.forValue(value);
+ assertThat(floatConstant.isValid(), is(true));
+ StackManipulation.Size size = floatConstant.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(1));
+ assertThat(size.getMaximalSize(), is(1));
+ verify(methodVisitor).visitLdcInsn(value);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(FloatConstant.ConstantPool.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/IntegerConstantObjectPropertiesTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/IntegerConstantObjectPropertiesTest.java
new file mode 100644
index 0000000..72a519a
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/IntegerConstantObjectPropertiesTest.java
@@ -0,0 +1,15 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+public class IntegerConstantObjectPropertiesTest {
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(IntegerConstant.class).apply();
+ ObjectPropertyAssertion.of(IntegerConstant.ConstantPool.class).apply();
+ ObjectPropertyAssertion.of(IntegerConstant.SingleBytePush.class).apply();
+ ObjectPropertyAssertion.of(IntegerConstant.TwoBytePush.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/IntegerConstantOpcodeTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/IntegerConstantOpcodeTest.java
new file mode 100644
index 0000000..bfe08fe
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/IntegerConstantOpcodeTest.java
@@ -0,0 +1,76 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.After;
+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.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class IntegerConstantOpcodeTest {
+
+ private final int value;
+
+ private final int opcode;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ public IntegerConstantOpcodeTest(int value, int opcode) {
+ this.value = value;
+ this.opcode = opcode;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {-1, Opcodes.ICONST_M1},
+ {0, Opcodes.ICONST_0},
+ {0, Opcodes.ICONST_0},
+ {1, Opcodes.ICONST_1},
+ {2, Opcodes.ICONST_2},
+ {3, Opcodes.ICONST_3},
+ {4, Opcodes.ICONST_4},
+ {5, Opcodes.ICONST_5}
+ });
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testConstant() throws Exception {
+ StackManipulation loading = IntegerConstant.forValue(value);
+ if (value == 0 || value == 1) {
+ assertThat(loading, is(IntegerConstant.forValue(value == 1)));
+ }
+ assertThat(loading.isValid(), is(true));
+ StackManipulation.Size size = loading.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(1));
+ assertThat(size.getMaximalSize(), is(1));
+ verify(methodVisitor).visitInsn(opcode);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/IntegerConstantTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/IntegerConstantTest.java
new file mode 100644
index 0000000..8911fb2
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/IntegerConstantTest.java
@@ -0,0 +1,106 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+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.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class IntegerConstantTest {
+
+ private final int value;
+
+ private final PushType pushType;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ public IntegerConstantTest(int value, PushType pushType) {
+ this.value = value;
+ this.pushType = pushType;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {Integer.MIN_VALUE, PushType.LDC},
+ {Short.MIN_VALUE - 1, PushType.LDC},
+ {Short.MIN_VALUE, PushType.SIPUSH},
+ {Byte.MIN_VALUE - 1, PushType.SIPUSH},
+ {Byte.MIN_VALUE, PushType.BIPUSH},
+ {-100, PushType.BIPUSH},
+ {-2, PushType.BIPUSH},
+ {6, PushType.BIPUSH},
+ {7, PushType.BIPUSH},
+ {100, PushType.BIPUSH},
+ {Byte.MAX_VALUE, PushType.BIPUSH},
+ {Byte.MAX_VALUE + 1, PushType.SIPUSH},
+ {Short.MAX_VALUE, PushType.SIPUSH},
+ {Short.MAX_VALUE + 1, PushType.LDC},
+ {Integer.MAX_VALUE, PushType.LDC},
+ });
+ }
+
+ @Test
+ public void testBiPush() throws Exception {
+ StackManipulation integerConstant = IntegerConstant.forValue(value);
+ assertThat(integerConstant.isValid(), is(true));
+ StackManipulation.Size size = integerConstant.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(1));
+ assertThat(size.getMaximalSize(), is(1));
+ pushType.verifyInstruction(methodVisitor, value);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(IntegerConstant.SingleBytePush.class).apply();
+ ObjectPropertyAssertion.of(IntegerConstant.TwoBytePush.class).apply();
+ ObjectPropertyAssertion.of(IntegerConstant.ConstantPool.class).apply();
+ }
+
+ private enum PushType {
+
+ BIPUSH,
+ SIPUSH,
+ LDC;
+
+ private void verifyInstruction(MethodVisitor methodVisitor, int value) {
+ switch (this) {
+ case BIPUSH:
+ verify(methodVisitor).visitIntInsn(Opcodes.BIPUSH, value);
+ break;
+ case SIPUSH:
+ verify(methodVisitor).visitIntInsn(Opcodes.SIPUSH, value);
+ break;
+ case LDC:
+ verify(methodVisitor).visitLdcInsn(value);
+ break;
+ default:
+ throw new AssertionError();
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/JavaConstantValueTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/JavaConstantValueTest.java
new file mode 100644
index 0000000..d5013ab
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/JavaConstantValueTest.java
@@ -0,0 +1,52 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.utility.JavaConstant;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.MethodVisitor;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class JavaConstantValueTest {
+
+ private static final String FOO = "foo";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private JavaConstant javaConstant;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @Test
+ public void testMethodHandle() throws Exception {
+ when(javaConstant.asConstantPoolValue()).thenReturn(FOO);
+ StackManipulation stackManipulation = new JavaConstantValue(javaConstant);
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(1));
+ assertThat(size.getMaximalSize(), is(1));
+ verify(javaConstant).asConstantPoolValue();
+ verifyNoMoreInteractions(javaConstant);
+ verify(methodVisitor).visitLdcInsn(FOO);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(JavaConstantValue.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/LongConstantObjectPropertiesTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/LongConstantObjectPropertiesTest.java
new file mode 100644
index 0000000..b5cc628
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/LongConstantObjectPropertiesTest.java
@@ -0,0 +1,13 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+public class LongConstantObjectPropertiesTest {
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(LongConstant.class).apply();
+ ObjectPropertyAssertion.of(LongConstant.ConstantPool.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/LongConstantOpcodeTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/LongConstantOpcodeTest.java
new file mode 100644
index 0000000..3db0422
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/LongConstantOpcodeTest.java
@@ -0,0 +1,65 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.After;
+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.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class LongConstantOpcodeTest {
+
+ private final long value;
+
+ private final int opcode;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ public LongConstantOpcodeTest(long value, int opcode) {
+ this.value = value;
+ this.opcode = opcode;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {0L, Opcodes.LCONST_0},
+ {1L, Opcodes.LCONST_1}
+ });
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testConstant() throws Exception {
+ StackManipulation.Size size = LongConstant.forValue(value).apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(2));
+ assertThat(size.getMaximalSize(), is(2));
+ verify(methodVisitor).visitInsn(opcode);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/LongConstantTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/LongConstantTest.java
new file mode 100644
index 0000000..ba0a27f
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/LongConstantTest.java
@@ -0,0 +1,71 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+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.MethodVisitor;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class LongConstantTest {
+
+ private final long value;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ public LongConstantTest(long value) {
+ this.value = value;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {Long.MIN_VALUE},
+ {Integer.MIN_VALUE},
+ {-100L},
+ {-2L},
+ {6L},
+ {7L},
+ {100L},
+ {Integer.MAX_VALUE},
+ {Long.MAX_VALUE},
+ });
+ }
+
+ @Test
+ public void testBiPush() throws Exception {
+ StackManipulation longConstant = LongConstant.forValue(value);
+ assertThat(longConstant.isValid(), is(true));
+ StackManipulation.Size size = longConstant.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(2));
+ assertThat(size.getMaximalSize(), is(2));
+ verify(methodVisitor).visitLdcInsn(value);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(LongConstant.ConstantPool.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/MethodConstantTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/MethodConstantTest.java
new file mode 100644
index 0000000..e1f8bbc
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/MethodConstantTest.java
@@ -0,0 +1,160 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+import net.bytebuddy.ClassFileVersion;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class MethodConstantTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux", BAZ = "baz";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodDescription.InDefinedShape methodDescription;
+
+ @Mock
+ private TypeDescription declaringType, parameterType, fieldType, instrumentedType;
+
+ @Mock
+ private ClassFileVersion classFileVersion;
+
+ @Mock
+ private TypeDescription.Generic genericFieldType;
+
+ @Mock
+ private ParameterList<?> parameterList;
+
+ @Mock
+ private TypeList.Generic typeList;
+
+ @Mock
+ private TypeList rawTypeList;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @Mock
+ private FieldDescription.InDefinedShape fieldDescription;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ when(declaringType.asErasure()).thenReturn(declaringType);
+ when(methodDescription.getDeclaringType()).thenReturn(declaringType);
+ when(methodDescription.getInternalName()).thenReturn(FOO);
+ when(methodDescription.getParameters()).thenReturn((ParameterList) parameterList);
+ when(parameterList.asTypeList()).thenReturn(typeList);
+ when(declaringType.getDescriptor()).thenReturn(BAR);
+ when(typeList.asErasures()).thenReturn(rawTypeList);
+ when(rawTypeList.iterator()).thenReturn(Collections.singletonList(parameterType).iterator());
+ when(parameterType.getDescriptor()).thenReturn(QUX);
+ when(fieldDescription.getType()).thenReturn(genericFieldType);
+ when(fieldDescription.isStatic()).thenReturn(true);
+ when(genericFieldType.asErasure()).thenReturn(fieldType);
+ when(genericFieldType.getStackSize()).thenReturn(StackSize.SINGLE);
+ when(fieldDescription.getDeclaringType()).thenReturn(declaringType);
+ when(declaringType.getInternalName()).thenReturn(BAZ);
+ when(fieldDescription.getInternalName()).thenReturn(FOO);
+ when(fieldDescription.getDescriptor()).thenReturn(QUX);
+ when(fieldDescription.asDefined()).thenReturn(fieldDescription);
+ when(implementationContext.getClassFileVersion()).thenReturn(classFileVersion);
+ when(implementationContext.getInstrumentedType()).thenReturn(instrumentedType);
+ }
+
+ @Test
+ public void testMethod() throws Exception {
+ StackManipulation.Size size = MethodConstant.forMethod(methodDescription).apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(1));
+ assertThat(size.getMaximalSize(), is(6));
+ verify(methodVisitor).visitMethodInsn(Opcodes.INVOKEVIRTUAL,
+ Type.getInternalName(Class.class),
+ "getDeclaredMethod",
+ "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;",
+ false);
+ }
+
+ @Test
+ public void testMethodCached() throws Exception {
+ when(implementationContext.cache(any(StackManipulation.class), any(TypeDescription.class))).thenReturn(fieldDescription);
+ StackManipulation.Size size = MethodConstant.forMethod(methodDescription).cached().apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(1));
+ assertThat(size.getMaximalSize(), is(1));
+ verify(methodVisitor).visitFieldInsn(Opcodes.GETSTATIC, BAZ, FOO, QUX);
+ verifyNoMoreInteractions(methodVisitor);
+ verify(implementationContext).cache(MethodConstant.forMethod(methodDescription), new TypeDescription.ForLoadedType(Method.class));
+ verifyNoMoreInteractions(implementationContext);
+ }
+
+ @Test
+ public void testConstructor() throws Exception {
+ when(methodDescription.isConstructor()).thenReturn(true);
+ StackManipulation.Size size = MethodConstant.forMethod(methodDescription).apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(1));
+ assertThat(size.getMaximalSize(), is(5));
+ verify(methodVisitor).visitMethodInsn(Opcodes.INVOKEVIRTUAL,
+ Type.getInternalName(Class.class),
+ "getDeclaredConstructor",
+ "([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;",
+ false);
+ }
+
+ @Test
+ public void testConstructorCached() throws Exception {
+ when(methodDescription.isConstructor()).thenReturn(true);
+ when(implementationContext.cache(any(StackManipulation.class), any(TypeDescription.class))).thenReturn(fieldDescription);
+ StackManipulation.Size size = MethodConstant.forMethod(methodDescription).cached().apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(1));
+ assertThat(size.getMaximalSize(), is(1));
+ verify(methodVisitor).visitFieldInsn(Opcodes.GETSTATIC, BAZ, FOO, QUX);
+ verifyNoMoreInteractions(methodVisitor);
+ verify(implementationContext).cache(MethodConstant.forMethod(methodDescription), new TypeDescription.ForLoadedType(Constructor.class));
+ verifyNoMoreInteractions(implementationContext);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testTypeInitializer() throws Exception {
+ when(methodDescription.isTypeInitializer()).thenReturn(true);
+ MethodConstant.CanCache methodConstant = MethodConstant.forMethod(methodDescription);
+ assertThat(methodConstant.isValid(), is(false));
+ assertThat(methodConstant.cached().isValid(), is(false));
+ methodConstant.apply(methodVisitor, implementationContext);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodConstant.ForMethod.class).apply();
+ ObjectPropertyAssertion.of(MethodConstant.ForConstructor.class).apply();
+ ObjectPropertyAssertion.of(MethodConstant.CachedMethod.class).apply();
+ ObjectPropertyAssertion.of(MethodConstant.CachedConstructor.class).apply();
+ ObjectPropertyAssertion.of(MethodConstant.CanCacheIllegal.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/NullConstantTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/NullConstantTest.java
new file mode 100644
index 0000000..ba6d8fa
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/NullConstantTest.java
@@ -0,0 +1,43 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class NullConstantTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @Test
+ public void testTextValue() throws Exception {
+ StackManipulation.Size size = NullConstant.INSTANCE.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(1));
+ assertThat(size.getMaximalSize(), is(1));
+ verify(methodVisitor).visitInsn(Opcodes.ACONST_NULL);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(NullConstant.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/SerializedConstantTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/SerializedConstantTest.java
new file mode 100644
index 0000000..dfdfc5a
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/SerializedConstantTest.java
@@ -0,0 +1,35 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+import org.objectweb.asm.MethodVisitor;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class SerializedConstantTest {
+
+ private static final String FOO = "foo";
+
+ @Test
+ public void testNullValue() throws Exception {
+ assertThat(SerializedConstant.of(null), is((StackManipulation) NullConstant.INSTANCE));
+ }
+
+ @Test
+ public void testSerialization() throws Exception {
+ MethodVisitor methodVisitor = mock(MethodVisitor.class);
+ Implementation.Context implementationContext = mock(Implementation.Context.class);
+ SerializedConstant.of(FOO).apply(methodVisitor, implementationContext);
+ verify(methodVisitor).visitLdcInsn(contains(FOO));
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(SerializedConstant.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/TextConstantTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/TextConstantTest.java
new file mode 100644
index 0000000..59852d7
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/constant/TextConstantTest.java
@@ -0,0 +1,44 @@
+package net.bytebuddy.implementation.bytecode.constant;
+
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.MethodVisitor;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class TextConstantTest {
+
+ private static final String FOO = "foo";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @Test
+ public void testTextValue() throws Exception {
+ StackManipulation.Size size = new TextConstant(FOO).apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(1));
+ assertThat(size.getMaximalSize(), is(1));
+ verify(methodVisitor).visitLdcInsn(FOO);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TextConstant.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/FieldAccessOtherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/FieldAccessOtherTest.java
new file mode 100644
index 0000000..98f15a1
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/FieldAccessOtherTest.java
@@ -0,0 +1,153 @@
+package net.bytebuddy.implementation.bytecode.member;
+
+import net.bytebuddy.description.enumeration.EnumerationDescription;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.TypeCasting;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class FieldAccessOtherTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private EnumerationDescription enumerationDescription;
+
+ @Mock
+ private TypeDescription.Generic genericType, declaredType;
+
+ @Mock
+ private TypeDescription enumerationType;
+
+ @Mock
+ private FieldDescription.InDefinedShape fieldDescription;
+
+ @Mock
+ private FieldDescription genericField;
+
+ @Before
+ public void setUp() throws Exception {
+ when(genericField.asDefined()).thenReturn(fieldDescription);
+ when(genericField.getType()).thenReturn(genericType);
+ when(fieldDescription.getType()).thenReturn(declaredType);
+ when(enumerationDescription.getEnumerationType()).thenReturn(enumerationType);
+ when(enumerationDescription.getValue()).thenReturn(FOO);
+ when(enumerationType.getDeclaredFields()).thenReturn(new FieldList.Explicit<FieldDescription.InDefinedShape>(fieldDescription));
+ }
+
+ @Test
+ public void testEnumerationDescription() throws Exception {
+ when(fieldDescription.isPublic()).thenReturn(true);
+ when(fieldDescription.isStatic()).thenReturn(true);
+ when(fieldDescription.isEnum()).thenReturn(true);
+ when(fieldDescription.getActualName()).thenReturn(FOO);
+ StackManipulation stackManipulation = FieldAccess.forEnumeration(enumerationDescription);
+ assertThat(stackManipulation.isValid(), is(true));
+ }
+
+ @Test
+ public void testEnumerationDescriptionWithIllegalName() throws Exception {
+ when(fieldDescription.isPublic()).thenReturn(true);
+ when(fieldDescription.isStatic()).thenReturn(true);
+ when(fieldDescription.isEnum()).thenReturn(true);
+ when(fieldDescription.getActualName()).thenReturn(BAR);
+ StackManipulation stackManipulation = FieldAccess.forEnumeration(enumerationDescription);
+ assertThat(stackManipulation.isValid(), is(false));
+ }
+
+ @Test
+ public void testEnumerationDescriptionWithIllegalOwnership() throws Exception {
+ when(fieldDescription.isPublic()).thenReturn(true);
+ when(fieldDescription.isStatic()).thenReturn(false);
+ when(fieldDescription.isEnum()).thenReturn(true);
+ when(fieldDescription.getActualName()).thenReturn(FOO);
+ StackManipulation stackManipulation = FieldAccess.forEnumeration(enumerationDescription);
+ assertThat(stackManipulation.isValid(), is(false));
+ }
+
+ @Test
+ public void testEnumerationDescriptionWithIllegalVisibility() throws Exception {
+ when(fieldDescription.isPublic()).thenReturn(false);
+ when(fieldDescription.isStatic()).thenReturn(true);
+ when(fieldDescription.isEnum()).thenReturn(true);
+ when(fieldDescription.getActualName()).thenReturn(FOO);
+ StackManipulation stackManipulation = FieldAccess.forEnumeration(enumerationDescription);
+ assertThat(stackManipulation.isValid(), is(false));
+ }
+
+ @Test
+ public void testEnumerationDescriptionNonEnumeration() throws Exception {
+ when(fieldDescription.isPublic()).thenReturn(true);
+ when(fieldDescription.isStatic()).thenReturn(true);
+ when(fieldDescription.isEnum()).thenReturn(false);
+ when(fieldDescription.getActualName()).thenReturn(FOO);
+ StackManipulation stackManipulation = FieldAccess.forEnumeration(enumerationDescription);
+ assertThat(stackManipulation.isValid(), is(false));
+ }
+
+ @Test
+ public void testGenericFieldAccessGetter() throws Exception {
+ TypeDescription genericErasure = mock(TypeDescription.class), declaredErasure = mock(TypeDescription.class);
+ when(genericErasure.asErasure()).thenReturn(genericErasure);
+ when(genericType.asErasure()).thenReturn(genericErasure);
+ when(declaredType.asErasure()).thenReturn(declaredErasure);
+ StackManipulation stackManipulation = FieldAccess.forField(genericField).read();
+ assertThat(stackManipulation.isValid(), is(true));
+ assertThat(stackManipulation, is((StackManipulation) new StackManipulation.Compound(FieldAccess.forField(fieldDescription).read(), TypeCasting.to(genericErasure))));
+ }
+
+ @Test
+ public void testGenericFieldAccessPutter() throws Exception {
+ TypeDescription genericErasure = mock(TypeDescription.class), declaredErasure = mock(TypeDescription.class);
+ when(genericType.asErasure()).thenReturn(genericErasure);
+ when(declaredType.asErasure()).thenReturn(declaredErasure);
+ StackManipulation stackManipulation = FieldAccess.forField(genericField).write();
+ assertThat(stackManipulation.isValid(), is(true));
+ assertThat(stackManipulation, is(FieldAccess.forField(fieldDescription).write()));
+ }
+
+ @Test
+ public void testGenericFieldAccessGetterEqualErasure() throws Exception {
+ TypeDescription declaredErasure = mock(TypeDescription.class);
+ when(genericType.asErasure()).thenReturn(declaredErasure);
+ when(declaredType.asErasure()).thenReturn(declaredErasure);
+ StackManipulation stackManipulation = FieldAccess.forField(genericField).read();
+ assertThat(stackManipulation.isValid(), is(true));
+ assertThat(stackManipulation, is(FieldAccess.forField(fieldDescription).read()));
+ }
+
+ @Test
+ public void testGenericFieldAccessPutterEqualErasure() throws Exception {
+ TypeDescription declaredErasure = mock(TypeDescription.class);
+ when(genericType.asErasure()).thenReturn(declaredErasure);
+ when(declaredType.asErasure()).thenReturn(declaredErasure);
+ StackManipulation stackManipulation = FieldAccess.forField(genericField).write();
+ assertThat(stackManipulation.isValid(), is(true));
+ assertThat(stackManipulation, is(FieldAccess.forField(fieldDescription).write()));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(FieldAccess.class).apply();
+ ObjectPropertyAssertion.of(FieldAccess.OfGenericField.class).apply();
+ ObjectPropertyAssertion.of(FieldAccess.AccessDispatcher.class).apply();
+ ObjectPropertyAssertion.of(FieldAccess.AccessDispatcher.FieldGetInstruction.class).apply();
+ ObjectPropertyAssertion.of(FieldAccess.AccessDispatcher.FieldPutInstruction.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/FieldAccessTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/FieldAccessTest.java
new file mode 100644
index 0000000..50197dc
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/FieldAccessTest.java
@@ -0,0 +1,125 @@
+package net.bytebuddy.implementation.bytecode.member;
+
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.After;
+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.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class FieldAccessTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux";
+
+ private final boolean isStatic;
+
+ private final StackSize fieldSize;
+
+ private final int getterChange, getterMaximum, getterOpcode;
+
+ private final int putterChange, putterMaximum, putterOpcode;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private FieldDescription.InDefinedShape fieldDescription;
+
+ @Mock
+ private TypeDescription declaringType, fieldType;
+
+ @Mock
+ private TypeDescription.Generic genericFieldType;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ public FieldAccessTest(boolean isStatic,
+ StackSize fieldSize,
+ int getterChange,
+ int getterMaximum,
+ int getterOpcode,
+ int putterChange,
+ int putterMaximum,
+ int putterOpcode) {
+ this.isStatic = isStatic;
+ this.fieldSize = fieldSize;
+ this.getterChange = getterChange;
+ this.getterMaximum = getterMaximum;
+ this.getterOpcode = getterOpcode;
+ this.putterChange = putterChange;
+ this.putterMaximum = putterMaximum;
+ this.putterOpcode = putterOpcode;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {true, StackSize.SINGLE, 1, 1, Opcodes.GETSTATIC, -1, 0, Opcodes.PUTSTATIC},
+ {true, StackSize.DOUBLE, 2, 2, Opcodes.GETSTATIC, -2, 0, Opcodes.PUTSTATIC},
+ {false, StackSize.SINGLE, 0, 0, Opcodes.GETFIELD, -2, 0, Opcodes.PUTFIELD},
+ {false, StackSize.DOUBLE, 1, 1, Opcodes.GETFIELD, -3, 0, Opcodes.PUTFIELD}
+ });
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(declaringType.asErasure()).thenReturn(declaringType);
+ when(fieldDescription.getDeclaringType()).thenReturn(declaringType);
+ when(fieldDescription.getType()).thenReturn(genericFieldType);
+ when(genericFieldType.asErasure()).thenReturn(fieldType);
+ when(genericFieldType.getStackSize()).thenReturn(fieldSize);
+ when(declaringType.getInternalName()).thenReturn(FOO);
+ when(fieldDescription.getInternalName()).thenReturn(BAR);
+ when(fieldDescription.getDescriptor()).thenReturn(QUX);
+ when(fieldDescription.isStatic()).thenReturn(isStatic);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testGetter() throws Exception {
+ FieldAccess.Defined getter = FieldAccess.forField(fieldDescription);
+ assertThat(getter.read().isValid(), is(true));
+ StackManipulation.Size size = getter.read().apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(getterChange));
+ assertThat(size.getMaximalSize(), is(getterMaximum));
+ verify(methodVisitor).visitFieldInsn(getterOpcode, FOO, BAR, QUX);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testPutter() throws Exception {
+ FieldAccess.Defined setter = FieldAccess.forField(fieldDescription);
+ assertThat(setter.write().isValid(), is(true));
+ StackManipulation.Size size = setter.write().apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(putterChange));
+ assertThat(size.getMaximalSize(), is(putterMaximum));
+ verify(methodVisitor).visitFieldInsn(putterOpcode, FOO, BAR, QUX);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/HandleInvocationTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/HandleInvocationTest.java
new file mode 100644
index 0000000..57ceb12
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/HandleInvocationTest.java
@@ -0,0 +1,60 @@
+package net.bytebuddy.implementation.bytecode.member;
+
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.utility.JavaConstant;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class HandleInvocationTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @Test
+ public void testInvocationDecreasingStack() throws Exception {
+ JavaConstant.MethodType methodType = JavaConstant.MethodType.of(void.class, Object.class);
+ StackManipulation stackManipulation = new HandleInvocation(methodType);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(-1));
+ assertThat(size.getMaximalSize(), is(0));
+ verify(methodVisitor).visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "invokeExact", "(Ljava/lang/Object;)V", false);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testInvocationIncreasingStack() throws Exception {
+ JavaConstant.MethodType methodType = JavaConstant.MethodType.of(Object.class);
+ StackManipulation stackManipulation = new HandleInvocation(methodType);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(1));
+ assertThat(size.getMaximalSize(), is(1));
+ verify(methodVisitor).visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "invokeExact", "()Ljava/lang/Object;", false);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(HandleInvocation.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/MethodInvocationDynamicTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/MethodInvocationDynamicTest.java
new file mode 100644
index 0000000..2d06a4d
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/MethodInvocationDynamicTest.java
@@ -0,0 +1,97 @@
+package net.bytebuddy.implementation.bytecode.member;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class MethodInvocationDynamicTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux", BAZ = "baz";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodDescription.InDefinedShape methodDescription;
+
+ @Mock
+ private TypeDescription returnType, declaringType, firstType, secondType;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Object argument;
+
+ @Before
+ public void setUp() throws Exception {
+ when(methodDescription.asDefined()).thenReturn(methodDescription);
+ when(methodDescription.getDeclaringType()).thenReturn(declaringType);
+ when(firstType.getStackSize()).thenReturn(StackSize.ZERO);
+ when(firstType.getDescriptor()).thenReturn(FOO);
+ when(secondType.getDescriptor()).thenReturn(BAR);
+ when(secondType.getStackSize()).thenReturn(StackSize.ZERO);
+ when(returnType.getStackSize()).thenReturn(StackSize.ZERO);
+ when(returnType.getDescriptor()).thenReturn(QUX);
+ when(methodDescription.getInternalName()).thenReturn(QUX);
+ when(methodDescription.getDescriptor()).thenReturn(BAZ);
+ when(declaringType.getInternalName()).thenReturn(BAR);
+ when(methodDescription.getParameters()).thenReturn(new ParameterList.Explicit.ForTypes(methodDescription, firstType, secondType));
+ }
+
+ @Test
+ public void testDynamicStaticBootstrap() throws Exception {
+ when(methodDescription.isBootstrap()).thenReturn(true);
+ when(methodDescription.isStatic()).thenReturn(true);
+ StackManipulation stackManipulation = MethodInvocation.invoke(methodDescription)
+ .dynamic(FOO, returnType, Arrays.asList(firstType, secondType), Collections.singletonList(argument));
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(0));
+ assertThat(size.getMaximalSize(), is(0));
+ verify(methodVisitor).visitInvokeDynamicInsn(FOO, "(" + FOO + BAR + ")" + QUX, new Handle(Opcodes.H_INVOKESTATIC, BAR, QUX, BAZ, false), argument);
+ }
+
+ @Test
+ public void testDynamicConstructorBootstrap() throws Exception {
+ when(methodDescription.isBootstrap()).thenReturn(true);
+ when(methodDescription.isConstructor()).thenReturn(true);
+ StackManipulation stackManipulation = MethodInvocation.invoke(methodDescription)
+ .dynamic(FOO, returnType, Arrays.asList(firstType, secondType), Collections.singletonList(argument));
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(0));
+ assertThat(size.getMaximalSize(), is(0));
+ verify(methodVisitor).visitInvokeDynamicInsn(FOO, "(" + FOO + BAR + ")" + QUX, new Handle(Opcodes.H_NEWINVOKESPECIAL, BAR, QUX, BAZ, false), argument);
+ }
+
+ @Test
+ public void testIllegalBootstrap() throws Exception {
+ StackManipulation stackManipulation = MethodInvocation.invoke(methodDescription)
+ .dynamic(FOO, returnType, Arrays.asList(firstType, secondType), Collections.singletonList(argument));
+ assertThat(stackManipulation.isValid(), is(false));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/MethodInvocationGenericTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/MethodInvocationGenericTest.java
new file mode 100644
index 0000000..a1af95b
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/MethodInvocationGenericTest.java
@@ -0,0 +1,150 @@
+package net.bytebuddy.implementation.bytecode.member;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.TypeCasting;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class MethodInvocationGenericTest {
+
+ private static final String FOO = "foo";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodDescription.InDefinedShape declaredMethod;
+
+ @Mock
+ private MethodDescription methodDescription;
+
+ @Mock
+ private TypeDescription.Generic methodReturnType, declaredReturnType;
+
+ @Mock
+ private TypeDescription declaredErasure, declaringType, targetType, otherType;
+
+ @Mock
+ private MethodDescription.SignatureToken token;
+
+ @Before
+ public void setUp() throws Exception {
+ when(methodDescription.asDefined()).thenReturn(declaredMethod);
+ when(methodDescription.getReturnType()).thenReturn(methodReturnType);
+ when(declaredMethod.getReturnType()).thenReturn(declaredReturnType);
+ when(declaredReturnType.asErasure()).thenReturn(declaredErasure);
+ when(declaredMethod.getDeclaringType()).thenReturn(declaringType);
+ when(declaringType.asErasure()).thenReturn(declaringType);
+ when(declaredMethod.asSignatureToken()).thenReturn(token);
+ when(declaredMethod.isSpecializableFor(targetType)).thenReturn(true);
+ when(declaredMethod.asDefined()).thenReturn(declaredMethod);
+ }
+
+ @Test
+ public void testGenericMethod() throws Exception {
+ TypeDescription genericErasure = mock(TypeDescription.class);
+ when(methodReturnType.asErasure()).thenReturn(genericErasure);
+ StackManipulation stackManipulation = MethodInvocation.invoke(methodDescription);
+ assertThat(stackManipulation.isValid(), is(true));
+ assertThat(stackManipulation, is((StackManipulation) new MethodInvocation.OfGenericMethod(genericErasure, MethodInvocation.invoke(declaredMethod))));
+ }
+
+ @Test
+ public void testGenericMethodErasureEqual() throws Exception {
+ when(methodReturnType.asErasure()).thenReturn(declaredErasure);
+ StackManipulation stackManipulation = MethodInvocation.invoke(methodDescription);
+ assertThat(stackManipulation.isValid(), is(true));
+ assertThat(stackManipulation, is((StackManipulation) MethodInvocation.invoke(declaredMethod)));
+ }
+
+ @Test
+ public void testGenericMethodVirtual() throws Exception {
+ TypeDescription genericErasure = mock(TypeDescription.class);
+ when(methodReturnType.asErasure()).thenReturn(genericErasure);
+ when(genericErasure.asErasure()).thenReturn(genericErasure);
+ StackManipulation stackManipulation = MethodInvocation.invoke(methodDescription).virtual(targetType);
+ assertThat(stackManipulation.isValid(), is(true));
+ assertThat(stackManipulation, is((StackManipulation) new StackManipulation.Compound(MethodInvocation.invoke(declaredMethod).virtual(targetType),
+ TypeCasting.to(genericErasure))));
+ }
+
+ @Test
+ public void testGenericMethodVirtualErasureEqual() throws Exception {
+ when(methodReturnType.asErasure()).thenReturn(declaredErasure);
+ StackManipulation stackManipulation = MethodInvocation.invoke(methodDescription).virtual(targetType);
+ assertThat(stackManipulation.isValid(), is(true));
+ assertThat(stackManipulation, is(MethodInvocation.invoke(declaredMethod).virtual(targetType)));
+ }
+
+ @Test
+ public void testGenericMethodSpecial() throws Exception {
+ TypeDescription genericErasure = mock(TypeDescription.class);
+ when(methodReturnType.asErasure()).thenReturn(genericErasure);
+ when(genericErasure.asErasure()).thenReturn(genericErasure);
+ StackManipulation stackManipulation = MethodInvocation.invoke(methodDescription).special(targetType);
+ assertThat(stackManipulation.isValid(), is(true));
+ assertThat(stackManipulation, is((StackManipulation) new StackManipulation.Compound(MethodInvocation.invoke(declaredMethod).special(targetType),
+ TypeCasting.to(genericErasure))));
+ }
+
+ @Test
+ public void testGenericMethodSpecialErasureEqual() throws Exception {
+ when(methodReturnType.asErasure()).thenReturn(declaredErasure);
+ StackManipulation stackManipulation = MethodInvocation.invoke(methodDescription).special(targetType);
+ assertThat(stackManipulation.isValid(), is(true));
+ assertThat(stackManipulation, is(MethodInvocation.invoke(declaredMethod).special(targetType)));
+ }
+
+ @Test
+ public void testGenericMethodDynamic() throws Exception {
+ TypeDescription genericErasure = mock(TypeDescription.class);
+ when(methodReturnType.asErasure()).thenReturn(genericErasure);
+ when(declaredMethod.isBootstrap()).thenReturn(true);
+ StackManipulation stackManipulation = MethodInvocation.invoke(methodDescription).dynamic(FOO,
+ otherType,
+ Collections.<TypeDescription>emptyList(),
+ Collections.emptyList());
+ assertThat(stackManipulation.isValid(), is(true));
+ assertThat(stackManipulation, is(MethodInvocation.invoke(declaredMethod).dynamic(FOO,
+ otherType,
+ Collections.<TypeDescription>emptyList(),
+ Collections.emptyList())));
+ }
+
+ @Test
+ public void testGenericMethodDynamicErasureEqual() throws Exception {
+ when(methodReturnType.asErasure()).thenReturn(declaredErasure);
+ when(declaredMethod.isBootstrap()).thenReturn(true);
+ StackManipulation stackManipulation = MethodInvocation.invoke(methodDescription).dynamic(FOO,
+ otherType,
+ Collections.<TypeDescription>emptyList(),
+ Collections.emptyList());
+ assertThat(stackManipulation.isValid(), is(true));
+ assertThat(stackManipulation, is(MethodInvocation.invoke(declaredMethod).dynamic(FOO,
+ otherType,
+ Collections.<TypeDescription>emptyList(),
+ Collections.emptyList())));
+ }
+
+ @Test
+ public void testIllegal() throws Exception {
+ TypeDescription genericErasure = mock(TypeDescription.class);
+ when(methodReturnType.asErasure()).thenReturn(genericErasure);
+ when(declaredMethod.isTypeInitializer()).thenReturn(true);
+ StackManipulation stackManipulation = MethodInvocation.invoke(methodDescription);
+ assertThat(stackManipulation.isValid(), is(false));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/MethodInvocationHandleTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/MethodInvocationHandleTest.java
new file mode 100644
index 0000000..1c86648
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/MethodInvocationHandleTest.java
@@ -0,0 +1,99 @@
+package net.bytebuddy.implementation.bytecode.member;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class MethodInvocationHandleTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux", BAZ = "baz";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodDescription.InDefinedShape methodDescription;
+
+ @Mock
+ private TypeDescription.Generic returnType;
+
+ @Mock
+ private TypeDescription declaringType, firstType, secondType;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Before
+ public void setUp() throws Exception {
+ when(methodDescription.asDefined()).thenReturn(methodDescription);
+ when(methodDescription.getReturnType()).thenReturn(returnType);
+ when(methodDescription.getDeclaringType()).thenReturn(declaringType);
+ when(returnType.getStackSize()).thenReturn(StackSize.ZERO);
+ when(firstType.getStackSize()).thenReturn(StackSize.ZERO);
+ when(firstType.getDescriptor()).thenReturn(FOO);
+ when(secondType.getDescriptor()).thenReturn(BAR);
+ when(secondType.getStackSize()).thenReturn(StackSize.ZERO);
+ when(returnType.getStackSize()).thenReturn(StackSize.ZERO);
+ when(methodDescription.getInternalName()).thenReturn(QUX);
+ when(methodDescription.getDescriptor()).thenReturn(BAZ);
+ when(declaringType.getDescriptor()).thenReturn(BAR);
+ when(methodDescription.getParameters()).thenReturn(new ParameterList.Explicit.ForTypes(methodDescription, firstType, secondType));
+ }
+
+ @Test
+ public void testExactHandleStatic() throws Exception {
+ when(methodDescription.isStatic()).thenReturn(true);
+ StackManipulation stackManipulation = MethodInvocation.invoke(methodDescription).onHandle(MethodInvocation.HandleType.EXACT);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(-1));
+ assertThat(size.getMaximalSize(), is(0));
+ verify(methodVisitor).visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "invokeExact", BAZ, false);
+ }
+
+ @Test
+ public void testExactHandleConstructor() throws Exception {
+ when(methodDescription.isConstructor()).thenReturn(true);
+ StackManipulation stackManipulation = MethodInvocation.invoke(methodDescription).onHandle(MethodInvocation.HandleType.EXACT);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(-1));
+ assertThat(size.getMaximalSize(), is(0));
+ verify(methodVisitor).visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "invokeExact", BAZ, false);
+ }
+
+ @Test
+ public void testExactHandleNonStatic() throws Exception {
+ StackManipulation stackManipulation = MethodInvocation.invoke(methodDescription).onHandle(MethodInvocation.HandleType.EXACT);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(-1));
+ assertThat(size.getMaximalSize(), is(0));
+ verify(methodVisitor).visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "invokeExact", "(" + BAR + BAZ.substring(1), false);
+ }
+
+ @Test
+ public void testMethodNames() throws Exception {
+ assertThat(MethodInvocation.HandleType.EXACT.getMethodName(), is("invokeExact"));
+ assertThat(MethodInvocation.HandleType.REGULAR.getMethodName(), is("invoke"));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/MethodInvocationOtherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/MethodInvocationOtherTest.java
new file mode 100644
index 0000000..8d53778
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/MethodInvocationOtherTest.java
@@ -0,0 +1,56 @@
+package net.bytebuddy.implementation.bytecode.member;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+import org.objectweb.asm.MethodVisitor;
+
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class MethodInvocationOtherTest {
+
+ private static final String FOO = "foo";
+
+ @Test(expected = IllegalStateException.class)
+ public void testIllegal() throws Exception {
+ assertThat(MethodInvocation.IllegalInvocation.INSTANCE.isValid(), is(false));
+ assertThat(MethodInvocation.IllegalInvocation.INSTANCE.special(mock(TypeDescription.class)),
+ is((StackManipulation) StackManipulation.Illegal.INSTANCE));
+ assertThat(MethodInvocation.IllegalInvocation.INSTANCE.virtual(mock(TypeDescription.class)),
+ is((StackManipulation) StackManipulation.Illegal.INSTANCE));
+ assertThat(MethodInvocation.IllegalInvocation.INSTANCE.dynamic(FOO, mock(TypeDescription.class), mock(TypeList.class), mock(List.class)),
+ is((StackManipulation) StackManipulation.Illegal.INSTANCE));
+ assertThat(MethodInvocation.IllegalInvocation.INSTANCE.onHandle(null),
+ is((StackManipulation) StackManipulation.Illegal.INSTANCE));
+ MethodInvocation.IllegalInvocation.INSTANCE.apply(mock(MethodVisitor.class), mock(Implementation.Context.class));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodInvocation.class).apply();
+ ObjectPropertyAssertion.of(MethodInvocation.OfGenericMethod.class).apply();
+ ObjectPropertyAssertion.of(MethodInvocation.IllegalInvocation.class).apply();
+ ObjectPropertyAssertion.of(MethodInvocation.Invocation.class).refine(new ObjectPropertyAssertion.Refinement<MethodDescription>() {
+ @Override
+ public void apply(MethodDescription mock) {
+ when(mock.asSignatureToken()).thenReturn(mock(MethodDescription.SignatureToken.class));
+ TypeDescription declaringType = mock(TypeDescription.class);
+ when(declaringType.asErasure()).thenReturn(declaringType);
+ when(mock.getDeclaringType()).thenReturn(declaringType);
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(MethodInvocation.DynamicInvocation.class).apply();
+ ObjectPropertyAssertion.of(MethodInvocation.HandleInvocation.class).apply();
+ ObjectPropertyAssertion.of(MethodInvocation.HandleType.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/MethodInvocationTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/MethodInvocationTest.java
new file mode 100644
index 0000000..668dc37
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/MethodInvocationTest.java
@@ -0,0 +1,213 @@
+package net.bytebuddy.implementation.bytecode.member;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.After;
+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.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class MethodInvocationTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux", BAZ = "baz";
+
+ private static final int ARGUMENT_STACK_SIZE = 1;
+
+ private final StackSize stackSize;
+
+ private final int expectedSize;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodDescription.InDefinedShape methodDescription;
+
+ @Mock
+ private TypeDescription.Generic returnType, otherType;
+
+ @Mock
+ private TypeDescription declaringType, rawOtherType;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ public MethodInvocationTest(StackSize stackSize) {
+ this.stackSize = stackSize;
+ this.expectedSize = stackSize.getSize() - ARGUMENT_STACK_SIZE;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {StackSize.ZERO},
+ {StackSize.SINGLE},
+ {StackSize.DOUBLE}
+ });
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(declaringType.asErasure()).thenReturn(declaringType);
+ when(methodDescription.getReturnType()).thenReturn(returnType);
+ when(methodDescription.getDeclaringType()).thenReturn(declaringType);
+ when(methodDescription.getStackSize()).thenReturn(ARGUMENT_STACK_SIZE);
+ when(declaringType.getInternalName()).thenReturn(FOO);
+ when(otherType.asErasure()).thenReturn(rawOtherType);
+ when(rawOtherType.getInternalName()).thenReturn(BAZ);
+ when(methodDescription.getInternalName()).thenReturn(BAR);
+ when(methodDescription.getDescriptor()).thenReturn(QUX);
+ when(returnType.getStackSize()).thenReturn(stackSize);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testTypeInitializerInvocation() throws Exception {
+ when(methodDescription.isTypeInitializer()).thenReturn(true);
+ assertThat(MethodInvocation.invoke(methodDescription).isValid(), is(false));
+ }
+
+ @Test
+ public void testStaticMethodInvocation() throws Exception {
+ when(methodDescription.isStatic()).thenReturn(true);
+ assertInvocation(MethodInvocation.invoke(methodDescription), Opcodes.INVOKESTATIC, FOO, false);
+ }
+
+ @Test
+ public void testStaticPrivateMethodInvocation() throws Exception {
+ when(methodDescription.isStatic()).thenReturn(true);
+ when(methodDescription.isPrivate()).thenReturn(true);
+ assertInvocation(MethodInvocation.invoke(methodDescription), Opcodes.INVOKESTATIC, FOO, false);
+ }
+
+ @Test
+ public void testPrivateMethodInvocation() throws Exception {
+ when(methodDescription.isPrivate()).thenReturn(true);
+ assertInvocation(MethodInvocation.invoke(methodDescription), Opcodes.INVOKESPECIAL, FOO, false);
+ }
+
+ @Test
+ public void testConstructorMethodInvocation() throws Exception {
+ when(methodDescription.isConstructor()).thenReturn(true);
+ assertInvocation(MethodInvocation.invoke(methodDescription), Opcodes.INVOKESPECIAL, FOO, false);
+ }
+
+ @Test
+ public void testPublicMethodInvocation() throws Exception {
+ assertInvocation(MethodInvocation.invoke(methodDescription), Opcodes.INVOKEVIRTUAL, FOO, false);
+ }
+
+ @Test
+ public void testInterfaceMethodInvocation() throws Exception {
+ when(declaringType.isInterface()).thenReturn(true);
+ assertInvocation(MethodInvocation.invoke(methodDescription), Opcodes.INVOKEINTERFACE, FOO, true);
+ }
+
+ @Test
+ public void testStaticInterfaceMethodInvocation() throws Exception {
+ when(declaringType.isInterface()).thenReturn(true);
+ when(methodDescription.isStatic()).thenReturn(true);
+ assertInvocation(MethodInvocation.invoke(methodDescription), Opcodes.INVOKESTATIC, FOO, true);
+ }
+
+ @Test
+ public void testDefaultInterfaceMethodInvocation() throws Exception {
+ when(methodDescription.isDefaultMethod()).thenReturn(true);
+ when(declaringType.isInterface()).thenReturn(true);
+ assertInvocation(MethodInvocation.invoke(methodDescription), Opcodes.INVOKEINTERFACE, FOO, true);
+ }
+
+ @Test
+ public void testExplicitlySpecialDefaultInterfaceMethodInvocation() throws Exception {
+ when(methodDescription.isDefaultMethod()).thenReturn(true);
+ when(methodDescription.isSpecializableFor(declaringType)).thenReturn(true);
+ when(declaringType.isInterface()).thenReturn(true);
+ assertInvocation(MethodInvocation.invoke(methodDescription).special(declaringType), Opcodes.INVOKESPECIAL, FOO, true);
+ }
+
+ @Test
+ public void testExplicitlySpecialDefaultInterfaceMethodInvocationOnOther() throws Exception {
+ when(methodDescription.isDefaultMethod()).thenReturn(true);
+ when(methodDescription.isSpecializableFor(rawOtherType)).thenReturn(false);
+ assertThat(MethodInvocation.invoke(methodDescription).special(rawOtherType).isValid(), is(false));
+ }
+
+ @Test
+ public void testExplicitlySpecialMethodInvocation() throws Exception {
+ when(methodDescription.isSpecializableFor(rawOtherType)).thenReturn(true);
+ assertInvocation(MethodInvocation.invoke(methodDescription).special(rawOtherType), Opcodes.INVOKESPECIAL, BAZ, false);
+ }
+
+ @Test
+ public void testIllegalSpecialMethodInvocation() throws Exception {
+ assertThat(MethodInvocation.invoke(methodDescription).special(rawOtherType).isValid(), is(false));
+ }
+
+ @Test
+ public void testExplicitlyVirtualMethodInvocation() throws Exception {
+ when(declaringType.isAssignableFrom(rawOtherType)).thenReturn(true);
+ assertInvocation(MethodInvocation.invoke(methodDescription).virtual(rawOtherType), Opcodes.INVOKEVIRTUAL, BAZ, false);
+ }
+
+ @Test
+ public void testExplicitlyVirtualMethodInvocationOfInterface() throws Exception {
+ when(declaringType.isAssignableFrom(rawOtherType)).thenReturn(true);
+ when(rawOtherType.isInterface()).thenReturn(true);
+ assertInvocation(MethodInvocation.invoke(methodDescription).virtual(rawOtherType), Opcodes.INVOKEINTERFACE, BAZ, true);
+ }
+
+ @Test
+ public void testStaticVirtualInvocation() throws Exception {
+ when(methodDescription.isStatic()).thenReturn(true);
+ assertThat(MethodInvocation.invoke(methodDescription).virtual(rawOtherType).isValid(), is(false));
+ }
+
+ @Test
+ public void testPrivateVirtualInvocation() throws Exception {
+ when(methodDescription.isPrivate()).thenReturn(true);
+ assertThat(MethodInvocation.invoke(methodDescription).virtual(rawOtherType).isValid(), is(false));
+ }
+
+ @Test
+ public void testConstructorVirtualInvocation() throws Exception {
+ when(methodDescription.isConstructor()).thenReturn(true);
+ assertThat(MethodInvocation.invoke(methodDescription).virtual(rawOtherType).isValid(), is(false));
+ }
+
+ private void assertInvocation(StackManipulation stackManipulation,
+ int opcode,
+ String typeName,
+ boolean interfaceInvocation) {
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(expectedSize));
+ assertThat(size.getMaximalSize(), is(Math.max(0, expectedSize)));
+ verify(methodVisitor).visitMethodInsn(opcode, typeName, BAR, QUX, interfaceInvocation);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/MethodReturnObjectPropertiesTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/MethodReturnObjectPropertiesTest.java
new file mode 100644
index 0000000..20b9401
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/MethodReturnObjectPropertiesTest.java
@@ -0,0 +1,12 @@
+package net.bytebuddy.implementation.bytecode.member;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+public class MethodReturnObjectPropertiesTest {
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodReturn.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/MethodReturnTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/MethodReturnTest.java
new file mode 100644
index 0000000..a66b468
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/MethodReturnTest.java
@@ -0,0 +1,90 @@
+package net.bytebuddy.implementation.bytecode.member;
+
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.After;
+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.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class MethodReturnTest {
+
+ private final Class<?> type;
+
+ private final int opcode;
+
+ private final int sizeChange;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private TypeDefinition typeDefinition;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ public MethodReturnTest(Class<?> type, int opcode, int sizeChange) {
+ this.type = type;
+ this.opcode = opcode;
+ this.sizeChange = sizeChange;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {void.class, Opcodes.RETURN, 0},
+ {Object.class, Opcodes.ARETURN, 1},
+ {Object[].class, Opcodes.ARETURN, 1},
+ {long.class, Opcodes.LRETURN, 2},
+ {double.class, Opcodes.DRETURN, 2},
+ {float.class, Opcodes.FRETURN, 1},
+ {int.class, Opcodes.IRETURN, 1},
+ {char.class, Opcodes.IRETURN, 1},
+ {short.class, Opcodes.IRETURN, 1},
+ {byte.class, Opcodes.IRETURN, 1},
+ {boolean.class, Opcodes.IRETURN, 1},
+ });
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(typeDefinition.isPrimitive()).thenReturn(type.isPrimitive());
+ when(typeDefinition.represents(type)).thenReturn(true);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testVoidReturn() throws Exception {
+ StackManipulation stackManipulation = MethodReturn.of(typeDefinition);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(-1 * sizeChange));
+ assertThat(size.getMaximalSize(), is(0));
+ verify(methodVisitor).visitInsn(opcode);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/MethodVariableAccessOfMethodArgumentsTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/MethodVariableAccessOfMethodArgumentsTest.java
new file mode 100644
index 0000000..889cbc5
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/MethodVariableAccessOfMethodArgumentsTest.java
@@ -0,0 +1,151 @@
+package net.bytebuddy.implementation.bytecode.member;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.test.utility.MockitoRule;
+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 org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class MethodVariableAccessOfMethodArgumentsTest {
+
+ private static final String FOO = "foo";
+
+ private static final int PARAMETER_STACK_SIZE = 2;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodDescription.InDefinedShape methodDescription, bridgeMethod;
+
+ @Mock
+ private TypeDescription declaringType, firstRawParameterType, secondRawParameterType;
+
+ @Mock
+ private TypeDescription.Generic firstParameterType, secondParameterType;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ when(methodDescription.getDeclaringType()).thenReturn(declaringType);
+ when(declaringType.getStackSize()).thenReturn(StackSize.SINGLE);
+ when(firstParameterType.getStackSize()).thenReturn(StackSize.SINGLE);
+ when(firstParameterType.asErasure()).thenReturn(firstRawParameterType);
+ when(firstParameterType.asGenericType()).thenReturn(firstParameterType);
+ when(secondParameterType.asErasure()).thenReturn(secondRawParameterType);
+ when(secondParameterType.getStackSize()).thenReturn(StackSize.SINGLE);
+ when(secondParameterType.asGenericType()).thenReturn(secondParameterType);
+ when(methodDescription.getParameters()).thenReturn(new ParameterList.Explicit.ForTypes(methodDescription, firstParameterType, secondParameterType));
+ when(bridgeMethod.getDeclaringType()).thenReturn(declaringType);
+ when(secondRawParameterType.getInternalName()).thenReturn(FOO);
+ when(firstParameterType.accept(any(TypeDescription.Generic.Visitor.class))).thenReturn(firstParameterType);
+ when(secondParameterType.accept(any(TypeDescription.Generic.Visitor.class))).thenReturn(secondParameterType);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testStaticMethod() throws Exception {
+ when(methodDescription.isStatic()).thenReturn(true);
+ StackManipulation stackManipulation = MethodVariableAccess.allArgumentsOf(methodDescription);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(PARAMETER_STACK_SIZE));
+ assertThat(size.getMaximalSize(), is(PARAMETER_STACK_SIZE));
+ verify(methodVisitor).visitVarInsn(Opcodes.ALOAD, 0);
+ verify(methodVisitor).visitVarInsn(Opcodes.ALOAD, 1);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testNonStaticMethod() throws Exception {
+ StackManipulation stackManipulation = MethodVariableAccess.allArgumentsOf(methodDescription);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(PARAMETER_STACK_SIZE));
+ assertThat(size.getMaximalSize(), is(PARAMETER_STACK_SIZE));
+ verify(methodVisitor).visitVarInsn(Opcodes.ALOAD, 1);
+ verify(methodVisitor).visitVarInsn(Opcodes.ALOAD, 2);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testStaticMethodWithPrepending() throws Exception {
+ when(methodDescription.isStatic()).thenReturn(true);
+ StackManipulation stackManipulation = MethodVariableAccess.allArgumentsOf(methodDescription).prependThisReference();
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(PARAMETER_STACK_SIZE));
+ assertThat(size.getMaximalSize(), is(PARAMETER_STACK_SIZE));
+ verify(methodVisitor).visitVarInsn(Opcodes.ALOAD, 0);
+ verify(methodVisitor).visitVarInsn(Opcodes.ALOAD, 1);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testNonStaticMethodWithPrepending() throws Exception {
+ StackManipulation stackManipulation = MethodVariableAccess.allArgumentsOf(methodDescription).prependThisReference();
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(PARAMETER_STACK_SIZE + 1));
+ assertThat(size.getMaximalSize(), is(PARAMETER_STACK_SIZE + 1));
+ verify(methodVisitor).visitVarInsn(Opcodes.ALOAD, 0);
+ verify(methodVisitor).visitVarInsn(Opcodes.ALOAD, 1);
+ verify(methodVisitor).visitVarInsn(Opcodes.ALOAD, 2);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testBridgeMethodWithoutCasting() throws Exception {
+ when(bridgeMethod.getParameters()).thenReturn(new ParameterList.Explicit.ForTypes(bridgeMethod,
+ Arrays.asList(firstParameterType, secondParameterType)));
+ StackManipulation stackManipulation = MethodVariableAccess.allArgumentsOf(methodDescription).asBridgeOf(bridgeMethod);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(PARAMETER_STACK_SIZE));
+ assertThat(size.getMaximalSize(), is(PARAMETER_STACK_SIZE));
+ verify(methodVisitor).visitVarInsn(Opcodes.ALOAD, 1);
+ verify(methodVisitor).visitVarInsn(Opcodes.ALOAD, 2);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testBridgeMethodWithCasting() throws Exception {
+ when(secondRawParameterType.asErasure()).thenReturn(secondRawParameterType);
+ when(bridgeMethod.getParameters()).thenReturn(new ParameterList.Explicit.ForTypes(bridgeMethod, secondParameterType, secondParameterType));
+ StackManipulation stackManipulation = MethodVariableAccess.allArgumentsOf(methodDescription).asBridgeOf(bridgeMethod);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(PARAMETER_STACK_SIZE));
+ assertThat(size.getMaximalSize(), is(PARAMETER_STACK_SIZE));
+ verify(methodVisitor).visitVarInsn(Opcodes.ALOAD, 1);
+ verify(methodVisitor).visitTypeInsn(Opcodes.CHECKCAST, FOO);
+ verify(methodVisitor).visitVarInsn(Opcodes.ALOAD, 2);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/MethodVariableAccessOtherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/MethodVariableAccessOtherTest.java
new file mode 100644
index 0000000..aeb8592
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/MethodVariableAccessOtherTest.java
@@ -0,0 +1,100 @@
+package net.bytebuddy.implementation.bytecode.member;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+import org.objectweb.asm.MethodVisitor;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class MethodVariableAccessOtherTest {
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testVoidArgument() throws Exception {
+ TypeDescription voidTypeDescription = mock(TypeDescription.class);
+ when(voidTypeDescription.isPrimitive()).thenReturn(true);
+ when(voidTypeDescription.represents(void.class)).thenReturn(true);
+ MethodVariableAccess.of(voidTypeDescription);
+ }
+
+ @Test
+ public void testIncrement() throws Exception {
+ StackManipulation stackManipulation = MethodVariableAccess.INTEGER.increment(4, 1);
+ assertThat(stackManipulation.isValid(), is(true));
+ MethodVisitor methodVisitor = mock(MethodVisitor.class);
+ Implementation.Context implementationContext = mock(Implementation.Context.class);
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(0));
+ assertThat(size.getMaximalSize(), is(0));
+ verify(methodVisitor).visitIincInsn(4, 1);
+ verifyNoMoreInteractions(methodVisitor);
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testThisReference() throws Exception {
+ assertThat(MethodVariableAccess.loadThis(), is(MethodVariableAccess.REFERENCE.loadFrom(0)));
+ }
+
+ @Test
+ public void testLoadParameter() throws Exception {
+ ParameterDescription parameterDescription = mock(ParameterDescription.class);
+ when(parameterDescription.getType()).thenReturn(new TypeDescription.Generic.OfNonGenericType.ForLoadedType(int.class));
+ when(parameterDescription.getOffset()).thenReturn(4);
+ assertThat(MethodVariableAccess.load(parameterDescription), is(MethodVariableAccess.INTEGER.loadFrom(4)));
+ }
+
+ @Test
+ public void testStoreParameter() throws Exception {
+ ParameterDescription parameterDescription = mock(ParameterDescription.class);
+ when(parameterDescription.getType()).thenReturn(new TypeDescription.Generic.OfNonGenericType.ForLoadedType(int.class));
+ when(parameterDescription.getOffset()).thenReturn(4);
+ assertThat(MethodVariableAccess.store(parameterDescription), is(MethodVariableAccess.INTEGER.storeAt(4)));
+ }
+
+ @Test
+ public void testIncrementParameter() throws Exception {
+ ParameterDescription parameterDescription = mock(ParameterDescription.class);
+ when(parameterDescription.getType()).thenReturn(new TypeDescription.Generic.OfNonGenericType.ForLoadedType(int.class));
+ when(parameterDescription.getOffset()).thenReturn(4);
+ assertThat(MethodVariableAccess.increment(parameterDescription, 42), is(MethodVariableAccess.INTEGER.increment(4, 42)));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testReferenceCannotIncrement() throws Exception {
+ MethodVariableAccess.REFERENCE.increment(0, 1);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testLongCannotIncrement() throws Exception {
+ MethodVariableAccess.LONG.increment(0, 1);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testFloatCannotIncrement() throws Exception {
+ MethodVariableAccess.FLOAT.increment(0, 1);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testDoubleCannotIncrement() throws Exception {
+ MethodVariableAccess.DOUBLE.increment(0, 1);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodVariableAccess.class).apply();
+ ObjectPropertyAssertion.of(MethodVariableAccess.OffsetLoading.class).apply();
+ ObjectPropertyAssertion.of(MethodVariableAccess.OffsetWriting.class).apply();
+ ObjectPropertyAssertion.of(MethodVariableAccess.OffsetIncrementing.class).apply();
+ ObjectPropertyAssertion.of(MethodVariableAccess.MethodLoading.class).apply();
+ ObjectPropertyAssertion.of(MethodVariableAccess.MethodLoading.TypeCastingHandler.NoOp.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/MethodVariableAccessTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/MethodVariableAccessTest.java
new file mode 100644
index 0000000..ab9abb4
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bytecode/member/MethodVariableAccessTest.java
@@ -0,0 +1,92 @@
+package net.bytebuddy.implementation.bytecode.member;
+
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.After;
+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.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class MethodVariableAccessTest {
+
+ private final TypeDefinition typeDefinition;
+
+ private final int readCode, writeCode;
+
+ private final int size;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Mock
+ private Implementation.Context implementationContext;
+
+ public MethodVariableAccessTest(Class<?> type, int readCode, int writeCode, int size) {
+ typeDefinition = mock(TypeDefinition.class);
+ when(typeDefinition.isPrimitive()).thenReturn(type.isPrimitive());
+ when(typeDefinition.represents(type)).thenReturn(true);
+ this.readCode = readCode;
+ this.writeCode = writeCode;
+ this.size = size;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {Object.class, Opcodes.ALOAD, Opcodes.ASTORE, 1},
+ {boolean.class, Opcodes.ILOAD, Opcodes.ISTORE, 1},
+ {byte.class, Opcodes.ILOAD, Opcodes.ISTORE, 1},
+ {short.class, Opcodes.ILOAD, Opcodes.ISTORE, 1},
+ {char.class, Opcodes.ILOAD, Opcodes.ISTORE, 1},
+ {int.class, Opcodes.ILOAD, Opcodes.ISTORE, 1},
+ {long.class, Opcodes.LLOAD, Opcodes.LSTORE, 2},
+ {float.class, Opcodes.FLOAD, Opcodes.FSTORE, 1},
+ {double.class, Opcodes.DLOAD, Opcodes.DSTORE, 2},
+ });
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ verifyZeroInteractions(implementationContext);
+ }
+
+ @Test
+ public void testLoading() throws Exception {
+ StackManipulation stackManipulation = MethodVariableAccess.of(typeDefinition).loadFrom(4);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(this.size));
+ assertThat(size.getMaximalSize(), is(this.size));
+ verify(methodVisitor).visitVarInsn(readCode, 4);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+
+ @Test
+ public void testStoring() throws Exception {
+ StackManipulation stackManipulation = MethodVariableAccess.of(typeDefinition).storeAt(4);
+ assertThat(stackManipulation.isValid(), is(true));
+ StackManipulation.Size size = stackManipulation.apply(methodVisitor, implementationContext);
+ assertThat(size.getSizeImpact(), is(-this.size));
+ assertThat(size.getMaximalSize(), is(0));
+ verify(methodVisitor).visitVarInsn(writeCode, 4);
+ verifyNoMoreInteractions(methodVisitor);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/AbstractElementMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/AbstractElementMatcherTest.java
new file mode 100644
index 0000000..c3d72b3
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/AbstractElementMatcherTest.java
@@ -0,0 +1,40 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+
+import java.security.AccessControlContext;
+import java.security.ProtectionDomain;
+
+import static org.mockito.Mockito.mock;
+
+public abstract class AbstractElementMatcherTest<T extends ElementMatcher<?>> {
+
+ private final Class<? extends T> type;
+
+ protected final String startsWith;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ protected AbstractElementMatcherTest(Class<? extends T> type, String startsWith) {
+ this.type = type;
+ this.startsWith = startsWith;
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ modify(ObjectPropertyAssertion.of(type)).specificToString(makeRegex(startsWith)).apply();
+ }
+
+ protected String makeRegex(String startsWith) {
+ return "^" + startsWith + "\\(.*\\)$";
+ }
+
+ protected <S> ObjectPropertyAssertion<S> modify(ObjectPropertyAssertion<S> propertyAssertion) {
+ return propertyAssertion;
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/AbstractFilterableListTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/AbstractFilterableListTest.java
new file mode 100644
index 0000000..7538490
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/AbstractFilterableListTest.java
@@ -0,0 +1,64 @@
+package net.bytebuddy.matcher;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public abstract class AbstractFilterableListTest<T, S extends FilterableList<T, S>, U> {
+
+ protected abstract U getFirst() throws Exception;
+
+ protected abstract U getSecond() throws Exception;
+
+ protected abstract S asList(List<U> elements);
+
+ @SuppressWarnings("unchecked") // In absence of @SafeVarargs for Java 6
+ protected S asList(U... element) {
+ return asList(Arrays.asList(element));
+ }
+
+ protected S asList(U element) {
+ return asList(Collections.singletonList(element));
+ };
+
+ protected S emptyList() {
+ return asList(Collections.<U>emptyList());
+ };
+
+ protected abstract T asElement(U element);
+
+ @Test(expected = IllegalStateException.class)
+ @SuppressWarnings("unchecked")
+ public void testGetOnlyTwoElementList() throws Exception {
+ asList(Arrays.asList(getFirst(), getSecond())).getOnly();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ @SuppressWarnings("unchecked")
+ public void testGetOnlyEmptyList() throws Exception {
+ asList(Collections.<U>emptyList()).getOnly();
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testGetOnlySingleList() throws Exception {
+ assertThat(asList(Collections.singletonList(getFirst())).getOnly(), is(asElement(getFirst())));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testFilter() throws Exception {
+ assertThat(asList(Arrays.asList(getFirst(), getSecond())).filter(ElementMatchers.is(asElement(getFirst()))).getOnly(), is(asElement(getFirst())));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testSubList() throws Exception {
+ assertThat(asList(Arrays.asList(getFirst(), getSecond())).subList(0, 1).getOnly(), is(asElement(getFirst())));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/AccessibilityMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/AccessibilityMatcherTest.java
new file mode 100644
index 0000000..4fdcbae
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/AccessibilityMatcherTest.java
@@ -0,0 +1,42 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.ByteCodeElement;
+import net.bytebuddy.description.type.TypeDescription;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class AccessibilityMatcherTest extends AbstractElementMatcherTest<AccessibilityMatcher<?>> {
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Mock
+ private ByteCodeElement byteCodeElement;
+
+ @SuppressWarnings("unchecked")
+ public AccessibilityMatcherTest() {
+ super((Class<? extends AccessibilityMatcher<?>>) (Object) AccessibilityMatcher.class, "isAccessibleTo");
+ }
+
+ @Test
+ public void testMatch() throws Exception {
+ when(byteCodeElement.isAccessibleTo(typeDescription)).thenReturn(true);
+ assertThat(new AccessibilityMatcher<ByteCodeElement>(typeDescription).matches(byteCodeElement), is(true));
+ verify(byteCodeElement).isAccessibleTo(typeDescription);
+ verifyNoMoreInteractions(byteCodeElement);
+ verifyZeroInteractions(typeDescription);
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ when(byteCodeElement.isAccessibleTo(typeDescription)).thenReturn(false);
+ assertThat(new AccessibilityMatcher<ByteCodeElement>(typeDescription).matches(byteCodeElement), is(false));
+ verify(byteCodeElement).isAccessibleTo(typeDescription);
+ verifyNoMoreInteractions(byteCodeElement);
+ verifyZeroInteractions(typeDescription);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/AnnotationTypeMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/AnnotationTypeMatcherTest.java
new file mode 100644
index 0000000..2a744a7
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/AnnotationTypeMatcherTest.java
@@ -0,0 +1,49 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class AnnotationTypeMatcherTest extends AbstractElementMatcherTest<AnnotationTypeMatcher<?>> {
+
+ @Mock
+ private ElementMatcher<? super TypeDescription> typeMatcher;
+
+ @Mock
+ private AnnotationDescription annotatedElement;
+
+ @Mock
+ private TypeDescription annotationType;
+
+ @SuppressWarnings("unchecked")
+ public AnnotationTypeMatcherTest() {
+ super((Class<AnnotationTypeMatcher<?>>) (Object) AnnotationTypeMatcher.class, "ofAnnotationType");
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(annotatedElement.getAnnotationType()).thenReturn(annotationType);
+ }
+
+ @Test
+ public void testMatch() throws Exception {
+ when(typeMatcher.matches(annotationType)).thenReturn(true);
+ assertThat(new AnnotationTypeMatcher<AnnotationDescription>(typeMatcher).matches(annotatedElement), is(true));
+ verify(typeMatcher).matches(annotationType);
+ verifyNoMoreInteractions(typeMatcher);
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ when(typeMatcher.matches(annotationType)).thenReturn(false);
+ assertThat(new AnnotationTypeMatcher<AnnotationDescription>(typeMatcher).matches(annotatedElement), is(false));
+ verify(typeMatcher).matches(annotationType);
+ verifyNoMoreInteractions(typeMatcher);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/BooleanMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/BooleanMatcherTest.java
new file mode 100644
index 0000000..e852d25
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/BooleanMatcherTest.java
@@ -0,0 +1,41 @@
+package net.bytebuddy.matcher;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+public class BooleanMatcherTest extends AbstractElementMatcherTest<BooleanMatcher<?>> {
+
+ @SuppressWarnings("unchecked")
+ public BooleanMatcherTest() {
+ super((Class<BooleanMatcher<?>>) (Object) BooleanMatcher.class, "");
+ }
+
+ @Test
+ public void testMatch() throws Exception {
+ Object target = mock(Object.class);
+ assertThat(new BooleanMatcher<Object>(true).matches(target), is(true));
+ verifyZeroInteractions(target);
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ Object target = mock(Object.class);
+ assertThat(new BooleanMatcher<Object>(false).matches(target), is(false));
+ verifyZeroInteractions(target);
+ }
+
+ @Test
+ public void testToString() throws Exception {
+ assertThat(new BooleanMatcher<Object>(true).toString(), is("true"));
+ assertThat(new BooleanMatcher<Object>(false).toString(), is("false"));
+ }
+
+ @Override
+ protected String makeRegex(String startsWith) {
+ return "(true)|(false)";
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/CachingMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/CachingMatcherTest.java
new file mode 100644
index 0000000..24bcb2a
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/CachingMatcherTest.java
@@ -0,0 +1,84 @@
+package net.bytebuddy.matcher;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class CachingMatcherTest extends AbstractElementMatcherTest<CachingMatcher<?>> {
+
+ @Mock
+ private Object target;
+
+ @Mock
+ private ElementMatcher<? super Object> matcher;
+
+ private ConcurrentMap<Object, Boolean> map;
+
+ @SuppressWarnings("unchecked")
+ public CachingMatcherTest() {
+ super((Class<CachingMatcher<?>>) (Object) CachingMatcher.class, "cached");
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ map = new ConcurrentHashMap<Object, Boolean>();
+ when(matcher.matches(target)).thenReturn(true);
+ }
+
+ @Test
+ public void testMatchCachesNoEviction() throws Exception {
+ ElementMatcher<Object> matcher = new CachingMatcher<Object>(this.matcher, map);
+ assertThat(matcher.matches(target), is(true));
+ assertThat(matcher.matches(target), is(true));
+ verify(this.matcher).matches(target);
+ verifyNoMoreInteractions(this.matcher);
+ verifyZeroInteractions(target);
+ }
+
+ @Test
+ public void testMatchCachesEviction() throws Exception {
+ ElementMatcher<Object> matcher = new CachingMatcher.WithInlineEviction<Object>(this.matcher, map, 1);
+ Object other = mock(Object.class);
+ assertThat(matcher.matches(target), is(true));
+ assertThat(matcher.matches(other), is(false));
+ assertThat(matcher.matches(other), is(false));
+ assertThat(matcher.matches(target), is(true));
+ verify(this.matcher, times(2)).matches(target);
+ verify(this.matcher).matches(other);
+ verifyNoMoreInteractions(this.matcher);
+ verifyZeroInteractions(target);
+ }
+
+ @Test
+ public void testMatchNoCachesEviction() throws Exception {
+ ElementMatcher<Object> matcher = new CachingMatcher.WithInlineEviction<Object>(this.matcher, map, 2);
+ Object other = mock(Object.class);
+ assertThat(matcher.matches(target), is(true));
+ assertThat(matcher.matches(other), is(false));
+ assertThat(matcher.matches(other), is(false));
+ assertThat(matcher.matches(target), is(true));
+ verify(this.matcher).matches(target);
+ verify(this.matcher).matches(other);
+ verifyNoMoreInteractions(this.matcher);
+ verifyZeroInteractions(target);
+ }
+
+ @Override
+ @Test
+ public void testObjectProperties() throws Exception {
+ CachingMatcher<?> cachingMatcher = new CachingMatcher<Object>(matcher, map);
+ assertThat(cachingMatcher.equals(cachingMatcher), is(true));
+ assertThat(cachingMatcher.equals(new CachingMatcher<Object>(matcher, new ConcurrentHashMap<Object, Boolean>())), is(true));
+ assertThat(cachingMatcher.equals(null), is(false));
+ assertThat(cachingMatcher.equals(new Object()), is(false));
+ assertThat(cachingMatcher.hashCode(), is(matcher.hashCode()));
+ assertThat(cachingMatcher.toString().startsWith(startsWith), is(true));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/ClassLoaderHierarchyMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/ClassLoaderHierarchyMatcherTest.java
new file mode 100644
index 0000000..e15d6ae
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/ClassLoaderHierarchyMatcherTest.java
@@ -0,0 +1,47 @@
+package net.bytebuddy.matcher;
+
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class ClassLoaderHierarchyMatcherTest extends AbstractElementMatcherTest<ClassLoaderHierarchyMatcher<?>> {
+
+ @Mock
+ private ElementMatcher<? super ClassLoader> classLoaderMatcher;
+
+ @Mock
+ private ClassLoader classLoader;
+
+ @SuppressWarnings("unchecked")
+ public ClassLoaderHierarchyMatcherTest() {
+ super((Class<ClassLoaderHierarchyMatcher<?>>) (Object) ClassLoaderHierarchyMatcher.class, "hasChild");
+ }
+
+ @Test
+ public void testMatchesChild() throws Exception {
+ when(classLoaderMatcher.matches(classLoader)).thenReturn(true);
+ assertThat(new ClassLoaderHierarchyMatcher<ClassLoader>(classLoaderMatcher).matches(classLoader), is(true));
+ verify(classLoaderMatcher).matches(classLoader);
+ verifyNoMoreInteractions(classLoaderMatcher);
+ }
+
+ @Test
+ public void testMatchesParent() throws Exception {
+ when(classLoaderMatcher.matches(null)).thenReturn(true);
+ assertThat(new ClassLoaderHierarchyMatcher<ClassLoader>(classLoaderMatcher).matches(classLoader), is(true));
+ verify(classLoaderMatcher).matches(classLoader);
+ verify(classLoaderMatcher).matches(null);
+ verifyNoMoreInteractions(classLoaderMatcher);
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ assertThat(new ClassLoaderHierarchyMatcher<ClassLoader>(classLoaderMatcher).matches(classLoader), is(false));
+ verify(classLoaderMatcher).matches(classLoader);
+ verify(classLoaderMatcher).matches(null);
+ verifyNoMoreInteractions(classLoaderMatcher);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/ClassLoaderParentMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/ClassLoaderParentMatcherTest.java
new file mode 100644
index 0000000..9099a75
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/ClassLoaderParentMatcherTest.java
@@ -0,0 +1,42 @@
+package net.bytebuddy.matcher;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class ClassLoaderParentMatcherTest extends AbstractElementMatcherTest<ClassLoaderParentMatcher<?>> {
+
+ private ClassLoader parent, child, noChild;
+
+ @SuppressWarnings("unchecked")
+ public ClassLoaderParentMatcherTest() {
+ super((Class<ClassLoaderParentMatcher<?>>) (Object) ClassLoaderParentMatcher.class, "isParentOf");
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ parent = new URLClassLoader(new URL[0], null);
+ noChild = new URLClassLoader(new URL[0], null);
+ child = new URLClassLoader(new URL[0], parent);
+ }
+
+ @Test
+ public void testMatch() throws Exception {
+ assertThat(new ClassLoaderParentMatcher<ClassLoader>(child).matches(parent), is(true));
+ }
+
+ @Test
+ public void testMatchBootstrap() throws Exception {
+ assertThat(new ClassLoaderParentMatcher<ClassLoader>(child).matches(null), is(true));
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ assertThat(new ClassLoaderParentMatcher<ClassLoader>(noChild).matches(parent), is(false));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/CollectionElementMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/CollectionElementMatcherTest.java
new file mode 100644
index 0000000..a276c7d
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/CollectionElementMatcherTest.java
@@ -0,0 +1,60 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.Arrays;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class CollectionElementMatcherTest extends AbstractElementMatcherTest<CollectionElementMatcher<?>> {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ private Iterable<Object> iterable;
+
+ private Object elemet;
+
+ @Mock
+ private ElementMatcher<? super Object> elementMatcher;
+
+ @SuppressWarnings("unchecked")
+ public CollectionElementMatcherTest() {
+ super((Class<CollectionElementMatcher<?>>) (Object) CollectionElementMatcher.class, "with");
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ elemet = new Object();
+ iterable = Arrays.asList(new Object(), elemet);
+ }
+
+ @Test
+ public void testMatch() throws Exception {
+ when(elementMatcher.matches(elemet)).thenReturn(true);
+ assertThat(new CollectionElementMatcher<Object>(1, elementMatcher).matches(iterable), is(true));
+ verify(elementMatcher).matches(elemet);
+ verifyNoMoreInteractions(elementMatcher);
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ when(elementMatcher.matches(elemet)).thenReturn(false);
+ assertThat(new CollectionElementMatcher<Object>(1, elementMatcher).matches(iterable), is(false));
+ verify(elementMatcher).matches(elemet);
+ verifyNoMoreInteractions(elementMatcher);
+ }
+
+ @Test
+ public void testNoMatchIndex() throws Exception {
+ assertThat(new CollectionElementMatcher<Object>(2, elementMatcher).matches(iterable), is(false));
+ verifyZeroInteractions(elementMatcher);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/CollectionErasureMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/CollectionErasureMatcherTest.java
new file mode 100644
index 0000000..3744c04
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/CollectionErasureMatcherTest.java
@@ -0,0 +1,60 @@
+package net.bytebuddy.matcher;
+
+
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.util.Arrays;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class CollectionErasureMatcherTest extends AbstractElementMatcherTest<CollectionErasureMatcher<?>> {
+
+ @Mock
+ private ElementMatcher<? super Iterable<? extends TypeDefinition>> matcher;
+
+ @Mock
+ private TypeDefinition first, second, other;
+
+ @Mock
+ private TypeDescription firstRaw, secondRaw;
+
+ @SuppressWarnings("unchecked")
+ public CollectionErasureMatcherTest() {
+ super((Class<CollectionErasureMatcher<?>>) (Object) CollectionErasureMatcher.class, "erasures");
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(first.asErasure()).thenReturn(firstRaw);
+ when(second.asErasure()).thenReturn(secondRaw);
+ }
+
+ @Test
+ public void testMatch() throws Exception {
+ when(matcher.matches(Arrays.asList(firstRaw, secondRaw))).thenReturn(true);
+ assertThat(new CollectionErasureMatcher<Iterable<TypeDefinition>>(matcher).matches(Arrays.asList(first, second)), is(true));
+ verify(matcher).matches(Arrays.asList(firstRaw, secondRaw));
+ verifyNoMoreInteractions(matcher);
+ verify(first).asErasure();
+ verifyNoMoreInteractions(first);
+ verify(second).asErasure();
+ verifyNoMoreInteractions(second);
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ assertThat(new CollectionErasureMatcher<Iterable<TypeDefinition>>(matcher).matches(Arrays.asList(first, second)), is(false));
+ verify(matcher).matches(Arrays.asList(firstRaw, secondRaw));
+ verifyNoMoreInteractions(matcher);
+ verify(first).asErasure();
+ verifyNoMoreInteractions(first);
+ verify(second).asErasure();
+ verifyNoMoreInteractions(second);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/CollectionItemMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/CollectionItemMatcherTest.java
new file mode 100644
index 0000000..87f6338
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/CollectionItemMatcherTest.java
@@ -0,0 +1,61 @@
+package net.bytebuddy.matcher;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.util.Arrays;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class CollectionItemMatcherTest extends AbstractElementMatcherTest<CollectionItemMatcher<?>> {
+
+ private Iterable<Object> iterable;
+
+ private Object first, second;
+
+ @Mock
+ private ElementMatcher<Object> elementMatcher;
+
+ @SuppressWarnings("unchecked")
+ public CollectionItemMatcherTest() {
+ super((Class<CollectionItemMatcher<?>>) (Object) CollectionItemMatcher.class, "whereOne");
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ first = new Object();
+ second = new Object();
+ iterable = Arrays.asList(first, second);
+ }
+
+ @Test
+ public void testMatchFirst() throws Exception {
+ when(elementMatcher.matches(first)).thenReturn(true);
+ assertThat(new CollectionItemMatcher<Object>(elementMatcher).matches(iterable), is(true));
+ verify(elementMatcher).matches(first);
+ verifyNoMoreInteractions(elementMatcher);
+ }
+
+ @Test
+ public void testMatchSecond() throws Exception {
+ when(elementMatcher.matches(first)).thenReturn(false);
+ when(elementMatcher.matches(second)).thenReturn(true);
+ assertThat(new CollectionItemMatcher<Object>(elementMatcher).matches(iterable), is(true));
+ verify(elementMatcher).matches(first);
+ verify(elementMatcher).matches(second);
+ verifyNoMoreInteractions(elementMatcher);
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ when(elementMatcher.matches(first)).thenReturn(false);
+ when(elementMatcher.matches(second)).thenReturn(false);
+ assertThat(new CollectionItemMatcher<Object>(elementMatcher).matches(iterable), is(false));
+ verify(elementMatcher).matches(first);
+ verify(elementMatcher).matches(second);
+ verifyNoMoreInteractions(elementMatcher);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/CollectionOneToOneMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/CollectionOneToOneMatcherTest.java
new file mode 100644
index 0000000..35d3a02
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/CollectionOneToOneMatcherTest.java
@@ -0,0 +1,89 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class CollectionOneToOneMatcherTest extends AbstractElementMatcherTest<CollectionOneToOneMatcher<?>> {
+
+ private Iterable<Object> iterable;
+
+ private Object first, second;
+
+ @Mock
+ private ElementMatcher<Object> firstMatcher, secondMatcher;
+
+ @SuppressWarnings("unchecked")
+ public CollectionOneToOneMatcherTest() {
+ super((Class<CollectionOneToOneMatcher<?>>) (Object) CollectionOneToOneMatcher.class, "containing");
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ first = new Object();
+ second = new Object();
+ iterable = Arrays.asList(first, second);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMatch() throws Exception {
+ when(firstMatcher.matches(first)).thenReturn(true);
+ when(secondMatcher.matches(second)).thenReturn(true);
+ assertThat(new CollectionOneToOneMatcher<Object>(Arrays.asList(firstMatcher, secondMatcher)).matches(iterable), is(true));
+ verify(firstMatcher).matches(first);
+ verifyNoMoreInteractions(firstMatcher);
+ verify(secondMatcher).matches(second);
+ verifyNoMoreInteractions(secondMatcher);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testNoMatchFirst() throws Exception {
+ when(firstMatcher.matches(first)).thenReturn(false);
+ assertThat(new CollectionOneToOneMatcher<Object>(Arrays.asList(firstMatcher, secondMatcher)).matches(iterable), is(false));
+ verify(firstMatcher).matches(first);
+ verifyNoMoreInteractions(firstMatcher);
+ verifyZeroInteractions(secondMatcher);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testNoMatchSecond() throws Exception {
+ when(firstMatcher.matches(first)).thenReturn(true);
+ when(secondMatcher.matches(second)).thenReturn(false);
+ assertThat(new CollectionOneToOneMatcher<Object>(Arrays.asList(firstMatcher, secondMatcher)).matches(iterable), is(false));
+ verify(firstMatcher).matches(first);
+ verifyNoMoreInteractions(firstMatcher);
+ verify(secondMatcher).matches(second);
+ verifyNoMoreInteractions(secondMatcher);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testNoMatchSize() throws Exception {
+ assertThat(new CollectionOneToOneMatcher<Object>(Arrays.asList(firstMatcher, secondMatcher)).matches(Collections.singletonList(firstMatcher)), is(false));
+ verifyZeroInteractions(firstMatcher);
+ verifyZeroInteractions(secondMatcher);
+ }
+
+ @Override
+ protected <S> ObjectPropertyAssertion<S> modify(ObjectPropertyAssertion<S> propertyAssertion) {
+ return propertyAssertion.create(new ObjectPropertyAssertion.Creator<List<?>>() {
+ @Override
+ @SuppressWarnings("unchecked")
+ public List<?> create() {
+ return Arrays.asList(mock(ElementMatcher.class), mock(ElementMatcher.class));
+ }
+ });
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/CollectionSizeMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/CollectionSizeMatcherTest.java
new file mode 100644
index 0000000..45e9439
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/CollectionSizeMatcherTest.java
@@ -0,0 +1,47 @@
+package net.bytebuddy.matcher;
+
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class CollectionSizeMatcherTest extends AbstractElementMatcherTest<CollectionSizeMatcher<?>> {
+
+ @Mock
+ private Iterable<Object> collection;
+
+ @SuppressWarnings("unchecked")
+ public CollectionSizeMatcherTest() {
+ super((Class<CollectionSizeMatcher<?>>) (Object) CollectionSizeMatcher.class, "ofSize");
+ }
+
+ @Test
+ public void testMatch() throws Exception {
+ when(collection.iterator()).thenReturn(Collections.singletonList(new Object()).iterator());
+ assertThat(new CollectionSizeMatcher<Iterable<?>>(1).matches(collection), is(true));
+ verify(collection).iterator();
+ verifyNoMoreInteractions(collection);
+ }
+
+ @Test
+ public void testMatchCollection() throws Exception {
+ assertThat(new CollectionSizeMatcher<Iterable<?>>(1).matches(Collections.singletonList(new Object())), is(true));
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ when(collection.iterator()).thenReturn(Collections.emptyList().iterator());
+ assertThat(new CollectionSizeMatcher<Iterable<?>>(1).matches(collection), is(false));
+ verify(collection).iterator();
+ verifyNoMoreInteractions(collection);
+ }
+
+ @Test
+ public void testNoMatchCollection() throws Exception {
+ assertThat(new CollectionSizeMatcher<Iterable<?>>(0).matches(Collections.singletonList(new Object())), is(false));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/DeclaringAnnotationMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/DeclaringAnnotationMatcherTest.java
new file mode 100644
index 0000000..997d516
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/DeclaringAnnotationMatcherTest.java
@@ -0,0 +1,49 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.annotation.AnnotationSource;
+import net.bytebuddy.description.annotation.AnnotationList;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class DeclaringAnnotationMatcherTest extends AbstractElementMatcherTest<DeclaringAnnotationMatcher<?>> {
+
+ @Mock
+ private ElementMatcher<? super AnnotationList> annotationMatcher;
+
+ @Mock
+ private AnnotationSource annotationSource;
+
+ @Mock
+ private AnnotationList annotationList;
+
+ @SuppressWarnings("unchecked")
+ public DeclaringAnnotationMatcherTest() {
+ super((Class<DeclaringAnnotationMatcher<?>>) (Object) DeclaringAnnotationMatcher.class, "declaresAnnotations");
+ }
+
+ @Test
+ public void testMatch() throws Exception {
+ when(annotationSource.getDeclaredAnnotations()).thenReturn(annotationList);
+ when(annotationMatcher.matches(annotationList)).thenReturn(true);
+ assertThat(new DeclaringAnnotationMatcher<AnnotationSource>(annotationMatcher).matches(annotationSource), is(true));
+ verify(annotationMatcher).matches(annotationList);
+ verifyNoMoreInteractions(annotationMatcher);
+ verify(annotationSource).getDeclaredAnnotations();
+ verifyNoMoreInteractions(annotationSource);
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ when(annotationSource.getDeclaredAnnotations()).thenReturn(annotationList);
+ when(annotationMatcher.matches(annotationList)).thenReturn(false);
+ assertThat(new DeclaringAnnotationMatcher<AnnotationSource>(annotationMatcher).matches(annotationSource), is(false));
+ verify(annotationMatcher).matches(annotationList);
+ verifyNoMoreInteractions(annotationMatcher);
+ verify(annotationSource).getDeclaredAnnotations();
+ verifyNoMoreInteractions(annotationSource);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/DeclaringFieldMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/DeclaringFieldMatcherTest.java
new file mode 100644
index 0000000..43eb3e7
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/DeclaringFieldMatcherTest.java
@@ -0,0 +1,50 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.type.TypeDescription;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class DeclaringFieldMatcherTest extends AbstractElementMatcherTest<DeclaringFieldMatcher<?>> {
+
+ @Mock
+ private ElementMatcher<? super FieldList<?>> fieldMatcher;
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Mock
+ private FieldList<FieldDescription.InDefinedShape> fieldList;
+
+ @SuppressWarnings("unchecked")
+ public DeclaringFieldMatcherTest() {
+ super((Class<DeclaringFieldMatcher<?>>) (Object) DeclaringFieldMatcher.class, "declaresFields");
+ }
+
+ @Test
+ public void testMatch() throws Exception {
+ when(typeDescription.getDeclaredFields()).thenReturn(fieldList);
+ when(fieldMatcher.matches(fieldList)).thenReturn(true);
+ assertThat(new DeclaringFieldMatcher<TypeDescription>(fieldMatcher).matches(typeDescription), is(true));
+ verify(fieldMatcher).matches(fieldList);
+ verifyNoMoreInteractions(fieldMatcher);
+ verify(typeDescription).getDeclaredFields();
+ verifyNoMoreInteractions(typeDescription);
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ when(typeDescription.getDeclaredFields()).thenReturn(fieldList);
+ when(fieldMatcher.matches(fieldList)).thenReturn(false);
+ assertThat(new DeclaringFieldMatcher<TypeDescription>(fieldMatcher).matches(typeDescription), is(false));
+ verify(fieldMatcher).matches(fieldList);
+ verifyNoMoreInteractions(fieldMatcher);
+ verify(typeDescription).getDeclaredFields();
+ verifyNoMoreInteractions(typeDescription);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/DeclaringMethodMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/DeclaringMethodMatcherTest.java
new file mode 100644
index 0000000..40943e5
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/DeclaringMethodMatcherTest.java
@@ -0,0 +1,51 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.type.TypeDescription;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class DeclaringMethodMatcherTest extends AbstractElementMatcherTest<DeclaringMethodMatcher<?>> {
+
+ @Mock
+ private ElementMatcher<? super MethodList<?>> methodMatcher;
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Mock
+ private MethodList<?> methodList;
+
+ @SuppressWarnings("unchecked")
+ public DeclaringMethodMatcherTest() {
+ super((Class<DeclaringMethodMatcher<?>>) (Object) DeclaringMethodMatcher.class, "declaresMethods");
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMatch() throws Exception {
+ when(typeDescription.getDeclaredMethods()).thenReturn((MethodList) methodList);
+ when(methodMatcher.matches(methodList)).thenReturn(true);
+ assertThat(new DeclaringMethodMatcher<TypeDescription>(methodMatcher).matches(typeDescription), is(true));
+ verify(methodMatcher).matches(methodList);
+ verifyNoMoreInteractions(methodMatcher);
+ verify(typeDescription).getDeclaredMethods();
+ verifyNoMoreInteractions(typeDescription);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testNoMatch() throws Exception {
+ when(typeDescription.getDeclaredMethods()).thenReturn((MethodList) methodList);
+ when(methodMatcher.matches(methodList)).thenReturn(false);
+ assertThat(new DeclaringMethodMatcher<TypeDescription>(methodMatcher).matches(typeDescription), is(false));
+ verify(methodMatcher).matches(methodList);
+ verifyNoMoreInteractions(methodMatcher);
+ verify(typeDescription).getDeclaredMethods();
+ verifyNoMoreInteractions(typeDescription);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/DeclaringTypeMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/DeclaringTypeMatcherTest.java
new file mode 100644
index 0000000..1973046
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/DeclaringTypeMatcherTest.java
@@ -0,0 +1,63 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.DeclaredByType;
+import net.bytebuddy.description.type.TypeDescription;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class DeclaringTypeMatcherTest extends AbstractElementMatcherTest<DeclaringTypeMatcher<?>> {
+
+ @Mock
+ private ElementMatcher<? super TypeDescription.Generic> typeMatcher;
+
+ @Mock
+ private DeclaredByType declaredByType;
+
+ @Mock
+ private TypeDescription.Generic typeDescription;
+
+ @SuppressWarnings("unchecked")
+ public DeclaringTypeMatcherTest() {
+ super((Class<DeclaringTypeMatcher<?>>) (Object) DeclaringTypeMatcher.class, "declaredBy");
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(typeDescription.asGenericType()).thenReturn(typeDescription);
+ }
+
+ @Test
+ public void testMatch() throws Exception {
+ when(declaredByType.getDeclaringType()).thenReturn(typeDescription);
+ when(typeMatcher.matches(typeDescription)).thenReturn(true);
+ assertThat(new DeclaringTypeMatcher<DeclaredByType>(typeMatcher).matches(declaredByType), is(true));
+ verify(typeMatcher).matches(typeDescription);
+ verifyNoMoreInteractions(typeMatcher);
+ verify(declaredByType).getDeclaringType();
+ verifyNoMoreInteractions(declaredByType);
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ when(declaredByType.getDeclaringType()).thenReturn(typeDescription);
+ when(typeMatcher.matches(typeDescription)).thenReturn(false);
+ assertThat(new DeclaringTypeMatcher<DeclaredByType>(typeMatcher).matches(declaredByType), is(false));
+ verify(typeMatcher).matches(typeDescription);
+ verifyNoMoreInteractions(typeMatcher);
+ verify(declaredByType).getDeclaringType();
+ verifyNoMoreInteractions(declaredByType);
+ }
+
+ @Test
+ public void testNoMatchWhenNull() throws Exception {
+ assertThat(new DeclaringTypeMatcher<DeclaredByType>(typeMatcher).matches(declaredByType), is(false));
+ verifyZeroInteractions(typeMatcher);
+ verify(declaredByType).getDeclaringType();
+ verifyNoMoreInteractions(declaredByType);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/DefinedShapeMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/DefinedShapeMatcherTest.java
new file mode 100644
index 0000000..7aa7659
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/DefinedShapeMatcherTest.java
@@ -0,0 +1,47 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.ByteCodeElement;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class DefinedShapeMatcherTest extends AbstractElementMatcherTest<DefinedShapeMatcher<?, ?>> {
+
+ @Mock
+ private ByteCodeElement.TypeDependant<?, ?> dependent, resolvedDependant, otherResolvedDependant;
+
+ @Mock
+ private ElementMatcher<ByteCodeElement.TypeDependant<?, ?>> matcher;
+
+ @SuppressWarnings("unchecked")
+ public DefinedShapeMatcherTest() {
+ super((Class<? extends DefinedShapeMatcher<?, ?>>) (Object) DefinedShapeMatcher.class, "isDefinedAs");
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMatch() throws Exception {
+ when(matcher.matches(resolvedDependant)).thenReturn(true);
+ when(dependent.asDefined()).thenReturn((ByteCodeElement.TypeDependant) resolvedDependant);
+ assertThat(new DefinedShapeMatcher(matcher).matches(dependent), is(true));
+ verify(dependent).asDefined();
+ verifyNoMoreInteractions(dependent);
+ verify(matcher).matches(resolvedDependant);
+ verifyNoMoreInteractions(matcher);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testNoMatch() throws Exception {
+ when(matcher.matches(resolvedDependant)).thenReturn(true);
+ when(dependent.asDefined()).thenReturn((ByteCodeElement.TypeDependant) otherResolvedDependant);
+ assertThat(new DefinedShapeMatcher(matcher).matches(dependent), is(false));
+ verify(dependent).asDefined();
+ verifyNoMoreInteractions(dependent);
+ verify(matcher).matches(otherResolvedDependant);
+ verifyNoMoreInteractions(matcher);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/DescriptorMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/DescriptorMatcherTest.java
new file mode 100644
index 0000000..9359f18
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/DescriptorMatcherTest.java
@@ -0,0 +1,47 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.ByteCodeElement;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class DescriptorMatcherTest extends AbstractElementMatcherTest<DescriptorMatcher<?>> {
+
+ private static final String FOO = "foo";
+
+ @Mock
+ private ElementMatcher<String> descriptorMatcher;
+
+ @Mock
+ private ByteCodeElement byteCodeElement;
+
+ @SuppressWarnings("unchecked")
+ public DescriptorMatcherTest() {
+ super((Class<DescriptorMatcher<?>>) (Object) DescriptorMatcher.class, "hasDescriptor");
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(byteCodeElement.getDescriptor()).thenReturn(FOO);
+ }
+
+ @Test
+ public void testMatch() throws Exception {
+ when(descriptorMatcher.matches(FOO)).thenReturn(true);
+ assertThat(new DescriptorMatcher<ByteCodeElement>(descriptorMatcher).matches(byteCodeElement), is(true));
+ verify(descriptorMatcher).matches(FOO);
+ verifyNoMoreInteractions(descriptorMatcher);
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ when(descriptorMatcher.matches(FOO)).thenReturn(false);
+ assertThat(new DescriptorMatcher<ByteCodeElement>(descriptorMatcher).matches(byteCodeElement), is(false));
+ verify(descriptorMatcher).matches(FOO);
+ verifyNoMoreInteractions(descriptorMatcher);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/ElementMatcherJunctionConjunctionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/ElementMatcherJunctionConjunctionTest.java
new file mode 100644
index 0000000..9f6f690
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/ElementMatcherJunctionConjunctionTest.java
@@ -0,0 +1,58 @@
+package net.bytebuddy.matcher;
+
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class ElementMatcherJunctionConjunctionTest extends AbstractElementMatcherTest<ElementMatcher.Junction.Conjunction<?>> {
+
+ @Mock
+ private ElementMatcher<? super Object> first, second;
+
+ @SuppressWarnings("unchecked")
+ public ElementMatcherJunctionConjunctionTest() {
+ super((Class<? extends ElementMatcher.Junction.Conjunction<?>>) (Object) ElementMatcher.Junction.Conjunction.class, "");
+ }
+
+ @Test
+ public void testApplicationBoth() throws Exception {
+ Object target = new Object();
+ when(first.matches(target)).thenReturn(true);
+ when(second.matches(target)).thenReturn(true);
+ assertThat(new ElementMatcher.Junction.Conjunction<Object>(first, second).matches(target), is(true));
+ verify(first).matches(target);
+ verifyNoMoreInteractions(first);
+ verify(second).matches(target);
+ verifyNoMoreInteractions(second);
+ }
+
+ @Test
+ public void testApplicationFirstOnly() throws Exception {
+ Object target = new Object();
+ when(first.matches(target)).thenReturn(false);
+ assertThat(new ElementMatcher.Junction.Conjunction<Object>(first, second).matches(target), is(false));
+ verify(first).matches(target);
+ verifyNoMoreInteractions(first);
+ verifyZeroInteractions(second);
+ }
+
+ @Test
+ public void testApplicationBothNegative() throws Exception {
+ Object target = new Object();
+ when(first.matches(target)).thenReturn(true);
+ when(second.matches(target)).thenReturn(false);
+ assertThat(new ElementMatcher.Junction.Conjunction<Object>(first, second).matches(target), is(false));
+ verify(first).matches(target);
+ verifyNoMoreInteractions(first);
+ verify(second).matches(target);
+ verifyNoMoreInteractions(second);
+ }
+
+ @Override
+ protected String makeRegex(String startsWith) {
+ return "^(.* and .*)$";
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/ElementMatcherJunctionDisjunctionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/ElementMatcherJunctionDisjunctionTest.java
new file mode 100644
index 0000000..73dfc2a
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/ElementMatcherJunctionDisjunctionTest.java
@@ -0,0 +1,58 @@
+package net.bytebuddy.matcher;
+
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class ElementMatcherJunctionDisjunctionTest extends AbstractElementMatcherTest<ElementMatcher.Junction.Disjunction<?>> {
+
+ @Mock
+ private ElementMatcher<? super Object> first, second;
+
+ @SuppressWarnings("unchecked")
+ public ElementMatcherJunctionDisjunctionTest() {
+ super((Class<? extends ElementMatcher.Junction.Disjunction<?>>) (Object) ElementMatcher.Junction.Disjunction.class, "");
+ }
+
+ @Test
+ public void testApplicationBoth() throws Exception {
+ Object target = new Object();
+ when(first.matches(target)).thenReturn(false);
+ when(second.matches(target)).thenReturn(false);
+ assertThat(new ElementMatcher.Junction.Disjunction<Object>(first, second).matches(target), is(false));
+ verify(first).matches(target);
+ verifyNoMoreInteractions(first);
+ verify(second).matches(target);
+ verifyNoMoreInteractions(second);
+ }
+
+ @Test
+ public void testApplicationFirstOnly() throws Exception {
+ Object target = new Object();
+ when(first.matches(target)).thenReturn(true);
+ assertThat(new ElementMatcher.Junction.Disjunction<Object>(first, second).matches(target), is(true));
+ verify(first).matches(target);
+ verifyNoMoreInteractions(first);
+ verifyZeroInteractions(second);
+ }
+
+ @Test
+ public void testApplicationBothPositive() throws Exception {
+ Object target = new Object();
+ when(first.matches(target)).thenReturn(false);
+ when(second.matches(target)).thenReturn(true);
+ assertThat(new ElementMatcher.Junction.Disjunction<Object>(first, second).matches(target), is(true));
+ verify(first).matches(target);
+ verifyNoMoreInteractions(first);
+ verify(second).matches(target);
+ verifyNoMoreInteractions(second);
+ }
+
+ @Override
+ protected String makeRegex(String startsWith) {
+ return "^(.* or .*)$";
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/ElementMatchersTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/ElementMatchersTest.java
new file mode 100644
index 0000000..6fd5641
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/ElementMatchersTest.java
@@ -0,0 +1,1516 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.ByteCodeElement;
+import net.bytebuddy.description.ModifierReviewable;
+import net.bytebuddy.description.NamedElement;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import net.bytebuddy.utility.JavaModule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+import org.objectweb.asm.Opcodes;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.*;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.*;
+
+public class ElementMatchersTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux";
+
+ private static final String SINGLE_DEFAULT_METHOD = "net.bytebuddy.test.precompiled.SingleDefaultMethodInterface";
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ @Test
+ @SuppressWarnings({"unchecked", "row"})
+ public void testFailSafe() throws Exception {
+ ElementMatcher<Object> exceptional = mock(ElementMatcher.class), nonExceptional = mock(ElementMatcher.class);
+ when(exceptional.matches(any())).thenThrow(RuntimeException.class);
+ when(nonExceptional.matches(any())).thenReturn(true);
+ assertThat(ElementMatchers.failSafe(exceptional).matches(new Object()), is(false));
+ assertThat(ElementMatchers.failSafe(nonExceptional).matches(new Object()), is(true));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testCachedNegativeSize() throws Exception {
+ ElementMatchers.cached(new BooleanMatcher<Object>(true), -1);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testCachingMatcherEvictionSize() throws Exception {
+ ElementMatcher<Object> delegate = mock(ElementMatcher.class);
+ ElementMatcher<Object> matcher = ElementMatchers.cached(delegate, 1);
+ Object target = new Object();
+ when(delegate.matches(target)).thenReturn(true);
+ assertThat(matcher.matches(target), is(true));
+ assertThat(matcher.matches(target), is(true));
+ verify(delegate).matches(target);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testCachingMatcherMap() throws Exception {
+ ElementMatcher<Object> delegate = mock(ElementMatcher.class);
+ ConcurrentMap<Object, Boolean> map = new ConcurrentHashMap<Object, Boolean>();
+ ElementMatcher<Object> matcher = ElementMatchers.cached(delegate, map);
+ Object target = new Object();
+ when(delegate.matches(target)).thenReturn(true);
+ assertThat(matcher.matches(target), is(true));
+ assertThat(matcher.matches(target), is(true));
+ verify(delegate).matches(target);
+ assertThat(map.get(target), is(true));
+ }
+
+ @Test
+ public void testIs() throws Exception {
+ Object value = new Object();
+ assertThat(ElementMatchers.is(value).matches(value), is(true));
+ assertThat(ElementMatchers.is(value).matches(new Object()), is(false));
+ assertThat(ElementMatchers.is((Object) null).matches(null), is(true));
+ assertThat(ElementMatchers.is((Object) null).matches(new Object()), is(false));
+ }
+
+ @Test
+ public void testIsInterface() throws Exception {
+ assertThat(ElementMatchers.isInterface().matches(new TypeDescription.ForLoadedType(Collection.class)), is(true));
+ assertThat(ElementMatchers.isInterface().matches(new TypeDescription.ForLoadedType(ArrayList.class)), is(false));
+ }
+
+ @Test
+ public void testIsType() throws Exception {
+ assertThat(ElementMatchers.is(Object.class).matches(TypeDescription.OBJECT), is(true));
+ assertThat(ElementMatchers.is(String.class).matches(TypeDescription.OBJECT), is(false));
+ }
+
+ @Test
+ public void testIsField() throws Exception {
+ assertThat(ElementMatchers.is(FieldSample.class.getDeclaredField("foo"))
+ .matches(new FieldDescription.ForLoadedField(FieldSample.class.getDeclaredField("foo"))), is(true));
+ assertThat(ElementMatchers.is(FieldSample.class.getDeclaredField("bar"))
+ .matches(new FieldDescription.ForLoadedField(FieldSample.class.getDeclaredField("foo"))), is(false));
+ }
+
+ @Test
+ public void testIsVolatile() throws Exception {
+ assertThat(ElementMatchers.isVolatile().matches(new FieldDescription.ForLoadedField(FieldSample.class.getDeclaredField("foo"))), is(false));
+ assertThat(ElementMatchers.isVolatile().matches(new FieldDescription.ForLoadedField(FieldSample.class.getDeclaredField("qux"))), is(true));
+ assertThat(ElementMatchers.isVolatile().matches(new FieldDescription.ForLoadedField(FieldSample.class.getDeclaredField("baz"))), is(false));
+ }
+
+ @Test
+ public void testIsTransient() throws Exception {
+ assertThat(ElementMatchers.isTransient().matches(new FieldDescription.ForLoadedField(FieldSample.class.getDeclaredField("foo"))), is(false));
+ assertThat(ElementMatchers.isTransient().matches(new FieldDescription.ForLoadedField(FieldSample.class.getDeclaredField("qux"))), is(false));
+ assertThat(ElementMatchers.isTransient().matches(new FieldDescription.ForLoadedField(FieldSample.class.getDeclaredField("baz"))), is(true));
+ }
+
+ @Test
+ public void testIsFieldDefinedShape() throws Exception {
+ Field field = GenericFieldType.class.getDeclaredField(FOO);
+ FieldDescription fieldDescription = new TypeDescription.ForLoadedType(GenericFieldType.Inner.class).getSuperClass()
+ .getDeclaredFields().filter(named(FOO)).getOnly();
+ assertThat(ElementMatchers.is(field).matches(fieldDescription), is(true));
+ assertThat(ElementMatchers.definedField(ElementMatchers.is(fieldDescription.asDefined())).matches(fieldDescription), is(true));
+ assertThat(ElementMatchers.is(fieldDescription.asDefined()).matches(fieldDescription.asDefined()), is(true));
+ assertThat(ElementMatchers.is(fieldDescription.asDefined()).matches(fieldDescription), is(true));
+ assertThat(ElementMatchers.is(fieldDescription).matches(fieldDescription.asDefined()), is(false));
+ }
+
+ @Test
+ public void testIsMethodOrConstructor() throws Exception {
+ assertThat(ElementMatchers.is(Object.class.getDeclaredMethod("toString"))
+ .matches(new MethodDescription.ForLoadedMethod(Object.class.getDeclaredMethod("toString"))), is(true));
+ assertThat(ElementMatchers.is(Object.class.getDeclaredMethod("toString"))
+ .matches(new MethodDescription.ForLoadedMethod(Object.class.getDeclaredMethod("hashCode"))), is(false));
+ assertThat(ElementMatchers.is(Object.class.getDeclaredConstructor())
+ .matches(new MethodDescription.ForLoadedConstructor(Object.class.getDeclaredConstructor())), is(true));
+ assertThat(ElementMatchers.is(Object.class.getDeclaredConstructor())
+ .matches(new MethodDescription.ForLoadedMethod(Object.class.getDeclaredMethod("hashCode"))), is(false));
+ }
+
+ @Test
+ public void testIsMethodDefinedShape() throws Exception {
+ Method method = GenericMethodType.class.getDeclaredMethod("foo", Exception.class);
+ MethodDescription methodDescription = new TypeDescription.ForLoadedType(GenericMethodType.Inner.class).getInterfaces().getOnly()
+ .getDeclaredMethods().filter(named(FOO)).getOnly();
+ assertThat(ElementMatchers.is(method).matches(methodDescription), is(true));
+ assertThat(ElementMatchers.definedMethod(ElementMatchers.is(methodDescription.asDefined())).matches(methodDescription), is(true));
+ assertThat(ElementMatchers.is(methodDescription.asDefined()).matches(methodDescription.asDefined()), is(true));
+ assertThat(ElementMatchers.is(methodDescription.asDefined()).matches(methodDescription), is(true));
+ assertThat(ElementMatchers.is(methodDescription).matches(methodDescription.asDefined()), is(false));
+ }
+
+ @Test
+ public void testIsConstructorDefinedShape() throws Exception {
+ Constructor<?> constructor = GenericConstructorType.class.getDeclaredConstructor(Exception.class);
+ MethodDescription methodDescription = new TypeDescription.ForLoadedType(GenericConstructorType.Inner.class).getSuperClass()
+ .getDeclaredMethods().filter(isConstructor()).getOnly();
+ assertThat(ElementMatchers.is(constructor).matches(methodDescription), is(true));
+ assertThat(ElementMatchers.definedMethod(ElementMatchers.is(methodDescription.asDefined())).matches(methodDescription), is(true));
+ assertThat(ElementMatchers.is(methodDescription.asDefined()).matches(methodDescription.asDefined()), is(true));
+ assertThat(ElementMatchers.is(methodDescription.asDefined()).matches(methodDescription), is(true));
+ assertThat(ElementMatchers.is(methodDescription).matches(methodDescription.asDefined()), is(false));
+ }
+
+ @Test
+ public void testIsParameterDefinedShape() throws Exception {
+ ParameterDescription parameterDescription = new TypeDescription.ForLoadedType(GenericMethodType.Inner.class).getInterfaces().getOnly()
+ .getDeclaredMethods().filter(named(FOO)).getOnly().getParameters().getOnly();
+ assertThat(ElementMatchers.definedParameter(ElementMatchers.is(parameterDescription.asDefined())).matches(parameterDescription), is(true));
+ assertThat(ElementMatchers.is(parameterDescription.asDefined()).matches(parameterDescription.asDefined()), is(true));
+ assertThat(ElementMatchers.is(parameterDescription.asDefined()).matches(parameterDescription), is(true));
+ assertThat(ElementMatchers.is(parameterDescription).matches(parameterDescription.asDefined()), is(false));
+ }
+
+ @Test
+ public void testIsAnnotation() throws Exception {
+ AnnotationDescription annotationDescription = new TypeDescription.ForLoadedType(IsAnnotatedWith.class)
+ .getDeclaredAnnotations().ofType(IsAnnotatedWithAnnotation.class);
+ assertThat(ElementMatchers.is(IsAnnotatedWith.class.getAnnotation(IsAnnotatedWithAnnotation.class)).matches(annotationDescription), is(true));
+ assertThat(ElementMatchers.is(Other.class.getAnnotation(OtherAnnotation.class)).matches(annotationDescription), is(false));
+ }
+
+ @Test
+ public void testNot() throws Exception {
+ Object value = new Object();
+ @SuppressWarnings("unchecked")
+ ElementMatcher<Object> elementMatcher = mock(ElementMatcher.class);
+ when(elementMatcher.matches(value)).thenReturn(true);
+ assertThat(ElementMatchers.not(elementMatcher).matches(value), is(false));
+ verify(elementMatcher).matches(value);
+ Object otherValue = new Object();
+ assertThat(ElementMatchers.not(elementMatcher).matches(otherValue), is(true));
+ verify(elementMatcher).matches(otherValue);
+ verifyNoMoreInteractions(elementMatcher);
+ }
+
+ @Test
+ public void testAny() throws Exception {
+ assertThat(ElementMatchers.any().matches(new Object()), is(true));
+ }
+
+ @Test
+ public void testAnyOfType() throws Exception {
+ assertThat(ElementMatchers.anyOf(Object.class).matches(TypeDescription.OBJECT), is(true));
+ assertThat(ElementMatchers.anyOf(String.class, Object.class).matches(TypeDescription.OBJECT), is(true));
+ assertThat(ElementMatchers.anyOf(String.class).matches(TypeDescription.OBJECT), is(false));
+ }
+
+ @Test
+ public void testAnyOfMethodOrConstructor() throws Exception {
+ Method toString = Object.class.getDeclaredMethod("toString"), hashCode = Object.class.getDeclaredMethod("hashCode");
+ assertThat(ElementMatchers.anyOf(toString)
+ .matches(new MethodDescription.ForLoadedMethod(Object.class.getDeclaredMethod("toString"))), is(true));
+ assertThat(ElementMatchers.anyOf(toString, hashCode)
+ .matches(new MethodDescription.ForLoadedMethod(Object.class.getDeclaredMethod("toString"))), is(true));
+ assertThat(ElementMatchers.anyOf(toString)
+ .matches(new MethodDescription.ForLoadedMethod(Object.class.getDeclaredMethod("hashCode"))), is(false));
+ assertThat(ElementMatchers.anyOf(Object.class.getDeclaredConstructor())
+ .matches(new MethodDescription.ForLoadedConstructor(Object.class.getDeclaredConstructor())), is(true));
+ assertThat(ElementMatchers.anyOf(Object.class.getDeclaredConstructor(), String.class.getDeclaredConstructor(String.class))
+ .matches(new MethodDescription.ForLoadedConstructor(Object.class.getDeclaredConstructor())), is(true));
+ assertThat(ElementMatchers.anyOf(Object.class.getDeclaredConstructor())
+ .matches(new MethodDescription.ForLoadedMethod(Object.class.getDeclaredMethod("hashCode"))), is(false));
+ }
+
+ @Test
+ public void testAnyMethodDefinedShape() throws Exception {
+ Method method = GenericMethodType.class.getDeclaredMethod("foo", Exception.class);
+ MethodDescription methodDescription = new TypeDescription.ForLoadedType(GenericMethodType.Inner.class).getInterfaces().getOnly()
+ .getDeclaredMethods().filter(named(FOO)).getOnly();
+ assertThat(ElementMatchers.anyOf(method).matches(methodDescription), is(true));
+ assertThat(ElementMatchers.definedMethod(ElementMatchers.anyOf(methodDescription.asDefined())).matches(methodDescription), is(true));
+ assertThat(ElementMatchers.anyOf(methodDescription.asDefined()).matches(methodDescription.asDefined()), is(true));
+ assertThat(ElementMatchers.anyOf(methodDescription.asDefined()).matches(methodDescription), is(false));
+ assertThat(ElementMatchers.anyOf(methodDescription).matches(methodDescription.asDefined()), is(false));
+ }
+
+ @Test
+ public void testAnyOfConstructorDefinedShape() throws Exception {
+ Constructor<?> constructor = GenericConstructorType.class.getDeclaredConstructor(Exception.class);
+ MethodDescription methodDescription = new TypeDescription.ForLoadedType(GenericConstructorType.Inner.class).getSuperClass()
+ .getDeclaredMethods().filter(isConstructor()).getOnly();
+ assertThat(ElementMatchers.anyOf(constructor).matches(methodDescription), is(true));
+ assertThat(ElementMatchers.definedMethod(ElementMatchers.anyOf(methodDescription.asDefined())).matches(methodDescription), is(true));
+ assertThat(ElementMatchers.anyOf(methodDescription.asDefined()).matches(methodDescription.asDefined()), is(true));
+ assertThat(ElementMatchers.anyOf(methodDescription.asDefined()).matches(methodDescription), is(false));
+ assertThat(ElementMatchers.anyOf(methodDescription).matches(methodDescription.asDefined()), is(false));
+ }
+
+ @Test
+ public void testAnyOfField() throws Exception {
+ assertThat(ElementMatchers.anyOf(Integer.class.getDeclaredField("MAX_VALUE"))
+ .matches(new FieldDescription.ForLoadedField(Integer.class.getDeclaredField("MAX_VALUE"))), is(true));
+ assertThat(ElementMatchers.anyOf(Integer.class.getDeclaredField("MAX_VALUE"), Integer.class.getDeclaredField("MIN_VALUE"))
+ .matches(new FieldDescription.ForLoadedField(Integer.class.getDeclaredField("MAX_VALUE"))), is(true));
+ assertThat(ElementMatchers.anyOf(Integer.class.getDeclaredField("MAX_VALUE"), Integer.class.getDeclaredField("MIN_VALUE"))
+ .matches(new FieldDescription.ForLoadedField(Integer.class.getDeclaredField("SIZE"))), is(false));
+ }
+
+ @Test
+ public void testAnyOfFieldDefinedShape() throws Exception {
+ Field field = GenericFieldType.class.getDeclaredField(FOO);
+ FieldDescription fieldDescription = new TypeDescription.ForLoadedType(GenericFieldType.Inner.class).getSuperClass()
+ .getDeclaredFields().filter(named(FOO)).getOnly();
+ assertThat(ElementMatchers.anyOf(field).matches(fieldDescription), is(true));
+ assertThat(ElementMatchers.definedField(ElementMatchers.anyOf(fieldDescription.asDefined())).matches(fieldDescription), is(true));
+ assertThat(ElementMatchers.anyOf(fieldDescription.asDefined()).matches(fieldDescription.asDefined()), is(true));
+ assertThat(ElementMatchers.anyOf(fieldDescription.asDefined()).matches(fieldDescription), is(false));
+ assertThat(ElementMatchers.anyOf(fieldDescription).matches(fieldDescription.asDefined()), is(false));
+ }
+
+ @Test
+ public void testAnyOfAnnotation() throws Exception {
+ AnnotationDescription annotationDescription = new TypeDescription.ForLoadedType(IsAnnotatedWith.class)
+ .getDeclaredAnnotations().ofType(IsAnnotatedWithAnnotation.class);
+ assertThat(ElementMatchers.anyOf(IsAnnotatedWith.class.getAnnotation(IsAnnotatedWithAnnotation.class))
+ .matches(annotationDescription), is(true));
+ assertThat(ElementMatchers.anyOf(IsAnnotatedWith.class.getAnnotation(IsAnnotatedWithAnnotation.class),
+ Other.class.getAnnotation(OtherAnnotation.class)).matches(annotationDescription), is(true));
+ assertThat(ElementMatchers.anyOf(Other.class.getAnnotation(OtherAnnotation.class)).matches(annotationDescription), is(false));
+ }
+
+ @Test
+ public void testAnnotationType() throws Exception {
+ AnnotationDescription annotationDescription = new TypeDescription.ForLoadedType(IsAnnotatedWith.class)
+ .getDeclaredAnnotations().ofType(IsAnnotatedWithAnnotation.class);
+ assertThat(ElementMatchers.annotationType(IsAnnotatedWithAnnotation.class).matches(annotationDescription), is(true));
+ assertThat(ElementMatchers.annotationType(OtherAnnotation.class).matches(annotationDescription), is(false));
+ assertThat(ElementMatchers.annotationType(IsAnnotatedWithAnnotation.class)
+ .matches(AnnotationDescription.ForLoadedAnnotation.of(Other.class.getAnnotation(OtherAnnotation.class))), is(false));
+ }
+
+ @Test
+ public void testNone() throws Exception {
+ assertThat(ElementMatchers.none().matches(new Object()), is(false));
+ }
+
+ @Test
+ public void testNoneOfType() throws Exception {
+ assertThat(ElementMatchers.noneOf(Object.class).matches(TypeDescription.OBJECT), is(false));
+ assertThat(ElementMatchers.noneOf(String.class, Object.class).matches(TypeDescription.OBJECT), is(false));
+ assertThat(ElementMatchers.noneOf(String.class).matches(TypeDescription.OBJECT), is(true));
+ }
+
+ @Test
+ public void testNoneOfConstructorDefinedShape() throws Exception {
+ Constructor<?> constructor = GenericConstructorType.class.getDeclaredConstructor(Exception.class);
+ MethodDescription methodDescription = new TypeDescription.ForLoadedType(GenericConstructorType.Inner.class).getSuperClass()
+ .getDeclaredMethods().filter(isConstructor()).getOnly();
+ assertThat(ElementMatchers.noneOf(constructor).matches(methodDescription), is(false));
+ assertThat(ElementMatchers.definedMethod(ElementMatchers.noneOf(methodDescription.asDefined())).matches(methodDescription), is(false));
+ assertThat(ElementMatchers.noneOf(methodDescription.asDefined()).matches(methodDescription.asDefined()), is(false));
+ assertThat(ElementMatchers.noneOf(methodDescription.asDefined()).matches(methodDescription), is(true));
+ assertThat(ElementMatchers.noneOf(methodDescription).matches(methodDescription.asDefined()), is(true));
+ }
+
+ @Test
+ public void testNoneOfMethodDefinedShape() throws Exception {
+ Method method = GenericMethodType.class.getDeclaredMethod("foo", Exception.class);
+ MethodDescription methodDescription = new TypeDescription.ForLoadedType(GenericMethodType.Inner.class).getInterfaces().getOnly()
+ .getDeclaredMethods().filter(named(FOO)).getOnly();
+ assertThat(ElementMatchers.noneOf(method).matches(methodDescription), is(false));
+ assertThat(ElementMatchers.definedMethod(ElementMatchers.noneOf(methodDescription.asDefined())).matches(methodDescription), is(false));
+ assertThat(ElementMatchers.noneOf(methodDescription.asDefined()).matches(methodDescription.asDefined()), is(false));
+ assertThat(ElementMatchers.noneOf(methodDescription.asDefined()).matches(methodDescription), is(true));
+ assertThat(ElementMatchers.noneOf(methodDescription).matches(methodDescription.asDefined()), is(true));
+ }
+
+ @Test
+ public void testNoneOfField() throws Exception {
+ assertThat(ElementMatchers.noneOf(FieldSample.class.getDeclaredField("foo"))
+ .matches(new FieldDescription.ForLoadedField(FieldSample.class.getDeclaredField("foo"))), is(false));
+ assertThat(ElementMatchers.noneOf(FieldSample.class.getDeclaredField("bar"))
+ .matches(new FieldDescription.ForLoadedField(FieldSample.class.getDeclaredField("foo"))), is(true));
+ assertThat(ElementMatchers.noneOf(FieldSample.class.getDeclaredField("foo"), FieldSample.class.getDeclaredField("bar"))
+ .matches(new FieldDescription.ForLoadedField(FieldSample.class.getDeclaredField("foo"))), is(false));
+ }
+
+ @Test
+ public void testNoneOfFieldDefinedShape() throws Exception {
+ Field field = GenericFieldType.class.getDeclaredField(FOO);
+ FieldDescription fieldDescription = new TypeDescription.ForLoadedType(GenericFieldType.Inner.class).getSuperClass()
+ .getDeclaredFields().filter(named(FOO)).getOnly();
+ assertThat(ElementMatchers.noneOf(field).matches(fieldDescription), is(false));
+ assertThat(ElementMatchers.definedField(ElementMatchers.noneOf(fieldDescription.asDefined())).matches(fieldDescription), is(false));
+ assertThat(ElementMatchers.noneOf(fieldDescription.asDefined()).matches(fieldDescription.asDefined()), is(false));
+ assertThat(ElementMatchers.noneOf(fieldDescription.asDefined()).matches(fieldDescription), is(true));
+ assertThat(ElementMatchers.noneOf(fieldDescription).matches(fieldDescription.asDefined()), is(true));
+ }
+
+ @Test
+ public void testNoneAnnotation() throws Exception {
+ AnnotationDescription annotationDescription = new TypeDescription.ForLoadedType(IsAnnotatedWith.class)
+ .getDeclaredAnnotations().ofType(IsAnnotatedWithAnnotation.class);
+ assertThat(ElementMatchers.noneOf(IsAnnotatedWith.class.getAnnotation(IsAnnotatedWithAnnotation.class))
+ .matches(annotationDescription), is(false));
+ assertThat(ElementMatchers.noneOf(IsAnnotatedWith.class.getAnnotation(IsAnnotatedWithAnnotation.class),
+ Other.class.getAnnotation(OtherAnnotation.class)).matches(annotationDescription), is(false));
+ assertThat(ElementMatchers.noneOf(Other.class.getAnnotation(OtherAnnotation.class)).matches(annotationDescription), is(true));
+ }
+
+ @Test
+ public void testAnyOf() throws Exception {
+ Object value = new Object(), otherValue = new Object();
+ assertThat(ElementMatchers.anyOf(value, otherValue).matches(value), is(true));
+ assertThat(ElementMatchers.anyOf(value, otherValue).matches(otherValue), is(true));
+ assertThat(ElementMatchers.anyOf(value, otherValue).matches(new Object()), is(false));
+ }
+
+ @Test
+ public void testNoneOf() throws Exception {
+ Object value = new Object(), otherValue = new Object();
+ assertThat(ElementMatchers.noneOf(value, otherValue).matches(value), is(false));
+ assertThat(ElementMatchers.noneOf(value, otherValue).matches(otherValue), is(false));
+ assertThat(ElementMatchers.noneOf(value, otherValue).matches(new Object()), is(true));
+ }
+
+ @Test
+ public void testWhereAny() throws Exception {
+ assertThat(ElementMatchers.whereAny(ElementMatchers.is(FOO)).matches(Arrays.asList(FOO, BAR)), is(true));
+ assertThat(ElementMatchers.whereAny(ElementMatchers.is(FOO)).matches(Arrays.asList(BAR, QUX)), is(false));
+ }
+
+ @Test
+ public void testWhereNone() throws Exception {
+ assertThat(ElementMatchers.whereNone(ElementMatchers.is(FOO)).matches(Arrays.asList(FOO, BAR)), is(false));
+ assertThat(ElementMatchers.whereNone(ElementMatchers.is(FOO)).matches(Arrays.asList(BAR, QUX)), is(true));
+ }
+
+ @Test
+ public void testRawType() throws Exception {
+ assertThat(ElementMatchers.erasure(Exception.class).matches(TypeDefinition.Sort.describe(GenericMethodType.class.getTypeParameters()[0])), is(true));
+ assertThat(ElementMatchers.erasure(Object.class).matches(TypeDefinition.Sort.describe(GenericMethodType.class.getTypeParameters()[0])), is(false));
+ }
+
+ @Test
+ public void testRawTypes() throws Exception {
+ assertThat(ElementMatchers.erasures(Exception.class)
+ .matches(Collections.singletonList(TypeDefinition.Sort.describe(GenericMethodType.class.getTypeParameters()[0]))), is(true));
+ assertThat(ElementMatchers.erasures(Object.class)
+ .matches(Collections.singletonList(TypeDefinition.Sort.describe(GenericMethodType.class.getTypeParameters()[0]))), is(false));
+ }
+
+ @Test
+ public void testIsTypeVariable() throws Exception {
+ assertThat(ElementMatchers.isVariable("T").matches(new TypeDescription.ForLoadedType(GenericDeclaredBy.class).getTypeVariables().getOnly()), is(true));
+ assertThat(ElementMatchers.isVariable(FOO).matches(new TypeDescription.ForLoadedType(GenericDeclaredBy.class).getTypeVariables().getOnly()), is(false));
+ assertThat(ElementMatchers.isVariable(FOO).matches(TypeDescription.OBJECT), is(false));
+ }
+
+ @Test
+ public void testMethodName() throws Exception {
+ assertThat(ElementMatchers.hasMethodName(MethodDescription.TYPE_INITIALIZER_INTERNAL_NAME), is(ElementMatchers.isTypeInitializer()));
+ assertThat(ElementMatchers.hasMethodName(MethodDescription.CONSTRUCTOR_INTERNAL_NAME), is(ElementMatchers.isConstructor()));
+ ElementMatcher<MethodDescription> nameMatcher = named(FOO);
+ assertThat(ElementMatchers.hasMethodName(FOO), is(nameMatcher));
+ }
+
+ @Test
+ public void testNamed() throws Exception {
+ ByteCodeElement byteCodeElement = mock(ByteCodeElement.class);
+ when(byteCodeElement.getActualName()).thenReturn(FOO);
+ assertThat(named(FOO).matches(byteCodeElement), is(true));
+ assertThat(named(FOO.toUpperCase()).matches(byteCodeElement), is(false));
+ assertThat(named(BAR).matches(byteCodeElement), is(false));
+ }
+
+ @Test
+ public void testNamedIgnoreCase() throws Exception {
+ ByteCodeElement byteCodeElement = mock(ByteCodeElement.class);
+ when(byteCodeElement.getActualName()).thenReturn(FOO);
+ assertThat(ElementMatchers.namedIgnoreCase(FOO).matches(byteCodeElement), is(true));
+ assertThat(ElementMatchers.namedIgnoreCase(FOO.toUpperCase()).matches(byteCodeElement), is(true));
+ assertThat(ElementMatchers.namedIgnoreCase(BAR).matches(byteCodeElement), is(false));
+ }
+
+ @Test
+ public void testNameStartsWith() throws Exception {
+ ByteCodeElement byteCodeElement = mock(ByteCodeElement.class);
+ when(byteCodeElement.getActualName()).thenReturn(FOO);
+ assertThat(ElementMatchers.nameStartsWith(FOO.substring(0, 2)).matches(byteCodeElement), is(true));
+ assertThat(ElementMatchers.nameStartsWith(FOO.substring(0, 2).toUpperCase()).matches(byteCodeElement), is(false));
+ assertThat(ElementMatchers.nameStartsWith(BAR).matches(byteCodeElement), is(false));
+ }
+
+ @Test
+ public void testNameStartsWithIgnoreCase() throws Exception {
+ ByteCodeElement byteCodeElement = mock(ByteCodeElement.class);
+ when(byteCodeElement.getActualName()).thenReturn(FOO);
+ assertThat(ElementMatchers.nameStartsWithIgnoreCase(FOO.substring(0, 2)).matches(byteCodeElement), is(true));
+ assertThat(ElementMatchers.nameStartsWithIgnoreCase(FOO.substring(0, 2).toUpperCase()).matches(byteCodeElement), is(true));
+ assertThat(ElementMatchers.nameStartsWithIgnoreCase(BAR).matches(byteCodeElement), is(false));
+ }
+
+ @Test
+ public void testNameEndsWith() throws Exception {
+ ByteCodeElement byteCodeElement = mock(ByteCodeElement.class);
+ when(byteCodeElement.getActualName()).thenReturn(FOO);
+ assertThat(ElementMatchers.nameEndsWith(FOO.substring(1)).matches(byteCodeElement), is(true));
+ assertThat(ElementMatchers.nameEndsWith(FOO.substring(1).toUpperCase()).matches(byteCodeElement), is(false));
+ assertThat(ElementMatchers.nameEndsWith(BAR).matches(byteCodeElement), is(false));
+ }
+
+ @Test
+ public void testNameEndsWithIgnoreCase() throws Exception {
+ ByteCodeElement byteCodeElement = mock(ByteCodeElement.class);
+ when(byteCodeElement.getActualName()).thenReturn(FOO);
+ assertThat(ElementMatchers.nameEndsWithIgnoreCase(FOO.substring(1)).matches(byteCodeElement), is(true));
+ assertThat(ElementMatchers.nameEndsWithIgnoreCase(FOO.substring(1).toUpperCase()).matches(byteCodeElement), is(true));
+ assertThat(ElementMatchers.nameEndsWithIgnoreCase(BAR).matches(byteCodeElement), is(false));
+ }
+
+ @Test
+ public void testNameContains() throws Exception {
+ ByteCodeElement byteCodeElement = mock(ByteCodeElement.class);
+ when(byteCodeElement.getActualName()).thenReturn(FOO);
+ assertThat(ElementMatchers.nameContains(FOO.substring(1, 2)).matches(byteCodeElement), is(true));
+ assertThat(ElementMatchers.nameContains(FOO.substring(1, 2).toUpperCase()).matches(byteCodeElement), is(false));
+ assertThat(ElementMatchers.nameContains(BAR).matches(byteCodeElement), is(false));
+ }
+
+ @Test
+ public void testNameContainsIgnoreCase() throws Exception {
+ ByteCodeElement byteCodeElement = mock(ByteCodeElement.class);
+ when(byteCodeElement.getActualName()).thenReturn(FOO);
+ assertThat(ElementMatchers.nameContainsIgnoreCase(FOO.substring(1, 2)).matches(byteCodeElement), is(true));
+ assertThat(ElementMatchers.nameContainsIgnoreCase(FOO.substring(1, 2).toUpperCase()).matches(byteCodeElement), is(true));
+ assertThat(ElementMatchers.nameContainsIgnoreCase(BAR).matches(byteCodeElement), is(false));
+ }
+
+ @Test
+ public void testNameMatches() throws Exception {
+ ByteCodeElement byteCodeElement = mock(ByteCodeElement.class);
+ when(byteCodeElement.getActualName()).thenReturn(FOO);
+ assertThat(ElementMatchers.nameMatches("^" + FOO + "$").matches(byteCodeElement), is(true));
+ assertThat(ElementMatchers.nameMatches(FOO.toUpperCase()).matches(byteCodeElement), is(false));
+ assertThat(ElementMatchers.nameMatches(BAR).matches(byteCodeElement), is(false));
+ }
+
+ @Test
+ public void testIsNamed() throws Exception {
+ NamedElement.WithOptionalName namedElement = mock(NamedElement.WithOptionalName.class);
+ assertThat(ElementMatchers.isNamed().matches(namedElement), is(false));
+ when(namedElement.isNamed()).thenReturn(true);
+ assertThat(ElementMatchers.isNamed().matches(namedElement), is(true));
+ }
+
+ @Test
+ public void testHasDescriptor() throws Exception {
+ ByteCodeElement byteCodeElement = mock(ByteCodeElement.class);
+ when(byteCodeElement.getDescriptor()).thenReturn(FOO);
+ assertThat(ElementMatchers.hasDescriptor(FOO).matches(byteCodeElement), is(true));
+ assertThat(ElementMatchers.hasDescriptor(FOO.toUpperCase()).matches(byteCodeElement), is(false));
+ assertThat(ElementMatchers.hasDescriptor(BAR).matches(byteCodeElement), is(false));
+ }
+
+ @Test
+ public void testIsDeclaredBy() throws Exception {
+ assertThat(ElementMatchers.isDeclaredBy(IsDeclaredBy.class).matches(new TypeDescription.ForLoadedType(IsDeclaredBy.Inner.class)), is(true));
+ assertThat(ElementMatchers.isDeclaredBy(IsDeclaredBy.class).matches(mock(ByteCodeElement.class)), is(false));
+ assertThat(ElementMatchers.isDeclaredBy(Object.class).matches(mock(ByteCodeElement.class)), is(false));
+ }
+
+ @Test
+ public void testIsDeclaredByGeneric() throws Exception {
+ assertThat(ElementMatchers.isDeclaredByGeneric(GenericDeclaredBy.Inner.class.getGenericInterfaces()[0])
+ .matches(new TypeDescription.ForLoadedType(GenericDeclaredBy.Inner.class)
+ .getInterfaces().getOnly().getDeclaredMethods().filter(ElementMatchers.isMethod()).getOnly()), is(true));
+ assertThat(ElementMatchers.isDeclaredByGeneric(GenericDeclaredBy.Inner.class.getGenericInterfaces()[0])
+ .matches(new TypeDescription.ForLoadedType(GenericDeclaredBy.class)
+ .getDeclaredMethods().filter(ElementMatchers.isMethod()).getOnly()), is(false));
+ assertThat(ElementMatchers.isDeclaredByGeneric(GenericDeclaredBy.class)
+ .matches(new TypeDescription.ForLoadedType(GenericDeclaredBy.Inner.class)
+ .getInterfaces().getOnly().getDeclaredMethods().filter(ElementMatchers.isMethod()).getOnly()), is(false));
+ }
+
+ @Test
+ public void testIsOverriddenFrom() throws Exception {
+ assertThat(ElementMatchers.isOverriddenFrom(Object.class).matches(new MethodDescription.ForLoadedMethod(String.class.getDeclaredMethod("toString"))), is(true));
+ assertThat(ElementMatchers.isOverriddenFrom(Object.class).matches(new MethodDescription.ForLoadedMethod(String.class.getDeclaredMethod("substring", int.class))), is(false));
+ assertThat(ElementMatchers.isOverriddenFrom(Comparable.class).matches(new MethodDescription.ForLoadedMethod(String.class.getDeclaredMethod("compareTo", String.class))), is(true));
+ assertThat(ElementMatchers.isOverriddenFromGeneric(Object.class).matches(new MethodDescription.ForLoadedMethod(String.class.getDeclaredMethod("toString"))), is(true));
+ assertThat(ElementMatchers.isOverriddenFromGeneric(Object.class).matches(new MethodDescription.ForLoadedMethod(String.class.getDeclaredMethod("substring", int.class))), is(false));
+ assertThat(ElementMatchers.isOverriddenFromGeneric(Comparable.class).matches(new MethodDescription.ForLoadedMethod(String.class.getDeclaredMethod("compareTo", String.class))), is(false));
+ assertThat(ElementMatchers.isOverriddenFromGeneric(String.class.getGenericInterfaces()[1])
+ .matches(new MethodDescription.ForLoadedMethod(String.class.getDeclaredMethod("compareTo", String.class))), is(true));
+ }
+
+ @Test
+ public void testIsVisibleTo() throws Exception {
+ assertThat(ElementMatchers.isVisibleTo(Object.class).matches(new TypeDescription.ForLoadedType(IsVisibleTo.class)), is(true));
+ assertThat(ElementMatchers.isVisibleTo(Object.class).matches(new TypeDescription.ForLoadedType(IsNotVisibleTo.class)), is(false));
+ }
+
+ @Test
+ public void testIsAccessibleTo() throws Exception {
+ assertThat(ElementMatchers.isAccessibleTo(Object.class).matches(new TypeDescription.ForLoadedType(IsVisibleTo.class)), is(true));
+ assertThat(ElementMatchers.isAccessibleTo(Object.class).matches(new TypeDescription.ForLoadedType(IsNotVisibleTo.class)), is(false));
+ }
+
+ @Test
+ public void testIsAnnotatedWith() throws Exception {
+ assertThat(ElementMatchers.isAnnotatedWith(IsAnnotatedWithAnnotation.class)
+ .matches(new TypeDescription.ForLoadedType(IsAnnotatedWith.class)), is(true));
+ assertThat(ElementMatchers.isAnnotatedWith(IsAnnotatedWithAnnotation.class)
+ .matches(TypeDescription.OBJECT), is(false));
+ }
+
+ @Test
+ public void testIsPublic() throws Exception {
+ ModifierReviewable.OfByteCodeElement modifierReviewable = mock(ModifierReviewable.OfByteCodeElement.class);
+ when(modifierReviewable.getModifiers()).thenReturn(Opcodes.ACC_PUBLIC);
+ assertThat(ElementMatchers.isPublic().matches(modifierReviewable), is(true));
+ assertThat(ElementMatchers.isPublic().matches(mock(ModifierReviewable.OfByteCodeElement.class)), is(false));
+ }
+
+ @Test
+ public void testIsProtected() throws Exception {
+ ModifierReviewable.OfByteCodeElement modifierReviewable = mock(ModifierReviewable.OfByteCodeElement.class);
+ when(modifierReviewable.getModifiers()).thenReturn(Opcodes.ACC_PROTECTED);
+ assertThat(ElementMatchers.isProtected().matches(modifierReviewable), is(true));
+ assertThat(ElementMatchers.isProtected().matches(mock(ModifierReviewable.OfByteCodeElement.class)), is(false));
+ }
+
+ @Test
+ public void testIsPackagePrivate() throws Exception {
+ ModifierReviewable.OfByteCodeElement modifierReviewable = mock(ModifierReviewable.OfByteCodeElement.class);
+ when(modifierReviewable.getModifiers()).thenReturn(Opcodes.ACC_PUBLIC | Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED);
+ assertThat(ElementMatchers.isPackagePrivate().matches(mock(ModifierReviewable.OfByteCodeElement.class)), is(true));
+ assertThat(ElementMatchers.isPackagePrivate().matches(modifierReviewable), is(false));
+ }
+
+ @Test
+ public void testIsPrivate() throws Exception {
+ ModifierReviewable.OfByteCodeElement modifierReviewable = mock(ModifierReviewable.OfByteCodeElement.class);
+ when(modifierReviewable.getModifiers()).thenReturn(Opcodes.ACC_PRIVATE);
+ assertThat(ElementMatchers.isPrivate().matches(modifierReviewable), is(true));
+ assertThat(ElementMatchers.isPrivate().matches(mock(ModifierReviewable.OfByteCodeElement.class)), is(false));
+ }
+
+ @Test
+ public void testIsAbstract() throws Exception {
+ ModifierReviewable.OfAbstraction modifierReviewable = mock(ModifierReviewable.OfAbstraction.class);
+ when(modifierReviewable.getModifiers()).thenReturn(Opcodes.ACC_ABSTRACT);
+ assertThat(ElementMatchers.isAbstract().matches(modifierReviewable), is(true));
+ assertThat(ElementMatchers.isAbstract().matches(mock(ModifierReviewable.OfAbstraction.class)), is(false));
+ }
+
+ @Test
+ public void testIsEnum() throws Exception {
+ ModifierReviewable.OfEnumeration modifierReviewable = mock(ModifierReviewable.OfEnumeration.class);
+ when(modifierReviewable.getModifiers()).thenReturn(Opcodes.ACC_ENUM);
+ assertThat(ElementMatchers.isEnum().matches(modifierReviewable), is(true));
+ assertThat(ElementMatchers.isEnum().matches(mock(ModifierReviewable.OfEnumeration.class)), is(false));
+ }
+
+ @Test
+ public void testIsMandated() throws Exception {
+ ParameterDescription parameterDescription = mock(ParameterDescription.class);
+ when(parameterDescription.getModifiers()).thenReturn(Opcodes.ACC_MANDATED);
+ assertThat(ElementMatchers.isMandated().matches(parameterDescription), is(true));
+ assertThat(ElementMatchers.isMandated().matches(mock(ParameterDescription.class)), is(false));
+ }
+
+ @Test
+ public void testIsFinal() throws Exception {
+ ModifierReviewable.OfByteCodeElement modifierReviewable = mock(ModifierReviewable.OfByteCodeElement.class);
+ when(modifierReviewable.getModifiers()).thenReturn(Opcodes.ACC_FINAL);
+ assertThat(ElementMatchers.isFinal().matches(modifierReviewable), is(true));
+ assertThat(ElementMatchers.isFinal().matches(mock(ModifierReviewable.OfByteCodeElement.class)), is(false));
+ }
+
+ @Test
+ public void testIsStatic() throws Exception {
+ ModifierReviewable.OfByteCodeElement modifierReviewable = mock(ModifierReviewable.OfByteCodeElement.class);
+ when(modifierReviewable.getModifiers()).thenReturn(Opcodes.ACC_STATIC);
+ assertThat(ElementMatchers.isStatic().matches(modifierReviewable), is(true));
+ assertThat(ElementMatchers.isStatic().matches(mock(ModifierReviewable.OfByteCodeElement.class)), is(false));
+ }
+
+ @Test
+ public void testIsSynthetic() throws Exception {
+ ModifierReviewable modifierReviewable = mock(ModifierReviewable.class);
+ when(modifierReviewable.getModifiers()).thenReturn(Opcodes.ACC_SYNTHETIC);
+ assertThat(ElementMatchers.isSynthetic().matches(modifierReviewable), is(true));
+ assertThat(ElementMatchers.isSynthetic().matches(mock(ModifierReviewable.class)), is(false));
+ }
+
+ @Test
+ public void testIsSynchronized() throws Exception {
+ MethodDescription methodDescription = mock(MethodDescription.class);
+ when(methodDescription.getModifiers()).thenReturn(Opcodes.ACC_SYNCHRONIZED);
+ assertThat(ElementMatchers.isSynchronized().matches(methodDescription), is(true));
+ assertThat(ElementMatchers.isSynchronized().matches(mock(MethodDescription.class)), is(false));
+ }
+
+ @Test
+ public void testIsNative() throws Exception {
+ MethodDescription methodDescription = mock(MethodDescription.class);
+ when(methodDescription.getModifiers()).thenReturn(Opcodes.ACC_NATIVE);
+ assertThat(ElementMatchers.isNative().matches(methodDescription), is(true));
+ assertThat(ElementMatchers.isNative().matches(mock(MethodDescription.class)), is(false));
+ }
+
+ @Test
+ public void testIsStrict() throws Exception {
+ MethodDescription methodDescription = mock(MethodDescription.class);
+ when(methodDescription.getModifiers()).thenReturn(Opcodes.ACC_STRICT);
+ assertThat(ElementMatchers.isStrict().matches(methodDescription), is(true));
+ assertThat(ElementMatchers.isStrict().matches(mock(MethodDescription.class)), is(false));
+ }
+
+ @Test
+ public void testIsVarArgs() throws Exception {
+ MethodDescription modifierReviewable = mock(MethodDescription.class);
+ when(modifierReviewable.getModifiers()).thenReturn(Opcodes.ACC_VARARGS);
+ assertThat(ElementMatchers.isVarArgs().matches(modifierReviewable), is(true));
+ assertThat(ElementMatchers.isVarArgs().matches(mock(MethodDescription.class)), is(false));
+ }
+
+ @Test
+ public void testIsBridge() throws Exception {
+ MethodDescription modifierReviewable = mock(MethodDescription.class);
+ when(modifierReviewable.getModifiers()).thenReturn(Opcodes.ACC_BRIDGE);
+ assertThat(ElementMatchers.isBridge().matches(modifierReviewable), is(true));
+ assertThat(ElementMatchers.isBridge().matches(mock(MethodDescription.class)), is(false));
+ }
+
+ @Test
+ public void testIsMethod() throws Exception {
+ assertThat(ElementMatchers.is(IsEqual.class.getDeclaredMethod(FOO))
+ .matches(new MethodDescription.ForLoadedMethod(IsEqual.class.getDeclaredMethod(FOO))), is(true));
+ assertThat(ElementMatchers.is(IsEqual.class.getDeclaredMethod(FOO))
+ .matches(mock(MethodDescription.class)), is(false));
+ assertThat(ElementMatchers.is(IsEqual.class.getDeclaredConstructor())
+ .matches(new MethodDescription.ForLoadedConstructor(IsEqual.class.getDeclaredConstructor())), is(true));
+ assertThat(ElementMatchers.is(IsEqual.class.getDeclaredConstructor())
+ .matches(mock(MethodDescription.class)), is(false));
+ }
+
+ @Test
+ public void testReturnsGeneric() throws Exception {
+ assertThat(ElementMatchers.returnsGeneric(GenericMethodType.class.getTypeParameters()[0])
+ .matches(new MethodDescription.ForLoadedMethod(GenericMethodType.class.getDeclaredMethod(FOO, Exception.class))), is(true));
+ assertThat(ElementMatchers.returnsGeneric(Exception.class)
+ .matches(new MethodDescription.ForLoadedMethod(GenericMethodType.class.getDeclaredMethod(FOO, Exception.class))), is(false));
+ assertThat(ElementMatchers.returns(Exception.class)
+ .matches(new MethodDescription.ForLoadedMethod(GenericMethodType.class.getDeclaredMethod(FOO, Exception.class))), is(true));
+ }
+
+ @Test
+ public void testReturns() throws Exception {
+ assertThat(ElementMatchers.returns(void.class)
+ .matches(new MethodDescription.ForLoadedMethod(Returns.class.getDeclaredMethod(FOO))), is(true));
+ assertThat(ElementMatchers.returns(void.class)
+ .matches(new MethodDescription.ForLoadedMethod(Returns.class.getDeclaredMethod(BAR))), is(false));
+ assertThat(ElementMatchers.returns(String.class)
+ .matches(new MethodDescription.ForLoadedMethod(Returns.class.getDeclaredMethod(BAR))), is(true));
+ assertThat(ElementMatchers.returns(String.class)
+ .matches(new MethodDescription.ForLoadedMethod(Returns.class.getDeclaredMethod(FOO))), is(false));
+ }
+
+ @Test
+ public void testTakesArgumentsGeneric() throws Exception {
+ assertThat(ElementMatchers.takesGenericArguments(GenericMethodType.class.getTypeParameters()[0])
+ .matches(new MethodDescription.ForLoadedMethod(GenericMethodType.class.getDeclaredMethod(FOO, Exception.class))), is(true));
+ assertThat(ElementMatchers.takesGenericArguments(TypeDefinition.Sort.describe(GenericMethodType.class.getTypeParameters()[0]))
+ .matches(new MethodDescription.ForLoadedMethod(GenericMethodType.class.getDeclaredMethod(FOO, Exception.class))), is(true));
+ assertThat(ElementMatchers.takesGenericArguments(Exception.class)
+ .matches(new MethodDescription.ForLoadedMethod(GenericMethodType.class.getDeclaredMethod(FOO, Exception.class))), is(false));
+ assertThat(ElementMatchers.takesGenericArguments(Collections.singletonList(new TypeDescription.ForLoadedType(Exception.class)))
+ .matches(new MethodDescription.ForLoadedMethod(GenericMethodType.class.getDeclaredMethod(FOO, Exception.class))), is(false));
+ assertThat(ElementMatchers.takesArguments(Exception.class)
+ .matches(new MethodDescription.ForLoadedMethod(GenericMethodType.class.getDeclaredMethod(FOO, Exception.class))), is(true));
+ assertThat(ElementMatchers.takesArguments(Collections.singletonList(new TypeDescription.ForLoadedType(Exception.class)))
+ .matches(new MethodDescription.ForLoadedMethod(GenericMethodType.class.getDeclaredMethod(FOO, Exception.class))), is(true));
+ }
+
+ @Test
+ public void testTakesArguments() throws Exception {
+ assertThat(ElementMatchers.takesArguments(Void.class)
+ .matches(new MethodDescription.ForLoadedMethod(TakesArguments.class.getDeclaredMethod(FOO, Void.class))), is(true));
+ assertThat(ElementMatchers.takesArguments(Void.class, Object.class)
+ .matches(new MethodDescription.ForLoadedMethod(TakesArguments.class.getDeclaredMethod(FOO, Void.class))), is(false));
+ assertThat(ElementMatchers.takesArguments(String.class, int.class)
+ .matches(new MethodDescription.ForLoadedMethod(TakesArguments.class.getDeclaredMethod(BAR, String.class, int.class))), is(true));
+ assertThat(ElementMatchers.takesArguments(String.class, Integer.class)
+ .matches(new MethodDescription.ForLoadedMethod(TakesArguments.class.getDeclaredMethod(BAR, String.class, int.class))), is(false));
+ }
+
+ @Test
+ public void testTakesArgumentGeneric() throws Exception {
+ assertThat(ElementMatchers.takesGenericArgument(0, GenericMethodType.class.getTypeParameters()[0])
+ .matches(new MethodDescription.ForLoadedMethod(GenericMethodType.class.getDeclaredMethod(FOO, Exception.class))), is(true));
+ assertThat(ElementMatchers.takesGenericArgument(0, Exception.class)
+ .matches(new MethodDescription.ForLoadedMethod(GenericMethodType.class.getDeclaredMethod(FOO, Exception.class))), is(false));
+ assertThat(ElementMatchers.takesGenericArgument(1, GenericMethodType.class.getTypeParameters()[0])
+ .matches(new MethodDescription.ForLoadedMethod(GenericMethodType.class.getDeclaredMethod(FOO, Exception.class))), is(false));
+ }
+
+ @Test
+ public void testTakesArgument() throws Exception {
+ assertThat(ElementMatchers.takesArgument(0, Void.class)
+ .matches(new MethodDescription.ForLoadedMethod(TakesArguments.class.getDeclaredMethod(FOO, Void.class))), is(true));
+ assertThat(ElementMatchers.takesArgument(0, Object.class)
+ .matches(new MethodDescription.ForLoadedMethod(TakesArguments.class.getDeclaredMethod(FOO, Void.class))), is(false));
+ assertThat(ElementMatchers.takesArgument(1, int.class)
+ .matches(new MethodDescription.ForLoadedMethod(TakesArguments.class.getDeclaredMethod(BAR, String.class, int.class))), is(true));
+ assertThat(ElementMatchers.takesArgument(1, Integer.class)
+ .matches(new MethodDescription.ForLoadedMethod(TakesArguments.class.getDeclaredMethod(BAR, String.class, int.class))), is(false));
+ assertThat(ElementMatchers.takesArgument(2, int.class)
+ .matches(new MethodDescription.ForLoadedMethod(TakesArguments.class.getDeclaredMethod(BAR, String.class, int.class))), is(false));
+ }
+
+ @Test
+ public void testTakesArgumentsLength() throws Exception {
+ assertThat(ElementMatchers.takesArguments(1)
+ .matches(new MethodDescription.ForLoadedMethod(TakesArguments.class.getDeclaredMethod(FOO, Void.class))), is(true));
+ assertThat(ElementMatchers.takesArguments(2)
+ .matches(new MethodDescription.ForLoadedMethod(TakesArguments.class.getDeclaredMethod(FOO, Void.class))), is(false));
+ assertThat(ElementMatchers.takesArguments(2)
+ .matches(new MethodDescription.ForLoadedMethod(TakesArguments.class.getDeclaredMethod(BAR, String.class, int.class))), is(true));
+ assertThat(ElementMatchers.takesArguments(3)
+ .matches(new MethodDescription.ForLoadedMethod(TakesArguments.class.getDeclaredMethod(BAR, String.class, int.class))), is(false));
+ }
+
+ @Test
+ public void testDeclaresException() throws Exception {
+ assertThat(ElementMatchers.declaresException(IOException.class)
+ .matches(new MethodDescription.ForLoadedMethod(CanThrow.class.getDeclaredMethod(FOO))), is(true));
+ assertThat(ElementMatchers.declaresException(SQLException.class)
+ .matches(new MethodDescription.ForLoadedMethod(CanThrow.class.getDeclaredMethod(FOO))), is(false));
+ assertThat(ElementMatchers.declaresException(Error.class)
+ .matches(new MethodDescription.ForLoadedMethod(CanThrow.class.getDeclaredMethod(FOO))), is(false));
+ assertThat(ElementMatchers.declaresException(RuntimeException.class)
+ .matches(new MethodDescription.ForLoadedMethod(CanThrow.class.getDeclaredMethod(FOO))), is(false));
+ assertThat(ElementMatchers.declaresException(IOException.class)
+ .matches(new MethodDescription.ForLoadedMethod(CanThrow.class.getDeclaredMethod(BAR))), is(false));
+ assertThat(ElementMatchers.declaresException(SQLException.class)
+ .matches(new MethodDescription.ForLoadedMethod(CanThrow.class.getDeclaredMethod(BAR))), is(false));
+ assertThat(ElementMatchers.declaresException(Error.class)
+ .matches(new MethodDescription.ForLoadedMethod(CanThrow.class.getDeclaredMethod(BAR))), is(false));
+ assertThat(ElementMatchers.declaresException(RuntimeException.class)
+ .matches(new MethodDescription.ForLoadedMethod(CanThrow.class.getDeclaredMethod(BAR))), is(false));
+ }
+
+ @Test
+ public void testDeclaresGenericException() throws Exception {
+ assertThat(ElementMatchers.declaresGenericException(GenericMethodType.class.getTypeParameters()[0])
+ .matches(new MethodDescription.ForLoadedMethod(GenericMethodType.class.getDeclaredMethod(FOO, Exception.class))), is(true));
+ assertThat(ElementMatchers.declaresGenericException(Exception.class)
+ .matches(new MethodDescription.ForLoadedMethod(GenericMethodType.class.getDeclaredMethod(FOO, Exception.class))), is(false));
+ assertThat(ElementMatchers.declaresException(Exception.class)
+ .matches(new MethodDescription.ForLoadedMethod(GenericMethodType.class.getDeclaredMethod(FOO, Exception.class))), is(true));
+ }
+
+ @Test
+ public void testCanThrow() throws Exception {
+ assertThat(ElementMatchers.canThrow(IOException.class)
+ .matches(new MethodDescription.ForLoadedMethod(CanThrow.class.getDeclaredMethod(FOO))), is(true));
+ assertThat(ElementMatchers.canThrow(SQLException.class)
+ .matches(new MethodDescription.ForLoadedMethod(CanThrow.class.getDeclaredMethod(FOO))), is(false));
+ assertThat(ElementMatchers.canThrow(Error.class)
+ .matches(new MethodDescription.ForLoadedMethod(CanThrow.class.getDeclaredMethod(FOO))), is(true));
+ assertThat(ElementMatchers.canThrow(RuntimeException.class)
+ .matches(new MethodDescription.ForLoadedMethod(CanThrow.class.getDeclaredMethod(FOO))), is(true));
+ assertThat(ElementMatchers.canThrow(IOException.class)
+ .matches(new MethodDescription.ForLoadedMethod(CanThrow.class.getDeclaredMethod(BAR))), is(false));
+ assertThat(ElementMatchers.canThrow(SQLException.class)
+ .matches(new MethodDescription.ForLoadedMethod(CanThrow.class.getDeclaredMethod(BAR))), is(false));
+ assertThat(ElementMatchers.canThrow(Error.class)
+ .matches(new MethodDescription.ForLoadedMethod(CanThrow.class.getDeclaredMethod(BAR))), is(true));
+ assertThat(ElementMatchers.canThrow(RuntimeException.class)
+ .matches(new MethodDescription.ForLoadedMethod(CanThrow.class.getDeclaredMethod(BAR))), is(true));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testDeclaresExceptionForNonThrowableType() throws Exception {
+ ElementMatcher<Object> elementMatcher = (ElementMatcher) ElementMatchers.declaresException((Class) Object.class);
+ assertThat(elementMatcher.matches(new Object()), is(false));
+ }
+
+ @Test
+ public void testSortIsMethod() throws Exception {
+ assertThat(ElementMatchers.isMethod().matches(new MethodDescription.ForLoadedMethod(Object.class.getDeclaredMethod("toString"))), is(true));
+ assertThat(ElementMatchers.isMethod().matches(new MethodDescription.ForLoadedConstructor(Object.class.getDeclaredConstructor())), is(false));
+ assertThat(ElementMatchers.isMethod().matches(new MethodDescription.Latent.TypeInitializer(mock(TypeDescription.class))), is(false));
+ }
+
+ @Test
+ public void testSortIsConstructor() throws Exception {
+ assertThat(ElementMatchers.isConstructor()
+ .matches(new MethodDescription.ForLoadedMethod(Object.class.getDeclaredMethod("toString"))), is(false));
+ assertThat(ElementMatchers.isConstructor()
+ .matches(new MethodDescription.ForLoadedConstructor(Object.class.getDeclaredConstructor())), is(true));
+ assertThat(ElementMatchers.isConstructor()
+ .matches(new MethodDescription.Latent.TypeInitializer(mock(TypeDescription.class))), is(false));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testIsDefaultMethod() throws Exception {
+ assertThat(ElementMatchers.isDefaultMethod().matches(new MethodDescription.ForLoadedMethod(Class.forName(SINGLE_DEFAULT_METHOD)
+ .getDeclaredMethod(FOO))), is(true));
+ assertThat(ElementMatchers.isDefaultMethod()
+ .matches(new MethodDescription.ForLoadedMethod(Runnable.class.getDeclaredMethod("run"))), is(false));
+ }
+
+ @Test
+ public void testSortIsTypeInitializer() throws Exception {
+ assertThat(ElementMatchers.isTypeInitializer()
+ .matches(new MethodDescription.ForLoadedMethod(Object.class.getDeclaredMethod("toString"))), is(false));
+ assertThat(ElementMatchers.isTypeInitializer()
+ .matches(new MethodDescription.ForLoadedConstructor(Object.class.getDeclaredConstructor())), is(false));
+ assertThat(ElementMatchers.isTypeInitializer()
+ .matches(new MethodDescription.Latent.TypeInitializer(mock(TypeDescription.class))), is(true));
+ }
+
+ @Test
+ public void testSortIsBridge() throws Exception {
+ assertThat(ElementMatchers.isBridge()
+ .matches(new MethodDescription.ForLoadedMethod(GenericType.Extension.class.getDeclaredMethod("foo", Object.class))), is(true));
+ assertThat(ElementMatchers.isBridge()
+ .matches(new MethodDescription.ForLoadedMethod(GenericType.Extension.class.getDeclaredMethod("foo", Void.class))), is(false));
+ assertThat(ElementMatchers.isBridge()
+ .matches(new MethodDescription.ForLoadedMethod(Object.class.getDeclaredMethod("toString"))), is(false));
+ }
+
+ @Test
+ public void testIsVirtual() throws Exception {
+ assertThat(ElementMatchers.isVirtual().matches(new MethodDescription.ForLoadedMethod(IsVirtual.class.getDeclaredMethod("baz"))), is(true));
+ assertThat(ElementMatchers.isVirtual().matches(new MethodDescription.ForLoadedMethod(IsVirtual.class.getDeclaredMethod("foo"))), is(false));
+ assertThat(ElementMatchers.isVirtual().matches(new MethodDescription.ForLoadedMethod(IsVirtual.class.getDeclaredMethod("bar"))), is(false));
+ assertThat(ElementMatchers.isVirtual().matches(new MethodDescription.ForLoadedMethod(IsVirtual.class.getDeclaredMethod("qux"))), is(true));
+ assertThat(ElementMatchers.isVirtual().matches(new MethodDescription.ForLoadedConstructor(IsVirtual.class.getDeclaredConstructor())), is(false));
+ assertThat(ElementMatchers.isVirtual().matches(new MethodDescription.Latent.TypeInitializer(TypeDescription.OBJECT)), is(false));
+ }
+
+ @Test
+ public void testIsDefaultFinalizer() throws Exception {
+ assertThat(ElementMatchers.isDefaultFinalizer()
+ .matches(new MethodDescription.ForLoadedMethod(Object.class.getDeclaredMethod("finalize"))), is(true));
+ assertThat(ElementMatchers.isDefaultFinalizer()
+ .matches(new MethodDescription.ForLoadedMethod(ObjectMethods.class.getDeclaredMethod("finalize"))), is(false));
+ assertThat(ElementMatchers.isDefaultFinalizer()
+ .matches(new MethodDescription.ForLoadedMethod(Object.class.getDeclaredMethod("toString"))), is(false));
+ }
+
+ @Test
+ public void testIsFinalizer() throws Exception {
+ assertThat(ElementMatchers.isFinalizer()
+ .matches(new MethodDescription.ForLoadedMethod(Object.class.getDeclaredMethod("finalize"))), is(true));
+ assertThat(ElementMatchers.isFinalizer()
+ .matches(new MethodDescription.ForLoadedMethod(ObjectMethods.class.getDeclaredMethod("finalize"))), is(true));
+ assertThat(ElementMatchers.isFinalizer()
+ .matches(new MethodDescription.ForLoadedMethod(Object.class.getDeclaredMethod("toString"))), is(false));
+ }
+
+ @Test
+ public void testIsHashCode() throws Exception {
+ assertThat(ElementMatchers.isHashCode()
+ .matches(new MethodDescription.ForLoadedMethod(Object.class.getDeclaredMethod("hashCode"))), is(true));
+ assertThat(ElementMatchers.isHashCode()
+ .matches(new MethodDescription.ForLoadedMethod(ObjectMethods.class.getDeclaredMethod("hashCode"))), is(true));
+ assertThat(ElementMatchers.isHashCode()
+ .matches(new MethodDescription.ForLoadedMethod(Runnable.class.getDeclaredMethod("run"))), is(false));
+ }
+
+ @Test
+ public void testIsEquals() throws Exception {
+ assertThat(ElementMatchers.isEquals()
+ .matches(new MethodDescription.ForLoadedMethod(Object.class.getDeclaredMethod("equals", Object.class))), is(true));
+ assertThat(ElementMatchers.isEquals()
+ .matches(new MethodDescription.ForLoadedMethod(ObjectMethods.class.getDeclaredMethod("equals", Object.class))), is(true));
+ assertThat(ElementMatchers.isEquals()
+ .matches(new MethodDescription.ForLoadedMethod(Runnable.class.getDeclaredMethod("run"))), is(false));
+ }
+
+ @Test
+ public void testIsClone() throws Exception {
+ assertThat(ElementMatchers.isClone()
+ .matches(new MethodDescription.ForLoadedMethod(Object.class.getDeclaredMethod("clone"))), is(true));
+ assertThat(ElementMatchers.isClone()
+ .matches(new MethodDescription.ForLoadedMethod(ObjectMethods.class.getDeclaredMethod("clone"))), is(true));
+ assertThat(ElementMatchers.isClone()
+ .matches(new MethodDescription.ForLoadedMethod(Runnable.class.getDeclaredMethod("run"))), is(false));
+ }
+
+ @Test
+ public void testIsToString() throws Exception {
+ assertThat(ElementMatchers.isToString()
+ .matches(new MethodDescription.ForLoadedMethod(Object.class.getDeclaredMethod("toString"))), is(true));
+ assertThat(ElementMatchers.isToString()
+ .matches(new MethodDescription.ForLoadedMethod(ObjectMethods.class.getDeclaredMethod("toString"))), is(true));
+ assertThat(ElementMatchers.isToString()
+ .matches(new MethodDescription.ForLoadedMethod(Runnable.class.getDeclaredMethod("run"))), is(false));
+ }
+
+ @Test
+ public void testIsDefaultConstructor() throws Exception {
+ assertThat(ElementMatchers.isDefaultConstructor()
+ .matches(new MethodDescription.ForLoadedConstructor(Object.class.getDeclaredConstructor())), is(true));
+ assertThat(ElementMatchers.isDefaultConstructor()
+ .matches(new MethodDescription.ForLoadedConstructor(String.class.getDeclaredConstructor(String.class))), is(false));
+ assertThat(ElementMatchers.isDefaultConstructor()
+ .matches(new MethodDescription.ForLoadedMethod(Runnable.class.getDeclaredMethod("run"))), is(false));
+ }
+
+ @Test
+ public void testIsGetter() throws Exception {
+ assertThat(ElementMatchers.isGetter()
+ .matches(new MethodDescription.ForLoadedMethod(Getters.class.getDeclaredMethod("getFoo"))), is(false));
+ assertThat(ElementMatchers.isGetter()
+ .matches(new MethodDescription.ForLoadedMethod(Getters.class.getDeclaredMethod("isQux"))), is(true));
+ assertThat(ElementMatchers.isGetter()
+ .matches(new MethodDescription.ForLoadedMethod(Getters.class.getDeclaredMethod("getQux"))), is(true));
+ assertThat(ElementMatchers.isGetter()
+ .matches(new MethodDescription.ForLoadedMethod(Getters.class.getDeclaredMethod("isBar"))), is(true));
+ assertThat(ElementMatchers.isGetter()
+ .matches(new MethodDescription.ForLoadedMethod(Getters.class.getDeclaredMethod("getBar"))), is(true));
+ assertThat(ElementMatchers.isGetter()
+ .matches(new MethodDescription.ForLoadedMethod(Getters.class.getDeclaredMethod("isBaz"))), is(false));
+ assertThat(ElementMatchers.isGetter()
+ .matches(new MethodDescription.ForLoadedMethod(Getters.class.getDeclaredMethod("getBaz"))), is(true));
+ assertThat(ElementMatchers.isGetter()
+ .matches(new MethodDescription.ForLoadedMethod(Getters.class.getDeclaredMethod("getBaz", Void.class))), is(false));
+ assertThat(ElementMatchers.isGetter()
+ .matches(new MethodDescription.ForLoadedMethod(Getters.class.getDeclaredMethod("get"))), is(true));
+ assertThat(ElementMatchers.isGetter()
+ .matches(new MethodDescription.ForLoadedMethod(Getters.class.getDeclaredMethod("is"))), is(true));
+ }
+
+ @Test
+ public void testPropertyGetter() throws Exception {
+ assertThat(ElementMatchers.isGetter("qux")
+ .matches(new MethodDescription.ForLoadedMethod(Getters.class.getDeclaredMethod("getQux"))), is(true));
+ assertThat(ElementMatchers.isGetter("bar")
+ .matches(new MethodDescription.ForLoadedMethod(Getters.class.getDeclaredMethod("getQux"))), is(false));
+ assertThat(ElementMatchers.isGetter("foo")
+ .matches(new MethodDescription.ForLoadedMethod(Getters.class.getDeclaredMethod("getFoo"))), is(false));
+ assertThat(ElementMatchers.isGetter("")
+ .matches(new MethodDescription.ForLoadedMethod(Getters.class.getDeclaredMethod("get"))), is(true));
+ assertThat(ElementMatchers.isGetter("")
+ .matches(new MethodDescription.ForLoadedMethod(Getters.class.getDeclaredMethod("is"))), is(true));
+ }
+
+ @Test
+ public void testIsSetter() throws Exception {
+ assertThat(ElementMatchers.isSetter()
+ .matches(new MethodDescription.ForLoadedMethod(Setters.class.getDeclaredMethod("setFoo"))), is(false));
+ assertThat(ElementMatchers.isSetter()
+ .matches(new MethodDescription.ForLoadedMethod(Setters.class.getDeclaredMethod("setBar", boolean.class))), is(true));
+ assertThat(ElementMatchers.isSetter()
+ .matches(new MethodDescription.ForLoadedMethod(Setters.class.getDeclaredMethod("setQux", Boolean.class))), is(true));
+ assertThat(ElementMatchers.isSetter()
+ .matches(new MethodDescription.ForLoadedMethod(Setters.class.getDeclaredMethod("setBaz", String.class))), is(true));
+ assertThat(ElementMatchers.isSetter()
+ .matches(new MethodDescription.ForLoadedMethod(Setters.class.getDeclaredMethod("setBaz", String.class, Void.class))), is(false));
+ assertThat(ElementMatchers.isSetter()
+ .matches(new MethodDescription.ForLoadedMethod(Setters.class.getDeclaredMethod("set", Object.class))), is(true));
+ }
+
+ @Test
+ public void testPropertySetter() throws Exception {
+ assertThat(ElementMatchers.isSetter("foo")
+ .matches(new MethodDescription.ForLoadedMethod(Setters.class.getDeclaredMethod("setFoo"))), is(false));
+ assertThat(ElementMatchers.isSetter("qux")
+ .matches(new MethodDescription.ForLoadedMethod(Setters.class.getDeclaredMethod("setQux", Boolean.class))), is(true));
+ assertThat(ElementMatchers.isSetter("bar")
+ .matches(new MethodDescription.ForLoadedMethod(Setters.class.getDeclaredMethod("setQux", Boolean.class))), is(false));
+ assertThat(ElementMatchers.isSetter("")
+ .matches(new MethodDescription.ForLoadedMethod(Setters.class.getDeclaredMethod("set", Object.class))), is(true));
+ }
+
+ @Test
+ public void testIsNonGenericGetter() throws Exception {
+ assertThat(ElementMatchers.isGetter(String.class)
+ .matches(new MethodDescription.ForLoadedMethod(Getters.class.getDeclaredMethod("getBaz"))), is(true));
+ assertThat(ElementMatchers.isGetter(Void.class)
+ .matches(new MethodDescription.ForLoadedMethod(Getters.class.getDeclaredMethod("getBaz"))), is(false));
+ assertThat(ElementMatchers.isGetter(Object.class)
+ .matches(new MethodDescription.ForLoadedMethod(Getters.class.getDeclaredMethod("getQuxbaz"))), is(true));
+ }
+
+ @Test
+ public void testIsNonGenericSetter() throws Exception {
+ assertThat(ElementMatchers.isSetter(String.class)
+ .matches(new MethodDescription.ForLoadedMethod(Setters.class.getDeclaredMethod("setBaz", String.class))), is(true));
+ assertThat(ElementMatchers.isSetter(Void.class)
+ .matches(new MethodDescription.ForLoadedMethod(Setters.class.getDeclaredMethod("setBaz", String.class))), is(false));
+ assertThat(ElementMatchers.isSetter(Object.class)
+ .matches(new MethodDescription.ForLoadedMethod(Setters.class.getDeclaredMethod("setQuxbaz", Object.class))), is(true));
+ }
+
+ @Test
+ public void testIsGenericGetter() throws Exception {
+ assertThat(ElementMatchers.isGenericGetter(String.class)
+ .matches(new MethodDescription.ForLoadedMethod(Getters.class.getDeclaredMethod("getBaz"))), is(true));
+ assertThat(ElementMatchers.isGenericGetter(Void.class)
+ .matches(new MethodDescription.ForLoadedMethod(Getters.class.getDeclaredMethod("getBaz"))), is(false));
+ assertThat(ElementMatchers.isGenericGetter(Getters.class.getTypeParameters()[0])
+ .matches(new MethodDescription.ForLoadedMethod(Getters.class.getDeclaredMethod("getQuxbaz"))), is(true));
+ assertThat(ElementMatchers.isGenericGetter(Object.class)
+ .matches(new MethodDescription.ForLoadedMethod(Getters.class.getDeclaredMethod("getQuxbaz"))), is(false));
+ }
+
+ @Test
+ public void testIsGenericSetter() throws Exception {
+ assertThat(ElementMatchers.isGenericSetter(String.class)
+ .matches(new MethodDescription.ForLoadedMethod(Setters.class.getDeclaredMethod("setBaz", String.class))), is(true));
+ assertThat(ElementMatchers.isGenericSetter(Void.class)
+ .matches(new MethodDescription.ForLoadedMethod(Setters.class.getDeclaredMethod("setBaz", String.class))), is(false));
+ assertThat(ElementMatchers.isGenericSetter(Setters.class.getTypeParameters()[0])
+ .matches(new MethodDescription.ForLoadedMethod(Setters.class.getDeclaredMethod("setQuxbaz", Object.class))), is(true));
+ assertThat(ElementMatchers.isGenericSetter(Object.class)
+ .matches(new MethodDescription.ForLoadedMethod(Setters.class.getDeclaredMethod("setQuxbaz", Object.class))), is(false));
+ }
+
+ @Test
+ public void testHasSignature() throws Exception {
+ MethodDescription.SignatureToken signatureToken = new MethodDescription.SignatureToken("toString", TypeDescription.STRING, Collections.<TypeDescription>emptyList());
+ assertThat(ElementMatchers.hasSignature(signatureToken)
+ .matches(new MethodDescription.ForLoadedMethod(Object.class.getDeclaredMethod("toString"))), is(true));
+ assertThat(ElementMatchers.hasSignature(signatureToken)
+ .matches(new MethodDescription.ForLoadedMethod(Object.class.getDeclaredMethod("hashCode"))), is(false));
+ }
+
+ @Test
+ public void testIsSubOrSuperType() throws Exception {
+ assertThat(ElementMatchers.isSubTypeOf(String.class).matches(TypeDescription.OBJECT), is(false));
+ assertThat(ElementMatchers.isSubTypeOf(Object.class).matches(TypeDescription.STRING), is(true));
+ assertThat(ElementMatchers.isSubTypeOf(Serializable.class).matches(TypeDescription.STRING), is(true));
+ assertThat(ElementMatchers.isSuperTypeOf(Object.class).matches(TypeDescription.STRING), is(false));
+ assertThat(ElementMatchers.isSuperTypeOf(String.class).matches(TypeDescription.OBJECT), is(true));
+ assertThat(ElementMatchers.isSuperTypeOf(String.class).matches(new TypeDescription.ForLoadedType(Serializable.class)), is(true));
+ }
+
+ @Test
+ public void testHasSuperType() throws Exception {
+ assertThat(ElementMatchers.hasSuperType(ElementMatchers.is(Object.class)).matches(TypeDescription.STRING), is(true));
+ assertThat(ElementMatchers.hasSuperType(ElementMatchers.is(String.class)).matches(TypeDescription.OBJECT), is(false));
+ assertThat(ElementMatchers.hasSuperType(ElementMatchers.is(Serializable.class)).matches(TypeDescription.STRING), is(true));
+ assertThat(ElementMatchers.hasSuperType(ElementMatchers.is(Serializable.class)).matches(TypeDescription.OBJECT), is(false));
+ }
+
+ @Test
+ public void testIsAnnotatedInheritedWith() throws Exception {
+ assertThat(ElementMatchers.inheritsAnnotation(OtherAnnotation.class)
+ .matches(new TypeDescription.ForLoadedType(OtherInherited.class)), is(true));
+ assertThat(ElementMatchers.isAnnotatedWith(OtherAnnotation.class)
+ .matches(new TypeDescription.ForLoadedType(OtherInherited.class)), is(false));
+ }
+
+ @Test
+ public void testTypeSort() throws Exception {
+ assertThat(ElementMatchers.ofSort(TypeDefinition.Sort.NON_GENERIC).matches(TypeDescription.OBJECT), is(true));
+ assertThat(ElementMatchers.ofSort(TypeDefinition.Sort.VARIABLE).matches(TypeDescription.OBJECT), is(false));
+ }
+
+ @Test
+ public void testDeclaresField() throws Exception {
+ assertThat(ElementMatchers.declaresField(ElementMatchers.isAnnotatedWith(OtherAnnotation.class))
+ .matches(new TypeDescription.ForLoadedType(DeclaresFieldOrMethod.class)), is(true));
+ assertThat(ElementMatchers.declaresField(ElementMatchers.isAnnotatedWith(OtherAnnotation.class))
+ .matches(TypeDescription.OBJECT), is(false));
+ assertThat(ElementMatchers.declaresMethod(ElementMatchers.isAnnotatedWith(OtherAnnotation.class))
+ .matches(new TypeDescription.ForLoadedType(DeclaresFieldOrMethod.class)), is(true));
+ assertThat(ElementMatchers.declaresMethod(ElementMatchers.isAnnotatedWith(OtherAnnotation.class))
+ .matches(TypeDescription.OBJECT), is(false));
+ }
+
+ @Test
+ public void testFieldType() throws Exception {
+ assertThat(ElementMatchers.fieldType(GenericFieldType.class).matches(new FieldDescription.ForLoadedField(GenericFieldType.class.getDeclaredField(FOO))), is(true));
+ assertThat(ElementMatchers.fieldType(Object.class).matches(new FieldDescription.ForLoadedField(GenericFieldType.class.getDeclaredField(FOO))), is(false));
+ }
+
+ @Test
+ public void testGenericFieldType() throws Exception {
+ assertThat(ElementMatchers.genericFieldType(GenericFieldType.class.getTypeParameters()[0])
+ .matches(new FieldDescription.ForLoadedField(GenericFieldType.class.getDeclaredField(BAR))), is(true));
+ assertThat(ElementMatchers.genericFieldType(Object.class)
+ .matches(new FieldDescription.ForLoadedField(GenericFieldType.class.getDeclaredField(BAR))), is(false));
+ assertThat(ElementMatchers.fieldType(Object.class)
+ .matches(new FieldDescription.ForLoadedField(GenericFieldType.class.getDeclaredField(BAR))), is(true));
+ }
+
+ @Test
+ public void testIsBootstrapClassLoader() throws Exception {
+ assertThat(ElementMatchers.isBootstrapClassLoader().matches(null), is(true));
+ assertThat(ElementMatchers.isBootstrapClassLoader().matches(mock(ClassLoader.class)), is(false));
+ }
+
+ @Test
+ public void testIsSystemClassLoader() throws Exception {
+ assertThat(ElementMatchers.isSystemClassLoader().matches(ClassLoader.getSystemClassLoader()), is(true));
+ assertThat(ElementMatchers.isSystemClassLoader().matches(null), is(false));
+ assertThat(ElementMatchers.isSystemClassLoader().matches(ClassLoader.getSystemClassLoader().getParent()), is(false));
+ assertThat(ElementMatchers.isSystemClassLoader().matches(mock(ClassLoader.class)), is(false));
+ }
+
+ @Test
+ public void testIsExtensionClassLoader() throws Exception {
+ assertThat(ElementMatchers.isExtensionClassLoader().matches(ClassLoader.getSystemClassLoader().getParent()), is(true));
+ assertThat(ElementMatchers.isExtensionClassLoader().matches(ClassLoader.getSystemClassLoader()), is(false));
+ assertThat(ElementMatchers.isExtensionClassLoader().matches(null), is(false));
+ assertThat(ElementMatchers.isExtensionClassLoader().matches(mock(ClassLoader.class)), is(false));
+ }
+
+ @Test
+ public void testIsChildOf() throws Exception {
+ ClassLoader parent = new URLClassLoader(new URL[0], null);
+ assertThat(ElementMatchers.isChildOf(parent).matches(new URLClassLoader(new URL[0], parent)), is(true));
+ assertThat(ElementMatchers.isChildOf(parent).matches(new URLClassLoader(new URL[0], null)), is(false));
+ assertThat(ElementMatchers.isChildOf(parent).matches(null), is(false));
+ assertThat(ElementMatchers.isChildOf(null).matches(mock(ClassLoader.class)), is(true));
+ }
+
+ @Test
+ public void testIsParentOf() throws Exception {
+ ClassLoader parent = new URLClassLoader(new URL[0], null);
+ assertThat(ElementMatchers.isParentOf(new URLClassLoader(new URL[0], parent)).matches(parent), is(true));
+ assertThat(ElementMatchers.isParentOf(new URLClassLoader(new URL[0], null)).matches(parent), is(false));
+ assertThat(ElementMatchers.isParentOf(null).matches(new URLClassLoader(new URL[0], null)), is(false));
+ assertThat(ElementMatchers.isParentOf(null).matches(null), is(true));
+ assertThat(ElementMatchers.isParentOf(mock(ClassLoader.class)).matches(null), is(true));
+ }
+
+ @Test
+ public void testOfType() throws Exception {
+ ClassLoader classLoader = new URLClassLoader(new URL[0], null);
+ assertThat(ElementMatchers.ofType(ElementMatchers.is(URLClassLoader.class)).matches(classLoader), is(true));
+ assertThat(ElementMatchers.ofType(ElementMatchers.is(ClassLoader.class)).matches(classLoader), is(false));
+ assertThat(ElementMatchers.ofType(ElementMatchers.is(URLClassLoader.class)).matches(null), is(false));
+ }
+
+ @Test
+ public void testSupportsModules() throws Exception {
+ assertThat(ElementMatchers.supportsModules().matches(mock(JavaModule.class)), is(true));
+ assertThat(ElementMatchers.supportsModules().matches(null), is(false));
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testConstructorIsHidden() throws Exception {
+ assertThat(Modifier.isPrivate(ElementMatchers.class.getDeclaredConstructor().getModifiers()), is(true));
+ Constructor<?> constructor = ElementMatchers.class.getDeclaredConstructor();
+ constructor.setAccessible(true);
+ try {
+ constructor.newInstance();
+ fail();
+ } catch (InvocationTargetException exception) {
+ throw (UnsupportedOperationException) exception.getCause();
+ }
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ private @interface IsAnnotatedWithAnnotation {
+ /* empty */
+ }
+
+ @Inherited
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface OtherAnnotation {
+ /* empty */
+ }
+
+ public interface GenericMethodType<T extends Exception> {
+
+ T foo(T t) throws T;
+
+ interface Inner extends GenericMethodType<RuntimeException> {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public interface GenericDeclaredBy<T> {
+
+ void foo();
+
+ interface Inner extends GenericDeclaredBy<String> {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class FieldSample {
+
+ String foo;
+
+ Object bar;
+
+ volatile Object qux;
+
+ transient Object baz;
+ }
+
+ private static class IsDeclaredBy {
+
+ static class Inner {
+ /* empty */
+ }
+ }
+
+ public static class IsVisibleTo {
+ /* empty */
+ }
+
+ private static class IsNotVisibleTo {
+ /* empty */
+ }
+
+ @IsAnnotatedWithAnnotation
+ private static class IsAnnotatedWith {
+
+ }
+
+ @SuppressWarnings("unused")
+ private abstract static class IsEqual {
+
+ abstract void foo();
+ }
+
+ @SuppressWarnings("unused")
+ private abstract static class Returns {
+
+ abstract void foo();
+
+ abstract String bar();
+ }
+
+ @SuppressWarnings("unused")
+ private abstract static class TakesArguments {
+
+ abstract void foo(Void a);
+
+ abstract void bar(String a, int b);
+ }
+
+ private abstract static class CanThrow {
+
+ protected abstract void foo() throws IOException;
+
+ protected abstract void bar();
+ }
+
+ public static class GenericType<T> {
+
+ public void foo(T t) {
+ /* empty */
+ }
+
+ public static class Extension extends GenericType<Void> {
+
+ @Override
+ public void foo(Void t) {
+ /* empty */
+ }
+ }
+ }
+
+ private static class ObjectMethods {
+
+ @Override
+ public int hashCode() {
+ return super.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return super.equals(other);
+ }
+
+ @Override
+ protected Object clone() throws CloneNotSupportedException {
+ return super.clone();
+ }
+
+ @Override
+ public String toString() {
+ return super.toString();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ super.finalize();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class IsVirtual {
+
+ public static void bar() {
+ /* empty */
+ }
+
+ private void foo() {
+ /* empty */
+ }
+
+ public final void qux() {
+ /* empty */
+ }
+
+ public void baz() {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class Getters<T> {
+
+ public void getFoo() {
+ /* empty */
+ }
+
+ public Boolean isBar() {
+ return null;
+ }
+
+ public boolean isQux() {
+ return false;
+ }
+
+ public Boolean getBar() {
+ return null;
+ }
+
+ public boolean getQux() {
+ return false;
+ }
+
+ public String isBaz() {
+ return null;
+ }
+
+ public String getBaz() {
+ return null;
+ }
+
+ public String getBaz(Void argument) {
+ return null;
+ }
+
+ public T getQuxbaz() {
+ return null;
+ }
+
+ public Object get() {
+ return null;
+ }
+
+ public boolean is() {
+ return false;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class Setters<T> {
+
+ public void setFoo() {
+ /* empty */
+ }
+
+ public void setBar(boolean argument) {
+ /* empty */
+ }
+
+ public void setQux(Boolean argument) {
+ /* empty */
+ }
+
+ public void setBaz(String argument) {
+ /* empty */
+ }
+
+ public void setBaz(String argument, Void argument2) {
+ /* empty */
+ }
+
+ public void setQuxbaz(T argument) {
+ /* empty */
+ }
+
+ public void set(Object argument) {
+ /* empty */
+ }
+ }
+
+ @OtherAnnotation
+ public static class Other {
+ /* empty */
+ }
+
+ public static class OtherInherited extends Other {
+ /* empty */
+ }
+
+ @SuppressWarnings("unused")
+ public static class DeclaresFieldOrMethod {
+
+ @OtherAnnotation
+ Void field;
+
+ @OtherAnnotation
+ void method() {
+
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class GenericFieldType<T> {
+
+ GenericFieldType<?> foo;
+
+ T bar;
+
+ public static class Inner extends GenericFieldType<Void> {
+ /* empty */
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class GenericConstructorType<T extends Exception> {
+
+ GenericConstructorType(T t) throws T {
+ /* empty */
+ }
+
+ public static class Inner extends GenericConstructorType<RuntimeException> {
+
+ public Inner(RuntimeException exception) throws RuntimeException {
+ super(exception);
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/EqualityMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/EqualityMatcherTest.java
new file mode 100644
index 0000000..e5f9088
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/EqualityMatcherTest.java
@@ -0,0 +1,25 @@
+package net.bytebuddy.matcher;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class EqualityMatcherTest extends AbstractElementMatcherTest<EqualityMatcher<?>> {
+
+ @SuppressWarnings("unchecked")
+ public EqualityMatcherTest() {
+ super((Class<EqualityMatcher<?>>) (Object) EqualityMatcher.class, "is");
+ }
+
+ @Test
+ public void testMatch() throws Exception {
+ Object target = new Object();
+ assertThat(new EqualityMatcher<Object>(target).matches(target), is(true));
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ assertThat(new EqualityMatcher<Object>(new Object()).matches(new Object()), is(false));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/ErasureMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/ErasureMatcherTest.java
new file mode 100644
index 0000000..1409e23
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/ErasureMatcherTest.java
@@ -0,0 +1,57 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class ErasureMatcherTest extends AbstractElementMatcherTest<ErasureMatcher<?>> {
+
+ @Mock
+ private TypeDefinition typeDefinition;
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Mock
+ private ElementMatcher<TypeDescription> elementMatcher;
+
+ @SuppressWarnings("unchecked")
+ public ErasureMatcherTest() {
+ super((Class<? extends ErasureMatcher<?>>) (Object) ErasureMatcher.class, "erasure");
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(typeDefinition.asErasure()).thenReturn(typeDescription);
+ }
+
+ @Test
+ public void testMatch() throws Exception {
+ when(elementMatcher.matches(typeDescription)).thenReturn(true);
+ when(typeDefinition.getSort()).thenReturn(TypeDefinition.Sort.NON_GENERIC);
+ assertThat(new ErasureMatcher<TypeDefinition>(elementMatcher).matches(typeDefinition), is(true));
+ verify(typeDefinition).asErasure();
+ verifyNoMoreInteractions(typeDefinition);
+ verify(elementMatcher).matches(typeDescription);
+ verifyNoMoreInteractions(elementMatcher);
+ verifyZeroInteractions(typeDescription);
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ when(elementMatcher.matches(typeDescription)).thenReturn(false);
+ when(typeDefinition.getSort()).thenReturn(TypeDefinition.Sort.NON_GENERIC);
+ assertThat(new ErasureMatcher<TypeDefinition>(elementMatcher).matches(typeDefinition), is(false));
+ verify(typeDefinition).asErasure();
+ verifyNoMoreInteractions(typeDefinition);
+ verify(elementMatcher).matches(typeDescription);
+ verifyNoMoreInteractions(elementMatcher);
+ verifyZeroInteractions(typeDescription);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/FailSafeMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/FailSafeMatcherTest.java
new file mode 100644
index 0000000..be9d307
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/FailSafeMatcherTest.java
@@ -0,0 +1,60 @@
+package net.bytebuddy.matcher;
+
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class FailSafeMatcherTest extends AbstractElementMatcherTest<FailSafeMatcher<?>> {
+
+ @SuppressWarnings("unchecked")
+ public FailSafeMatcherTest() {
+ super((Class<FailSafeMatcher<?>>) (Object) FailSafeMatcher.class, "failSafe");
+ }
+
+ @Mock
+ private ElementMatcher<Object> elementMatcher;
+
+ @Mock
+ private Object target;
+
+ @Test
+ public void testMatch() throws Exception {
+ when(elementMatcher.matches(target)).thenReturn(true);
+ assertThat(new FailSafeMatcher<Object>(elementMatcher, false).matches(target), is(true));
+ verifyZeroInteractions(target);
+ verify(elementMatcher).matches(target);
+ verifyNoMoreInteractions(elementMatcher);
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ when(elementMatcher.matches(target)).thenReturn(false);
+ assertThat(new FailSafeMatcher<Object>(elementMatcher, false).matches(target), is(false));
+ verifyZeroInteractions(target);
+ verify(elementMatcher).matches(target);
+ verifyNoMoreInteractions(elementMatcher);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMatchOnFailure() throws Exception {
+ when(elementMatcher.matches(target)).thenThrow(RuntimeException.class);
+ assertThat(new FailSafeMatcher<Object>(elementMatcher, true).matches(target), is(true));
+ verifyZeroInteractions(target);
+ verify(elementMatcher).matches(target);
+ verifyNoMoreInteractions(elementMatcher);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testNoMatchOnFailure() throws Exception {
+ when(elementMatcher.matches(target)).thenThrow(RuntimeException.class);
+ assertThat(new FailSafeMatcher<Object>(elementMatcher, false).matches(target), is(false));
+ verifyZeroInteractions(target);
+ verify(elementMatcher).matches(target);
+ verifyNoMoreInteractions(elementMatcher);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/FieldTypeMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/FieldTypeMatcherTest.java
new file mode 100644
index 0000000..06b5c6f
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/FieldTypeMatcherTest.java
@@ -0,0 +1,49 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class FieldTypeMatcherTest extends AbstractElementMatcherTest<FieldTypeMatcher<?>> {
+
+ @Mock
+ private ElementMatcher<? super TypeDescription.Generic> typeMatcher;
+
+ @Mock
+ private TypeDescription.Generic fieldType;
+
+ @Mock
+ private FieldDescription fieldDescription;
+
+ @SuppressWarnings("unchecked")
+ public FieldTypeMatcherTest() {
+ super((Class<? extends FieldTypeMatcher<?>>) (Object) FieldTypeMatcher.class, "ofType");
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(fieldDescription.getType()).thenReturn(fieldType);
+ }
+
+ @Test
+ public void testMatch() throws Exception {
+ when(typeMatcher.matches(fieldType)).thenReturn(true);
+ assertThat(new FieldTypeMatcher<FieldDescription>(typeMatcher).matches(fieldDescription), is(true));
+ verify(typeMatcher).matches(fieldType);
+ verifyNoMoreInteractions(typeMatcher);
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ when(typeMatcher.matches(fieldType)).thenReturn(false);
+ assertThat(new FieldTypeMatcher<FieldDescription>(typeMatcher).matches(fieldDescription), is(false));
+ verify(typeMatcher).matches(fieldType);
+ verifyNoMoreInteractions(typeMatcher);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/FilterableListEmptyTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/FilterableListEmptyTest.java
new file mode 100644
index 0000000..92d57e3
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/FilterableListEmptyTest.java
@@ -0,0 +1,52 @@
+package net.bytebuddy.matcher;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class FilterableListEmptyTest {
+
+ @SuppressWarnings("unchecked")
+ private FilterableList empty = new FilterableList.Empty();
+
+ @Test(expected = IndexOutOfBoundsException.class)
+ public void testGet() throws Exception {
+ empty.get(0);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testGetOnly() throws Exception {
+ empty.getOnly();
+ }
+
+ @Test
+ public void testSize() throws Exception {
+ assertThat(empty.size(), is(0));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testFilter() throws Exception {
+ assertThat(empty.filter(mock(ElementMatcher.class)), is(empty));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testSubListZero() throws Exception {
+ assertThat(empty.subList(0, 0), is(empty));
+ }
+
+ @Test(expected = IndexOutOfBoundsException.class)
+ @SuppressWarnings("unchecked")
+ public void testSubListOverflow() throws Exception {
+ empty.subList(1, 1);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ @SuppressWarnings("unchecked")
+ public void testSubListBounds() throws Exception {
+ empty.subList(1, 0);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/HasSuperTypeMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/HasSuperTypeMatcherTest.java
new file mode 100644
index 0000000..50d253b
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/HasSuperTypeMatcherTest.java
@@ -0,0 +1,69 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class HasSuperTypeMatcherTest extends AbstractElementMatcherTest<HasSuperTypeMatcher<?>> {
+
+ @Mock
+ private ElementMatcher<? super TypeDescription.Generic> typeMatcher;
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Mock
+ private TypeDescription.Generic superType, interfaceType, implicitInterfaceType;
+
+ @Before
+ public void setUp() throws Exception {
+ when(superType.asGenericType()).thenReturn(superType);
+ when(interfaceType.asGenericType()).thenReturn(interfaceType);
+ when(interfaceType.asErasure()).thenReturn(mock(TypeDescription.class));
+ when(implicitInterfaceType.asGenericType()).thenReturn(implicitInterfaceType);
+ when(implicitInterfaceType.asErasure()).thenReturn(mock(TypeDescription.class));
+ when(typeDescription.iterator()).thenReturn(Collections.<TypeDefinition>singletonList(superType).iterator());
+ when(superType.getInterfaces()).thenReturn(new TypeList.Generic.Explicit(interfaceType));
+ when(interfaceType.getInterfaces()).thenReturn(new TypeList.Generic.Explicit(implicitInterfaceType));
+ when(implicitInterfaceType.getInterfaces()).thenReturn(new TypeList.Generic.Empty());
+ }
+
+ @SuppressWarnings("unchecked")
+ public HasSuperTypeMatcherTest() {
+ super((Class<HasSuperTypeMatcher<?>>) (Object) HasSuperTypeMatcher.class, "hasSuperType");
+ }
+
+ @Test
+ public void testMatchSuperClass() throws Exception {
+ when(typeMatcher.matches(superType)).thenReturn(true);
+ assertThat(new HasSuperTypeMatcher<TypeDescription>(typeMatcher).matches(typeDescription), is(true));
+ }
+
+ @Test
+ public void testMatchSuperInterface() throws Exception {
+ when(typeMatcher.matches(interfaceType)).thenReturn(true);
+ assertThat(new HasSuperTypeMatcher<TypeDescription>(typeMatcher).matches(typeDescription), is(true));
+ }
+
+ @Test
+ public void testMatchSuperInterfaceImplicit() throws Exception {
+ when(typeMatcher.matches(implicitInterfaceType)).thenReturn(true);
+ assertThat(new HasSuperTypeMatcher<TypeDescription>(typeMatcher).matches(typeDescription), is(true));
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ assertThat(new HasSuperTypeMatcher<TypeDescription>(typeMatcher).matches(typeDescription), is(false));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/InheritedAnnotationMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/InheritedAnnotationMatcherTest.java
new file mode 100644
index 0000000..3e328f0
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/InheritedAnnotationMatcherTest.java
@@ -0,0 +1,49 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.type.TypeDescription;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class InheritedAnnotationMatcherTest extends AbstractElementMatcherTest<InheritedAnnotationMatcher<?>> {
+
+ @Mock
+ private ElementMatcher<? super AnnotationList> annotationMatcher;
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Mock
+ private AnnotationList annotationList;
+
+ @SuppressWarnings("unchecked")
+ public InheritedAnnotationMatcherTest() {
+ super((Class<InheritedAnnotationMatcher<?>>) (Object) InheritedAnnotationMatcher.class, "inheritsAnnotations");
+ }
+
+ @Test
+ public void testMatch() throws Exception {
+ when(typeDescription.getInheritedAnnotations()).thenReturn(annotationList);
+ when(annotationMatcher.matches(annotationList)).thenReturn(true);
+ assertThat(new InheritedAnnotationMatcher<TypeDescription>(annotationMatcher).matches(typeDescription), is(true));
+ verify(annotationMatcher).matches(annotationList);
+ verifyNoMoreInteractions(annotationMatcher);
+ verify(typeDescription).getInheritedAnnotations();
+ verifyNoMoreInteractions(typeDescription);
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ when(typeDescription.getInheritedAnnotations()).thenReturn(annotationList);
+ when(annotationMatcher.matches(annotationList)).thenReturn(false);
+ assertThat(new InheritedAnnotationMatcher<TypeDescription>(annotationMatcher).matches(typeDescription), is(false));
+ verify(annotationMatcher).matches(annotationList);
+ verifyNoMoreInteractions(annotationMatcher);
+ verify(typeDescription).getInheritedAnnotations();
+ verifyNoMoreInteractions(typeDescription);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/InstanceTypeMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/InstanceTypeMatcherTest.java
new file mode 100644
index 0000000..4526744
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/InstanceTypeMatcherTest.java
@@ -0,0 +1,40 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.type.TypeDescription;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class InstanceTypeMatcherTest extends AbstractElementMatcherTest<InstanceTypeMatcher<?>> {
+
+ @Mock
+ private Object object;
+
+ @Mock
+ private ElementMatcher<? super TypeDescription> matcher;
+
+ @SuppressWarnings("unchecked")
+ public InstanceTypeMatcherTest() {
+ super((Class<InstanceTypeMatcher<?>>) (Object) InstanceTypeMatcher.class, "ofType");
+ }
+
+ @Test
+ public void testMatch() throws Exception {
+ when(matcher.matches(new TypeDescription.ForLoadedType(object.getClass()))).thenReturn(true);
+ assertThat(new InstanceTypeMatcher<Object>(matcher).matches(object), is(true));
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ when(matcher.matches(new TypeDescription.ForLoadedType(object.getClass()))).thenReturn(false);
+ assertThat(new InstanceTypeMatcher<Object>(matcher).matches(object), is(false));
+ }
+
+ @Test
+ public void testNoMatchNull() throws Exception {
+ assertThat(new InstanceTypeMatcher<Object>(matcher).matches(null), is(false));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/IsNamedMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/IsNamedMatcherTest.java
new file mode 100644
index 0000000..968aa60
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/IsNamedMatcherTest.java
@@ -0,0 +1,32 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.NamedElement;
+import net.bytebuddy.description.type.TypeDescription;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class IsNamedMatcherTest extends AbstractElementMatcherTest<IsNamedMatcher<?>> {
+
+ @Mock
+ private NamedElement.WithOptionalName namedElement;
+
+ @SuppressWarnings("unchecked")
+ public IsNamedMatcherTest() {
+ super((Class<IsNamedMatcher<?>>) (Object) IsNamedMatcher.class, "isNamed");
+ }
+
+ @Test
+ public void testMatch() throws Exception {
+ when(namedElement.isNamed()).thenReturn(true);
+ assertThat(new IsNamedMatcher<NamedElement.WithOptionalName>().matches(namedElement), is(true));
+ }
+
+ @Test
+ public void testPositiveToNegative() throws Exception {
+ assertThat(new IsNamedMatcher<NamedElement.WithOptionalName>().matches(namedElement), is(false));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/LatentMatcherAccessorTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/LatentMatcherAccessorTest.java
new file mode 100644
index 0000000..6fc23e5
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/LatentMatcherAccessorTest.java
@@ -0,0 +1,39 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+public class LatentMatcherAccessorTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private ElementMatcher<? super Object> matcher;
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testManifestation() throws Exception {
+ LatentMatcher<Object> matcher = new LatentMatcher.Resolved<Object>(this.matcher);
+ assertThat(matcher.resolve(typeDescription), is((ElementMatcher) this.matcher));
+ verifyZeroInteractions(this.matcher);
+ verifyZeroInteractions(typeDescription);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(LatentMatcher.Resolved.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/LatentMatcherConjunctionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/LatentMatcherConjunctionTest.java
new file mode 100644
index 0000000..52ffb65
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/LatentMatcherConjunctionTest.java
@@ -0,0 +1,49 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static net.bytebuddy.matcher.ElementMatchers.any;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class LatentMatcherConjunctionTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private LatentMatcher<?> left, right;
+
+ @Mock
+ private ElementMatcher<?> leftMatcher, rightMatcher;
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ when(left.resolve(typeDescription)).thenReturn((ElementMatcher) leftMatcher);
+ when(right.resolve(typeDescription)).thenReturn((ElementMatcher) rightMatcher);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testManifestation() throws Exception {
+ assertThat(new LatentMatcher.Conjunction(left, right).resolve(typeDescription),
+ is((ElementMatcher) any().and((ElementMatcher) leftMatcher).and((ElementMatcher) rightMatcher)));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(LatentMatcher.Conjunction.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/LatentMatcherDisjunctionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/LatentMatcherDisjunctionTest.java
new file mode 100644
index 0000000..051d1a5
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/LatentMatcherDisjunctionTest.java
@@ -0,0 +1,49 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static net.bytebuddy.matcher.ElementMatchers.none;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class LatentMatcherDisjunctionTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private LatentMatcher<?> left, right;
+
+ @Mock
+ private ElementMatcher<?> leftMatcher, rightMatcher;
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ when(left.resolve(typeDescription)).thenReturn((ElementMatcher) leftMatcher);
+ when(right.resolve(typeDescription)).thenReturn((ElementMatcher) rightMatcher);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testManifestation() throws Exception {
+ assertThat(new LatentMatcher.Disjunction(left, right).resolve(typeDescription),
+ is((ElementMatcher) none().or((ElementMatcher) leftMatcher).or((ElementMatcher) rightMatcher)));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(LatentMatcher.Disjunction.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/LatentMatcherForFieldTokenTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/LatentMatcherForFieldTokenTest.java
new file mode 100644
index 0000000..ef41fd5
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/LatentMatcherForFieldTokenTest.java
@@ -0,0 +1,60 @@
+package net.bytebuddy.matcher;
+
+
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class LatentMatcherForFieldTokenTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private FieldDescription.Token token;
+
+ @Mock
+ private FieldDescription.SignatureToken signatureToken, otherSignatureToken;
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Mock
+ private FieldDescription fieldDescription;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ when(token.accept(any(TypeDescription.Generic.Visitor.class))).thenReturn(token);
+ }
+
+ @Test
+ public void testMatch() throws Exception {
+ when(fieldDescription.asSignatureToken()).thenReturn(signatureToken);
+ when(token.asSignatureToken(typeDescription)).thenReturn(signatureToken);
+ assertThat(new LatentMatcher.ForFieldToken(token).resolve(typeDescription).matches(fieldDescription), is(true));
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ when(fieldDescription.asSignatureToken()).thenReturn(otherSignatureToken);
+ when(token.asSignatureToken(typeDescription)).thenReturn(signatureToken);
+ assertThat(new LatentMatcher.ForFieldToken(token).resolve(typeDescription).matches(fieldDescription), is(false));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(LatentMatcher.ForFieldToken.class).apply();
+ ObjectPropertyAssertion.of(LatentMatcher.ForFieldToken.ResolvedMatcher.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/LatentMatcherForMethodTokenTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/LatentMatcherForMethodTokenTest.java
new file mode 100644
index 0000000..4319506
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/LatentMatcherForMethodTokenTest.java
@@ -0,0 +1,53 @@
+package net.bytebuddy.matcher;
+
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class LatentMatcherForMethodTokenTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodDescription.Token token;
+
+ @Mock
+ private MethodDescription.SignatureToken signatureToken, otherToken;
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Mock
+ private MethodDescription methodDescription;
+
+ @Test
+ public void testMatch() throws Exception {
+ when(methodDescription.asSignatureToken()).thenReturn(signatureToken);
+ when(token.asSignatureToken(typeDescription)).thenReturn(signatureToken);
+ assertThat(new LatentMatcher.ForMethodToken(token).resolve(typeDescription).matches(methodDescription), is(true));
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ when(methodDescription.asSignatureToken()).thenReturn(signatureToken);
+ when(token.asSignatureToken(typeDescription)).thenReturn(otherToken);
+ assertThat(new LatentMatcher.ForMethodToken(token).resolve(typeDescription).matches(methodDescription), is(false));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(LatentMatcher.ForMethodToken.class).apply();
+ ObjectPropertyAssertion.of(LatentMatcher.ForMethodToken.ResolvedMatcher.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/LatentMatcherForSelfDeclaredMethodTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/LatentMatcherForSelfDeclaredMethodTest.java
new file mode 100644
index 0000000..8b3dd3d
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/LatentMatcherForSelfDeclaredMethodTest.java
@@ -0,0 +1,40 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
+import static net.bytebuddy.matcher.ElementMatchers.not;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class LatentMatcherForSelfDeclaredMethodTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testDeclared() throws Exception {
+ assertThat(LatentMatcher.ForSelfDeclaredMethod.DECLARED.resolve(typeDescription), is((ElementMatcher) isDeclaredBy(typeDescription)));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testNotDeclared() throws Exception {
+ assertThat(LatentMatcher.ForSelfDeclaredMethod.NOT_DECLARED.resolve(typeDescription), is((ElementMatcher) not(isDeclaredBy(typeDescription))));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(LatentMatcher.ForSelfDeclaredMethod.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/MethodExceptionTypeMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/MethodExceptionTypeMatcherTest.java
new file mode 100644
index 0000000..7ed6d9b
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/MethodExceptionTypeMatcherTest.java
@@ -0,0 +1,52 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class MethodExceptionTypeMatcherTest extends AbstractElementMatcherTest<MethodExceptionTypeMatcher<?>> {
+
+ @Mock
+ private ElementMatcher<? super List<? extends TypeDescription.Generic>> exceptionMatcher;
+
+ @Mock
+ private MethodDescription methodDescription;
+
+ @Mock
+ private TypeList.Generic typeList;
+
+ @SuppressWarnings("unchecked")
+ public MethodExceptionTypeMatcherTest() {
+ super((Class<MethodExceptionTypeMatcher<?>>) (Object) MethodExceptionTypeMatcher.class, "exceptions");
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(methodDescription.getExceptionTypes()).thenReturn(typeList);
+ }
+
+ @Test
+ public void testMatch() throws Exception {
+ when(exceptionMatcher.matches(typeList)).thenReturn(true);
+ assertThat(new MethodExceptionTypeMatcher<MethodDescription>(exceptionMatcher).matches(methodDescription), is(true));
+ verify(exceptionMatcher).matches(typeList);
+ verifyNoMoreInteractions(exceptionMatcher);
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ when(exceptionMatcher.matches(typeList)).thenReturn(false);
+ assertThat(new MethodExceptionTypeMatcher<MethodDescription>(exceptionMatcher).matches(methodDescription), is(false));
+ verify(exceptionMatcher).matches(typeList);
+ verifyNoMoreInteractions(exceptionMatcher);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/MethodOverrideMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/MethodOverrideMatcherTest.java
new file mode 100644
index 0000000..7000a27
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/MethodOverrideMatcherTest.java
@@ -0,0 +1,116 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.util.Arrays;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+public class MethodOverrideMatcherTest extends AbstractElementMatcherTest<MethodOverrideMatcher<?>> {
+
+ @Mock
+ private ElementMatcher<? super TypeDescription.Generic> typeMatcher;
+
+ @Mock
+ private TypeDescription.Generic declaringType, superType, interfaceType;
+
+ @Mock
+ private TypeDescription rawDeclaringType, rawSuperType, rawInterfaceType;
+
+ @Mock
+ private MethodDescription methodDescription;
+
+ @Mock
+ private MethodDescription.InGenericShape declaredTypeMethod, superTypeMethod, interfaceTypeMethod;
+
+ @Mock
+ private MethodDescription.SignatureToken token, otherToken;
+
+ @SuppressWarnings("unchecked")
+ public MethodOverrideMatcherTest() {
+ super((Class<? extends MethodOverrideMatcher<?>>) (Object) MethodOverrideMatcher.class, "isOverriddenFrom");
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(declaredTypeMethod.isVirtual()).thenReturn(true);
+ when(superTypeMethod.isVirtual()).thenReturn(true);
+ when(interfaceTypeMethod.isVirtual()).thenReturn(true);
+ when(methodDescription.getDeclaringType()).thenReturn(declaringType);
+ when(methodDescription.asSignatureToken()).thenReturn(token);
+ when(declaringType.asGenericType()).thenReturn(declaringType);
+ when(superType.asGenericType()).thenReturn(superType);
+ when(interfaceType.asGenericType()).thenReturn(interfaceType);
+ when(declaringType.asErasure()).thenReturn(rawDeclaringType);
+ when(superType.asErasure()).thenReturn(rawSuperType);
+ when(interfaceType.asErasure()).thenReturn(rawInterfaceType);
+ when(declaringType.iterator()).thenReturn(Arrays.<TypeDefinition>asList(declaringType, superType).iterator());
+ when(declaringType.getInterfaces()).thenReturn(new TypeList.Generic.Explicit(interfaceType));
+ when(superType.getInterfaces()).thenReturn(new TypeList.Generic.Empty());
+ when(interfaceType.getInterfaces()).thenReturn(new TypeList.Generic.Empty());
+ when(declaringType.getDeclaredMethods()).thenReturn(new MethodList.Explicit<MethodDescription.InGenericShape>(declaredTypeMethod));
+ when(superType.getDeclaredMethods()).thenReturn(new MethodList.Explicit<MethodDescription.InGenericShape>(superTypeMethod));
+ when(interfaceType.getDeclaredMethods()).thenReturn(new MethodList.Explicit<MethodDescription.InGenericShape>(interfaceTypeMethod));
+ }
+
+ @Test
+ public void testDirectMatch() throws Exception {
+ when(declaredTypeMethod.asSignatureToken()).thenReturn(token);
+ when(typeMatcher.matches(declaringType)).thenReturn(true);
+ assertThat(new MethodOverrideMatcher<MethodDescription>(typeMatcher).matches(methodDescription), is(true));
+ verify(typeMatcher).matches(declaringType);
+ verifyNoMoreInteractions(typeMatcher);
+ }
+
+ @Test
+ public void testSuperTypeMatch() throws Exception {
+ when(declaredTypeMethod.asSignatureToken()).thenReturn(otherToken);
+ when(interfaceTypeMethod.asSignatureToken()).thenReturn(otherToken);
+ when(superTypeMethod.asSignatureToken()).thenReturn(token);
+ when(typeMatcher.matches(superType)).thenReturn(true);
+ assertThat(new MethodOverrideMatcher<MethodDescription>(typeMatcher).matches(methodDescription), is(true));
+ verify(typeMatcher).matches(superType);
+ verifyNoMoreInteractions(typeMatcher);
+ }
+
+ @Test
+ public void testInterfaceTypeMatch() throws Exception {
+ when(declaredTypeMethod.asSignatureToken()).thenReturn(otherToken);
+ when(superTypeMethod.asSignatureToken()).thenReturn(otherToken);
+ when(interfaceTypeMethod.asSignatureToken()).thenReturn(token);
+ when(typeMatcher.matches(interfaceType)).thenReturn(true);
+ assertThat(new MethodOverrideMatcher<MethodDescription>(typeMatcher).matches(methodDescription), is(true));
+ verify(typeMatcher).matches(interfaceType);
+ verifyNoMoreInteractions(typeMatcher);
+ }
+
+ @Test
+ public void testNoMatchMatcher() throws Exception {
+ when(declaredTypeMethod.asSignatureToken()).thenReturn(token);
+ when(superTypeMethod.asSignatureToken()).thenReturn(token);
+ when(interfaceTypeMethod.asSignatureToken()).thenReturn(token);
+ assertThat(new MethodOverrideMatcher<MethodDescription>(typeMatcher).matches(methodDescription), is(false));
+ verify(typeMatcher).matches(declaringType);
+ verify(typeMatcher).matches(superType);
+ verify(typeMatcher).matches(interfaceType);
+ verifyNoMoreInteractions(typeMatcher);
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ when(declaredTypeMethod.asSignatureToken()).thenReturn(otherToken);
+ when(superTypeMethod.asSignatureToken()).thenReturn(otherToken);
+ when(interfaceTypeMethod.asSignatureToken()).thenReturn(otherToken);
+ assertThat(new MethodOverrideMatcher<MethodDescription>(typeMatcher).matches(methodDescription), is(false));
+ verifyZeroInteractions(typeMatcher);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/MethodParameterMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/MethodParameterMatcherTest.java
new file mode 100644
index 0000000..f09206b
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/MethodParameterMatcherTest.java
@@ -0,0 +1,50 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterList;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class MethodParameterMatcherTest extends AbstractElementMatcherTest<MethodParametersMatcher<?>> {
+
+ @Mock
+ private ElementMatcher<? super ParameterList<?>> parameterListMatcher;
+
+ @Mock
+ private MethodDescription methodDescription;
+
+ @Mock
+ private ParameterList<?> parameterList;
+
+ @SuppressWarnings("unchecked")
+ public MethodParameterMatcherTest() {
+ super((Class<MethodParametersMatcher<?>>) (Object) MethodParametersMatcher.class, "hasParameter");
+ }
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ when(methodDescription.getParameters()).thenReturn((ParameterList) parameterList);
+ }
+
+ @Test
+ public void testMatch() throws Exception {
+ when(parameterListMatcher.matches(parameterList)).thenReturn(true);
+ assertThat(new MethodParametersMatcher<MethodDescription>(parameterListMatcher).matches(methodDescription), is(true));
+ verify(parameterListMatcher).matches(parameterList);
+ verifyNoMoreInteractions(parameterListMatcher);
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ when(parameterListMatcher.matches(parameterList)).thenReturn(false);
+ assertThat(new MethodParametersMatcher<MethodDescription>(parameterListMatcher).matches(methodDescription), is(false));
+ verify(parameterListMatcher).matches(parameterList);
+ verifyNoMoreInteractions(parameterListMatcher);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/MethodParameterTypeMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/MethodParameterTypeMatcherTest.java
new file mode 100644
index 0000000..989b6a1
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/MethodParameterTypeMatcherTest.java
@@ -0,0 +1,49 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class MethodParameterTypeMatcherTest extends AbstractElementMatcherTest<MethodParameterTypeMatcher<?>> {
+
+ @Mock
+ private ElementMatcher<? super TypeDescription.Generic> parameterMatcher;
+
+ @Mock
+ private TypeDescription.Generic typeDescription;
+
+ @Mock
+ private ParameterDescription parameterDescription;
+
+ @SuppressWarnings("unchecked")
+ public MethodParameterTypeMatcherTest() {
+ super((Class<MethodParameterTypeMatcher<?>>) (Object) MethodParameterTypeMatcher.class, "hasType");
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(parameterDescription.getType()).thenReturn(typeDescription);
+ }
+
+ @Test
+ public void testMatch() throws Exception {
+ when(parameterMatcher.matches(typeDescription)).thenReturn(true);
+ assertThat(new MethodParameterTypeMatcher<ParameterDescription>(parameterMatcher).matches(parameterDescription), is(true));
+ verify(parameterMatcher).matches(typeDescription);
+ verifyNoMoreInteractions(parameterMatcher);
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ when(parameterMatcher.matches(typeDescription)).thenReturn(false);
+ assertThat(new MethodParameterTypeMatcher<ParameterDescription>(parameterMatcher).matches(parameterDescription), is(false));
+ verify(parameterMatcher).matches(typeDescription);
+ verifyNoMoreInteractions(parameterMatcher);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/MethodParameterTypesMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/MethodParameterTypesMatcherTest.java
new file mode 100644
index 0000000..f9948c9
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/MethodParameterTypesMatcherTest.java
@@ -0,0 +1,52 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.method.ParameterList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class MethodParameterTypesMatcherTest extends AbstractElementMatcherTest<MethodParameterTypesMatcher<?>> {
+
+ @Mock
+ private ElementMatcher<? super List<? extends TypeDescription.Generic>> parameterMatcher;
+
+ @Mock
+ private TypeList.Generic typeList;
+
+ @Mock
+ private ParameterList parameterList;
+
+ @SuppressWarnings("unchecked")
+ public MethodParameterTypesMatcherTest() {
+ super((Class<MethodParameterTypesMatcher<?>>) (Object) MethodParameterTypesMatcher.class, "hasTypes");
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(parameterList.asTypeList()).thenReturn(typeList);
+ }
+
+ @Test
+ public void testMatch() throws Exception {
+ when(parameterMatcher.matches(typeList)).thenReturn(true);
+ assertThat(new MethodParameterTypesMatcher<ParameterList<?>>(parameterMatcher).matches(parameterList), is(true));
+ verify(parameterMatcher).matches(typeList);
+ verifyNoMoreInteractions(parameterMatcher);
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ when(parameterMatcher.matches(typeList)).thenReturn(false);
+ assertThat(new MethodParameterTypesMatcher<ParameterList<?>>(parameterMatcher).matches(parameterList), is(false));
+ verify(parameterMatcher).matches(typeList);
+ verifyNoMoreInteractions(parameterMatcher);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/MethodReturnTypeMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/MethodReturnTypeMatcherTest.java
new file mode 100644
index 0000000..62b1b68
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/MethodReturnTypeMatcherTest.java
@@ -0,0 +1,49 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class MethodReturnTypeMatcherTest extends AbstractElementMatcherTest<MethodReturnTypeMatcher<?>> {
+
+ @Mock
+ private ElementMatcher<? super TypeDescription.Generic> typeMatcher;
+
+ @Mock
+ private TypeDescription.Generic returnType;
+
+ @Mock
+ private MethodDescription methodDescription;
+
+ @SuppressWarnings("unchecked")
+ public MethodReturnTypeMatcherTest() {
+ super((Class<? extends MethodReturnTypeMatcher<?>>) (Object) MethodReturnTypeMatcher.class, "returns");
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(methodDescription.getReturnType()).thenReturn(returnType);
+ }
+
+ @Test
+ public void testMatch() throws Exception {
+ when(typeMatcher.matches(returnType)).thenReturn(true);
+ assertThat(new MethodReturnTypeMatcher<MethodDescription>(typeMatcher).matches(methodDescription), is(true));
+ verify(typeMatcher).matches(returnType);
+ verifyNoMoreInteractions(typeMatcher);
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ when(typeMatcher.matches(returnType)).thenReturn(false);
+ assertThat(new MethodReturnTypeMatcher<MethodDescription>(typeMatcher).matches(methodDescription), is(false));
+ verify(typeMatcher).matches(returnType);
+ verifyNoMoreInteractions(typeMatcher);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/MethodSortMatcherObjectPropertiesTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/MethodSortMatcherObjectPropertiesTest.java
new file mode 100644
index 0000000..eb3ce27
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/MethodSortMatcherObjectPropertiesTest.java
@@ -0,0 +1,12 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+public class MethodSortMatcherObjectPropertiesTest {
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MethodSortMatcher.Sort.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/MethodSortMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/MethodSortMatcherTest.java
new file mode 100644
index 0000000..d3c514a
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/MethodSortMatcherTest.java
@@ -0,0 +1,106 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.method.MethodDescription;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.Mock;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+ at RunWith(Parameterized.class)
+public class MethodSortMatcherTest extends AbstractElementMatcherTest<MethodSortMatcher<?>> {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ private final MethodSortMatcher.Sort sort;
+
+ private final MockImplementation mockImplementation;
+
+ @Mock
+ private MethodDescription methodDescription;
+
+ @SuppressWarnings("unchecked")
+ public MethodSortMatcherTest(MethodSortMatcher.Sort sort, MockImplementation mockImplementation) {
+ super((Class<MethodSortMatcher<?>>) (Object) MethodSortMatcher.class, sort.getDescription());
+ this.sort = sort;
+ this.mockImplementation = mockImplementation;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {MethodSortMatcher.Sort.CONSTRUCTOR, MockImplementation.CONSTRUCTOR},
+ {MethodSortMatcher.Sort.DEFAULT_METHOD, MockImplementation.DEFAULT_METHOD},
+ {MethodSortMatcher.Sort.METHOD, MockImplementation.METHOD},
+ {MethodSortMatcher.Sort.VIRTUAL, MockImplementation.VIRTUAL},
+ {MethodSortMatcher.Sort.TYPE_INITIALIZER, MockImplementation.TYPE_INITIALIZER},
+ });
+ }
+
+ @Test
+ public void testMatch() throws Exception {
+ mockImplementation.prepare(methodDescription);
+ assertThat(new MethodSortMatcher<MethodDescription>(sort).matches(methodDescription), is(true));
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ assertThat(new MethodSortMatcher<MethodDescription>(sort).matches(methodDescription), is(false));
+ }
+
+ @Override
+ protected String makeRegex(String startsWith) {
+ return null;
+ }
+
+ @Test
+ public void testToString() throws Exception {
+ assertThat(new MethodSortMatcher<MethodDescription>(sort).toString(), is(sort.getDescription()));
+ }
+
+ private enum MockImplementation {
+
+ CONSTRUCTOR {
+ @Override
+ protected void prepare(MethodDescription target) {
+ when(target.isConstructor()).thenReturn(true);
+ }
+ },
+
+ DEFAULT_METHOD {
+ @Override
+ protected void prepare(MethodDescription target) {
+ when(target.isDefaultMethod()).thenReturn(true);
+ }
+ },
+
+ METHOD {
+ @Override
+ protected void prepare(MethodDescription target) {
+ when(target.isMethod()).thenReturn(true);
+ }
+ },
+
+ VIRTUAL {
+ @Override
+ protected void prepare(MethodDescription target) {
+ when(target.isVirtual()).thenReturn(true);
+ }
+ },
+
+ TYPE_INITIALIZER {
+ @Override
+ protected void prepare(MethodDescription target) {
+ when(target.isTypeInitializer()).thenReturn(true);
+ }
+ };
+
+ protected abstract void prepare(MethodDescription target);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/ModifierMatcherObjectPropertiesTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/ModifierMatcherObjectPropertiesTest.java
new file mode 100644
index 0000000..d84b066
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/ModifierMatcherObjectPropertiesTest.java
@@ -0,0 +1,12 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+public class ModifierMatcherObjectPropertiesTest {
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(ModifierMatcher.Mode.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/ModifierMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/ModifierMatcherTest.java
new file mode 100644
index 0000000..7ea07c4
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/ModifierMatcherTest.java
@@ -0,0 +1,81 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.ModifierReviewable;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.Mock;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class ModifierMatcherTest extends AbstractElementMatcherTest<ModifierMatcher<?>> {
+
+ private final ModifierMatcher.Mode mode;
+
+ private final int modifiers;
+
+ @Mock
+ private ModifierReviewable modifierReviewable;
+
+ @SuppressWarnings("unchecked")
+ public ModifierMatcherTest(ModifierMatcher.Mode mode, int modifiers) {
+ super((Class<ModifierMatcher<?>>) (Object) ModifierMatcher.class, mode.getDescription());
+ this.mode = mode;
+ this.modifiers = modifiers;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {ModifierMatcher.Mode.ABSTRACT, Opcodes.ACC_ABSTRACT},
+ {ModifierMatcher.Mode.ANNOTATION, Opcodes.ACC_ANNOTATION},
+ {ModifierMatcher.Mode.BRIDGE, Opcodes.ACC_BRIDGE},
+ {ModifierMatcher.Mode.ENUMERATION, Opcodes.ACC_ENUM},
+ {ModifierMatcher.Mode.FINAL, Opcodes.ACC_FINAL},
+ {ModifierMatcher.Mode.INTERFACE, Opcodes.ACC_INTERFACE},
+ {ModifierMatcher.Mode.MANDATED, Opcodes.ACC_MANDATED},
+ {ModifierMatcher.Mode.NATIVE, Opcodes.ACC_NATIVE},
+ {ModifierMatcher.Mode.PRIVATE, Opcodes.ACC_PRIVATE},
+ {ModifierMatcher.Mode.PROTECTED, Opcodes.ACC_PROTECTED},
+ {ModifierMatcher.Mode.PUBLIC, Opcodes.ACC_PUBLIC},
+ {ModifierMatcher.Mode.STATIC, Opcodes.ACC_STATIC},
+ {ModifierMatcher.Mode.STRICT, Opcodes.ACC_STRICT},
+ {ModifierMatcher.Mode.SYNCHRONIZED, Opcodes.ACC_SYNCHRONIZED},
+ {ModifierMatcher.Mode.SYNTHETIC, Opcodes.ACC_SYNTHETIC},
+ {ModifierMatcher.Mode.TRANSIENT, Opcodes.ACC_TRANSIENT}
+ });
+ }
+
+ @Test
+ public void testMatch() throws Exception {
+ when(modifierReviewable.getModifiers()).thenReturn(modifiers);
+ assertThat(new ModifierMatcher<ModifierReviewable>(mode).matches(modifierReviewable), is(true));
+ verify(modifierReviewable).getModifiers();
+ verifyNoMoreInteractions(modifierReviewable);
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ when(modifierReviewable.getModifiers()).thenReturn(0);
+ assertThat(new ModifierMatcher<ModifierReviewable>(mode).matches(modifierReviewable), is(false));
+ verify(modifierReviewable).getModifiers();
+ verifyNoMoreInteractions(modifierReviewable);
+ }
+
+ @Override
+ protected String makeRegex(String startsWith) {
+ return null;
+ }
+
+ @Test
+ public void testToString() throws Exception {
+ assertThat(new ModifierMatcher<ModifierReviewable>(mode).toString(), is(mode.getDescription()));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/NameMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/NameMatcherTest.java
new file mode 100644
index 0000000..3f3d3a6
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/NameMatcherTest.java
@@ -0,0 +1,47 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.NamedElement;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class NameMatcherTest extends AbstractElementMatcherTest<NameMatcher<?>> {
+
+ private static final String FOO = "foo";
+
+ @Mock
+ private NamedElement namedElement;
+
+ @Mock
+ private ElementMatcher<String> nameMatcher;
+
+ @SuppressWarnings("unchecked")
+ public NameMatcherTest() {
+ super((Class<NameMatcher<?>>) (Object) NameMatcher.class, "name");
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ when(namedElement.getActualName()).thenReturn(FOO);
+ }
+
+ @Test
+ public void testMatch() throws Exception {
+ when(nameMatcher.matches(FOO)).thenReturn(true);
+ assertThat(new NameMatcher<NamedElement>(nameMatcher).matches(namedElement), is(true));
+ verify(nameMatcher).matches(FOO);
+ verifyNoMoreInteractions(nameMatcher);
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ when(nameMatcher.matches(FOO)).thenReturn(false);
+ assertThat(new NameMatcher<NamedElement>(nameMatcher).matches(namedElement), is(false));
+ verify(nameMatcher).matches(FOO);
+ verifyNoMoreInteractions(nameMatcher);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/NegatingMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/NegatingMatcherTest.java
new file mode 100644
index 0000000..468c411
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/NegatingMatcherTest.java
@@ -0,0 +1,37 @@
+package net.bytebuddy.matcher;
+
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class NegatingMatcherTest extends AbstractElementMatcherTest<NegatingMatcher<?>> {
+
+ @Mock
+ private ElementMatcher<? super Object> elementMatcher;
+
+ @SuppressWarnings("unchecked")
+ public NegatingMatcherTest() {
+ super((Class<NegatingMatcher<?>>) (Object) NegatingMatcher.class, "not");
+ }
+
+ @Test
+ public void testNegateToPositive() throws Exception {
+ Object target = new Object();
+ when(elementMatcher.matches(target)).thenReturn(true);
+ assertThat(new NegatingMatcher<Object>(elementMatcher).matches(target), is(false));
+ verify(elementMatcher).matches(target);
+ verifyNoMoreInteractions(elementMatcher);
+ }
+
+ @Test
+ public void testPositiveToNegative() throws Exception {
+ Object target = new Object();
+ when(elementMatcher.matches(target)).thenReturn(false);
+ assertThat(new NegatingMatcher<Object>(elementMatcher).matches(target), is(true));
+ verify(elementMatcher).matches(target);
+ verifyNoMoreInteractions(elementMatcher);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/NullMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/NullMatcherTest.java
new file mode 100644
index 0000000..eb25325
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/NullMatcherTest.java
@@ -0,0 +1,24 @@
+package net.bytebuddy.matcher;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class NullMatcherTest extends AbstractElementMatcherTest<NullMatcher<?>> {
+
+ @SuppressWarnings("unchecked")
+ public NullMatcherTest() {
+ super((Class<NullMatcher<?>>) (Object) NullMatcher.class, "isNull");
+ }
+
+ @Test
+ public void testMatch() throws Exception {
+ assertThat(new NullMatcher<Object>().matches(null), is(true));
+ }
+
+ @Test
+ public void testPositiveToNegative() throws Exception {
+ assertThat(new NullMatcher<Object>().matches(new Object()), is(false));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/SignatureTokenMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/SignatureTokenMatcherTest.java
new file mode 100644
index 0000000..e6a1bf7
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/SignatureTokenMatcherTest.java
@@ -0,0 +1,44 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.method.MethodDescription;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class SignatureTokenMatcherTest extends AbstractElementMatcherTest<SignatureTokenMatcher<?>> {
+
+ @Mock
+ private MethodDescription.SignatureToken token;
+
+ @Mock
+ private MethodDescription methodDescription;
+
+ @Mock
+ private ElementMatcher<? super MethodDescription.SignatureToken> matcher;
+
+ @SuppressWarnings("unchecked")
+ public SignatureTokenMatcherTest() {
+ super((Class<SignatureTokenMatcher<?>>) (Object) SignatureTokenMatcher.class, "signature");
+ }
+
+ @Test
+ public void testMatche() throws Exception {
+ when(methodDescription.asSignatureToken()).thenReturn(token);
+ when(matcher.matches(token)).thenReturn(true);
+ assertThat(new SignatureTokenMatcher<MethodDescription>(matcher).matches(methodDescription), is(true));
+ verify(matcher).matches(token);
+ verifyNoMoreInteractions(matcher);
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ when(methodDescription.asSignatureToken()).thenReturn(token);
+ when(matcher.matches(token)).thenReturn(false);
+ assertThat(new SignatureTokenMatcher<MethodDescription>(matcher).matches(methodDescription), is(false));
+ verify(matcher).matches(token);
+ verifyNoMoreInteractions(matcher);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/StringMatcherObjectPropertiesTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/StringMatcherObjectPropertiesTest.java
new file mode 100644
index 0000000..3318f49
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/StringMatcherObjectPropertiesTest.java
@@ -0,0 +1,12 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+public class StringMatcherObjectPropertiesTest {
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(StringMatcher.Mode.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/StringMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/StringMatcherTest.java
new file mode 100644
index 0000000..c5405ef
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/StringMatcherTest.java
@@ -0,0 +1,69 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.method.MethodDescription;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.Mock;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class StringMatcherTest extends AbstractElementMatcherTest<StringMatcher> {
+
+ private static final String FOO = "foo";
+
+ private final StringMatcher.Mode mode;
+
+ private final String matching, nonMatching;
+
+ @Mock
+ private MethodDescription methodDescription;
+
+ public StringMatcherTest(StringMatcher.Mode mode, String matching, String nonMatching) {
+ super(StringMatcher.class, mode.getDescription());
+ this.mode = mode;
+ this.matching = matching;
+ this.nonMatching = nonMatching;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {StringMatcher.Mode.CONTAINS, "fo", "fooo"},
+ {StringMatcher.Mode.CONTAINS_IGNORE_CASE, "FO", "fooo"},
+ {StringMatcher.Mode.ENDS_WITH, "oo", "f"},
+ {StringMatcher.Mode.ENDS_WITH_IGNORE_CASE, "OO", "f"},
+ {StringMatcher.Mode.EQUALS_FULLY, "foo", "bar"},
+ {StringMatcher.Mode.EQUALS_FULLY_IGNORE_CASE, "FOO", "bar"},
+ {StringMatcher.Mode.MATCHES, "[a-z]{3}", "bar"},
+ {StringMatcher.Mode.STARTS_WITH, "fo", "fooo"},
+ {StringMatcher.Mode.STARTS_WITH_IGNORE_CASE, "FO", "fooo"},
+ });
+ }
+
+ @Test
+ public void testMatch() throws Exception {
+ assertThat(new StringMatcher(matching, mode).matches(FOO), is(true));
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ assertThat(new StringMatcher(nonMatching, mode).matches(FOO), is(false));
+ }
+
+ @Override
+ protected String makeRegex(String startsWith) {
+ return null;
+ }
+
+ @Test
+ public void testToString() throws Exception {
+ assertThat(new StringMatcher(FOO, mode).toString(), startsWith(mode.getDescription()));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/SubTypeMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/SubTypeMatcherTest.java
new file mode 100644
index 0000000..dd49927
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/SubTypeMatcherTest.java
@@ -0,0 +1,38 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.type.TypeDescription;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class SubTypeMatcherTest extends AbstractElementMatcherTest<SubTypeMatcher<?>> {
+
+ @Mock
+ private TypeDescription typeDescription, otherType;
+
+ @SuppressWarnings("unchecked")
+ public SubTypeMatcherTest() {
+ super((Class<? extends SubTypeMatcher<?>>) (Object) SubTypeMatcher.class, "isSubTypeOf");
+ }
+
+ @Test
+ public void testMatch() throws Exception {
+ when(otherType.isAssignableTo(typeDescription)).thenReturn(true);
+ assertThat(new SubTypeMatcher<TypeDescription>(typeDescription).matches(otherType), is(true));
+ verify(otherType).isAssignableTo(typeDescription);
+ verifyNoMoreInteractions(otherType);
+ verifyZeroInteractions(typeDescription);
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ when(otherType.isAssignableTo(typeDescription)).thenReturn(false);
+ assertThat(new SubTypeMatcher<TypeDescription>(typeDescription).matches(otherType), is(false));
+ verify(otherType).isAssignableTo(typeDescription);
+ verifyNoMoreInteractions(otherType);
+ verifyZeroInteractions(typeDescription);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/SuperTypeMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/SuperTypeMatcherTest.java
new file mode 100644
index 0000000..e9de5c1
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/SuperTypeMatcherTest.java
@@ -0,0 +1,38 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.type.TypeDescription;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class SuperTypeMatcherTest extends AbstractElementMatcherTest<SuperTypeMatcher<?>> {
+
+ @Mock
+ private TypeDescription typeDescription, otherType;
+
+ @SuppressWarnings("unchecked")
+ public SuperTypeMatcherTest() {
+ super((Class<? extends SuperTypeMatcher<?>>) (Object) SuperTypeMatcher.class, "isSuperTypeOf");
+ }
+
+ @Test
+ public void testMatch() throws Exception {
+ when(otherType.isAssignableFrom(typeDescription)).thenReturn(true);
+ assertThat(new SuperTypeMatcher<TypeDescription>(typeDescription).matches(otherType), is(true));
+ verify(otherType).isAssignableFrom(typeDescription);
+ verifyNoMoreInteractions(otherType);
+ verifyZeroInteractions(typeDescription);
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ when(otherType.isAssignableFrom(typeDescription)).thenReturn(false);
+ assertThat(new SuperTypeMatcher<TypeDescription>(typeDescription).matches(otherType), is(false));
+ verify(otherType).isAssignableFrom(typeDescription);
+ verifyNoMoreInteractions(otherType);
+ verifyZeroInteractions(typeDescription);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/TypeSortMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/TypeSortMatcherTest.java
new file mode 100644
index 0000000..6bdc09e
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/TypeSortMatcherTest.java
@@ -0,0 +1,52 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class TypeSortMatcherTest extends AbstractElementMatcherTest<TypeSortMatcher<?>> {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypeDescription.Generic typeDescription;
+
+ @Mock
+ private ElementMatcher<TypeDefinition.Sort> matcher;
+
+ @SuppressWarnings("unchecked")
+ public TypeSortMatcherTest() {
+ super((Class<TypeSortMatcher<?>>) (Object) TypeSortMatcher.class, "ofSort");
+ }
+
+ @Test
+ public void testMatch() throws Exception {
+ when(typeDescription.getSort()).thenReturn(TypeDefinition.Sort.NON_GENERIC);
+ when(matcher.matches(TypeDefinition.Sort.NON_GENERIC)).thenReturn(true);
+ assertThat(new TypeSortMatcher<TypeDescription.Generic>(matcher).matches(typeDescription), is(true));
+ verify(typeDescription).getSort();
+ verifyNoMoreInteractions(typeDescription);
+ verify(matcher).matches(TypeDefinition.Sort.NON_GENERIC);
+ verifyNoMoreInteractions(matcher);
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ when(typeDescription.getSort()).thenReturn(TypeDefinition.Sort.NON_GENERIC);
+ when(matcher.matches(TypeDefinition.Sort.NON_GENERIC)).thenReturn(false);
+ assertThat(new TypeSortMatcher<TypeDescription.Generic>(matcher).matches(typeDescription), is(false));
+ verify(typeDescription).getSort();
+ verifyNoMoreInteractions(typeDescription);
+ verify(matcher).matches(TypeDefinition.Sort.NON_GENERIC);
+ verifyNoMoreInteractions(matcher);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/VisibilityMatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/VisibilityMatcherTest.java
new file mode 100644
index 0000000..60d2b87
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/VisibilityMatcherTest.java
@@ -0,0 +1,42 @@
+package net.bytebuddy.matcher;
+
+import net.bytebuddy.description.ByteCodeElement;
+import net.bytebuddy.description.type.TypeDescription;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class VisibilityMatcherTest extends AbstractElementMatcherTest<VisibilityMatcher<?>> {
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Mock
+ private ByteCodeElement byteCodeElement;
+
+ @SuppressWarnings("unchecked")
+ public VisibilityMatcherTest() {
+ super((Class<? extends VisibilityMatcher<?>>) (Object) VisibilityMatcher.class, "isVisibleTo");
+ }
+
+ @Test
+ public void testMatch() throws Exception {
+ when(byteCodeElement.isVisibleTo(typeDescription)).thenReturn(true);
+ assertThat(new VisibilityMatcher<ByteCodeElement>(typeDescription).matches(byteCodeElement), is(true));
+ verify(byteCodeElement).isVisibleTo(typeDescription);
+ verifyNoMoreInteractions(byteCodeElement);
+ verifyZeroInteractions(typeDescription);
+ }
+
+ @Test
+ public void testNoMatch() throws Exception {
+ when(byteCodeElement.isVisibleTo(typeDescription)).thenReturn(false);
+ assertThat(new VisibilityMatcher<ByteCodeElement>(typeDescription).matches(byteCodeElement), is(false));
+ verify(byteCodeElement).isVisibleTo(typeDescription);
+ verifyNoMoreInteractions(byteCodeElement);
+ verifyZeroInteractions(typeDescription);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolCacheProviderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolCacheProviderTest.java
new file mode 100644
index 0000000..895cef7
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolCacheProviderTest.java
@@ -0,0 +1,52 @@
+package net.bytebuddy.pool;
+
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class TypePoolCacheProviderTest {
+
+ private static final String FOO = "foo";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypePool.Resolution resolution;
+
+ @Test
+ public void testNoOp() throws Exception {
+ assertThat(TypePool.CacheProvider.NoOp.INSTANCE.find(FOO), nullValue(TypePool.Resolution.class));
+ assertThat(TypePool.CacheProvider.NoOp.INSTANCE.register(FOO, resolution), sameInstance(resolution));
+ assertThat(TypePool.CacheProvider.NoOp.INSTANCE.find(FOO), nullValue(TypePool.Resolution.class));
+ TypePool.CacheProvider.NoOp.INSTANCE.clear();
+ }
+
+ @Test
+ public void testSimple() throws Exception {
+ TypePool.CacheProvider simple = new TypePool.CacheProvider.Simple();
+ assertThat(simple.find(FOO), nullValue(TypePool.Resolution.class));
+ assertThat(simple.register(FOO, resolution), sameInstance(resolution));
+ assertThat(simple.find(FOO), sameInstance(resolution));
+ TypePool.Resolution resolution = mock(TypePool.Resolution.class);
+ assertThat(simple.register(FOO, resolution), sameInstance(this.resolution));
+ assertThat(simple.find(FOO), sameInstance(this.resolution));
+ simple.clear();
+ assertThat(simple.find(FOO), nullValue(TypePool.Resolution.class));
+ assertThat(simple.register(FOO, resolution), sameInstance(resolution));
+ assertThat(simple.find(FOO), sameInstance(resolution));
+ }
+
+ @Test
+ public void testSimpleObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypePool.CacheProvider.NoOp.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolClassLoadingTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolClassLoadingTest.java
new file mode 100644
index 0000000..472d66f
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolClassLoadingTest.java
@@ -0,0 +1,69 @@
+package net.bytebuddy.pool;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class TypePoolClassLoadingTest {
+
+ private final TypePool typePool;
+
+ public TypePoolClassLoadingTest(TypePool typePool) {
+ this.typePool = typePool;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] {
+ {TypePool.ClassLoading.ofBootPath()},
+ {TypePool.ClassLoading.ofClassPath()},
+ {TypePool.ClassLoading.of(TypePoolClassLoadingTest.class.getClassLoader())}
+ });
+ }
+
+ @Test
+ public void testLoadableBootstrapLoaderClass() throws Exception {
+ TypePool.Resolution resolution = typePool.describe(Object.class.getName());
+ assertThat(resolution.isResolved(), is(true));
+ assertThat(resolution.resolve(), is(TypeDescription.OBJECT));
+ }
+
+ @Test
+ public void testArrayClass() throws Exception {
+ TypePool.Resolution resolution = typePool.describe(Object[].class.getName());
+ assertThat(resolution.isResolved(), is(true));
+ assertThat(resolution.resolve(), is((TypeDescription) new TypeDescription.ForLoadedType(Object[].class)));
+ }
+
+ @Test
+ public void testPrimitiveClass() throws Exception {
+ TypePool.Resolution resolution = typePool.describe(int.class.getName());
+ assertThat(resolution.isResolved(), is(true));
+ assertThat(resolution.resolve(), is((TypeDescription) new TypeDescription.ForLoadedType(int.class)));
+ }
+
+ @Test
+ public void testClearRetainsFunctionality() throws Exception {
+ TypePool.Resolution resolution = typePool.describe(Object.class.getName());
+ assertThat(resolution.isResolved(), is(true));
+ assertThat(resolution.resolve(), is(TypeDescription.OBJECT));
+ typePool.clear();
+ TypePool.Resolution otherResolution = typePool.describe(Object.class.getName());
+ assertThat(otherResolution.isResolved(), is(true));
+ assertThat(resolution.resolve(), is(TypeDescription.OBJECT));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypePool.ClassLoading.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultAnnotationDescriptionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultAnnotationDescriptionTest.java
new file mode 100644
index 0000000..6e91b59
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultAnnotationDescriptionTest.java
@@ -0,0 +1,22 @@
+package net.bytebuddy.pool;
+
+import net.bytebuddy.description.annotation.AbstractAnnotationDescriptionTest;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import org.junit.After;
+import org.junit.Before;
+
+import java.lang.annotation.Annotation;
+
+public class TypePoolDefaultAnnotationDescriptionTest extends AbstractAnnotationDescriptionTest {
+
+ @Override
+ protected AnnotationDescription describe(Annotation annotation, Class<?> declaringType) {
+ TypePool typePool = TypePool.Default.of(declaringType.getClassLoader());
+ try {
+ return typePool.describe(declaringType.getName()).resolve()
+ .getDeclaredAnnotations().ofType(annotation.annotationType());
+ } finally {
+ typePool.clear();
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultCacheTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultCacheTest.java
new file mode 100644
index 0000000..bf7bd2a
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultCacheTest.java
@@ -0,0 +1,20 @@
+package net.bytebuddy.pool;
+
+import net.bytebuddy.description.type.TypeDescription;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class TypePoolDefaultCacheTest {
+
+ @Test
+ public void testCache() throws Exception {
+ TypePool typePool = TypePool.Default.ofClassPath();
+ TypeDescription typeDescription = typePool.describe(Void.class.getName()).resolve();
+ assertThat(typePool.describe(Void.class.getName()).resolve(), sameInstance(typeDescription));
+ typePool.clear();
+ assertThat(typePool.describe(Void.class.getName()).resolve(), not(sameInstance(typeDescription)));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultComponentPoolStrategyTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultComponentPoolStrategyTest.java
new file mode 100644
index 0000000..a117728
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultComponentPoolStrategyTest.java
@@ -0,0 +1,59 @@
+package net.bytebuddy.pool;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import net.bytebuddy.utility.RandomString;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class TypePoolDefaultComponentPoolStrategyTest {
+
+ private static final String FOO = "foo", BAR = "bar", BAR_DESCRIPTOR = "L" + BAR + ";", QUX = "qux", BAZ = "baz";
+
+ @Test(expected = IllegalStateException.class)
+ public void testIllegal() throws Exception {
+ TypePool.Default.ComponentTypeLocator.Illegal.INSTANCE.bind(FOO);
+ }
+
+ @Test
+ public void testForAnnotationProperty() throws Exception {
+ TypePool typePool = mock(TypePool.class);
+ TypeDescription typeDescription = mock(TypeDescription.class);
+ when(typePool.describe(BAR)).thenReturn(new TypePool.Resolution.Simple(typeDescription));
+ MethodDescription.InDefinedShape methodDescription = mock(MethodDescription.InDefinedShape.class);
+ when(typeDescription.getDeclaredMethods()).thenReturn(new MethodList.Explicit<MethodDescription.InDefinedShape>(methodDescription));
+ when(methodDescription.getActualName()).thenReturn(FOO);
+ TypeDescription.Generic returnType = mock(TypeDescription.Generic.class);
+ TypeDescription rawReturnType = mock(TypeDescription.class);
+ when(returnType.asErasure()).thenReturn(rawReturnType);
+ when(methodDescription.getReturnType()).thenReturn(returnType);
+ TypeDescription rawComponentType = mock(TypeDescription.class);
+ when(rawReturnType.getComponentType()).thenReturn(rawComponentType);
+ when(rawComponentType.getName()).thenReturn(QUX);
+ assertThat(new TypePool.Default.ComponentTypeLocator.ForAnnotationProperty(typePool, BAR_DESCRIPTOR).bind(FOO).lookup(), is(QUX));
+ }
+
+ @Test
+ public void testForArrayType() throws Exception {
+ assertThat(new TypePool.Default.ComponentTypeLocator.ForArrayType("()[" + BAR_DESCRIPTOR).bind(FOO).lookup(), is(BAR));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypePool.Default.ComponentTypeLocator.ForAnnotationProperty.class).apply();
+ ObjectPropertyAssertion.of(TypePool.Default.ComponentTypeLocator.ForAnnotationProperty.Bound.class).skipSynthetic().apply();
+ ObjectPropertyAssertion.of(TypePool.Default.ComponentTypeLocator.ForArrayType.class).create(new ObjectPropertyAssertion.Creator<String>() {
+ @Override
+ public String create() {
+ return "()L" + RandomString.make() + ";";
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(TypePool.Default.ComponentTypeLocator.Illegal.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultEnumerationDescriptionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultEnumerationDescriptionTest.java
new file mode 100644
index 0000000..447c009
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultEnumerationDescriptionTest.java
@@ -0,0 +1,38 @@
+package net.bytebuddy.pool;
+
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.enumeration.AbstractEnumerationDescriptionTest;
+import net.bytebuddy.description.enumeration.EnumerationDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import org.junit.After;
+import org.junit.Before;
+
+public class TypePoolDefaultEnumerationDescriptionTest extends AbstractEnumerationDescriptionTest {
+
+ private TypePool typePool;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ typePool = TypePool.Default.ofClassPath();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ typePool.clear();
+ }
+
+ @Override
+ protected EnumerationDescription describe(Enum<?> enumeration,
+ Class<?> carrierType,
+ MethodDescription.InDefinedShape annotationMethod) {
+ TypeDescription typeDescription = typePool.describe(carrierType.getName()).resolve();
+ for (AnnotationDescription annotationDescription : typeDescription.getDeclaredAnnotations()) {
+ if (annotationDescription.getAnnotationType().equals(annotationDescription.getAnnotationType())) {
+ return annotationDescription.getValue(annotationMethod).resolve(EnumerationDescription.class);
+ }
+ }
+ throw new AssertionError();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultFieldDescriptionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultFieldDescriptionTest.java
new file mode 100644
index 0000000..4967277
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultFieldDescriptionTest.java
@@ -0,0 +1,33 @@
+package net.bytebuddy.pool;
+
+import net.bytebuddy.description.field.AbstractFieldDescriptionTest;
+import net.bytebuddy.description.field.FieldDescription;
+import org.junit.After;
+import org.junit.Before;
+
+import java.lang.reflect.Field;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+
+public class TypePoolDefaultFieldDescriptionTest extends AbstractFieldDescriptionTest {
+
+ private TypePool typePool;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ typePool = TypePool.Default.ofClassPath();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ typePool.clear();
+ }
+
+ @Override
+ protected FieldDescription.InDefinedShape describe(Field field) {
+ return typePool.describe(field.getDeclaringClass().getName())
+ .resolve()
+ .getDeclaredFields().filter(named(field.getName())).getOnly();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultGenericTypeListTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultGenericTypeListTest.java
new file mode 100644
index 0000000..bab3ec2
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultGenericTypeListTest.java
@@ -0,0 +1,48 @@
+package net.bytebuddy.pool;
+
+import net.bytebuddy.description.type.AbstractTypeListGenericTest;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import org.junit.After;
+import org.junit.Before;
+
+import java.lang.reflect.Type;
+import java.util.List;
+
+import static net.bytebuddy.matcher.ElementMatchers.anyOf;
+
+public class TypePoolDefaultGenericTypeListTest extends AbstractTypeListGenericTest<Type> {
+
+ private TypePool typePool;
+
+ @Before
+ public void setUp() throws Exception {
+ typePool = TypePool.Default.ofClassPath();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ typePool.clear();
+ }
+
+ @Override
+ protected Type getFirst() throws Exception {
+ return Holder.class.getGenericInterfaces()[0];
+ }
+
+ @Override
+ protected Type getSecond() throws Exception {
+ return Holder.class.getGenericInterfaces()[1];
+ }
+
+ @Override
+ protected TypeList.Generic asList(List<Type> elements) {
+ return typePool.describe(Holder.class.getName()).resolve().getInterfaces().filter(anyOf(elements.toArray(new Type[elements.size()])));
+ }
+
+ @Override
+ protected TypeDescription.Generic asElement(Type element) {
+ return TypeDefinition.Sort.describe(element);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultHierarchyTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultHierarchyTest.java
new file mode 100644
index 0000000..ffd0b44
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultHierarchyTest.java
@@ -0,0 +1,90 @@
+package net.bytebuddy.pool;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.test.utility.ClassFileExtraction;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class TypePoolDefaultHierarchyTest {
+
+ private static final String FOO = "foo";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypePool parentPool;
+
+ @Mock
+ private TypePool.CacheProvider cacheProvider;
+
+ @Mock
+ private ClassFileLocator classFileLocator;
+
+ @Mock
+ private TypePool.Resolution resolution;
+
+ @Test
+ public void testParentFirst() throws Exception {
+ TypePool typePool = new TypePool.Default(cacheProvider, classFileLocator, TypePool.Default.ReaderMode.FAST, parentPool);
+ when(parentPool.describe(FOO)).thenReturn(resolution);
+ when(resolution.isResolved()).thenReturn(true);
+ assertThat(typePool.describe(FOO), is(resolution));
+ verifyZeroInteractions(cacheProvider);
+ verifyZeroInteractions(classFileLocator);
+ verify(parentPool).describe(FOO);
+ verifyNoMoreInteractions(parentPool);
+ verify(resolution).isResolved();
+ verifyNoMoreInteractions(resolution);
+ }
+
+ @Test
+ public void testChildSecond() throws Exception {
+ TypePool typePool = new TypePool.Default(cacheProvider, classFileLocator, TypePool.Default.ReaderMode.FAST, parentPool);
+ when(parentPool.describe(FOO)).thenReturn(resolution);
+ when(resolution.isResolved()).thenReturn(false);
+ when(classFileLocator.locate(FOO)).thenReturn(new ClassFileLocator.Resolution.Explicit(ClassFileExtraction.extract(Foo.class)));
+ when(cacheProvider.register(eq(FOO), any(TypePool.Resolution.class))).then(new Answer<TypePool.Resolution>() {
+ @Override
+ public TypePool.Resolution answer(InvocationOnMock invocationOnMock) throws Throwable {
+ return (TypePool.Resolution) invocationOnMock.getArguments()[1];
+ }
+ });
+ TypePool.Resolution resolution = typePool.describe(FOO);
+ assertThat(resolution.isResolved(), is(true));
+ assertThat(resolution.resolve(), is((TypeDescription) new TypeDescription.ForLoadedType(Foo.class)));
+ verify(cacheProvider).find(FOO);
+ verify(cacheProvider).register(FOO, resolution);
+ verifyZeroInteractions(cacheProvider);
+ verify(classFileLocator).locate(FOO);
+ verifyNoMoreInteractions(classFileLocator);
+ verify(parentPool).describe(FOO);
+ verifyNoMoreInteractions(parentPool);
+ verify(this.resolution).isResolved();
+ verifyNoMoreInteractions(this.resolution);
+ }
+
+ @Test
+ public void testClear() throws Exception {
+ TypePool typePool = new TypePool.Default(cacheProvider, classFileLocator, TypePool.Default.ReaderMode.FAST, parentPool);
+ typePool.clear();
+ verify(cacheProvider).clear();
+ verifyNoMoreInteractions(cacheProvider);
+ verify(parentPool).clear();
+ verifyNoMoreInteractions(parentPool);
+ }
+
+ private static class Foo {
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultLazyAnnotationListTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultLazyAnnotationListTest.java
new file mode 100644
index 0000000..2639773
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultLazyAnnotationListTest.java
@@ -0,0 +1,47 @@
+package net.bytebuddy.pool;
+
+import net.bytebuddy.description.annotation.AbstractAnnotationListTest;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationList;
+import org.junit.After;
+import org.junit.Before;
+
+import java.lang.annotation.Annotation;
+import java.util.List;
+
+import static net.bytebuddy.matcher.ElementMatchers.anyOf;
+
+public class TypePoolDefaultLazyAnnotationListTest extends AbstractAnnotationListTest<Annotation> {
+
+ private TypePool typePool;
+
+ @Before
+ public void setUp() throws Exception {
+ typePool = TypePool.Default.ofClassPath();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ typePool.clear();
+ }
+
+ @Override
+ protected Annotation getFirst() throws Exception {
+ return Holder.class.getAnnotation(Foo.class);
+ }
+
+ @Override
+ protected Annotation getSecond() throws Exception {
+ return Holder.class.getAnnotation(Bar.class);
+ }
+
+ @Override
+ protected AnnotationList asList(List<Annotation> elements) {
+ return typePool.describe(Holder.class.getName()).resolve().getDeclaredAnnotations().filter(anyOf(elements.toArray(new Annotation[elements.size()])));
+ }
+
+ @Override
+ protected AnnotationDescription asElement(Annotation element) {
+ return AnnotationDescription.ForLoadedAnnotation.of(element);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultLazyFieldListTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultLazyFieldListTest.java
new file mode 100644
index 0000000..55ae088
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultLazyFieldListTest.java
@@ -0,0 +1,47 @@
+package net.bytebuddy.pool;
+
+import net.bytebuddy.description.field.AbstractFieldListTest;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
+import org.junit.After;
+import org.junit.Before;
+
+import java.lang.reflect.Field;
+import java.util.List;
+
+import static net.bytebuddy.matcher.ElementMatchers.anyOf;
+
+public class TypePoolDefaultLazyFieldListTest extends AbstractFieldListTest<Field, FieldDescription.InDefinedShape> {
+
+ private TypePool typePool;
+
+ @Before
+ public void setUp() throws Exception {
+ typePool = TypePool.Default.ofClassPath();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ typePool.clear();
+ }
+
+ @Override
+ protected Field getFirst() throws Exception {
+ return Foo.class.getDeclaredField("foo");
+ }
+
+ @Override
+ protected Field getSecond() throws Exception {
+ return Foo.class.getDeclaredField("bar");
+ }
+
+ @Override
+ protected FieldList<FieldDescription.InDefinedShape> asList(List<Field> elements) {
+ return typePool.describe(Foo.class.getName()).resolve().getDeclaredFields().filter(anyOf(elements.toArray(new Field[elements.size()])));
+ }
+
+ @Override
+ protected FieldDescription.InDefinedShape asElement(Field element) {
+ return new FieldDescription.ForLoadedField(element);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultLazyMethodListTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultLazyMethodListTest.java
new file mode 100644
index 0000000..bdec7b3
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultLazyMethodListTest.java
@@ -0,0 +1,47 @@
+package net.bytebuddy.pool;
+
+import net.bytebuddy.description.method.AbstractMethodListTest;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import org.junit.After;
+import org.junit.Before;
+
+import java.lang.reflect.Method;
+import java.util.List;
+
+import static net.bytebuddy.matcher.ElementMatchers.anyOf;
+
+public class TypePoolDefaultLazyMethodListTest extends AbstractMethodListTest<Method, MethodDescription.InDefinedShape> {
+
+ private TypePool typePool;
+
+ @Before
+ public void setUp() throws Exception {
+ typePool = TypePool.Default.ofClassPath();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ typePool.clear();
+ }
+
+ @Override
+ protected Method getFirst() throws Exception {
+ return Foo.class.getDeclaredMethod("foo");
+ }
+
+ @Override
+ protected Method getSecond() throws Exception {
+ return Foo.class.getDeclaredMethod("bar");
+ }
+
+ @Override
+ protected MethodList<MethodDescription.InDefinedShape> asList(List<Method> elements) {
+ return typePool.describe(Foo.class.getName()).resolve().getDeclaredMethods().filter(anyOf(elements.toArray(new Method[elements.size()])));
+ }
+
+ @Override
+ protected MethodDescription.InDefinedShape asElement(Method element) {
+ return new MethodDescription.ForLoadedMethod(element);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultLazyObjectPropertiesTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultLazyObjectPropertiesTest.java
new file mode 100644
index 0000000..7cfe8d9
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultLazyObjectPropertiesTest.java
@@ -0,0 +1,140 @@
+package net.bytebuddy.pool;
+
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.Arrays;
+import java.util.Iterator;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class TypePoolDefaultLazyObjectPropertiesTest {
+
+ private static final String FOO = "foo";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypePool.Default.LazyTypeDescription.GenericTypeToken genericTypeToken;
+
+ @Mock
+ private TypePool typePool;
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypePool.Default.LazyTypeDescription.FieldToken.class).apply();
+ ObjectPropertyAssertion.of(TypePool.Default.LazyTypeDescription.MethodToken.class).apply();
+ final Iterator<Integer> iterator = Arrays.asList(1, 2).iterator();
+ ObjectPropertyAssertion.of(TypePool.Default.LazyTypeDescription.MethodToken.ParameterToken.class).create(new ObjectPropertyAssertion.Creator<Integer>() {
+ @Override
+ public Integer create() {
+ return iterator.next();
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(TypePool.Default.LazyTypeDescription.AnnotationToken.class).apply();
+ ObjectPropertyAssertion.of(TypePool.Default.LazyTypeDescription.AnnotationToken.Resolution.Simple.class).apply();
+ ObjectPropertyAssertion.of(TypePool.Default.LazyTypeDescription.AnnotationToken.Resolution.Illegal.class).apply();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIllegalResolutionThrowsException() throws Exception {
+ new TypePool.Default.LazyTypeDescription.AnnotationToken.Resolution.Illegal("foo").resolve();
+ }
+
+ @Test
+ public void testIllegalResolutionIsNotResolved() throws Exception {
+ assertThat(new TypePool.Default.LazyTypeDescription.AnnotationToken.Resolution.Illegal("foo").isResolved(), is(false));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testCannotResolveTypePathPrefixForPrimitiveType() throws Exception {
+ TypePool.Default.LazyTypeDescription.GenericTypeToken.ForPrimitiveType.VOID.getTypePathPrefix();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testCannotResolveTypePathPrefixForRawType() throws Exception {
+ new TypePool.Default.LazyTypeDescription.GenericTypeToken.ForPrimitiveType.ForRawType(FOO).getTypePathPrefix();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testCannotResolveTypePathPrefixForTypeVariable() throws Exception {
+ new TypePool.Default.LazyTypeDescription.GenericTypeToken.ForPrimitiveType.ForTypeVariable(FOO).getTypePathPrefix();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testCannotResolveTypePathPrefixForGenericArray() throws Exception {
+ new TypePool.Default.LazyTypeDescription.GenericTypeToken.ForPrimitiveType.ForGenericArray(genericTypeToken).getTypePathPrefix();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testCannotResolveTypePathPrefixForUnboundWildcard() throws Exception {
+ TypePool.Default.LazyTypeDescription.GenericTypeToken.ForPrimitiveType.ForUnboundWildcard.INSTANCE.getTypePathPrefix();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testCannotResolveTypePathPrefixForLowerBoundWildcard() throws Exception {
+ new TypePool.Default.LazyTypeDescription.GenericTypeToken.ForPrimitiveType.ForLowerBoundWildcard(genericTypeToken).getTypePathPrefix();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testCannotResolveTypePathPrefixForUpperBoundWildcard() throws Exception {
+ new TypePool.Default.LazyTypeDescription.GenericTypeToken.ForPrimitiveType.ForUpperBoundWildcard(genericTypeToken).getTypePathPrefix();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testCannotResolvePrimaryBoundPropertyForPrimitiveType() throws Exception {
+ TypePool.Default.LazyTypeDescription.GenericTypeToken.ForPrimitiveType.VOID.isPrimaryBound(typePool);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testCannotResolvePrimaryBoundPropertyForGenericArray() throws Exception {
+ new TypePool.Default.LazyTypeDescription.GenericTypeToken.ForPrimitiveType.ForGenericArray(genericTypeToken).isPrimaryBound(typePool);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testCannotResolvePrimaryBoundPropertyForUnboundWildcard() throws Exception {
+ TypePool.Default.LazyTypeDescription.GenericTypeToken.ForPrimitiveType.ForUnboundWildcard.INSTANCE.isPrimaryBound(typePool);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testCannotResolvePrimaryBoundPropertyForLowerBoundWildcard() throws Exception {
+ new TypePool.Default.LazyTypeDescription.GenericTypeToken.ForPrimitiveType.ForLowerBoundWildcard(genericTypeToken).isPrimaryBound(typePool);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testCannotResolvePrimaryBoundPropertyForUpperBoundWildcard() throws Exception {
+ new TypePool.Default.LazyTypeDescription.GenericTypeToken.ForPrimitiveType.ForUpperBoundWildcard(genericTypeToken).isPrimaryBound(typePool);
+ }
+
+ @Test
+ public void testGenericTypeTokenObjectPropertiesTest() throws Exception {
+ ObjectPropertyAssertion.of(TypePool.Default.LazyTypeDescription.GenericTypeToken.ForPrimitiveType.class).apply();
+ ObjectPropertyAssertion.of(TypePool.Default.LazyTypeDescription.GenericTypeToken.ForRawType.class).apply();
+ ObjectPropertyAssertion.of(TypePool.Default.LazyTypeDescription.GenericTypeToken.ForTypeVariable.class).apply();
+ ObjectPropertyAssertion.of(TypePool.Default.LazyTypeDescription.GenericTypeToken.ForTypeVariable.Formal.class).apply();
+ ObjectPropertyAssertion.of(TypePool.Default.LazyTypeDescription.GenericTypeToken.ForGenericArray.class).apply();
+ ObjectPropertyAssertion.of(TypePool.Default.LazyTypeDescription.GenericTypeToken.ForUnboundWildcard.class).apply();
+ ObjectPropertyAssertion.of(TypePool.Default.LazyTypeDescription.GenericTypeToken.ForLowerBoundWildcard.class).apply();
+ ObjectPropertyAssertion.of(TypePool.Default.LazyTypeDescription.GenericTypeToken.ForUpperBoundWildcard.class).apply();
+ ObjectPropertyAssertion.of(TypePool.Default.LazyTypeDescription.GenericTypeToken.ForParameterizedType.class).apply();
+ ObjectPropertyAssertion.of(TypePool.Default.LazyTypeDescription.GenericTypeToken.ForParameterizedType.Nested.class).apply();
+ ObjectPropertyAssertion.of(TypePool.Default.LazyTypeDescription.GenericTypeToken.Resolution.ForType.Tokenized.class).apply();
+ ObjectPropertyAssertion.of(TypePool.Default.LazyTypeDescription.GenericTypeToken.Resolution.ForMethod.Tokenized.class).apply();
+ ObjectPropertyAssertion.of(TypePool.Default.LazyTypeDescription.GenericTypeToken.Resolution.ForField.Tokenized.class).apply();
+ ObjectPropertyAssertion.of(TypePool.Default.LazyTypeDescription.GenericTypeToken.Resolution.Raw.class).apply();
+ ObjectPropertyAssertion.of(TypePool.Default.LazyTypeDescription.GenericTypeToken.Resolution.Malformed.class).apply();
+ }
+
+ @Test
+ public void testDeclarationContextObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypePool.Default.LazyTypeDescription.TypeContainment.WithinType.class).apply();
+ ObjectPropertyAssertion.of(TypePool.Default.LazyTypeDescription.TypeContainment.WithinMethod.class).apply();
+ ObjectPropertyAssertion.of(TypePool.Default.LazyTypeDescription.TypeContainment.SelfContained.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultLazyParameterListTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultLazyParameterListTest.java
new file mode 100644
index 0000000..b1a2e86
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultLazyParameterListTest.java
@@ -0,0 +1,57 @@
+package net.bytebuddy.pool;
+
+import net.bytebuddy.description.method.AbstractParameterListTest;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.method.ParameterList;
+import org.junit.After;
+import org.junit.Before;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static net.bytebuddy.matcher.ElementMatchers.is;
+
+public class TypePoolDefaultLazyParameterListTest extends AbstractParameterListTest<ParameterDescription.InDefinedShape, ParameterDescription> {
+
+ private TypePool typePool;
+
+ @Before
+ public void setUp() throws Exception {
+ typePool = TypePool.Default.ofClassPath();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ typePool.clear();
+ }
+
+ @Override
+ protected ParameterDescription getFirst() throws Exception {
+ return new MethodDescription.ForLoadedMethod(Foo.class.getDeclaredMethod("foo", Void.class)).getParameters().getOnly();
+ }
+
+ @Override
+ protected ParameterDescription getSecond() throws Exception {
+ return new MethodDescription.ForLoadedMethod(Foo.class.getDeclaredMethod("bar", Void.class)).getParameters().getOnly();
+ }
+
+ @Override
+ protected ParameterList<ParameterDescription.InDefinedShape> asList(List<ParameterDescription> elements) {
+ List<ParameterDescription.InDefinedShape> parameterDescriptions = new ArrayList<ParameterDescription.InDefinedShape>(elements.size());
+ for (ParameterDescription element : elements) {
+ parameterDescriptions.add(typePool.describe(Foo.class.getName()).resolve()
+ .getDeclaredMethods()
+ .filter(is(element.getDeclaringMethod()))
+ .getOnly()
+ .getParameters()
+ .getOnly());
+ }
+ return new ParameterList.Explicit<ParameterDescription.InDefinedShape>(parameterDescriptions);
+ }
+
+ @Override
+ protected ParameterDescription.InDefinedShape asElement(ParameterDescription element) {
+ return element.asDefined();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultLazyTypeContainmentTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultLazyTypeContainmentTest.java
new file mode 100644
index 0000000..a313297
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultLazyTypeContainmentTest.java
@@ -0,0 +1,109 @@
+package net.bytebuddy.pool;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.type.TypeDescription;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class TypePoolDefaultLazyTypeContainmentTest {
+
+ private static final String FOO = "baz.foo", FOO_INTERNAL = "baz/foo", BAR = "bar", QUX = "qux";
+
+ @Test
+ public void testSelfDeclared() throws Exception {
+ assertThat(TypePool.Default.LazyTypeDescription.TypeContainment.SelfContained.INSTANCE
+ .isLocalType(), is(false));
+ assertThat(TypePool.Default.LazyTypeDescription.TypeContainment.SelfContained.INSTANCE
+ .isMemberClass(), is(false));
+ assertThat(TypePool.Default.LazyTypeDescription.TypeContainment.SelfContained.INSTANCE
+ .isSelfContained(), is(true));
+ }
+
+ @Test
+ public void testSelfDeclaredGetTypeIsNull() throws Exception {
+ assertThat(TypePool.Default.LazyTypeDescription.TypeContainment.SelfContained.INSTANCE
+ .getEnclosingType(mock(TypePool.class)), nullValue(TypeDescription.class));
+ }
+
+ @Test
+ public void testSelfDeclaredGetMethodIsNull() throws Exception {
+ assertThat(TypePool.Default.LazyTypeDescription.TypeContainment.SelfContained.INSTANCE
+ .getEnclosingMethod(mock(TypePool.class)), nullValue(MethodDescription.class));
+ }
+
+ @Test
+ public void testDeclaredInType() throws Exception {
+ assertThat(new TypePool.Default.LazyTypeDescription.TypeContainment.WithinType(FOO_INTERNAL, false)
+ .isLocalType(), is(false));
+ assertThat(new TypePool.Default.LazyTypeDescription.TypeContainment.WithinType(FOO_INTERNAL, false)
+ .isMemberClass(), is(true));
+ assertThat(new TypePool.Default.LazyTypeDescription.TypeContainment.WithinType(FOO_INTERNAL, false)
+ .isSelfContained(), is(false));
+ assertThat(new TypePool.Default.LazyTypeDescription.TypeContainment.WithinType(FOO_INTERNAL, true)
+ .isLocalType(), is(true));
+ assertThat(new TypePool.Default.LazyTypeDescription.TypeContainment.WithinType(FOO_INTERNAL, true)
+ .isMemberClass(), is(false));
+ assertThat(new TypePool.Default.LazyTypeDescription.TypeContainment.WithinType(FOO_INTERNAL, true)
+ .isSelfContained(), is(false));
+ }
+
+ @Test
+ public void testDeclaredInTypeGetTypeIsNotNull() throws Exception {
+ TypePool typePool = mock(TypePool.class);
+ TypeDescription typeDescription = mock(TypeDescription.class);
+ when(typePool.describe(FOO)).thenReturn(new TypePool.Resolution.Simple(typeDescription));
+ assertThat(new TypePool.Default.LazyTypeDescription.TypeContainment.WithinType(FOO_INTERNAL, false).getEnclosingType(typePool), is(typeDescription));
+ assertThat(new TypePool.Default.LazyTypeDescription.TypeContainment.WithinType(FOO_INTERNAL, true).getEnclosingType(typePool), is(typeDescription));
+ }
+
+ @Test
+ public void testDeclaredInTypeGetMethodIsNull() throws Exception {
+ assertThat(new TypePool.Default.LazyTypeDescription.TypeContainment.WithinType(FOO_INTERNAL, false)
+ .getEnclosingMethod(mock(TypePool.class)), nullValue(MethodDescription.class));
+ assertThat(new TypePool.Default.LazyTypeDescription.TypeContainment.WithinType(FOO_INTERNAL, false)
+ .getEnclosingMethod(mock(TypePool.class)), nullValue(MethodDescription.class));
+ assertThat(new TypePool.Default.LazyTypeDescription.TypeContainment.WithinType(FOO_INTERNAL, true)
+ .getEnclosingMethod(mock(TypePool.class)), nullValue(MethodDescription.class));
+ assertThat(new TypePool.Default.LazyTypeDescription.TypeContainment.WithinType(FOO_INTERNAL, true)
+ .getEnclosingMethod(mock(TypePool.class)), nullValue(MethodDescription.class));
+ }
+
+ @Test
+ public void testDeclaredInMethod() throws Exception {
+ assertThat(new TypePool.Default.LazyTypeDescription.TypeContainment.WithinMethod(FOO_INTERNAL, BAR, QUX)
+ .isLocalType(), is(true));
+ assertThat(new TypePool.Default.LazyTypeDescription.TypeContainment.WithinMethod(FOO_INTERNAL, BAR, QUX)
+ .isMemberClass(), is(false));
+ assertThat(new TypePool.Default.LazyTypeDescription.TypeContainment.WithinMethod(FOO_INTERNAL, BAR, QUX)
+ .isSelfContained(), is(false));
+ }
+
+ @Test
+ public void testDeclaredInMethodGetTypeIsNotNull() throws Exception {
+ TypeDescription typeDescription = mock(TypeDescription.class);
+ TypePool typePool = mock(TypePool.class);
+ when(typePool.describe(FOO)).thenReturn(new TypePool.Resolution.Simple(typeDescription));
+ assertThat(new TypePool.Default.LazyTypeDescription.TypeContainment.WithinMethod(FOO_INTERNAL, BAR, QUX)
+ .getEnclosingType(typePool), is(typeDescription));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testDeclaredInMethodGetMethodIsNull() throws Exception {
+ MethodDescription methodDescription = mock(MethodDescription.class);
+ when(methodDescription.getActualName()).thenReturn(BAR);
+ when(methodDescription.getDescriptor()).thenReturn(QUX);
+ TypeDescription typeDescription = mock(TypeDescription.class);
+ TypePool typePool = mock(TypePool.class);
+ when(typePool.describe(FOO)).thenReturn(new TypePool.Resolution.Simple(typeDescription));
+ when(typeDescription.getDeclaredMethods()).thenReturn((MethodList) new MethodList.Explicit<MethodDescription>(methodDescription));
+ assertThat(new TypePool.Default.LazyTypeDescription.TypeContainment.WithinMethod(FOO_INTERNAL, BAR, QUX).getEnclosingMethod(typePool),
+ is(methodDescription));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultLazyTypeListTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultLazyTypeListTest.java
new file mode 100644
index 0000000..c3c8b7e
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultLazyTypeListTest.java
@@ -0,0 +1,46 @@
+package net.bytebuddy.pool;
+
+import net.bytebuddy.description.type.AbstractTypeListTest;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import org.junit.After;
+import org.junit.Before;
+
+import java.util.List;
+
+import static net.bytebuddy.matcher.ElementMatchers.anyOf;
+
+public class TypePoolDefaultLazyTypeListTest extends AbstractTypeListTest<Class<?>> {
+
+ private TypePool typePool;
+
+ @Before
+ public void setUp() throws Exception {
+ typePool = TypePool.Default.ofClassPath();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ typePool.clear();
+ }
+
+ @Override
+ protected Class<?> getFirst() throws Exception {
+ return Foo.class;
+ }
+
+ @Override
+ protected Class<?> getSecond() throws Exception {
+ return Bar.class;
+ }
+
+ @Override
+ protected TypeList asList(List<Class<?>> elements) {
+ return typePool.describe(Holder.class.getName()).resolve().getInterfaces().asErasures().filter(anyOf(elements.toArray(new Class<?>[elements.size()])));
+ }
+
+ @Override
+ protected TypeDescription asElement(Class<?> element) {
+ return new TypeDescription.ForLoadedType(element);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultMethodDescriptionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultMethodDescriptionTest.java
new file mode 100644
index 0000000..4f927b1
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultMethodDescriptionTest.java
@@ -0,0 +1,52 @@
+package net.bytebuddy.pool;
+
+import net.bytebuddy.description.method.AbstractMethodDescriptionTest;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import org.hamcrest.CoreMatchers;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+import static net.bytebuddy.matcher.ElementMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class TypePoolDefaultMethodDescriptionTest extends AbstractMethodDescriptionTest {
+
+ private TypePool typePool;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ typePool = new TypePool.Default(TypePool.CacheProvider.NoOp.INSTANCE,
+ ClassFileLocator.ForClassLoader.ofClassPath(),
+ TypePool.Default.ReaderMode.EXTENDED); // In order to allow debug information parsing.
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ typePool.clear();
+ }
+
+ @Override
+ protected MethodDescription.InDefinedShape describe(Method method) {
+ return typePool.describe(method.getDeclaringClass().getName())
+ .resolve()
+ .getDeclaredMethods().filter(is(method)).getOnly();
+ }
+
+ @Override
+ protected MethodDescription.InDefinedShape describe(Constructor<?> constructor) {
+ return typePool.describe(constructor.getDeclaringClass().getName())
+ .resolve()
+ .getDeclaredMethods().filter(is(constructor)).getOnly();
+ }
+
+ @Override
+ protected boolean canReadDebugInformation() {
+ return true;
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultPackageDescriptionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultPackageDescriptionTest.java
new file mode 100644
index 0000000..b126683
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultPackageDescriptionTest.java
@@ -0,0 +1,26 @@
+package net.bytebuddy.pool;
+
+import net.bytebuddy.description.type.AbstractPackageDescriptionTest;
+import net.bytebuddy.description.type.PackageDescription;
+import org.junit.After;
+import org.junit.Before;
+
+public class TypePoolDefaultPackageDescriptionTest extends AbstractPackageDescriptionTest {
+
+ private TypePool typePool;
+
+ @Before
+ public void setUp() throws Exception {
+ typePool = TypePool.Default.ofClassPath();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ typePool.clear();
+ }
+
+ @Override
+ protected PackageDescription describe(Class<?> type) {
+ return typePool.describe(type.getName()).resolve().getPackage();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultParameterBagTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultParameterBagTest.java
new file mode 100644
index 0000000..cd6f33c
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultParameterBagTest.java
@@ -0,0 +1,91 @@
+package net.bytebuddy.pool;
+
+import org.junit.Test;
+import org.objectweb.asm.Type;
+
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class TypePoolDefaultParameterBagTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux";
+
+ @Test
+ public void testFullResolutionStaticMethod() throws Exception {
+ Type[] type = new Type[3];
+ type[0] = Type.getType(Object.class);
+ type[1] = Type.getType(long.class);
+ type[2] = Type.getType(String.class);
+ TypePool.Default.ParameterBag parameterBag = new TypePool.Default.ParameterBag(type);
+ parameterBag.register(0, FOO);
+ parameterBag.register(1, BAR);
+ parameterBag.register(3, QUX);
+ List<TypePool.Default.LazyTypeDescription.MethodToken.ParameterToken> tokens = parameterBag.resolve(true);
+ assertThat(tokens.size(), is(3));
+ assertThat(tokens.get(0).getName(), is(FOO));
+ assertThat(tokens.get(1).getName(), is(BAR));
+ assertThat(tokens.get(2).getName(), is(QUX));
+ assertThat(tokens.get(0).getModifiers(), nullValue(Integer.class));
+ assertThat(tokens.get(1).getModifiers(), nullValue(Integer.class));
+ assertThat(tokens.get(2).getModifiers(), nullValue(Integer.class));
+ }
+
+ @Test
+ public void testFullResolutionNonStaticMethod() throws Exception {
+ Type[] type = new Type[3];
+ type[0] = Type.getType(Object.class);
+ type[1] = Type.getType(long.class);
+ type[2] = Type.getType(String.class);
+ TypePool.Default.ParameterBag parameterBag = new TypePool.Default.ParameterBag(type);
+ parameterBag.register(1, FOO);
+ parameterBag.register(2, BAR);
+ parameterBag.register(4, QUX);
+ List<TypePool.Default.LazyTypeDescription.MethodToken.ParameterToken> tokens = parameterBag.resolve(false);
+ assertThat(tokens.size(), is(3));
+ assertThat(tokens.get(0).getName(), is(FOO));
+ assertThat(tokens.get(1).getName(), is(BAR));
+ assertThat(tokens.get(2).getName(), is(QUX));
+ assertThat(tokens.get(0).getModifiers(), nullValue(Integer.class));
+ assertThat(tokens.get(1).getModifiers(), nullValue(Integer.class));
+ assertThat(tokens.get(2).getModifiers(), nullValue(Integer.class));
+ }
+
+ @Test
+ public void testPartlyResolutionStaticMethod() throws Exception {
+ Type[] type = new Type[3];
+ type[0] = Type.getType(Object.class);
+ type[1] = Type.getType(long.class);
+ type[2] = Type.getType(String.class);
+ TypePool.Default.ParameterBag parameterBag = new TypePool.Default.ParameterBag(type);
+ parameterBag.register(0, FOO);
+ parameterBag.register(3, QUX);
+ List<TypePool.Default.LazyTypeDescription.MethodToken.ParameterToken> tokens = parameterBag.resolve(true);
+ assertThat(tokens.size(), is(3));
+ assertThat(tokens.get(0).getName(), is(FOO));
+ assertThat(tokens.get(1).getName(), nullValue(String.class));
+ assertThat(tokens.get(2).getName(), is(QUX));
+ assertThat(tokens.get(0).getModifiers(), nullValue(Integer.class));
+ assertThat(tokens.get(1).getModifiers(), nullValue(Integer.class));
+ assertThat(tokens.get(2).getModifiers(), nullValue(Integer.class));
+ }
+
+ @Test
+ public void testEmptyResolutionStaticMethod() throws Exception {
+ Type[] type = new Type[3];
+ type[0] = Type.getType(Object.class);
+ type[1] = Type.getType(long.class);
+ type[2] = Type.getType(String.class);
+ TypePool.Default.ParameterBag parameterBag = new TypePool.Default.ParameterBag(type);
+ List<TypePool.Default.LazyTypeDescription.MethodToken.ParameterToken> tokens = parameterBag.resolve(true);
+ assertThat(tokens.size(), is(3));
+ assertThat(tokens.get(0).getName(), nullValue(String.class));
+ assertThat(tokens.get(1).getName(), nullValue(String.class));
+ assertThat(tokens.get(2).getName(), nullValue(String.class));
+ assertThat(tokens.get(0).getModifiers(), nullValue(Integer.class));
+ assertThat(tokens.get(1).getModifiers(), nullValue(Integer.class));
+ assertThat(tokens.get(2).getModifiers(), nullValue(Integer.class));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultPrimitiveTypeTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultPrimitiveTypeTest.java
new file mode 100644
index 0000000..67b20ec
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultPrimitiveTypeTest.java
@@ -0,0 +1,51 @@
+package net.bytebuddy.pool;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+ at RunWith(Parameterized.class)
+public class TypePoolDefaultPrimitiveTypeTest {
+
+ private final Class<?> primitiveType;
+
+ private TypePool typePool;
+
+ public TypePoolDefaultPrimitiveTypeTest(Class<?> primitiveType) {
+ this.primitiveType = primitiveType;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {boolean.class},
+ {byte.class},
+ {short.class},
+ {char.class},
+ {int.class},
+ {long.class},
+ {float.class},
+ {double.class},
+ {void.class}
+ });
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ typePool = TypePool.Default.ofClassPath();
+ }
+
+ @Test
+ public void testPrimitiveLookup() throws Exception {
+ assertThat(typePool.describe(primitiveType.getName())
+ .resolve()
+ .represents(primitiveType), is(true));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultReaderModeTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultReaderModeTest.java
new file mode 100644
index 0000000..5b55ee1
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultReaderModeTest.java
@@ -0,0 +1,28 @@
+package net.bytebuddy.pool;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+import org.objectweb.asm.ClassReader;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class TypePoolDefaultReaderModeTest {
+
+ @Test
+ public void testDefinition() throws Exception {
+ assertThat(TypePool.Default.ReaderMode.EXTENDED.isExtended(), is(true));
+ assertThat(TypePool.Default.ReaderMode.FAST.isExtended(), is(false));
+ }
+
+ @Test
+ public void testFlags() throws Exception {
+ assertThat(TypePool.Default.ReaderMode.EXTENDED.getFlags(), is(ClassReader.SKIP_FRAMES));
+ assertThat(TypePool.Default.ReaderMode.FAST.getFlags(), is(ClassReader.SKIP_CODE));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypePool.Default.ReaderMode.class).apply();
+ }
+}
\ No newline at end of file
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultTest.java
new file mode 100644
index 0000000..e54d173
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultTest.java
@@ -0,0 +1,107 @@
+package net.bytebuddy.pool;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.hamcrest.CoreMatchers;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.objectweb.asm.Opcodes;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.*;
+
+public class TypePoolDefaultTest {
+
+ private TypePool typePool;
+
+ @Before
+ public void setUp() throws Exception {
+ typePool = TypePool.Default.ofClassPath();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ typePool.clear();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNameCannotContainSlash() throws Exception {
+ typePool.describe("/");
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testCannotFindClass() throws Exception {
+ TypePool.Resolution resolution = typePool.describe("foo");
+ assertThat(resolution.isResolved(), is(false));
+ resolution.resolve();
+ fail();
+ }
+
+ @Test
+ public void testNoSuperFlag() throws Exception {
+ assertThat(typePool.describe(Object.class.getName()).resolve().getModifiers() & Opcodes.ACC_SUPER, is(0));
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ public void testNoDeprecationFlag() throws Exception {
+ assertThat(typePool.describe(DeprecationSample.class.getName()).resolve().getModifiers() & Opcodes.ACC_DEPRECATED, is(0));
+ assertThat(typePool.describe(DeprecationSample.class.getName()).resolve().getDeclaredFields().filter(named("foo")).getOnly().getModifiers(), is(0));
+ assertThat(typePool.describe(DeprecationSample.class.getName()).resolve().getDeclaredMethods().filter(named("foo")).getOnly().getModifiers(), is(0));
+ }
+
+ @Test
+ public void testGenericsObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypePool.Default.GenericTypeExtractor.IncompleteToken.ForTopLevelType.class).apply();
+ ObjectPropertyAssertion.of(TypePool.Default.GenericTypeExtractor.IncompleteToken.ForInnerClass.class).apply();
+ ObjectPropertyAssertion.of(TypePool.Default.GenericTypeExtractor.ForSignature.OfType.SuperClassRegistrant.class).apply();
+ ObjectPropertyAssertion.of(TypePool.Default.GenericTypeExtractor.ForSignature.OfType.InterfaceTypeRegistrant.class).apply();
+ ObjectPropertyAssertion.of(TypePool.Default.GenericTypeExtractor.ForSignature.OfMethod.ReturnTypeTypeRegistrant.class).apply();
+ ObjectPropertyAssertion.of(TypePool.Default.GenericTypeExtractor.ForSignature.OfMethod.ParameterTypeRegistrant.class).apply();
+ ObjectPropertyAssertion.of(TypePool.Default.GenericTypeExtractor.ForSignature.OfMethod.ExceptionTypeRegistrant.class).apply();
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypePool.Default.class).apply();
+ }
+
+ @Test
+ public void testTypeIsCached() throws Exception {
+ ClassFileLocator classFileLocator = spy(ClassFileLocator.ForClassLoader.ofClassPath());
+ TypePool typePool = TypePool.Default.of(classFileLocator);
+ TypePool.Resolution resolution = typePool.describe(Object.class.getName());
+ assertThat(typePool.describe(Object.class.getName()).resolve(), CoreMatchers.is(resolution.resolve()));
+ verify(classFileLocator).locate(Object.class.getName());
+ verifyNoMoreInteractions(classFileLocator);
+ }
+
+ @Test
+ public void testReferencedTypeIsCached() throws Exception {
+ ClassFileLocator classFileLocator = spy(ClassFileLocator.ForClassLoader.ofClassPath());
+ TypePool typePool = TypePool.Default.of(classFileLocator);
+ TypePool.Resolution resolution = typePool.describe(String.class.getName());
+ assertThat(typePool.describe(String.class.getName()).resolve(), CoreMatchers.is(resolution.resolve()));
+ assertThat(typePool.describe(String.class.getName()).resolve().getSuperClass().asErasure(), CoreMatchers.is(TypeDescription.OBJECT));
+ verify(classFileLocator).locate(String.class.getName());
+ verify(classFileLocator).locate(Object.class.getName());
+ verifyNoMoreInteractions(classFileLocator);
+ }
+
+ @Deprecated
+ private static class DeprecationSample {
+
+ @Deprecated
+ Void foo;
+
+ @Deprecated
+ void foo() {
+ /* empty */
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultTypeDescriptionSuperClassLoadingTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultTypeDescriptionSuperClassLoadingTest.java
new file mode 100644
index 0000000..2d0b73c
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultTypeDescriptionSuperClassLoadingTest.java
@@ -0,0 +1,62 @@
+package net.bytebuddy.pool;
+
+import net.bytebuddy.description.type.AbstractTypeDescriptionTest;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+import static net.bytebuddy.matcher.ElementMatchers.is;
+
+public class TypePoolDefaultTypeDescriptionSuperClassLoadingTest extends AbstractTypeDescriptionTest {
+
+ @Override
+ protected TypeDescription describe(Class<?> type) {
+ TypePool typePool = new TypePool.Default(TypePool.CacheProvider.NoOp.INSTANCE,
+ ClassFileLocator.ForClassLoader.of(type.getClassLoader()),
+ TypePool.Default.ReaderMode.EXTENDED);
+ try {
+ return new TypeDescription.SuperTypeLoading(typePool.describe(type.getName()).resolve(), type.getClassLoader());
+ } finally {
+ typePool.clear();
+ }
+ }
+
+ @Override
+ protected TypeDescription.Generic describeType(Field field) {
+ return describe(field.getDeclaringClass()).getDeclaredFields().filter(is(field)).getOnly().getType();
+ }
+
+ @Override
+ protected TypeDescription.Generic describeReturnType(Method method) {
+ return describe(method.getDeclaringClass()).getDeclaredMethods().filter(is(method)).getOnly().getReturnType();
+ }
+
+ @Override
+ protected TypeDescription.Generic describeParameterType(Method method, int index) {
+ return describe(method.getDeclaringClass()).getDeclaredMethods().filter(is(method)).getOnly().getParameters().get(index).getType();
+ }
+
+ @Override
+ protected TypeDescription.Generic describeExceptionType(Method method, int index) {
+ return describe(method.getDeclaringClass()).getDeclaredMethods().filter(is(method)).getOnly().getExceptionTypes().get(index);
+ }
+
+ @Override
+ protected TypeDescription.Generic describeSuperClass(Class<?> type) {
+ return describe(type).getSuperClass();
+ }
+
+ @Override
+ protected TypeDescription.Generic describeInterfaceType(Class<?> type, int index) {
+ return describe(type).getInterfaces().get(index);
+ }
+
+ @Test
+ public void testDelegateProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypeDescription.SuperTypeLoading.ClassLoadingDelegate.Simple.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultTypeDescriptionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultTypeDescriptionTest.java
new file mode 100644
index 0000000..54eb98c
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultTypeDescriptionTest.java
@@ -0,0 +1,55 @@
+package net.bytebuddy.pool;
+
+import net.bytebuddy.description.type.AbstractTypeDescriptionTest;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+import static net.bytebuddy.matcher.ElementMatchers.is;
+
+public class TypePoolDefaultTypeDescriptionTest extends AbstractTypeDescriptionTest {
+
+ @Override
+ protected TypeDescription describe(Class<?> type) {
+ TypePool typePool = new TypePool.Default(TypePool.CacheProvider.NoOp.INSTANCE,
+ ClassFileLocator.ForClassLoader.of(type.getClassLoader()),
+ TypePool.Default.ReaderMode.EXTENDED);
+ try {
+ return typePool.describe(type.getName()).resolve();
+ } finally {
+ typePool.clear();
+ }
+ }
+
+ @Override
+ protected TypeDescription.Generic describeType(Field field) {
+ return describe(field.getDeclaringClass()).getDeclaredFields().filter(is(field)).getOnly().getType();
+ }
+
+ @Override
+ protected TypeDescription.Generic describeReturnType(Method method) {
+ return describe(method.getDeclaringClass()).getDeclaredMethods().filter(is(method)).getOnly().getReturnType();
+ }
+
+ @Override
+ protected TypeDescription.Generic describeParameterType(Method method, int index) {
+ return describe(method.getDeclaringClass()).getDeclaredMethods().filter(is(method)).getOnly().getParameters().get(index).getType();
+ }
+
+ @Override
+ protected TypeDescription.Generic describeExceptionType(Method method, int index) {
+ return describe(method.getDeclaringClass()).getDeclaredMethods().filter(is(method)).getOnly().getExceptionTypes().get(index);
+ }
+
+ @Override
+ protected TypeDescription.Generic describeSuperClass(Class<?> type) {
+ return describe(type).getSuperClass();
+ }
+
+ @Override
+ protected TypeDescription.Generic describeInterfaceType(Class<?> type, int index) {
+ return describe(type).getInterfaces().get(index);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultWithLazyResolutionTypeDescriptionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultWithLazyResolutionTypeDescriptionTest.java
new file mode 100644
index 0000000..eb0f534
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolDefaultWithLazyResolutionTypeDescriptionTest.java
@@ -0,0 +1,274 @@
+package net.bytebuddy.pool;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.type.AbstractTypeDescriptionTest;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.dynamic.scaffold.MethodGraph;
+import net.bytebuddy.dynamic.scaffold.TypeValidation;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.hamcrest.CoreMatchers;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+import static net.bytebuddy.matcher.ElementMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class TypePoolDefaultWithLazyResolutionTypeDescriptionTest extends AbstractTypeDescriptionTest {
+
+ @Override
+ protected TypeDescription describe(Class<?> type) {
+ return describe(type, ClassFileLocator.ForClassLoader.of(type.getClassLoader()), TypePool.CacheProvider.NoOp.INSTANCE);
+ }
+
+ private static TypeDescription describe(Class<?> type, ClassFileLocator classFileLocator, TypePool.CacheProvider cacheProvider) {
+ return new TypePool.Default.WithLazyResolution(cacheProvider,
+ classFileLocator,
+ TypePool.Default.ReaderMode.EXTENDED).describe(type.getName()).resolve();
+ }
+
+ @Override
+ protected TypeDescription.Generic describeType(Field field) {
+ return describe(field.getDeclaringClass()).getDeclaredFields().filter(is(field)).getOnly().getType();
+ }
+
+ @Override
+ protected TypeDescription.Generic describeReturnType(Method method) {
+ return describe(method.getDeclaringClass()).getDeclaredMethods().filter(is(method)).getOnly().getReturnType();
+ }
+
+ @Override
+ protected TypeDescription.Generic describeParameterType(Method method, int index) {
+ return describe(method.getDeclaringClass()).getDeclaredMethods().filter(is(method)).getOnly().getParameters().get(index).getType();
+ }
+
+ @Override
+ protected TypeDescription.Generic describeExceptionType(Method method, int index) {
+ return describe(method.getDeclaringClass()).getDeclaredMethods().filter(is(method)).getOnly().getExceptionTypes().get(index);
+ }
+
+ @Override
+ protected TypeDescription.Generic describeSuperClass(Class<?> type) {
+ return describe(type).getSuperClass();
+ }
+
+ @Override
+ protected TypeDescription.Generic describeInterfaceType(Class<?> type, int index) {
+ return describe(type).getInterfaces().get(index);
+ }
+
+ @Test
+ public void testTypeIsLazy() throws Exception {
+ ClassFileLocator classFileLocator = spy(ClassFileLocator.ForClassLoader.ofClassPath());
+ TypePool typePool = TypePool.Default.WithLazyResolution.of(classFileLocator);
+ TypePool.Resolution resolution = typePool.describe(Object.class.getName());
+ assertThat(resolution.resolve().getName(), CoreMatchers.is(TypeDescription.OBJECT.getName()));
+ verifyZeroInteractions(classFileLocator);
+ }
+
+ @Test
+ public void testReferencedTypeIsLazy() throws Exception {
+ ClassFileLocator classFileLocator = spy(ClassFileLocator.ForClassLoader.ofClassPath());
+ TypePool typePool = TypePool.Default.WithLazyResolution.of(classFileLocator);
+ TypePool.Resolution resolution = typePool.describe(String.class.getName());
+ assertThat(resolution.resolve().getName(), CoreMatchers.is(TypeDescription.STRING.getName()));
+ assertThat(resolution.resolve().getSuperClass().asErasure().getName(), CoreMatchers.is(TypeDescription.OBJECT.getName()));
+ verify(classFileLocator).locate(String.class.getName());
+ verifyNoMoreInteractions(classFileLocator);
+ }
+
+ @Test
+ public void testTypeIsCached() throws Exception {
+ ClassFileLocator classFileLocator = spy(ClassFileLocator.ForClassLoader.ofClassPath());
+ TypePool typePool = TypePool.Default.WithLazyResolution.of(classFileLocator);
+ TypePool.Resolution resolution = typePool.describe(Object.class.getName());
+ assertThat(resolution.resolve().getModifiers(), CoreMatchers.is(TypeDescription.OBJECT.getModifiers()));
+ assertThat(resolution.resolve().getInterfaces(), CoreMatchers.is(TypeDescription.OBJECT.getInterfaces()));
+ assertThat(typePool.describe(Object.class.getName()).resolve(), CoreMatchers.is(resolution.resolve()));
+ verify(classFileLocator).locate(Object.class.getName());
+ verifyNoMoreInteractions(classFileLocator);
+ }
+
+ @Test
+ public void testReferencedTypeIsCached() throws Exception {
+ ClassFileLocator classFileLocator = spy(ClassFileLocator.ForClassLoader.ofClassPath());
+ TypePool typePool = TypePool.Default.WithLazyResolution.of(classFileLocator);
+ TypePool.Resolution resolution = typePool.describe(String.class.getName());
+ assertThat(resolution.resolve().getModifiers(), CoreMatchers.is(TypeDescription.STRING.getModifiers()));
+ TypeDescription superClass = resolution.resolve().getSuperClass().asErasure();
+ assertThat(superClass, CoreMatchers.is(TypeDescription.OBJECT));
+ assertThat(superClass.getModifiers(), CoreMatchers.is(TypeDescription.OBJECT.getModifiers()));
+ assertThat(superClass.getInterfaces(), CoreMatchers.is(TypeDescription.OBJECT.getInterfaces()));
+ assertThat(typePool.describe(String.class.getName()).resolve(), CoreMatchers.is(resolution.resolve()));
+ verify(classFileLocator).locate(String.class.getName());
+ verify(classFileLocator).locate(Object.class.getName());
+ verifyNoMoreInteractions(classFileLocator);
+ }
+
+ @Test
+ public void testNonGenericResolutionIsLazyForSimpleCreationNonFrozen() throws Exception {
+ ClassFileLocator classFileLocator = spy(ClassFileLocator.ForClassLoader.ofClassPath());
+ new ByteBuddy()
+ .with(TypeValidation.DISABLED)
+ .with(MethodGraph.Empty.INSTANCE)
+ .with(InstrumentedType.Factory.Default.MODIFIABLE)
+ .redefine(describe(NonGenericType.class, classFileLocator, new TypePool.CacheProvider.Simple()), classFileLocator)
+ .make();
+ verify(classFileLocator, times(2)).locate(NonGenericType.class.getName());
+ verifyNoMoreInteractions(classFileLocator);
+ }
+
+ @Test
+ public void testNonGenericResolutionIsLazyForSimpleCreation() throws Exception {
+ ClassFileLocator classFileLocator = spy(ClassFileLocator.ForClassLoader.ofClassPath());
+ new ByteBuddy()
+ .with(TypeValidation.DISABLED)
+ .with(MethodGraph.Empty.INSTANCE)
+ .with(InstrumentedType.Factory.Default.FROZEN)
+ .redefine(describe(NonGenericType.class, classFileLocator, new TypePool.CacheProvider.Simple()), classFileLocator)
+ .make();
+ verify(classFileLocator, times(2)).locate(NonGenericType.class.getName());
+ verifyNoMoreInteractions(classFileLocator);
+ }
+
+ @Test
+ public void testGenericResolutionIsLazyForSimpleCreation() throws Exception {
+ ClassFileLocator classFileLocator = spy(ClassFileLocator.ForClassLoader.ofClassPath());
+ new ByteBuddy()
+ .with(TypeValidation.DISABLED)
+ .with(MethodGraph.Empty.INSTANCE)
+ .with(InstrumentedType.Factory.Default.FROZEN)
+ .redefine(describe(GenericType.class, classFileLocator, new TypePool.CacheProvider.Simple()), classFileLocator)
+ .make();
+ verify(classFileLocator, times(2)).locate(GenericType.class.getName());
+ verifyNoMoreInteractions(classFileLocator);
+ }
+
+ @Test
+ public void testNonGenericSuperClassHierarchyResolutionIsLazy() throws Exception {
+ ClassFileLocator classFileLocator = spy(ClassFileLocator.ForClassLoader.ofClassPath());
+ assertThat(describe(NonGenericType.class, classFileLocator, new TypePool.CacheProvider.Simple()).getSuperClass().asErasure(),
+ CoreMatchers.is((TypeDescription) new TypeDescription.ForLoadedType(SampleClass.class)));
+ verify(classFileLocator).locate(NonGenericType.class.getName());
+ verifyNoMoreInteractions(classFileLocator);
+ }
+
+ @Test
+ public void testNonGenericSuperClassNavigatedHierarchyResolutionIsLazy() throws Exception {
+ ClassFileLocator classFileLocator = spy(ClassFileLocator.ForClassLoader.ofClassPath());
+ assertThat(describe(NonGenericType.class, classFileLocator, new TypePool.CacheProvider.Simple()).getSuperClass().getSuperClass().asErasure(),
+ CoreMatchers.is((TypeDescription) new TypeDescription.ForLoadedType(SuperClass.class)));
+ verify(classFileLocator).locate(NonGenericType.class.getName());
+ verify(classFileLocator).locate(SampleClass.class.getName());
+ verifyNoMoreInteractions(classFileLocator);
+ }
+
+ @Test
+ public void testNonGenericSuperInterfaceHierarchyResolutionIsLazy() throws Exception {
+ ClassFileLocator classFileLocator = spy(ClassFileLocator.ForClassLoader.ofClassPath());
+ assertThat(describe(NonGenericType.class, classFileLocator, new TypePool.CacheProvider.Simple()).getInterfaces().getOnly().asErasure(),
+ CoreMatchers.is((TypeDescription) new TypeDescription.ForLoadedType(SampleInterface.class)));
+ verify(classFileLocator).locate(NonGenericType.class.getName());
+ verifyNoMoreInteractions(classFileLocator);
+ }
+
+ @Test
+ public void testNonGenericSuperInterfaceNavigatedHierarchyResolutionIsLazy() throws Exception {
+ ClassFileLocator classFileLocator = spy(ClassFileLocator.ForClassLoader.ofClassPath());
+ assertThat(describe(NonGenericType.class, classFileLocator, new TypePool.CacheProvider.Simple()).getInterfaces().getOnly()
+ .getInterfaces().getOnly().asErasure(), CoreMatchers.is((TypeDescription) new TypeDescription.ForLoadedType(SuperInterface.class)));
+ verify(classFileLocator).locate(NonGenericType.class.getName());
+ verify(classFileLocator).locate(SampleInterface.class.getName());
+ verifyNoMoreInteractions(classFileLocator);
+ }
+
+ @Test
+ public void testGenericSuperClassHierarchyResolutionIsLazy() throws Exception {
+ ClassFileLocator classFileLocator = spy(ClassFileLocator.ForClassLoader.ofClassPath());
+ assertThat(describe(GenericType.class, classFileLocator, new TypePool.CacheProvider.Simple()).getSuperClass().asErasure(),
+ CoreMatchers.is((TypeDescription) new TypeDescription.ForLoadedType(SampleGenericClass.class)));
+ verify(classFileLocator).locate(GenericType.class.getName());
+ verifyNoMoreInteractions(classFileLocator);
+ }
+
+ @Test
+ public void testGenericSuperClassNavigatedHierarchyResolutionIsLazy() throws Exception {
+ ClassFileLocator classFileLocator = spy(ClassFileLocator.ForClassLoader.ofClassPath());
+ assertThat(describe(GenericType.class, classFileLocator, new TypePool.CacheProvider.Simple()).getSuperClass().getSuperClass().asErasure(),
+ CoreMatchers.is((TypeDescription) new TypeDescription.ForLoadedType(SuperClass.class)));
+ verify(classFileLocator).locate(GenericType.class.getName());
+ verify(classFileLocator).locate(SampleGenericClass.class.getName());
+ verifyNoMoreInteractions(classFileLocator);
+ }
+
+ @Test
+ public void testGenericSuperInterfaceHierarchyResolutionIsLazy() throws Exception {
+ ClassFileLocator classFileLocator = spy(ClassFileLocator.ForClassLoader.ofClassPath());
+ assertThat(describe(GenericType.class, classFileLocator, new TypePool.CacheProvider.Simple()).getInterfaces().getOnly().asErasure(),
+ CoreMatchers.is((TypeDescription) new TypeDescription.ForLoadedType(SampleGenericInterface.class)));
+ verify(classFileLocator).locate(GenericType.class.getName());
+ verifyNoMoreInteractions(classFileLocator);
+ }
+
+ @Test
+ public void testGenericSuperInterfaceNavigatedHierarchyResolutionIsLazy() throws Exception {
+ ClassFileLocator classFileLocator = spy(ClassFileLocator.ForClassLoader.ofClassPath());
+ assertThat(describe(GenericType.class, classFileLocator, new TypePool.CacheProvider.Simple()).getInterfaces().getOnly()
+ .getInterfaces().getOnly().asErasure(), CoreMatchers.is((TypeDescription) new TypeDescription.ForLoadedType(SuperInterface.class)));
+ verify(classFileLocator).locate(GenericType.class.getName());
+ verify(classFileLocator).locate(SampleGenericInterface.class.getName());
+ verifyNoMoreInteractions(classFileLocator);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypePool.Default.WithLazyResolution.LazyResolution.class).apply();
+ }
+
+ private static class SuperClass {
+ /* empty */
+ }
+
+ private interface SuperInterface {
+ /* empty */
+ }
+
+ private static class SampleClass extends SuperClass {
+ /* empty */
+ }
+
+ private interface SampleInterface extends SuperInterface {
+ /* empty */
+ }
+
+ private static class NonGenericType extends SampleClass implements SampleInterface {
+
+ Object foo;
+
+ Object foo(Object argument) throws Exception {
+ return argument;
+ }
+ }
+
+ private static class SampleGenericClass<T> extends SuperClass {
+ /* empty */
+ }
+
+ private interface SampleGenericInterface<T> extends SuperInterface {
+ /* empty */
+ }
+
+ private static class GenericType<T extends Exception> extends SampleGenericClass<T> implements SampleGenericInterface<T> {
+
+ T foo;
+
+ T foo(T argument) throws T {
+ return argument;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolEmptyTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolEmptyTest.java
new file mode 100644
index 0000000..1969767
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolEmptyTest.java
@@ -0,0 +1,26 @@
+package net.bytebuddy.pool;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class TypePoolEmptyTest {
+
+ private static final String FOO = "foo";
+
+ @Test
+ public void testResolutionUnresolved() throws Exception {
+ assertThat(TypePool.Empty.INSTANCE.describe(FOO).isResolved(), is(false));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testResolutionThrowsException() throws Exception {
+ TypePool.Empty.INSTANCE.describe(FOO).resolve();
+ }
+
+ @Test
+ public void testClearNoEffect() throws Exception {
+ TypePool.Empty.INSTANCE.clear();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolExplicitTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolExplicitTest.java
new file mode 100644
index 0000000..37b7159
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolExplicitTest.java
@@ -0,0 +1,71 @@
+package net.bytebuddy.pool;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class TypePoolExplicitTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ private TypePool typePool;
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Mock
+ private TypePool parent;
+
+ @Before
+ public void setUp() throws Exception {
+ when(parent.describe(anyString())).thenReturn(new TypePool.Resolution.Illegal(BAR));
+ typePool = new TypePool.Explicit(parent, Collections.singletonMap(FOO, typeDescription));
+ }
+
+ @Test
+ public void testSuccessfulLookup() throws Exception {
+ TypePool.Resolution resolution = typePool.describe(FOO);
+ assertThat(resolution.isResolved(), is(true));
+ assertThat(resolution.resolve(), is(typeDescription));
+ verify(parent).describe(FOO);
+ verifyNoMoreInteractions(parent);
+ }
+
+ @Test
+ public void testFailedLookup() throws Exception {
+ TypePool.Resolution resolution = typePool.describe(BAR);
+ assertThat(resolution.isResolved(), is(false));
+ verify(parent).describe(BAR);
+ verifyNoMoreInteractions(parent);
+ }
+
+ @Test
+ public void testDelegation() throws Exception {
+ TypePool.Resolution resolution = mock(TypePool.Resolution.class);
+ when(resolution.isResolved()).thenReturn(true);
+ when(parent.describe(BAR)).thenReturn(resolution);
+ assertThat(typePool.describe(BAR), sameInstance(resolution));
+ verify(parent).describe(BAR);
+ verifyNoMoreInteractions(parent);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypePool.Explicit.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolLazyFacadeTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolLazyFacadeTest.java
new file mode 100644
index 0000000..c502e2f
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolLazyFacadeTest.java
@@ -0,0 +1,76 @@
+package net.bytebuddy.pool;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class TypePoolLazyFacadeTest {
+
+ private static final String FOO = "foo";
+
+ private static final int MODIFIERS = 42;
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private TypePool typePool;
+
+ @Mock
+ private TypePool.Resolution resolution;
+
+ @Mock
+ private TypeDescription typeDescription;
+
+ @Before
+ public void setUp() throws Exception {
+ when(typePool.describe(FOO)).thenReturn(resolution);
+ when(resolution.isResolved()).thenReturn(true);
+ when(resolution.resolve()).thenReturn(typeDescription);
+ when(typeDescription.getModifiers()).thenReturn(MODIFIERS);
+ }
+
+ @Test
+ public void testDoesNotQueryActualTypePoolForName() throws Exception {
+ TypePool typePool = new TypePool.LazyFacade(this.typePool);
+ assertThat(typePool.describe(FOO).resolve().getName(), is(FOO));
+ verifyZeroInteractions(this.typePool);
+ }
+
+ @Test
+ public void testDoesQueryActualTypePoolForResolution() throws Exception {
+ TypePool typePool = new TypePool.LazyFacade(this.typePool);
+ assertThat(typePool.describe(FOO).isResolved(), is(true));
+ verify(this.typePool).describe(FOO);
+ verifyNoMoreInteractions(this.typePool);
+ verify(resolution).isResolved();
+ verifyNoMoreInteractions(resolution);
+ }
+
+ @Test
+ public void testDoesQueryActualTypePoolForNonNameProperty() throws Exception {
+ TypePool typePool = new TypePool.LazyFacade(this.typePool);
+ assertThat(typePool.describe(FOO).resolve().getModifiers(), is(MODIFIERS));
+ verify(this.typePool).describe(FOO);
+ verifyNoMoreInteractions(this.typePool);
+ verify(resolution).resolve();
+ verifyNoMoreInteractions(resolution);
+ verify(typeDescription).getModifiers();
+ verifyNoMoreInteractions(typeDescription);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypePool.LazyFacade.class).apply();
+ ObjectPropertyAssertion.of(TypePool.LazyFacade.LazyResolution.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolLazyFacadeTypeDescriptionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolLazyFacadeTypeDescriptionTest.java
new file mode 100644
index 0000000..cc68b2a
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolLazyFacadeTypeDescriptionTest.java
@@ -0,0 +1,55 @@
+package net.bytebuddy.pool;
+
+import net.bytebuddy.description.type.AbstractTypeDescriptionTest;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+import static net.bytebuddy.matcher.ElementMatchers.is;
+
+public class TypePoolLazyFacadeTypeDescriptionTest extends AbstractTypeDescriptionTest {
+
+ @Override
+ protected TypeDescription describe(Class<?> type) {
+ TypePool typePool = new TypePool.LazyFacade(new TypePool.Default(TypePool.CacheProvider.NoOp.INSTANCE,
+ ClassFileLocator.ForClassLoader.of(type.getClassLoader()),
+ TypePool.Default.ReaderMode.EXTENDED));
+ try {
+ return typePool.describe(type.getName()).resolve();
+ } finally {
+ typePool.clear();
+ }
+ }
+
+ @Override
+ protected TypeDescription.Generic describeType(Field field) {
+ return describe(field.getDeclaringClass()).getDeclaredFields().filter(is(field)).getOnly().getType();
+ }
+
+ @Override
+ protected TypeDescription.Generic describeReturnType(Method method) {
+ return describe(method.getDeclaringClass()).getDeclaredMethods().filter(is(method)).getOnly().getReturnType();
+ }
+
+ @Override
+ protected TypeDescription.Generic describeParameterType(Method method, int index) {
+ return describe(method.getDeclaringClass()).getDeclaredMethods().filter(is(method)).getOnly().getParameters().get(index).getType();
+ }
+
+ @Override
+ protected TypeDescription.Generic describeExceptionType(Method method, int index) {
+ return describe(method.getDeclaringClass()).getDeclaredMethods().filter(is(method)).getOnly().getExceptionTypes().get(index);
+ }
+
+ @Override
+ protected TypeDescription.Generic describeSuperClass(Class<?> type) {
+ return describe(type).getSuperClass();
+ }
+
+ @Override
+ protected TypeDescription.Generic describeInterfaceType(Class<?> type, int index) {
+ return describe(type).getInterfaces().get(index);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolResolutionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolResolutionTest.java
new file mode 100644
index 0000000..5b9b925
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/pool/TypePoolResolutionTest.java
@@ -0,0 +1,54 @@
+package net.bytebuddy.pool;
+
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class TypePoolResolutionTest {
+
+ private static final String FOO = "foo";
+
+ @Test
+ public void testSimpleResolution() throws Exception {
+ TypeDescription typeDescription = mock(TypeDescription.class);
+ assertThat(new TypePool.Resolution.Simple(typeDescription).isResolved(), is(true));
+ assertThat(new TypePool.Resolution.Simple(typeDescription).resolve(), is(typeDescription));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIllegalResolution() throws Exception {
+ assertThat(new TypePool.Resolution.Illegal(FOO).isResolved(), is(false));
+ new TypePool.Resolution.Illegal(FOO).resolve();
+ fail();
+ }
+
+ @Test
+ public void testArrayResolutionZeroArity() throws Exception {
+ TypePool.Resolution resolution = mock(TypePool.Resolution.class);
+ assertThat(TypePool.Default.ArrayTypeResolution.of(resolution, 0), is(resolution));
+ }
+
+ @Test
+ public void testArrayResolutionPositiveArity() throws Exception {
+ TypePool.Resolution resolution = mock(TypePool.Resolution.class);
+ when(resolution.isResolved()).thenReturn(true);
+ when(resolution.resolve()).thenReturn(mock(TypeDescription.class));
+ assertThat(TypePool.Default.ArrayTypeResolution.of(resolution, 1), not(resolution));
+ TypeDescription typeDescription = TypePool.Default.ArrayTypeResolution.of(resolution, 1).resolve();
+ assertThat(typeDescription.isArray(), is(true));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(TypePool.Resolution.Simple.class).apply();
+ ObjectPropertyAssertion.of(TypePool.Resolution.Illegal.class).apply();
+ ObjectPropertyAssertion.of(TypePool.Default.ArrayTypeResolution.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/test/packaging/PackagePrivateConstructor.java b/byte-buddy-dep/src/test/java/net/bytebuddy/test/packaging/PackagePrivateConstructor.java
new file mode 100644
index 0000000..60f20db
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/test/packaging/PackagePrivateConstructor.java
@@ -0,0 +1,9 @@
+package net.bytebuddy.test.packaging;
+
+ at SuppressWarnings("unused")
+public class PackagePrivateConstructor {
+
+ PackagePrivateConstructor() {
+ /* do nothing */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/test/packaging/PackagePrivateField.java b/byte-buddy-dep/src/test/java/net/bytebuddy/test/packaging/PackagePrivateField.java
new file mode 100644
index 0000000..7afea14
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/test/packaging/PackagePrivateField.java
@@ -0,0 +1,19 @@
+package net.bytebuddy.test.packaging;
+
+ at SuppressWarnings("unused")
+public class PackagePrivateField {
+
+ public static final String PROTECTED_FIELD_NAME = "foo";
+
+ public static final String PACKAGE_PRIVATE_FIELD_NAME = "bar";
+
+ public static final String PRIVATE_FIELD_NAME = "qux";
+
+ public static final String FIELD_VALUE = "baz";
+
+ protected String foo = FIELD_VALUE;
+
+ String bar = FIELD_VALUE;
+
+ private String qux = FIELD_VALUE;
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/test/packaging/PackagePrivateMethod.java b/byte-buddy-dep/src/test/java/net/bytebuddy/test/packaging/PackagePrivateMethod.java
new file mode 100644
index 0000000..a549874
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/test/packaging/PackagePrivateMethod.java
@@ -0,0 +1,23 @@
+package net.bytebuddy.test.packaging;
+
+ at SuppressWarnings("unused")
+public class PackagePrivateMethod {
+
+ public static final String PROTECTED_METHOD_NAME = "foo";
+
+ public static final String PACKAGE_PRIVATE_METHOD_NAME = "bar";
+
+ public static final String PRIVATE_METHOD_NAME = "qux";
+
+ protected void foo() {
+ /* do nothing */
+ }
+
+ void bar() {
+ /* do nothing */
+ }
+
+ private void qux() {
+ /* do nothing */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/test/packaging/PackagePrivateType.java b/byte-buddy-dep/src/test/java/net/bytebuddy/test/packaging/PackagePrivateType.java
new file mode 100644
index 0000000..633457d
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/test/packaging/PackagePrivateType.java
@@ -0,0 +1,23 @@
+package net.bytebuddy.test.packaging;
+
+ at SuppressWarnings("unused")
+public class PackagePrivateType {
+
+ public static final Class<?> TYPE = Type.class;
+
+ public static final Class<?> EXCEPTION_TYPE = ExceptionType.class;
+
+ public static final Class<?> INTERFACE_TYPE = InterfaceType.class;
+
+ static class Type {
+ /* empty */
+ }
+
+ static interface InterfaceType {
+ /* empty */
+ }
+
+ static class ExceptionType extends Exception{
+ /* empty */
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/test/packaging/SimpleOptionalType.java b/byte-buddy-dep/src/test/java/net/bytebuddy/test/packaging/SimpleOptionalType.java
new file mode 100644
index 0000000..46119e4
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/test/packaging/SimpleOptionalType.java
@@ -0,0 +1,12 @@
+package net.bytebuddy.test.packaging;
+
+public class SimpleOptionalType {
+
+ private SimpleType simpleType;
+
+ private static final String FOO = "foo";
+
+ public String foo() {
+ return FOO;
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/test/packaging/SimpleType.java b/byte-buddy-dep/src/test/java/net/bytebuddy/test/packaging/SimpleType.java
new file mode 100644
index 0000000..6bd3866
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/test/packaging/SimpleType.java
@@ -0,0 +1,10 @@
+package net.bytebuddy.test.packaging;
+
+public class SimpleType {
+
+ private static final String FOO = "foo";
+
+ public String foo() {
+ return FOO;
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/test/packaging/VisibilityFieldTestHelper.java b/byte-buddy-dep/src/test/java/net/bytebuddy/test/packaging/VisibilityFieldTestHelper.java
new file mode 100644
index 0000000..7fd2294
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/test/packaging/VisibilityFieldTestHelper.java
@@ -0,0 +1,7 @@
+package net.bytebuddy.test.packaging;
+
+import net.bytebuddy.description.field.AbstractFieldDescriptionTest;
+
+public class VisibilityFieldTestHelper extends AbstractFieldDescriptionTest.PublicType {
+ /* empty */
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/test/packaging/VisibilityMethodTestHelper.java b/byte-buddy-dep/src/test/java/net/bytebuddy/test/packaging/VisibilityMethodTestHelper.java
new file mode 100644
index 0000000..c71ef61
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/test/packaging/VisibilityMethodTestHelper.java
@@ -0,0 +1,7 @@
+package net.bytebuddy.test.packaging;
+
+import net.bytebuddy.description.method.AbstractMethodDescriptionTest;
+
+public abstract class VisibilityMethodTestHelper extends AbstractMethodDescriptionTest.PublicType {
+ /* empty */
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/test/scope/EnclosingType.java b/byte-buddy-dep/src/test/java/net/bytebuddy/test/scope/EnclosingType.java
new file mode 100644
index 0000000..67c075c
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/test/scope/EnclosingType.java
@@ -0,0 +1,136 @@
+package net.bytebuddy.test.scope;
+
+public class EnclosingType {
+
+ public static final Class<?> INNER = Bar.class;
+
+ public static final Class<?> NESTED = BarBaz.class;
+
+ @SuppressWarnings("deprecation")
+ public static final Class<?> DEPRECATED = DeprecatedClass.class;
+
+ public static final Class<?> FINAL_INNER = FinalBar.class;
+
+ public static final Class<?> FINAL_NESTED = FinalBarBaz.class;
+
+ public static final Class<?> PRIVATE_INNER = PrivateBar.class;
+
+ public static final Class<?> PRIVATE_NESTED = PrivateBarBaz.class;
+
+ public static final Class<?> PACKAGE_INNER = PackageBar.class;
+
+ public static final Class<?> PACKAGE_NESTED = PackageBarBaz.class;
+
+ public static final Class<?> PROTECTED_INNER = ProtectedBar.class;
+
+ public static final Class<?> PROTECTED_NESTED = ProtectedBarBaz.class;
+
+ public static final Class<?> ANONYMOUS_INITIALIZER;
+
+ public static final Class<?> LOCAL_INITIALIZER;
+
+ public static final Class<?> ANONYMOUS_METHOD = anonymousStatic();
+
+ public static final Class<?> LOCAL_METHOD = localStatic();
+
+ static {
+ ANONYMOUS_INITIALIZER = new Object() {
+ /* empty*/
+ }.getClass();
+ class Foo {
+ /* empty */
+ }
+ LOCAL_INITIALIZER = Foo.class;
+ }
+
+ private static Class<?> anonymousStatic() {
+ return new Object() {
+ /* empty */
+ }.getClass();
+ }
+
+ private static Class<?> localStatic() {
+ class FooBar {
+ /* empty */
+ }
+ return FooBar.class;
+ }
+
+ public class Bar {
+ /* empty */
+ }
+
+ public static class BarBaz {
+ /* empty */
+ }
+
+ public final class FinalBar {
+ /* empty */
+ }
+
+ public static final class FinalBarBaz {
+ /* empty */
+ }
+
+ private class PrivateBar {
+ /* empty */
+ }
+
+ private static class PrivateBarBaz {
+ /* empty */
+ }
+
+ class PackageBar {
+ /* empty */
+ }
+
+ static class PackageBarBaz {
+ /* empty */
+ }
+
+ protected class ProtectedBar {
+ /* empty */
+ }
+
+ protected static class ProtectedBarBaz {
+ /* empty */
+ }
+
+ @Deprecated
+ class DeprecatedClass {
+ /* empty */
+ }
+
+ public final Class<?> localConstructor;
+
+ public final Class<?> anonymousConstructor;
+
+ public final Class<?> localMethod;
+
+ public final Class<?> anonymousMethod;
+
+ public EnclosingType() {
+ class Qux {
+ /* empty */
+ }
+ localConstructor = Qux.class;
+ anonymousConstructor = new Object() {
+ /* empty */
+ }.getClass();
+ localMethod = local();
+ anonymousMethod = anonymous();
+ }
+
+ private Class<?> local() {
+ class Baz{
+ /* empty */
+ }
+ return Baz.class;
+ }
+
+ private Class<?> anonymous() {
+ return new Object() {
+ /* empty */
+ }.getClass();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/test/scope/GenericType.java b/byte-buddy-dep/src/test/java/net/bytebuddy/test/scope/GenericType.java
new file mode 100644
index 0000000..e1b8948
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/test/scope/GenericType.java
@@ -0,0 +1,21 @@
+package net.bytebuddy.test.scope;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+public class GenericType<U> {
+
+ public class Inner<T extends String, S extends T> extends GenericType<GenericType<U>.Inner<T, S>> implements Callable<Map<? super String, ? extends String>> {
+
+ <V extends T, W extends Exception> List<T[]> foo(V value) throws W {
+ return null;
+ }
+
+ @Override
+ public Map<? super String, ? extends String> call() throws Exception {
+ return null;
+ }
+ }
+
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/test/utility/AgentAttachmentRule.java b/byte-buddy-dep/src/test/java/net/bytebuddy/test/utility/AgentAttachmentRule.java
new file mode 100644
index 0000000..c078d23
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/test/utility/AgentAttachmentRule.java
@@ -0,0 +1,70 @@
+package net.bytebuddy.test.utility;
+
+import net.bytebuddy.agent.ByteBuddyAgent;
+import org.junit.rules.MethodRule;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.instrument.Instrumentation;
+import java.util.logging.Logger;
+
+/**
+ * This rules assures that the running JVM is a JDK JVM with an available
+ * <a href="https://blogs.oracle.com/CoreJavaTechTips/entry/the_attach_api">attach API</a>.
+ */
+public class AgentAttachmentRule implements MethodRule {
+
+ private final boolean available;
+
+ public AgentAttachmentRule() {
+ available = ByteBuddyAgent.AttachmentProvider.DEFAULT.attempt().isAvailable();
+ }
+
+ @Override
+ public Statement apply(Statement base, FrameworkMethod method, Object target) {
+ Enforce enforce = method.getAnnotation(Enforce.class);
+ if (enforce != null) {
+ if (!available) {
+ return new NoOpStatement("The executing JVM does not support runtime attachment");
+ }
+ Instrumentation instrumentation = ByteBuddyAgent.install(ByteBuddyAgent.AttachmentProvider.DEFAULT);
+ if (enforce.redefinesClasses() && !instrumentation.isRedefineClassesSupported()) {
+ return new NoOpStatement("The executing JVM does not support class redefinition");
+ } else if (enforce.retransformsClasses() && !instrumentation.isRetransformClassesSupported()) {
+ return new NoOpStatement("The executing JVM does not support class retransformation");
+ } else if (enforce.nativeMethodPrefix() && !instrumentation.isNativeMethodPrefixSupported()) {
+ return new NoOpStatement("The executing JVM does not support class native method prefixes");
+ }
+ }
+ return base;
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ public @interface Enforce {
+
+ boolean redefinesClasses() default false;
+
+ boolean retransformsClasses() default false;
+
+ boolean nativeMethodPrefix() default false;
+ }
+
+ private static class NoOpStatement extends Statement {
+
+ private final String reason;
+
+ public NoOpStatement(String reason) {
+ this.reason = reason;
+ }
+
+ @Override
+ public void evaluate() throws Throwable {
+ Logger.getLogger("net.bytebuddy").warning("Ignoring test case: " + reason);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/test/utility/CallTraceable.java b/byte-buddy-dep/src/test/java/net/bytebuddy/test/utility/CallTraceable.java
new file mode 100644
index 0000000..b88e3d3
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/test/utility/CallTraceable.java
@@ -0,0 +1,47 @@
+package net.bytebuddy.test.utility;
+
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class CallTraceable {
+
+ protected final List<MethodCall> methodCalls;
+
+ public CallTraceable() {
+ methodCalls = new ArrayList<MethodCall>();
+ }
+
+ public void register(String name, Object... arguments) {
+ methodCalls.add(new MethodCall(name, arguments));
+ }
+
+ public void assertOnlyCall(String name, Object... arguments) {
+ assertThat(methodCalls.size(), is(1));
+ assertThat(methodCalls.get(0).name, is(name));
+ assertThat(methodCalls.get(0).arguments, is(arguments));
+ }
+
+ public void assertZeroCalls() {
+ assertThat(methodCalls.size(), is(0));
+ }
+
+ public void reset() {
+ methodCalls.clear();
+ }
+
+ protected static class MethodCall {
+
+ public final String name;
+
+ public final Object arguments[];
+
+ public MethodCall(String name, Object... arguments) {
+ this.name = name;
+ this.arguments = arguments;
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/test/utility/ClassFileExtraction.java b/byte-buddy-dep/src/test/java/net/bytebuddy/test/utility/ClassFileExtraction.java
new file mode 100644
index 0000000..10855c4
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/test/utility/ClassFileExtraction.java
@@ -0,0 +1,107 @@
+package net.bytebuddy.test.utility;
+
+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.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.MethodAccessorFactory;
+import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.pool.TypePool;
+import org.junit.Test;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassWriter;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class ClassFileExtraction {
+
+ private static final int CA = 0xCA, FE = 0xFE, BA = 0xBA, BE = 0xBE;
+
+ public static Map<String, byte[]> of(Class<?>... type) throws IOException {
+ Map<String, byte[]> result = new HashMap<String, byte[]>();
+ for (Class<?> aType : type) {
+ result.put(aType.getName(), extract(aType));
+ }
+ return result;
+ }
+
+ public static byte[] extract(Class<?> type, AsmVisitorWrapper asmVisitorWrapper) throws IOException {
+ ClassReader classReader = new ClassReader(type.getName());
+ ClassWriter classWriter = new ClassWriter(classReader, AsmVisitorWrapper.NO_FLAGS);
+ classReader.accept(asmVisitorWrapper.wrap(new TypeDescription.ForLoadedType(type),
+ classWriter,
+ new IllegalContext(),
+ TypePool.Empty.INSTANCE,
+ new FieldList.Empty<FieldDescription.InDefinedShape>(),
+ new MethodList.Empty<MethodDescription>(),
+ AsmVisitorWrapper.NO_FLAGS,
+ AsmVisitorWrapper.NO_FLAGS), AsmVisitorWrapper.NO_FLAGS);
+ return classWriter.toByteArray();
+ }
+
+ public static byte[] extract(Class<?> type) throws IOException {
+ return extract(type, new AsmVisitorWrapper.Compound());
+ }
+
+ @Test
+ public void testClassFileExtraction() throws Exception {
+ byte[] binaryFoo = extract(Foo.class);
+ assertThat(binaryFoo.length > 4, is(true));
+ assertThat(binaryFoo[0], is(new Integer(CA).byteValue()));
+ assertThat(binaryFoo[1], is(new Integer(FE).byteValue()));
+ assertThat(binaryFoo[2], is(new Integer(BA).byteValue()));
+ assertThat(binaryFoo[3], is(new Integer(BE).byteValue()));
+ }
+
+ private static class Foo {
+ /* empty */
+ }
+
+ private static class IllegalContext implements Implementation.Context {
+
+ @Override
+ public TypeDescription register(AuxiliaryType auxiliaryType) {
+ throw new AssertionError("Did not expect method call");
+ }
+
+ @Override
+ public FieldDescription.InDefinedShape cache(StackManipulation fieldValue, TypeDescription fieldType) {
+ throw new AssertionError("Did not expect method call");
+ }
+
+ @Override
+ public TypeDescription getInstrumentedType() {
+ throw new AssertionError("Did not expect method call");
+ }
+
+ @Override
+ public ClassFileVersion getClassFileVersion() {
+ throw new AssertionError("Did not expect method call");
+ }
+
+ @Override
+ public MethodDescription.InDefinedShape registerAccessorFor(Implementation.SpecialMethodInvocation specialMethodInvocation, AccessType accessType) {
+ throw new AssertionError("Did not expect method call");
+ }
+
+ @Override
+ public MethodDescription.InDefinedShape registerGetterFor(FieldDescription fieldDescription, AccessType accessType) {
+ throw new AssertionError("Did not expect method call");
+ }
+
+ @Override
+ public MethodDescription.InDefinedShape registerSetterFor(FieldDescription fieldDescription, AccessType accessType) {
+ throw new AssertionError("Did not expect method call");
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/test/utility/CustomHamcrestMatchers.java b/byte-buddy-dep/src/test/java/net/bytebuddy/test/utility/CustomHamcrestMatchers.java
new file mode 100644
index 0000000..391b92a
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/test/utility/CustomHamcrestMatchers.java
@@ -0,0 +1,31 @@
+package net.bytebuddy.test.utility;
+
+import org.hamcrest.Matcher;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.hamcrest.CoreMatchers.hasItems;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class CustomHamcrestMatchers {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux", BAZ = "baz";
+
+ @SuppressWarnings("unchecked")
+ public static <T> Matcher<Iterable<T>> containsAllOf(Collection<T> items) {
+ // The Java compiler only accepts this type casting when it is confused by the additional object type casting
+ return (Matcher<Iterable<T>>) (Object) hasItems(items.toArray(new Object[items.size()]));
+ }
+
+ @Test
+ public void testContainMatcherSucceeds() throws Exception {
+ assertThat(Arrays.asList(FOO, BAR, QUX), containsAllOf(Arrays.asList(QUX, FOO, BAR)));
+ }
+
+ @Test(expected = AssertionError.class)
+ public void testContainMatcherFails() throws Exception {
+ assertThat(Arrays.asList(FOO, BAR, QUX), containsAllOf(Arrays.asList(QUX, FOO, BAZ)));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/test/utility/DebuggingWrapper.java b/byte-buddy-dep/src/test/java/net/bytebuddy/test/utility/DebuggingWrapper.java
new file mode 100644
index 0000000..a3ed426
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/test/utility/DebuggingWrapper.java
@@ -0,0 +1,72 @@
+package net.bytebuddy.test.utility;
+
+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 org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.util.CheckClassAdapter;
+import org.objectweb.asm.util.Printer;
+import org.objectweb.asm.util.Textifier;
+import org.objectweb.asm.util.TraceClassVisitor;
+
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.Writer;
+
+ at SuppressWarnings("unused")
+public class DebuggingWrapper implements AsmVisitorWrapper {
+
+ private final PrintWriter printWriter;
+
+ private final Printer printer;
+
+ private final boolean check;
+
+ public DebuggingWrapper(Writer writer, Printer printer, boolean check) {
+ this.check = check;
+ printWriter = new PrintWriter(writer);
+ this.printer = printer;
+ }
+
+ public DebuggingWrapper(OutputStream outputStream, Printer printer, boolean check) {
+ this.check = check;
+ printWriter = new PrintWriter(outputStream);
+ this.printer = printer;
+ }
+
+ public static AsmVisitorWrapper makeDefault() {
+ return makeDefault(true);
+ }
+
+ public static AsmVisitorWrapper makeDefault(boolean check) {
+ return new DebuggingWrapper(System.out, new Textifier(), check);
+ }
+
+ @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 check
+ ? new CheckClassAdapter(new TraceClassVisitor(classVisitor, printer, printWriter))
+ : new TraceClassVisitor(classVisitor, printer, printWriter);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/test/utility/IntegrationRule.java b/byte-buddy-dep/src/test/java/net/bytebuddy/test/utility/IntegrationRule.java
new file mode 100644
index 0000000..aa0dbff
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/test/utility/IntegrationRule.java
@@ -0,0 +1,40 @@
+package net.bytebuddy.test.utility;
+
+import org.junit.rules.MethodRule;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.logging.Logger;
+
+public class IntegrationRule implements MethodRule {
+
+ private static final String PROPERTY_KEY = "net.bytebuddy.test.integration";
+
+ private final boolean integration;
+
+ public IntegrationRule() {
+ integration = Boolean.getBoolean(PROPERTY_KEY);
+ }
+
+ @Override
+ public Statement apply(Statement base, FrameworkMethod method, Object target) {
+ return !integration && method.getAnnotation(Enforce.class) != null
+ ? new NoOpStatement()
+ : base;
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface Enforce {
+ /* empty */
+ }
+
+ private static class NoOpStatement extends Statement {
+
+ @Override
+ public void evaluate() throws Throwable {
+ Logger.getLogger("net.bytebuddy").warning("Ignored test case that is only to be run on the CI server due to long runtime");
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/test/utility/JavaVersionRule.java b/byte-buddy-dep/src/test/java/net/bytebuddy/test/utility/JavaVersionRule.java
new file mode 100644
index 0000000..f59b593
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/test/utility/JavaVersionRule.java
@@ -0,0 +1,100 @@
+package net.bytebuddy.test.utility;
+
+import net.bytebuddy.ClassFileVersion;
+import org.junit.rules.MethodRule;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.Locale;
+import java.util.logging.Logger;
+
+public class JavaVersionRule implements MethodRule {
+
+ private final ClassFileVersion currentVersion;
+
+ private final boolean hotSpot;
+
+ public JavaVersionRule() {
+ currentVersion = ClassFileVersion.ofThisVm();
+ hotSpot = System.getProperty("java.vm.name", "").toLowerCase(Locale.US).contains("hotspot");
+ }
+
+ @Override
+ public Statement apply(Statement base, FrameworkMethod method, Object target) {
+ Enforce enforce = method.getAnnotation(Enforce.class);
+ if (enforce != null) {
+ if (!enforce.sort().check(currentVersion, ClassFileVersion.ofJavaVersion(enforce.value()))) {
+ return new NoOpStatement(enforce.value());
+ } else if (!hotSpot) {
+ for (int javaVersion : enforce.hotSpot()) {
+ if (currentVersion.getJavaVersion() == javaVersion) {
+ return new NoOpHotSpotStatement(javaVersion);
+ }
+ }
+ }
+ }
+ return base;
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ public @interface Enforce {
+
+ int value();
+
+ Sort sort() default Sort.AT_LEAST;
+
+ int[] hotSpot() default {};
+ }
+
+ private static class NoOpStatement extends Statement {
+
+ private final int requiredVersion;
+
+ public NoOpStatement(int requiredVersion) {
+ this.requiredVersion = requiredVersion;
+ }
+
+ @Override
+ public void evaluate() throws Throwable {
+ Logger.getLogger("net.bytebuddy").warning("Ignoring test case: Requires a Java version of at least " + requiredVersion);
+ }
+ }
+
+ private static class NoOpHotSpotStatement extends Statement {
+
+ private final int restrictedVersion;
+
+ public NoOpHotSpotStatement(int restrictedVersion) {
+ this.restrictedVersion = restrictedVersion;
+ }
+
+ @Override
+ public void evaluate() throws Throwable {
+ Logger.getLogger("net.bytebuddy").warning("Ignoring test case: Only works on HotSpot for Java version " + restrictedVersion);
+ }
+ }
+
+ public enum Sort {
+
+ AT_LEAST {
+ @Override
+ protected boolean check(ClassFileVersion current, ClassFileVersion enforced) {
+ return current.isAtLeast(enforced);
+ }
+ },
+
+ AT_MOST {
+ @Override
+ protected boolean check(ClassFileVersion current, ClassFileVersion enforced) {
+ return current.isLessThan(enforced) || current.equals(enforced);
+ }
+ };
+
+ protected abstract boolean check(ClassFileVersion current, ClassFileVersion enforced);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/test/utility/MockitoRule.java b/byte-buddy-dep/src/test/java/net/bytebuddy/test/utility/MockitoRule.java
new file mode 100644
index 0000000..10df0c0
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/test/utility/MockitoRule.java
@@ -0,0 +1,30 @@
+package net.bytebuddy.test.utility;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * A rule that applies Mockito's annotations to any test. This is preferred over the Mockito runner since it allows
+ * to use tests with parameters that require a specific runner.
+ */
+public class MockitoRule implements TestRule {
+
+ private final Object target;
+
+ public MockitoRule(Object target) {
+ this.target = target;
+ }
+
+ @Override
+ public Statement apply(final Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ MockitoAnnotations.initMocks(target);
+ base.evaluate();
+ }
+ };
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/test/utility/ObjectPropertyAssertion.java b/byte-buddy-dep/src/test/java/net/bytebuddy/test/utility/ObjectPropertyAssertion.java
new file mode 100644
index 0000000..f0b86d0
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/test/utility/ObjectPropertyAssertion.java
@@ -0,0 +1,327 @@
+package net.bytebuddy.test.utility;
+
+import net.bytebuddy.utility.CompoundList;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class ObjectPropertyAssertion<T> {
+
+ private static final boolean DEFAULT_BOOLEAN = false, OTHER_BOOLEAN = true;
+
+ private static final byte DEFAULT_BYTE = 1, OTHER_BYTE = 42;
+
+ private static final char DEFAULT_CHAR = 1, OTHER_CHAR = 42;
+
+ private static final short DEFAULT_SHORT = 1, OTHER_SHORT = 42;
+
+ private static final int DEFAULT_INT = 1, OTHER_INT = 42;
+
+ private static final long DEFAULT_LONG = 1, OTHER_LONG = 42;
+
+ private static final float DEFAULT_FLOAT = 1, OTHER_FLOAT = 42;
+
+ private static final double DEFAULT_DOUBLE = 1, OTHER_DOUBLE = 42;
+
+ private static final String DEFAULT_STRING = "foo", OTHER_STRING = "bar";
+
+ private final Class<T> type;
+
+ private final ApplicableRefinement refinement;
+
+ private final ApplicableGenerator generator;
+
+ private final ApplicableCreator creator;
+
+ private final boolean skipSynthetic;
+
+ private final String optionalToStringRegex;
+
+ private ObjectPropertyAssertion(Class<T> type,
+ ApplicableGenerator generator,
+ ApplicableRefinement refinement,
+ ApplicableCreator creator,
+ boolean skipSynthetic,
+ String optionalToStringRegex) {
+ this.type = type;
+ this.generator = generator;
+ this.refinement = refinement;
+ this.creator = creator;
+ this.skipSynthetic = skipSynthetic;
+ this.optionalToStringRegex = optionalToStringRegex;
+ }
+
+ public static <S> ObjectPropertyAssertion<S> of(Class<S> type) {
+ return new ObjectPropertyAssertion<S>(type,
+ new ApplicableGenerator(),
+ new ApplicableRefinement(),
+ new ApplicableCreator(),
+ false,
+ null
+ );
+ }
+
+ public ObjectPropertyAssertion<T> refine(Refinement<?> refinement) {
+ return new ObjectPropertyAssertion<T>(type,
+ generator,
+ this.refinement.with(refinement),
+ creator,
+ skipSynthetic,
+ optionalToStringRegex
+ );
+ }
+
+ public ObjectPropertyAssertion<T> generate(Generator<?> generator) {
+ return new ObjectPropertyAssertion<T>(type,
+ this.generator.with(generator),
+ refinement,
+ creator,
+ skipSynthetic,
+ optionalToStringRegex
+ );
+ }
+
+ public ObjectPropertyAssertion<T> create(Creator<?> creator) {
+ return new ObjectPropertyAssertion<T>(type,
+ generator,
+ refinement,
+ this.creator.with(creator),
+ skipSynthetic,
+ optionalToStringRegex
+ );
+ }
+
+ public ObjectPropertyAssertion<T> skipSynthetic() {
+ return new ObjectPropertyAssertion<T>(type, generator, refinement, creator, true, optionalToStringRegex);
+ }
+
+ public ObjectPropertyAssertion<T> specificToString(String stringRegex) {
+ return new ObjectPropertyAssertion<T>(type, generator, refinement, creator, skipSynthetic, stringRegex);
+ }
+
+ public void apply() throws IllegalAccessException, InvocationTargetException, InstantiationException {
+ if (type.isEnum()) {
+ return;
+ }
+ for (Constructor<?> constructor : type.getDeclaredConstructors()) {
+ if (constructor.isSynthetic() && skipSynthetic) {
+ continue;
+ }
+ constructor.setAccessible(true);
+ Class<?>[] parameterTypes = constructor.getParameterTypes();
+ Object[] actualArguments = new Object[parameterTypes.length];
+ Object[] otherArguments = new Object[parameterTypes.length];
+ int index = 0;
+ for (Class<?> parameterType : parameterTypes) {
+ putInstance(parameterType, actualArguments, otherArguments, index++);
+ }
+ int testIndex = 0;
+ @SuppressWarnings("unchecked")
+ T instance = (T) constructor.newInstance(actualArguments);
+ assertThat(instance, is(instance));
+ assertThat(instance, not(equalTo(null)));
+ assertThat(instance, not(new Object()));
+ Object similarInstance = constructor.newInstance(actualArguments);
+ assertThat(instance.hashCode(), is(similarInstance.hashCode()));
+ assertThat(instance, is(similarInstance));
+ if (optionalToStringRegex != null) {
+ assertThat(instance.toString(), new RegexMatcher(optionalToStringRegex));
+ }
+ for (Object otherArgument : otherArguments) {
+ Object[] compareArguments = new Object[actualArguments.length];
+ int argumentIndex = 0;
+ for (Object actualArgument : actualArguments) {
+ if (argumentIndex == testIndex) {
+ compareArguments[argumentIndex] = otherArgument;
+ } else {
+ compareArguments[argumentIndex] = actualArgument;
+ }
+ argumentIndex++;
+ }
+ Object unlikeInstance = constructor.newInstance(compareArguments);
+ assertThat(instance.hashCode(), not(unlikeInstance));
+ assertThat(instance, not(unlikeInstance));
+ testIndex++;
+ }
+ }
+ }
+
+ private void putInstance(Class<?> parameterType, Object actualArguments, Object otherArguments, int index) {
+ Object actualArgument, otherArgument;
+ if (parameterType == boolean.class) {
+ actualArgument = DEFAULT_BOOLEAN;
+ otherArgument = OTHER_BOOLEAN;
+ } else if (parameterType == byte.class) {
+ actualArgument = DEFAULT_BYTE;
+ otherArgument = OTHER_BYTE;
+ } else if (parameterType == char.class) {
+ actualArgument = DEFAULT_CHAR;
+ otherArgument = OTHER_CHAR;
+ } else if (parameterType == short.class) {
+ actualArgument = DEFAULT_SHORT;
+ otherArgument = OTHER_SHORT;
+ } else if (parameterType == int.class) {
+ actualArgument = DEFAULT_INT;
+ otherArgument = OTHER_INT;
+ } else if (parameterType == long.class) {
+ actualArgument = DEFAULT_LONG;
+ otherArgument = OTHER_LONG;
+ } else if (parameterType == float.class) {
+ actualArgument = DEFAULT_FLOAT;
+ otherArgument = OTHER_FLOAT;
+ } else if (parameterType == double.class) {
+ actualArgument = DEFAULT_DOUBLE;
+ otherArgument = OTHER_DOUBLE;
+ } else if (parameterType.isEnum()) {
+ Object[] enumConstants = parameterType.getEnumConstants();
+ if (enumConstants.length == 1) {
+ throw new IllegalArgumentException("Enum with only one constant: " + parameterType);
+ }
+ actualArgument = enumConstants[0];
+ otherArgument = enumConstants[1];
+ } else if (parameterType.isArray()) {
+ actualArgument = Array.newInstance(parameterType.getComponentType(), 1);
+ otherArgument = Array.newInstance(parameterType.getComponentType(), 1);
+ putInstance(parameterType.getComponentType(), actualArgument, otherArgument, 0);
+ } else {
+ actualArgument = creator.replace(parameterType, generator, false);
+ refinement.apply(actualArgument);
+ otherArgument = creator.replace(parameterType, generator, true);
+ refinement.apply(otherArgument);
+ }
+ Array.set(actualArguments, index, actualArgument);
+ Array.set(otherArguments, index, otherArgument);
+ }
+
+ public interface Refinement<T> {
+
+ void apply(T mock);
+ }
+
+ public interface Generator<T> {
+
+ Class<? extends T> generate();
+ }
+
+ public interface Creator<T> {
+
+ T create();
+ }
+
+ private static class ApplicableRefinement {
+
+ private final List<Refinement<?>> refinements;
+
+ private ApplicableRefinement() {
+ refinements = Collections.emptyList();
+ }
+
+ private ApplicableRefinement(List<Refinement<?>> refinements) {
+ this.refinements = refinements;
+ }
+
+ @SuppressWarnings("unchecked")
+ private void apply(Object mock) {
+ for (Refinement refinement : refinements) {
+ ParameterizedType generic = (ParameterizedType) refinement.getClass().getGenericInterfaces()[0];
+ Class<?> restrained = generic.getActualTypeArguments()[0] instanceof ParameterizedType
+ ? (Class<?>) ((ParameterizedType) generic.getActualTypeArguments()[0]).getRawType()
+ : (Class<?>) generic.getActualTypeArguments()[0];
+ if (restrained.isInstance(mock)) {
+ refinement.apply(mock);
+ }
+ }
+ }
+
+ private ApplicableRefinement with(Refinement<?> refinement) {
+ return new ApplicableRefinement(CompoundList.of(refinements, refinement));
+ }
+ }
+
+ private static class ApplicableGenerator {
+
+ private final List<Generator<?>> generators;
+
+ private ApplicableGenerator() {
+ generators = Collections.emptyList();
+ }
+
+ private ApplicableGenerator(List<Generator<?>> generators) {
+ this.generators = generators;
+ }
+
+ private Object generate(Class<?> type, boolean alternative) {
+ for (Generator<?> generator : generators) {
+ ParameterizedType generic = (ParameterizedType) generator.getClass().getGenericInterfaces()[0];
+ Class<?> restrained = generic.getActualTypeArguments()[0] instanceof ParameterizedType
+ ? (Class<?>) ((ParameterizedType) generic.getActualTypeArguments()[0]).getRawType()
+ : (Class<?>) generic.getActualTypeArguments()[0];
+ if (type.isAssignableFrom(restrained)) {
+ type = generator.generate();
+ }
+ }
+ return type == String.class
+ ? alternative ? OTHER_STRING : DEFAULT_STRING
+ : mock(type);
+ }
+
+ private ApplicableGenerator with(Generator<?> generator) {
+ return new ApplicableGenerator(CompoundList.of(generators, generator));
+ }
+ }
+
+ private static class ApplicableCreator {
+
+ private final List<Creator<?>> creators;
+
+ private ApplicableCreator() {
+ creators = Collections.emptyList();
+ }
+
+ private ApplicableCreator(List<Creator<?>> creators) {
+ this.creators = creators;
+ }
+
+ private Object replace(Class<?> type, ApplicableGenerator generator, boolean alternative) {
+ for (Creator<?> creator : creators) {
+ ParameterizedType generic = (ParameterizedType) creator.getClass().getGenericInterfaces()[0];
+ Class<?> restrained = generic.getActualTypeArguments()[0] instanceof ParameterizedType
+ ? (Class<?>) ((ParameterizedType) generic.getActualTypeArguments()[0]).getRawType()
+ : (Class<?>) generic.getActualTypeArguments()[0];
+ if (type.isAssignableFrom(restrained)) {
+ return creator.create();
+ }
+ }
+ return generator.generate(type, alternative);
+ }
+
+ private ApplicableCreator with(Creator<?> creator) {
+ return new ApplicableCreator(CompoundList.of(creators, creator));
+ }
+ }
+
+ private static class RegexMatcher extends TypeSafeMatcher<String> {
+
+ private final String regex;
+
+ public RegexMatcher(final String regex) {
+ this.regex = regex;
+ }
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText("matches regex='" + regex + "'");
+ }
+
+ @Override
+ public boolean matchesSafely(final String string) {
+ return string.matches(regex);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/test/visibility/PackageAnnotation.java b/byte-buddy-dep/src/test/java/net/bytebuddy/test/visibility/PackageAnnotation.java
new file mode 100644
index 0000000..a5d91fa
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/test/visibility/PackageAnnotation.java
@@ -0,0 +1,9 @@
+package net.bytebuddy.test.visibility;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+ at Retention(RetentionPolicy.RUNTIME)
+public @interface PackageAnnotation {
+ /* empty */
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/test/visibility/Sample.java b/byte-buddy-dep/src/test/java/net/bytebuddy/test/visibility/Sample.java
new file mode 100644
index 0000000..2e912b5
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/test/visibility/Sample.java
@@ -0,0 +1,5 @@
+package net.bytebuddy.test.visibility;
+
+public class Sample {
+
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/test/visibility/child/Child.java b/byte-buddy-dep/src/test/java/net/bytebuddy/test/visibility/child/Child.java
new file mode 100644
index 0000000..28e6efc
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/test/visibility/child/Child.java
@@ -0,0 +1,5 @@
+package net.bytebuddy.test.visibility.child;
+
+public class Child {
+
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/test/visibility/package-info.java b/byte-buddy-dep/src/test/java/net/bytebuddy/test/visibility/package-info.java
new file mode 100644
index 0000000..2f291ab
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/test/visibility/package-info.java
@@ -0,0 +1 @@
+ at PackageAnnotation package net.bytebuddy.test.visibility;
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/utility/CompoundListTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/CompoundListTest.java
new file mode 100644
index 0000000..60a6f2e
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/CompoundListTest.java
@@ -0,0 +1,67 @@
+package net.bytebuddy.utility;
+
+import net.bytebuddy.test.utility.MockitoRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
+import java.util.List;
+
+import static junit.framework.TestCase.fail;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.*;
+
+public class CompoundListTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private Object first, second, third, forth;
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testConstruction() throws Throwable {
+ Constructor<?> constructor = CompoundList.class.getDeclaredConstructor();
+ constructor.setAccessible(true);
+ try {
+ constructor.newInstance();
+ fail();
+ } catch (InvocationTargetException exception) {
+ throw exception.getCause();
+ }
+ }
+
+ @Test
+ public void testElementAndList() throws Exception {
+ List<Object> list = CompoundList.of(first, Arrays.asList(second, third, forth));
+ assertThat(list.size(), is(4));
+ assertThat(list.get(0), is(first));
+ assertThat(list.get(1), is(second));
+ assertThat(list.get(2), is(third));
+ assertThat(list.get(3), is(forth));
+ }
+
+ @Test
+ public void testListAndElement() throws Exception {
+ List<Object> list = CompoundList.of(Arrays.asList(first, second, third), forth);
+ assertThat(list.size(), is(4));
+ assertThat(list.get(0), is(first));
+ assertThat(list.get(1), is(second));
+ assertThat(list.get(2), is(third));
+ assertThat(list.get(3), is(forth));
+ }
+
+ @Test
+ public void testListAndList() throws Exception {
+ List<Object> list = CompoundList.of(Arrays.asList(first, second), Arrays.asList(third, forth));
+ assertThat(list.size(), is(4));
+ assertThat(list.get(0), is(first));
+ assertThat(list.get(1), is(second));
+ assertThat(list.get(2), is(third));
+ assertThat(list.get(3), is(forth));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/utility/JavaConstantMethodHandleDispatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/JavaConstantMethodHandleDispatcherTest.java
new file mode 100644
index 0000000..15f4fbc
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/JavaConstantMethodHandleDispatcherTest.java
@@ -0,0 +1,112 @@
+package net.bytebuddy.utility;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Iterator;
+
+import static org.mockito.Mockito.mock;
+
+public class JavaConstantMethodHandleDispatcherTest {
+
+ @Test(expected = IllegalStateException.class)
+ public void testLegacyVmInitialization() throws Exception {
+ JavaConstant.MethodHandle.Dispatcher.ForLegacyVm.INSTANCE.initialize();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testLegacyVmPunlicLookup() throws Exception {
+ JavaConstant.MethodHandle.Dispatcher.ForLegacyVm.INSTANCE.publicLookup();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testLegacyVmLookupType() throws Exception {
+ JavaConstant.MethodHandle.Dispatcher.ForLegacyVm.INSTANCE.lookupType(mock(Object.class));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ final Iterator<Method> methods1 = Arrays.asList(Foo.class.getDeclaredMethods()).iterator();
+ ObjectPropertyAssertion.of(JavaConstant.MethodHandle.Dispatcher.ForJava8CapableVm.class).create(new ObjectPropertyAssertion.Creator<Method>() {
+ @Override
+ public Method create() {
+ return methods1.next();
+ }
+ }).apply();
+ final Iterator<Method> methods2 = Arrays.asList(Foo.class.getDeclaredMethods()).iterator();
+ final Iterator<Constructor<?>> constructors2 = Arrays.<Constructor<?>>asList(Foo.class.getDeclaredConstructors()).iterator();
+ ObjectPropertyAssertion.of(JavaConstant.MethodHandle.Dispatcher.ForJava7CapableVm.class).create(new ObjectPropertyAssertion.Creator<Method>() {
+ @Override
+ public Method create() {
+ return methods2.next();
+ }
+ }).create(new ObjectPropertyAssertion.Creator<Constructor<?>>() {
+ @Override
+ public Constructor<?> create() {
+ return constructors2.next();
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(JavaConstant.MethodHandle.Dispatcher.ForLegacyVm.class).apply();
+ }
+
+ @SuppressWarnings("unused")
+ private abstract class Foo {
+
+ private Foo(Void v) {
+ /* empty */
+ }
+
+ private Foo(String s) {
+ /* empty */
+ }
+
+ abstract void a1();
+
+ abstract void a2();
+
+ abstract void a3();
+
+ abstract void a4();
+
+ abstract void a5();
+
+ abstract void a6();
+
+ abstract void a7();
+
+ abstract void a8();
+
+ abstract void a9();
+
+ abstract void a10();
+
+ abstract void a11();
+
+ abstract void a12();
+
+ abstract void a13();
+
+ abstract void a14();
+
+ abstract void a15();
+
+ abstract void a16();
+
+ abstract void a17();
+
+ abstract void a18();
+
+ abstract void a19();
+
+ abstract void a20();
+
+ abstract void a21();
+
+ abstract void a22();
+
+ abstract void a23();
+ }
+}
\ No newline at end of file
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/utility/JavaConstantMethodHandleTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/JavaConstantMethodHandleTest.java
new file mode 100644
index 0000000..0a96a78
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/JavaConstantMethodHandleTest.java
@@ -0,0 +1,246 @@
+package net.bytebuddy.utility;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class JavaConstantMethodHandleTest {
+
+ private static final String BAR = "bar", QUX = "qux";
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMethodHandleOfMethod() throws Exception {
+ JavaConstant.MethodHandle methodHandle = JavaConstant.MethodHandle.of(Foo.class.getDeclaredMethod(BAR, Void.class));
+ assertThat(methodHandle.getHandleType(), is(JavaConstant.MethodHandle.HandleType.INVOKE_VIRTUAL));
+ assertThat(methodHandle.getName(), is(BAR));
+ assertThat(methodHandle.getOwnerType(), is((TypeDescription) new TypeDescription.ForLoadedType(Foo.class)));
+ assertThat(methodHandle.getReturnType(), is(TypeDescription.VOID));
+ assertThat(methodHandle.getParameterTypes(), is((List<TypeDescription>) new TypeList.ForLoadedTypes(Void.class)));
+ assertThat(methodHandle.getDescriptor(), is(new MethodDescription.ForLoadedMethod(Foo.class.getDeclaredMethod(BAR, Void.class)).getDescriptor()));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMethodHandleOfMethodSpecialInvocation() throws Exception {
+ JavaConstant.MethodHandle methodHandle = JavaConstant.MethodHandle.ofSpecial(Foo.class.getDeclaredMethod(BAR, Void.class), Foo.class);
+ assertThat(methodHandle.getHandleType(), is(JavaConstant.MethodHandle.HandleType.INVOKE_SPECIAL));
+ assertThat(methodHandle.getName(), is(BAR));
+ assertThat(methodHandle.getOwnerType(), is((TypeDescription) new TypeDescription.ForLoadedType(Foo.class)));
+ assertThat(methodHandle.getReturnType(), is(TypeDescription.VOID));
+ assertThat(methodHandle.getParameterTypes(), is((List<TypeDescription>) new TypeList.ForLoadedTypes(Void.class)));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMethodHandleOfStaticMethod() throws Exception {
+ JavaConstant.MethodHandle methodHandle = JavaConstant.MethodHandle.of(Foo.class.getDeclaredMethod(QUX, Void.class));
+ assertThat(methodHandle.getHandleType(), is(JavaConstant.MethodHandle.HandleType.INVOKE_STATIC));
+ assertThat(methodHandle.getName(), is(QUX));
+ assertThat(methodHandle.getOwnerType(), is((TypeDescription) new TypeDescription.ForLoadedType(Foo.class)));
+ assertThat(methodHandle.getReturnType(), is(TypeDescription.VOID));
+ assertThat(methodHandle.getParameterTypes(), is((List<TypeDescription>) new TypeList.ForLoadedTypes(Void.class)));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMethodHandleOfConstructor() throws Exception {
+ JavaConstant.MethodHandle methodHandle = JavaConstant.MethodHandle.of(Foo.class.getDeclaredConstructor(Void.class));
+ assertThat(methodHandle.getHandleType(), is(JavaConstant.MethodHandle.HandleType.INVOKE_SPECIAL_CONSTRUCTOR));
+ assertThat(methodHandle.getName(), is(MethodDescription.CONSTRUCTOR_INTERNAL_NAME));
+ assertThat(methodHandle.getOwnerType(), is((TypeDescription) new TypeDescription.ForLoadedType(Foo.class)));
+ assertThat(methodHandle.getReturnType(), is(TypeDescription.VOID));
+ assertThat(methodHandle.getParameterTypes(), is((List<TypeDescription>) new TypeList.ForLoadedTypes(Void.class)));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMethodHandleOfConstructorSpecialInvocation() throws Exception {
+ JavaConstant.MethodHandle methodHandle = JavaConstant.MethodHandle
+ .of(new MethodDescription.ForLoadedConstructor(Foo.class.getDeclaredConstructor(Void.class)));
+ assertThat(methodHandle.getHandleType(), is(JavaConstant.MethodHandle.HandleType.INVOKE_SPECIAL_CONSTRUCTOR));
+ assertThat(methodHandle.getName(), is(MethodDescription.CONSTRUCTOR_INTERNAL_NAME));
+ assertThat(methodHandle.getOwnerType(), is((TypeDescription) new TypeDescription.ForLoadedType(Foo.class)));
+ assertThat(methodHandle.getReturnType(), is(TypeDescription.VOID));
+ assertThat(methodHandle.getParameterTypes(), is((List<TypeDescription>) new TypeList.ForLoadedTypes(Void.class)));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMethodHandleOfGetter() throws Exception {
+ JavaConstant.MethodHandle methodHandle = JavaConstant.MethodHandle.ofGetter(Foo.class.getDeclaredField(BAR));
+ assertThat(methodHandle.getHandleType(), is(JavaConstant.MethodHandle.HandleType.GET_FIELD));
+ assertThat(methodHandle.getName(), is(BAR));
+ assertThat(methodHandle.getOwnerType(), is((TypeDescription) new TypeDescription.ForLoadedType(Foo.class)));
+ assertThat(methodHandle.getReturnType(), is((TypeDefinition) new TypeDescription.ForLoadedType(Void.class)));
+ assertThat(methodHandle.getParameterTypes(), is(Collections.<TypeDescription>emptyList()));
+ }
+
+ @Test
+ public void testMethodHandleOfStaticGetter() throws Exception {
+ JavaConstant.MethodHandle methodHandle = JavaConstant.MethodHandle.ofGetter(Foo.class.getDeclaredField(QUX));
+ assertThat(methodHandle.getHandleType(), is(JavaConstant.MethodHandle.HandleType.GET_STATIC_FIELD));
+ assertThat(methodHandle.getName(), is(QUX));
+ assertThat(methodHandle.getOwnerType(), is((TypeDescription) new TypeDescription.ForLoadedType(Foo.class)));
+ assertThat(methodHandle.getReturnType(), is((TypeDefinition) new TypeDescription.ForLoadedType(Void.class)));
+ assertThat(methodHandle.getParameterTypes(), is(Collections.<TypeDescription>emptyList()));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMethodHandleOfSetter() throws Exception {
+ JavaConstant.MethodHandle methodHandle = JavaConstant.MethodHandle.ofSetter(Foo.class.getDeclaredField(BAR));
+ assertThat(methodHandle.getHandleType(), is(JavaConstant.MethodHandle.HandleType.PUT_FIELD));
+ assertThat(methodHandle.getName(), is(BAR));
+ assertThat(methodHandle.getOwnerType(), is((TypeDescription) new TypeDescription.ForLoadedType(Foo.class)));
+ assertThat(methodHandle.getReturnType(), is(TypeDescription.VOID));
+ assertThat(methodHandle.getParameterTypes(), is((List<TypeDescription>) new TypeList.ForLoadedTypes(Void.class)));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMethodHandleOfStaticSetter() throws Exception {
+ JavaConstant.MethodHandle methodHandle = JavaConstant.MethodHandle.ofSetter(Foo.class.getDeclaredField(QUX));
+ assertThat(methodHandle.getHandleType(), is(JavaConstant.MethodHandle.HandleType.PUT_STATIC_FIELD));
+ assertThat(methodHandle.getName(), is(QUX));
+ assertThat(methodHandle.getOwnerType(), is((TypeDescription) new TypeDescription.ForLoadedType(Foo.class)));
+ assertThat(methodHandle.getReturnType(), is(TypeDescription.VOID));
+ assertThat(methodHandle.getParameterTypes(), is((List<TypeDescription>) new TypeList.ForLoadedTypes(Void.class)));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ @JavaVersionRule.Enforce(value = 7, hotSpot = 7)
+ public void testMethodHandleOfLoadedMethodHandle() throws Exception {
+ Method publicLookup = Class.forName("java.lang.invoke.MethodHandles").getDeclaredMethod("publicLookup");
+ Object lookup = publicLookup.invoke(null);
+ Method unreflected = Class.forName("java.lang.invoke.MethodHandles$Lookup").getDeclaredMethod("unreflect", Method.class);
+ Object methodHandleLoaded = unreflected.invoke(lookup, Foo.class.getDeclaredMethod(BAR, Void.class));
+ JavaConstant.MethodHandle methodHandle = JavaConstant.MethodHandle.ofLoaded(methodHandleLoaded);
+ assertThat(methodHandle.getHandleType(), is(JavaConstant.MethodHandle.HandleType.INVOKE_VIRTUAL));
+ assertThat(methodHandle.getName(), is(BAR));
+ assertThat(methodHandle.getOwnerType(), is((TypeDescription) new TypeDescription.ForLoadedType(Foo.class)));
+ assertThat(methodHandle.getReturnType(), is(TypeDescription.VOID));
+ assertThat(methodHandle.getParameterTypes(), is((List<TypeDescription>) new TypeList.ForLoadedTypes(Void.class)));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ @JavaVersionRule.Enforce(value = 7, hotSpot = 7)
+ public void testMethodHandleLoadedIllegal() throws Exception {
+ JavaConstant.MethodHandle.ofLoaded(new Object());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ @JavaVersionRule.Enforce(value = 7, hotSpot = 7)
+ public void testMethodHandleLoadedLookupIllegal() throws Exception {
+ Method publicLookup = Class.forName("java.lang.invoke.MethodHandles").getDeclaredMethod("publicLookup");
+ Object lookup = publicLookup.invoke(null);
+ Method unreflect = Class.forName("java.lang.invoke.MethodHandles$Lookup").getDeclaredMethod("unreflect", Method.class);
+ Object methodHandleLoaded = unreflect.invoke(lookup, Foo.class.getDeclaredMethod(BAR, Void.class));
+ JavaConstant.MethodHandle.ofLoaded(methodHandleLoaded, new Object());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testIllegalParameterThrowsException() throws Exception {
+ JavaConstant.MethodHandle.HandleType.of(-1);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testStaticMethodNotSpecial() throws Exception {
+ MethodDescription.InDefinedShape methodDescription = mock(MethodDescription.InDefinedShape.class);
+ TypeDescription typeDescription = mock(TypeDescription.class);
+ when(methodDescription.isStatic()).thenReturn(true);
+ when(methodDescription.isSpecializableFor(typeDescription)).thenReturn(true);
+ JavaConstant.MethodHandle.ofSpecial(methodDescription, typeDescription);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testAbstractMethodNotSpecial() throws Exception {
+ MethodDescription.InDefinedShape methodDescription = mock(MethodDescription.InDefinedShape.class);
+ TypeDescription typeDescription = mock(TypeDescription.class);
+ when(methodDescription.isAbstract()).thenReturn(true);
+ when(methodDescription.isSpecializableFor(typeDescription)).thenReturn(true);
+ JavaConstant.MethodHandle.ofSpecial(methodDescription, typeDescription);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testMethodNotSpecializable() throws Exception {
+ MethodDescription.InDefinedShape methodDescription = mock(MethodDescription.InDefinedShape.class);
+ TypeDescription typeDescription = mock(TypeDescription.class);
+ when(methodDescription.isSpecializableFor(typeDescription)).thenReturn(false);
+ JavaConstant.MethodHandle.ofSpecial(methodDescription, typeDescription);
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(JavaConstant.MethodHandle.class).apply();
+ ObjectPropertyAssertion.of(JavaConstant.MethodHandle.Dispatcher.CreationAction.class).apply();
+ final Iterator<Method> methods = Arrays.asList(String.class.getDeclaredMethods()).iterator();
+ final Iterator<Constructor<?>> constructors= Arrays.asList(String.class.getDeclaredConstructors()).iterator();
+ ObjectPropertyAssertion.of(JavaConstant.MethodHandle.Dispatcher.ForJava7CapableVm.class).create(new ObjectPropertyAssertion.Creator<Method>() {
+ @Override
+ public Method create() {
+ return methods.next();
+ }
+ }).create(new ObjectPropertyAssertion.Creator<Constructor<?>>() {
+ @Override
+ public Constructor<?> create() {
+ return constructors.next();
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(JavaConstant.MethodHandle.Dispatcher.ForJava8CapableVm.class).create(new ObjectPropertyAssertion.Creator<Method>() {
+ @Override
+ public Method create() {
+ return methods.next();
+ }
+ }).create(new ObjectPropertyAssertion.Creator<Constructor<?>>() {
+ @Override
+ public Constructor<?> create() {
+ return constructors.next();
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(JavaConstant.MethodHandle.Dispatcher.ForLegacyVm.class).apply();
+ ObjectPropertyAssertion.of(JavaConstant.MethodHandle.HandleType.class).apply();
+ }
+
+ @SuppressWarnings("unused")
+ public static class Foo {
+
+ public static Void qux;
+
+ public Void bar;
+
+ public Foo(Void value) {
+ /* empty*/
+ }
+
+ public static void qux(Void value) {
+ /* empty */
+ }
+
+ public void bar(Void value) {
+ /* empty */
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/utility/JavaConstantMethodTypeDispatcherTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/JavaConstantMethodTypeDispatcherTest.java
new file mode 100644
index 0000000..b2d2109
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/JavaConstantMethodTypeDispatcherTest.java
@@ -0,0 +1,35 @@
+package net.bytebuddy.utility;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Iterator;
+
+import static org.mockito.Mockito.mock;
+
+public class JavaConstantMethodTypeDispatcherTest {
+
+ @Test(expected = IllegalStateException.class)
+ public void testLegacyVmReturnType() throws Exception {
+ JavaConstant.MethodType.Dispatcher.ForLegacyVm.INSTANCE.returnType(mock(Object.class));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testLegacyVmParameterArray() throws Exception {
+ JavaConstant.MethodType.Dispatcher.ForLegacyVm.INSTANCE.parameterArray(mock(Object.class));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ final Iterator<Method> methods = Arrays.asList(Object.class.getDeclaredMethods()).iterator();
+ ObjectPropertyAssertion.of(JavaConstant.MethodType.Dispatcher.ForJava7CapableVm.class).create(new ObjectPropertyAssertion.Creator<Method>() {
+ @Override
+ public Method create() {
+ return methods.next();
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(JavaConstant.MethodType.Dispatcher.ForLegacyVm.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/utility/JavaConstantMethodTypeTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/JavaConstantMethodTypeTest.java
new file mode 100644
index 0000000..4df9692
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/JavaConstantMethodTypeTest.java
@@ -0,0 +1,150 @@
+package net.bytebuddy.utility;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class JavaConstantMethodTypeTest {
+
+ private static final String BAR = "bar", QUX = "qux";
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMethodTypeOfLoadedType() throws Exception {
+ JavaConstant.MethodType methodType = JavaConstant.MethodType.of(void.class, Foo.class);
+ assertThat(methodType.getReturnType(), is(TypeDescription.VOID));
+ assertThat(methodType.getParameterTypes(), is((List<TypeDescription>) new TypeList.ForLoadedTypes(Foo.class)));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMethodTypeOfMethod() throws Exception {
+ JavaConstant.MethodType methodType = JavaConstant.MethodType.of(Foo.class.getDeclaredMethod(BAR, Void.class));
+ assertThat(methodType.getReturnType(), is(TypeDescription.VOID));
+ assertThat(methodType.getParameterTypes(), is((List<TypeDescription>) new TypeList.ForLoadedTypes(Void.class)));
+ assertThat(methodType.getDescriptor(), is(new MethodDescription.ForLoadedMethod(Foo.class.getDeclaredMethod(BAR, Void.class)).getDescriptor()));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMethodTypeOfStaticMethod() throws Exception {
+ JavaConstant.MethodType methodType = JavaConstant.MethodType.of(Foo.class.getDeclaredMethod(QUX, Void.class));
+ assertThat(methodType.getReturnType(), is(TypeDescription.VOID));
+ assertThat(methodType.getParameterTypes(), is((List<TypeDescription>) new TypeList.ForLoadedTypes(Void.class)));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMethodTypeOfConstructor() throws Exception {
+ JavaConstant.MethodType methodType = JavaConstant.MethodType.of(Foo.class.getDeclaredConstructor(Void.class));
+ assertThat(methodType.getReturnType(), is(TypeDescription.VOID));
+ assertThat(methodType.getParameterTypes(), is((List<TypeDescription>) new TypeList.ForLoadedTypes(Void.class)));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMethodTypeOfGetter() throws Exception {
+ JavaConstant.MethodType methodType = JavaConstant.MethodType.ofGetter(Foo.class.getDeclaredField(BAR));
+ assertThat(methodType.getReturnType(), is((TypeDescription) new TypeDescription.ForLoadedType(Void.class)));
+ assertThat(methodType.getParameterTypes(), is(Collections.<TypeDescription>emptyList()));
+ }
+
+ @Test
+ public void testMethodTypeOfStaticGetter() throws Exception {
+ JavaConstant.MethodType methodType = JavaConstant.MethodType.ofGetter(Foo.class.getDeclaredField(QUX));
+ assertThat(methodType.getReturnType(), is((TypeDescription) new TypeDescription.ForLoadedType(Void.class)));
+ assertThat(methodType.getParameterTypes(), is(Collections.<TypeDescription>emptyList()));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMethodTypeOfSetter() throws Exception {
+ JavaConstant.MethodType methodType = JavaConstant.MethodType.ofSetter(Foo.class.getDeclaredField(BAR));
+ assertThat(methodType.getReturnType(), is(TypeDescription.VOID));
+ assertThat(methodType.getParameterTypes(), is((List<TypeDescription>) new TypeList.ForLoadedTypes(Void.class)));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testMethodTypeOfStaticSetter() throws Exception {
+ JavaConstant.MethodType methodType = JavaConstant.MethodType.ofSetter(Foo.class.getDeclaredField(QUX));
+ assertThat(methodType.getReturnType(), is(TypeDescription.VOID));
+ assertThat(methodType.getParameterTypes(), is((List<TypeDescription>) new TypeList.ForLoadedTypes(Void.class)));
+ }
+
+ @Test
+ public void testMethodTypeOfConstant() throws Exception {
+ JavaConstant.MethodType methodType = JavaConstant.MethodType.ofConstant(new Foo(null));
+ assertThat(methodType.getReturnType(), is((TypeDescription) new TypeDescription.ForLoadedType(Foo.class)));
+ assertThat(methodType.getParameterTypes(), is(Collections.<TypeDescription>emptyList()));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ @JavaVersionRule.Enforce(7)
+ public void testMethodTypeOfLoadedMethodType() throws Exception {
+ Object loadedMethodType = JavaType.METHOD_TYPE.load().getDeclaredMethod("methodType", Class.class, Class[].class)
+ .invoke(null, void.class, new Class<?>[]{Object.class});
+ JavaConstant.MethodType methodType = JavaConstant.MethodType.ofLoaded(loadedMethodType);
+ assertThat(methodType.getReturnType(), is(TypeDescription.VOID));
+ assertThat(methodType.getParameterTypes(), is((List<TypeDescription>) new TypeList.ForLoadedTypes(Object.class)));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(JavaConstant.MethodType.class).apply();
+ ObjectPropertyAssertion.of(JavaConstant.MethodType.Dispatcher.CreationAction.class).apply();
+ final Iterator<Method> methods = Arrays.asList(String.class.getDeclaredMethods()).iterator();
+ final Iterator<Constructor<?>> constructors = Arrays.asList(String.class.getDeclaredConstructors()).iterator();
+ ObjectPropertyAssertion.of(JavaConstant.MethodType.Dispatcher.ForJava7CapableVm.class).create(new ObjectPropertyAssertion.Creator<Method>() {
+ @Override
+ public Method create() {
+ return methods.next();
+ }
+ }).create(new ObjectPropertyAssertion.Creator<Constructor<?>>() {
+ @Override
+ public Constructor<?> create() {
+ return constructors.next();
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(JavaConstant.MethodType.Dispatcher.ForLegacyVm.class).apply();
+ }
+
+ @SuppressWarnings("unused")
+ public static class Foo {
+
+ static Void qux;
+
+ Void bar;
+
+ Foo(Void value) {
+ /* empty*/
+ }
+
+ static void qux(Void value) {
+ /* empty */
+ }
+
+ void bar(Void value) {
+ /* empty */
+ }
+ }
+}
\ No newline at end of file
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/utility/JavaModuleTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/JavaModuleTest.java
new file mode 100644
index 0000000..91efcf1
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/JavaModuleTest.java
@@ -0,0 +1,87 @@
+package net.bytebuddy.utility;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.lang.instrument.Instrumentation;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Iterator;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class JavaModuleTest {
+
+ private static final String FOO = "foo";
+
+ @Test
+ public void testSupportsDisabledThrowException() throws Exception {
+ assertThat(JavaModule.Dispatcher.Disabled.INSTANCE.isAlive(), is(false));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testExtractModule() throws Exception {
+ JavaModule.of(mock(Object.class));
+ }
+
+ @Test
+ public void testUnwrap() throws Exception {
+ Object object = new Object();
+ JavaModule module = new JavaModule(object);
+ assertThat(module.unwrap(), sameInstance(object));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIsNamedDisabledThrowException() throws Exception {
+ JavaModule.Dispatcher.Disabled.INSTANCE.isNamed(mock(Object.class));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testGetNameDisabledThrowException() throws Exception {
+ JavaModule.Dispatcher.Disabled.INSTANCE.getName(mock(Object.class));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testGetClassLoaderDisabledThrowException() throws Exception {
+ JavaModule.Dispatcher.Disabled.INSTANCE.getClassLoader(mock(Object.class));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testCanReadThrowsException() throws Exception {
+ JavaModule.Dispatcher.Disabled.INSTANCE.canRead(mock(Object.class), mock(Object.class));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testGetResourceAsStreamThrowsException() throws Exception {
+ JavaModule.Dispatcher.Disabled.INSTANCE.getResourceAsStream(mock(Object.class), FOO);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAddReadsThrowsException() throws Exception {
+ JavaModule.Dispatcher.Disabled.INSTANCE.addReads(mock(Instrumentation.class), mock(Object.class), mock(Object.class));
+ }
+
+ @Test
+ public void testDisabledModuleIsNull() throws Exception {
+ assertThat(JavaModule.Dispatcher.Disabled.INSTANCE.moduleOf(Object.class), nullValue(JavaModule.class));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(JavaModule.class).apply();
+ Object object = new Object();
+ assertThat(new JavaModule(object).hashCode(), is(object.hashCode()));
+ assertThat(new JavaModule(object).toString(), is(object.toString()));
+ final Iterator<Method> iterator = Arrays.asList(String.class.getDeclaredMethods()).iterator();
+ ObjectPropertyAssertion.of(JavaModule.Dispatcher.Enabled.class).create(new ObjectPropertyAssertion.Creator<Method>() {
+ @Override
+ public Method create() {
+ return iterator.next();
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(JavaModule.Dispatcher.Disabled.class).apply();
+ ObjectPropertyAssertion.of(JavaModule.Dispatcher.CreationAction.class).apply();
+ }
+}
\ No newline at end of file
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/utility/JavaTypeTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/JavaTypeTest.java
new file mode 100644
index 0000000..4c2f42c
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/JavaTypeTest.java
@@ -0,0 +1,107 @@
+package net.bytebuddy.utility;
+
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.test.utility.JavaVersionRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.MethodRule;
+import org.objectweb.asm.Opcodes;
+
+import java.io.Serializable;
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.GenericDeclaration;
+import java.lang.reflect.Member;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class JavaTypeTest {
+
+ @Rule
+ public MethodRule javaVersionRule = new JavaVersionRule();
+
+ @Test
+ public void testMethodHandle() throws Exception {
+ assertThat(JavaType.METHOD_HANDLE.getTypeStub().getName(), is("java.lang.invoke.MethodHandle"));
+ assertThat(JavaType.METHOD_HANDLE.getTypeStub().getModifiers(), is(Opcodes.ACC_PUBLIC | Opcodes.ACC_ABSTRACT));
+ assertThat(JavaType.METHOD_HANDLE.getTypeStub().getSuperClass(), is(TypeDescription.Generic.OBJECT));
+ assertThat(JavaType.METHOD_HANDLE.getTypeStub().getInterfaces().size(), is(0));
+ }
+
+ @Test
+ public void testMethodType() throws Exception {
+ assertThat(JavaType.METHOD_TYPE.getTypeStub().getName(), is("java.lang.invoke.MethodType"));
+ assertThat(JavaType.METHOD_TYPE.getTypeStub().getModifiers(), is(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL));
+ assertThat(JavaType.METHOD_TYPE.getTypeStub().getSuperClass(), is(TypeDescription.Generic.OBJECT));
+ assertThat(JavaType.METHOD_TYPE.getTypeStub().getInterfaces().size(), is(1));
+ assertThat(JavaType.METHOD_TYPE.getTypeStub().getInterfaces().contains(new TypeDescription.Generic.OfNonGenericType.ForLoadedType(Serializable.class)), is(true));
+ }
+
+ @Test
+ public void testMethodTypesLookup() throws Exception {
+ assertThat(JavaType.METHOD_HANDLES_LOOKUP.getTypeStub().getName(), is("java.lang.invoke.MethodHandles$Lookup"));
+ assertThat(JavaType.METHOD_HANDLES_LOOKUP.getTypeStub().getModifiers(), is(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL));
+ assertThat(JavaType.METHOD_HANDLES_LOOKUP.getTypeStub().getSuperClass(), is(TypeDescription.Generic.OBJECT));
+ assertThat(JavaType.METHOD_HANDLES_LOOKUP.getTypeStub().getInterfaces().size(), is(0));
+ }
+
+ @Test
+ public void testCallSite() throws Exception {
+ assertThat(JavaType.CALL_SITE.getTypeStub().getName(), is("java.lang.invoke.CallSite"));
+ assertThat(JavaType.CALL_SITE.getTypeStub().getModifiers(), is(Opcodes.ACC_PUBLIC | Opcodes.ACC_ABSTRACT));
+ assertThat(JavaType.CALL_SITE.getTypeStub().getSuperClass(), is(TypeDescription.Generic.OBJECT));
+ assertThat(JavaType.CALL_SITE.getTypeStub().getInterfaces().size(), is(0));
+ }
+
+ @Test
+ public void testParameter() throws Exception {
+ assertThat(JavaType.PARAMETER.getTypeStub().getName(), is("java.lang.reflect.Parameter"));
+ assertThat(JavaType.PARAMETER.getTypeStub().getModifiers(), is(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL));
+ assertThat(JavaType.PARAMETER.getTypeStub().getSuperClass(), is(TypeDescription.Generic.OBJECT));
+ assertThat(JavaType.PARAMETER.getTypeStub().getInterfaces().size(), is(1));
+ assertThat(JavaType.PARAMETER.getTypeStub().getInterfaces().contains(new TypeDescription.Generic.OfNonGenericType.ForLoadedType(AnnotatedElement.class)), is(true));
+ }
+
+ @Test
+ public void testExecutable() throws Exception {
+ assertThat(JavaType.EXECUTABLE.getTypeStub().getName(), is("java.lang.reflect.Executable"));
+ assertThat(JavaType.EXECUTABLE.getTypeStub().getModifiers(), is(Opcodes.ACC_PUBLIC | Opcodes.ACC_ABSTRACT));
+ assertThat(JavaType.EXECUTABLE.getTypeStub().getSuperClass(), is((TypeDefinition) new TypeDescription.ForLoadedType(AccessibleObject.class)));
+ assertThat(JavaType.EXECUTABLE.getTypeStub().getInterfaces().size(), is(2));
+ assertThat(JavaType.EXECUTABLE.getTypeStub().getInterfaces().contains(new TypeDescription.Generic.OfNonGenericType.ForLoadedType(Member.class)), is(true));
+ assertThat(JavaType.EXECUTABLE.getTypeStub().getInterfaces().contains(new TypeDescription.Generic.OfNonGenericType.ForLoadedType(GenericDeclaration.class)), is(true));
+ }
+
+ @Test
+ public void testModule() throws Exception {
+ assertThat(JavaType.MODULE.getTypeStub().getName(), is("java.lang.Module"));
+ assertThat(JavaType.MODULE.getTypeStub().getModifiers(), is(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL));
+ assertThat(JavaType.MODULE.getTypeStub().getSuperClass(), is(TypeDescription.Generic.OBJECT));
+ assertThat(JavaType.MODULE.getTypeStub().getInterfaces().size(), is(0));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(7)
+ public void testJava7Types() throws Exception {
+ assertThat(JavaType.METHOD_HANDLE.load(), notNullValue(Class.class));
+ assertThat(JavaType.METHOD_TYPE.load(), notNullValue(Class.class));
+ assertThat(JavaType.METHOD_HANDLES_LOOKUP.load(), notNullValue(Class.class));
+ assertThat(JavaType.CALL_SITE.load(), notNullValue(Class.class));
+ }
+
+ @Test
+ @JavaVersionRule.Enforce(8)
+ public void testJava8Types() throws Exception {
+ assertThat(JavaType.PARAMETER.load(), notNullValue(Class.class));
+ assertThat(JavaType.EXECUTABLE.load(), notNullValue(Class.class));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(JavaType.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/utility/RandomStringTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/RandomStringTest.java
new file mode 100644
index 0000000..b018740
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/RandomStringTest.java
@@ -0,0 +1,47 @@
+package net.bytebuddy.utility;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class RandomStringTest {
+
+ @Test
+ public void testRandomStringLength() throws Exception {
+ assertThat(new RandomString().nextString().length(), is(RandomString.DEFAULT_LENGTH));
+ assertThat(RandomString.make().length(), is(RandomString.DEFAULT_LENGTH));
+ assertThat(new RandomString(RandomString.DEFAULT_LENGTH * 2).nextString().length(), is(RandomString.DEFAULT_LENGTH * 2));
+ assertThat(RandomString.make(RandomString.DEFAULT_LENGTH * 2).length(), is(RandomString.DEFAULT_LENGTH * 2));
+ }
+
+ @Test
+ public void testRandom() throws Exception {
+ RandomString randomString = new RandomString();
+ assertThat(randomString.nextString(), not(randomString.nextString()));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNegativeLengthThrowsException() throws Exception {
+ new RandomString(-1);
+ }
+
+ @Test
+ public void testHashValueOnlyOneBits() throws Exception {
+ assertThat(RandomString.hashOf(-1).length(), not(0));
+ }
+
+ @Test
+ public void testHashValueOnlyZeroBits() throws Exception {
+ assertThat(RandomString.hashOf(0).length(), not(0));
+ }
+
+ @Test
+ public void testHashValueInequality() throws Exception {
+ assertThat(RandomString.hashOf(0), is(RandomString.hashOf(0)));
+ assertThat(RandomString.hashOf(0), not(RandomString.hashOf(-1)));
+ assertThat(RandomString.hashOf(0), not(RandomString.hashOf(1)));
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/utility/StreamDrainerTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/StreamDrainerTest.java
new file mode 100644
index 0000000..140baf8
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/StreamDrainerTest.java
@@ -0,0 +1,23 @@
+package net.bytebuddy.utility;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class StreamDrainerTest {
+
+ @Test
+ public void testDrainage() throws Exception {
+ byte[] input = new byte[]{1, 2, 3, 4};
+ assertThat(new StreamDrainer(1).drain(new ByteArrayInputStream(input)), is(input));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(StreamDrainer.class).apply();
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/utility/privilege/GetSystemPropertyActionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/privilege/GetSystemPropertyActionTest.java
new file mode 100644
index 0000000..f6f27be
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/privilege/GetSystemPropertyActionTest.java
@@ -0,0 +1,27 @@
+package net.bytebuddy.utility.privilege;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class GetSystemPropertyActionTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ @Test
+ public void testRun() throws Exception {
+ System.setProperty(FOO, BAR);
+ try {
+ assertThat(new GetSystemPropertyAction(FOO).run(), is(BAR));
+ } finally {
+ System.clearProperty(FOO);
+ }
+ }
+
+ @Test
+ public void testObjectProperty() throws Exception {
+ ObjectPropertyAssertion.of(GetSystemPropertyAction.class).apply();
+ }
+}
\ No newline at end of file
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/utility/privilege/SetAccessibleActionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/privilege/SetAccessibleActionTest.java
new file mode 100644
index 0000000..98b75db
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/privilege/SetAccessibleActionTest.java
@@ -0,0 +1,57 @@
+package net.bytebuddy.utility.privilege;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.lang.reflect.AccessibleObject;
+import java.util.Arrays;
+import java.util.Iterator;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class SetAccessibleActionTest {
+
+ private static final String BAR = "bar";
+
+ @Test
+ public void testAccessAction() throws Exception {
+ AccessibleObjectSpy accessibleObjectSpy = new AccessibleObjectSpy(Foo.class.getDeclaredField(BAR));
+ assertThat(new SetAccessibleAction<AccessibleObjectSpy>(accessibleObjectSpy).run(), is(accessibleObjectSpy));
+ assertThat(accessibleObjectSpy.accessible, is(true));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ final Iterator<AccessibleObject> iterator = Arrays.<AccessibleObject>asList(Foo.class.getDeclaredFields()).iterator();
+ ObjectPropertyAssertion.of(SetAccessibleAction.class).create(new ObjectPropertyAssertion.Creator<AccessibleObject>() {
+ @Override
+ public AccessibleObject create() {
+ return iterator.next();
+ }
+ }).apply();
+ }
+
+ @SuppressWarnings("unused")
+ private static class Foo {
+
+ Object bar, qux;
+ }
+
+ private static class AccessibleObjectSpy extends AccessibleObject {
+
+ private final AccessibleObject accessibleObject;
+
+ public boolean accessible;
+
+ public AccessibleObjectSpy(AccessibleObject accessibleObject) {
+ this.accessibleObject = accessibleObject;
+ }
+
+ @Override
+ public void setAccessible(boolean flag) {
+ accessible = flag;
+ accessibleObject.setAccessible(flag);
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/utility/visitor/ExceptionTableSensitiveMethodVisitorTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/visitor/ExceptionTableSensitiveMethodVisitorTest.java
new file mode 100644
index 0000000..68f4756
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/visitor/ExceptionTableSensitiveMethodVisitorTest.java
@@ -0,0 +1,100 @@
+package net.bytebuddy.utility.visitor;
+
+import net.bytebuddy.test.utility.MockitoRule;
+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.Handle;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.mockito.Mockito.*;
+
+ at RunWith(Parameterized.class)
+public class ExceptionTableSensitiveMethodVisitorTest {
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {Opcodes.ASM5, "visitLabel", new Class<?>[]{Label.class}, new Object[]{new Label()}},
+ {Opcodes.ASM5, "visitIntInsn", new Class<?>[]{int.class, int.class}, new Object[]{0, 0}},
+ {Opcodes.ASM5, "visitVarInsn", new Class<?>[]{int.class, int.class}, new Object[]{0, 0}},
+ {Opcodes.ASM5, "visitTypeInsn", new Class<?>[]{int.class, String.class}, new Object[]{0, ""}},
+ {Opcodes.ASM5, "visitFieldInsn", new Class<?>[]{int.class, String.class, String.class, String.class}, new Object[]{0, "", "", ""}},
+ {Opcodes.ASM4, "visitMethodInsn", new Class<?>[]{int.class, String.class, String.class, String.class}, new Object[]{0, "", "", ""}},
+ {Opcodes.ASM5, "visitMethodInsn", new Class<?>[]{int.class, String.class, String.class, String.class, boolean.class}, new Object[]{0, "", "", "", false}},
+ {Opcodes.ASM5, "visitInvokeDynamicInsn", new Class<?>[]{String.class, String.class, Handle.class, Object[].class}, new Object[]{"", "", new Handle(0, "", "", "", false), new Object[0]}},
+ {Opcodes.ASM5, "visitJumpInsn", new Class<?>[]{int.class, Label.class}, new Object[]{0, new Label()}},
+ {Opcodes.ASM5, "visitLdcInsn", new Class<?>[]{Object.class}, new Object[]{new Object()}},
+ {Opcodes.ASM5, "visitIincInsn", new Class<?>[]{int.class, int.class}, new Object[]{0, 0}},
+ {Opcodes.ASM5, "visitTableSwitchInsn", new Class<?>[]{int.class, int.class, Label.class, Label[].class}, new Object[]{0, 0, new Label(), new Label[0]}},
+ {Opcodes.ASM5, "visitLookupSwitchInsn", new Class<?>[]{Label.class, int[].class, Label[].class}, new Object[]{new Label(), new int[0], new Label[0]}},
+ {Opcodes.ASM5, "visitMultiANewArrayInsn", new Class<?>[]{String.class, int.class}, new Object[]{"", 0}},
+ {Opcodes.ASM5, "visitInsn", new Class<?>[]{int.class}, new Object[]{0}},
+ });
+ }
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ private final int api;
+
+ private final String name;
+
+ private final Class<?>[] type;
+
+ private final Object[] argument;
+
+ public ExceptionTableSensitiveMethodVisitorTest(int api, String name, Class<?>[] type, Object[] argument) {
+ this.api = api;
+ this.name = name;
+ this.type = type;
+ this.argument = argument;
+ }
+
+ @Test
+ public void testCallback() throws Exception {
+ Method method = MethodVisitor.class.getDeclaredMethod(name, type);
+ PseudoVisitor pseudoVisitor = new PseudoVisitor(api, methodVisitor);
+ method.invoke(pseudoVisitor, argument);
+ method.invoke(pseudoVisitor, argument);
+ method.invoke(verify(methodVisitor, times(2)), argument);
+ verifyNoMoreInteractions(methodVisitor);
+ pseudoVisitor.check();
+ }
+
+ private static class PseudoVisitor extends ExceptionTableSensitiveMethodVisitor {
+
+ private boolean called;
+
+ public PseudoVisitor(int api, MethodVisitor methodVisitor) {
+ super(api, methodVisitor);
+ }
+
+ @Override
+ protected void onAfterExceptionTable() {
+ if (called) {
+ throw new AssertionError();
+ }
+ called = true;
+ verifyZeroInteractions(mv);
+ }
+
+ protected void check() {
+ if (!called) {
+ throw new AssertionError();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/utility/visitor/LineNumberPrependingMethodVisitorTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/visitor/LineNumberPrependingMethodVisitorTest.java
new file mode 100644
index 0000000..200f102
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/visitor/LineNumberPrependingMethodVisitorTest.java
@@ -0,0 +1,25 @@
+package net.bytebuddy.utility.visitor;
+
+import org.junit.Test;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+
+import static org.mockito.AdditionalMatchers.not;
+import static org.mockito.Mockito.*;
+
+public class LineNumberPrependingMethodVisitorTest {
+
+ private static final int LINE = 42;
+
+ @Test
+ public void testPrepending() throws Exception {
+ MethodVisitor delegate = mock(MethodVisitor.class);
+ LineNumberPrependingMethodVisitor methodVisitor = new LineNumberPrependingMethodVisitor(delegate);
+ methodVisitor.onAfterExceptionTable();
+ Label label = new Label();
+ methodVisitor.visitLineNumber(LINE, label);
+ verify(delegate, times(2)).visitLabel(any(Label.class));
+ verify(delegate).visitLineNumber(eq(LINE), not(eq(label)));
+ verifyNoMoreInteractions(delegate);
+ }
+}
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/utility/visitor/StackAwareMethodVisitorTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/visitor/StackAwareMethodVisitorTest.java
new file mode 100644
index 0000000..6afe24f
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/visitor/StackAwareMethodVisitorTest.java
@@ -0,0 +1,153 @@
+package net.bytebuddy.utility.visitor;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.implementation.bytecode.StackSize;
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class StackAwareMethodVisitorTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private MethodDescription methodDescription;
+
+ @Mock
+ private MethodVisitor methodVisitor;
+
+ @Test
+ public void testDrainSingleSize() throws Exception {
+ StackAwareMethodVisitor methodVisitor = new StackAwareMethodVisitor(this.methodVisitor, methodDescription);
+ methodVisitor.visitLdcInsn(1);
+ methodVisitor.drainStack();
+ verify(this.methodVisitor).visitLdcInsn(1);
+ verify(this.methodVisitor).visitInsn(Opcodes.POP);
+ verifyNoMoreInteractions(this.methodVisitor);
+ }
+
+ @Test
+ public void testDrainDoubleSize() throws Exception {
+ StackAwareMethodVisitor methodVisitor = new StackAwareMethodVisitor(this.methodVisitor, methodDescription);
+ methodVisitor.visitLdcInsn(1L);
+ methodVisitor.drainStack();
+ verify(this.methodVisitor).visitLdcInsn(1L);
+ verify(this.methodVisitor).visitInsn(Opcodes.POP2);
+ verifyNoMoreInteractions(this.methodVisitor);
+ }
+
+ @Test
+ public void testDrainOrder() throws Exception {
+ StackAwareMethodVisitor methodVisitor = new StackAwareMethodVisitor(this.methodVisitor, methodDescription);
+ methodVisitor.visitLdcInsn(1);
+ methodVisitor.visitLdcInsn(1L);
+ methodVisitor.drainStack();
+ InOrder inOrder = inOrder(this.methodVisitor);
+ inOrder.verify(this.methodVisitor).visitLdcInsn(1);
+ inOrder.verify(this.methodVisitor).visitLdcInsn(1L);
+ inOrder.verify(this.methodVisitor).visitInsn(Opcodes.POP2);
+ inOrder.verify(this.methodVisitor).visitInsn(Opcodes.POP);
+ verifyNoMoreInteractions(this.methodVisitor);
+ }
+
+ @Test
+ public void testDrainRetainTopSingle() throws Exception {
+ when(methodDescription.getStackSize()).thenReturn(42);
+ StackAwareMethodVisitor methodVisitor = new StackAwareMethodVisitor(this.methodVisitor, methodDescription);
+ methodVisitor.visitLdcInsn(1L);
+ methodVisitor.visitLdcInsn(1);
+ assertThat(methodVisitor.drainStack(Opcodes.ISTORE, Opcodes.ILOAD, StackSize.SINGLE), is(43));
+ InOrder inOrder = inOrder(this.methodVisitor);
+ inOrder.verify(this.methodVisitor).visitLdcInsn(1L);
+ inOrder.verify(this.methodVisitor).visitLdcInsn(1);
+ inOrder.verify(this.methodVisitor).visitVarInsn(Opcodes.ISTORE, 42);
+ inOrder.verify(this.methodVisitor).visitInsn(Opcodes.POP2);
+ inOrder.verify(this.methodVisitor).visitVarInsn(Opcodes.ILOAD, 42);
+ verifyNoMoreInteractions(this.methodVisitor);
+ }
+
+ @Test
+ public void testDrainRetainTopDouble() throws Exception {
+ when(methodDescription.getStackSize()).thenReturn(42);
+ StackAwareMethodVisitor methodVisitor = new StackAwareMethodVisitor(this.methodVisitor, methodDescription);
+ methodVisitor.visitLdcInsn(1);
+ methodVisitor.visitLdcInsn(1L);
+ assertThat(methodVisitor.drainStack(Opcodes.LSTORE, Opcodes.LLOAD, StackSize.DOUBLE), is(44));
+ InOrder inOrder = inOrder(this.methodVisitor);
+ inOrder.verify(this.methodVisitor).visitLdcInsn(1);
+ inOrder.verify(this.methodVisitor).visitLdcInsn(1L);
+ inOrder.verify(this.methodVisitor).visitVarInsn(Opcodes.LSTORE, 42);
+ inOrder.verify(this.methodVisitor).visitInsn(Opcodes.POP);
+ inOrder.verify(this.methodVisitor).visitVarInsn(Opcodes.LLOAD, 42);
+ verifyNoMoreInteractions(this.methodVisitor);
+ }
+
+ @Test
+ public void testDrainFreeListOnly() throws Exception {
+ StackAwareMethodVisitor methodVisitor = new StackAwareMethodVisitor(this.methodVisitor, methodDescription);
+ methodVisitor.visitLdcInsn(1);
+ methodVisitor.visitVarInsn(Opcodes.ISTORE, 41);
+ methodVisitor.visitLdcInsn(1);
+ assertThat(methodVisitor.drainStack(Opcodes.ISTORE, Opcodes.ILOAD, StackSize.SINGLE), is(0));
+ InOrder inOrder = inOrder(this.methodVisitor);
+ inOrder.verify(this.methodVisitor).visitLdcInsn(1);
+ inOrder.verify(this.methodVisitor).visitVarInsn(Opcodes.ISTORE, 41);
+ inOrder.verify(this.methodVisitor).visitLdcInsn(1);
+ verifyNoMoreInteractions(this.methodVisitor);
+ }
+
+ @Test
+ public void testDrainFreeList() throws Exception {
+ StackAwareMethodVisitor methodVisitor = new StackAwareMethodVisitor(this.methodVisitor, methodDescription);
+ methodVisitor.visitLdcInsn(1);
+ methodVisitor.visitVarInsn(Opcodes.ISTORE, 41);
+ methodVisitor.visitLdcInsn(1);
+ methodVisitor.visitLdcInsn(1);
+ assertThat(methodVisitor.drainStack(Opcodes.ISTORE, Opcodes.ILOAD, StackSize.SINGLE), is(43));
+ InOrder inOrder = inOrder(this.methodVisitor);
+ inOrder.verify(this.methodVisitor).visitLdcInsn(1);
+ inOrder.verify(this.methodVisitor).visitVarInsn(Opcodes.ISTORE, 41);
+ inOrder.verify(this.methodVisitor, times(2)).visitLdcInsn(1);
+ inOrder.verify(this.methodVisitor).visitVarInsn(Opcodes.ISTORE, 42);
+ inOrder.verify(this.methodVisitor).visitInsn(Opcodes.POP);
+ inOrder.verify(this.methodVisitor).visitVarInsn(Opcodes.ILOAD, 42);
+ verifyNoMoreInteractions(this.methodVisitor);
+ }
+
+ @Test
+ public void testManualRegistration() throws Exception {
+ StackAwareMethodVisitor methodVisitor = new StackAwareMethodVisitor(this.methodVisitor, methodDescription);
+ Label label = new Label();
+ methodVisitor.register(label, Arrays.asList(StackSize.DOUBLE, StackSize.SINGLE));
+ methodVisitor.visitLabel(label);
+ methodVisitor.drainStack();
+ InOrder inOrder = inOrder(this.methodVisitor);
+ inOrder.verify(this.methodVisitor).visitLabel(label);
+ inOrder.verify(this.methodVisitor).visitInsn(Opcodes.POP);
+ inOrder.verify(this.methodVisitor).visitInsn(Opcodes.POP2);
+ verifyNoMoreInteractions(this.methodVisitor);
+ }
+
+ @Test
+ public void testStackCanUnderflow() throws Exception {
+ StackAwareMethodVisitor methodVisitor = new StackAwareMethodVisitor(this.methodVisitor, methodDescription);
+ methodVisitor.visitInsn(Opcodes.POP);
+ methodVisitor.drainStack();
+ verify(this.methodVisitor).visitInsn(Opcodes.POP);
+ verifyNoMoreInteractions(this.methodVisitor);
+ }
+}
\ No newline at end of file
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ArgumentBootstrap$SampleEnum.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ArgumentBootstrap$SampleEnum.class
new file mode 100644
index 0000000..5228067
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ArgumentBootstrap$SampleEnum.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ArgumentBootstrap.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ArgumentBootstrap.class
new file mode 100644
index 0000000..9f18aae
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ArgumentBootstrap.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ArgumentBootstrap.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ArgumentBootstrap.java
new file mode 100644
index 0000000..cb30dd5
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ArgumentBootstrap.java
@@ -0,0 +1,61 @@
+package net.bytebuddy.test.precompiled;
+
+import java.lang.invoke.*;
+
+public class ArgumentBootstrap {
+
+ public static CallSite bootstrap(MethodHandles.Lookup lookup, String methodName, MethodType methodType)
+ throws NoSuchMethodException, IllegalAccessException {
+ return new ConstantCallSite(lookup.findStatic(ArgumentBootstrap.class, methodName, methodType));
+ }
+
+ public static String foo(boolean arg0,
+ byte arg1,
+ short arg2,
+ char arg3,
+ int arg4,
+ long arg5,
+ float arg6,
+ double arg7,
+ Class<?> arg8,
+ SampleEnum arg9,
+ MethodType arg10,
+ MethodHandle arg11,
+ String arg12,
+ Class<?> arg13,
+ SampleEnum arg14,
+ MethodType arg15,
+ MethodHandle arg16,
+ Object arg17) {
+ return "" + arg0 + arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14 + arg15 + arg16 + arg17;
+ }
+
+ public static String bar(Boolean arg0,
+ Byte arg1,
+ Short arg2,
+ Character arg3,
+ Integer arg4,
+ Long arg5,
+ Float arg6,
+ Double arg7,
+ String arg8,
+ Class<?> arg9,
+ SampleEnum arg10,
+ MethodType arg11,
+ MethodHandle arg12,
+ Object arg13) {
+ return "" + arg0 + arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13;
+ }
+
+ public static String qux(String arg) {
+ return arg;
+ }
+
+ public static String baz(Object arg) {
+ return arg.toString();
+ }
+
+ public static enum SampleEnum {
+ INSTANCE;
+ }
+}
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/DelegationDefaultInterface.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/DelegationDefaultInterface.class
new file mode 100644
index 0000000..0f38bc4
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/DelegationDefaultInterface.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/DelegationDefaultInterface.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/DelegationDefaultInterface.java
new file mode 100644
index 0000000..8f61117
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/DelegationDefaultInterface.java
@@ -0,0 +1,10 @@
+package net.bytebuddy.test.precompiled;
+
+public interface DelegationDefaultInterface {
+
+ static final String FOO = "foo";
+
+ default String foo() {
+ return FOO;
+ }
+}
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/DelegationDefaultTarget.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/DelegationDefaultTarget.class
new file mode 100644
index 0000000..a86456a
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/DelegationDefaultTarget.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/DelegationDefaultTarget.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/DelegationDefaultTarget.java
new file mode 100644
index 0000000..88fbe6c
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/DelegationDefaultTarget.java
@@ -0,0 +1,19 @@
+package net.bytebuddy.test.precompiled;
+
+import net.bytebuddy.implementation.bind.annotation.Default;
+
+import java.io.Serializable;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class DelegationDefaultTarget {
+
+ private static final String BAR = "bar";
+
+ public static String intercept(@Default DelegationDefaultInterface proxy) {
+ assertThat(proxy, not(instanceOf(Serializable.class)));
+ return proxy.foo() + BAR;
+ }
+}
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/DelegationDefaultTargetExplicit.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/DelegationDefaultTargetExplicit.class
new file mode 100644
index 0000000..5d99261
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/DelegationDefaultTargetExplicit.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/DelegationDefaultTargetExplicit.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/DelegationDefaultTargetExplicit.java
new file mode 100644
index 0000000..77e2469
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/DelegationDefaultTargetExplicit.java
@@ -0,0 +1,19 @@
+package net.bytebuddy.test.precompiled;
+
+import net.bytebuddy.implementation.bind.annotation.Default;
+
+import java.io.Serializable;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class DelegationDefaultTargetExplicit {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ public static String intercept(@Default(proxyType = DelegationDefaultInterface.class) Object proxy) throws Exception {
+ assertThat(proxy, not(instanceOf(Serializable.class)));
+ return DelegationDefaultInterface.class.getDeclaredMethod(FOO).invoke(proxy) + BAR;
+ }
+}
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/DelegationDefaultTargetSerializable.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/DelegationDefaultTargetSerializable.class
new file mode 100644
index 0000000..5e13142
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/DelegationDefaultTargetSerializable.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/DelegationDefaultTargetSerializable.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/DelegationDefaultTargetSerializable.java
new file mode 100644
index 0000000..ac0d819
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/DelegationDefaultTargetSerializable.java
@@ -0,0 +1,18 @@
+package net.bytebuddy.test.precompiled;
+
+import net.bytebuddy.implementation.bind.annotation.Default;
+
+import java.io.Serializable;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class DelegationDefaultTargetSerializable {
+
+ private static final String BAR = "bar";
+
+ public static String intercept(@Default(serializableProxy = true) DelegationDefaultInterface proxy) {
+ assertThat(proxy, instanceOf(Serializable.class));
+ return proxy.foo() + BAR;
+ }
+}
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/LambdaSampleFactory.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/LambdaSampleFactory.class
new file mode 100644
index 0000000..1253d5d
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/LambdaSampleFactory.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/LambdaSampleFactory.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/LambdaSampleFactory.java
new file mode 100644
index 0000000..129dbda
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/LambdaSampleFactory.java
@@ -0,0 +1,40 @@
+package net.bytebuddy.test.precompiled;
+
+import java.io.Serializable;
+import java.util.concurrent.Callable;
+import java.util.function.Function;
+
+public class LambdaSampleFactory {
+
+ private static final String FOO = "foo";
+
+ private String foo = FOO;
+
+ public Callable<String> nonCapturing() {
+ return () -> FOO;
+ }
+
+ public Callable<String> argumentCapturing(String foo) {
+ return () -> foo;
+ }
+
+ public Callable<String> instanceCapturing() {
+ return () -> foo;
+ }
+
+ public Function<String, String> nonCapturingWithArguments() {
+ return argument -> argument;
+ }
+
+ public Function<String, String> capturingWithArguments(String foo) {
+ return argument -> argument + this.foo + foo;
+ }
+
+ public Callable<String> serializable(String foo) {
+ return (Callable<String> & Serializable) () -> foo;
+ }
+
+ public Runnable returnTypeTransforming() { return this::nonCapturing; }
+
+ public Callable<Object> instanceReturning() { return Object::new; }
+}
\ No newline at end of file
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/LegacyInterface.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/LegacyInterface.class
new file mode 100644
index 0000000..825184c
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/LegacyInterface.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/LegacyInterface.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/LegacyInterface.java
new file mode 100644
index 0000000..f7d3f65
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/LegacyInterface.java
@@ -0,0 +1,5 @@
+package net.bytebuddy.test.precompiled;
+
+public interface LegacyInterface {
+ /* empty */
+}
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/MorphDefaultDelegationTargetExplicit.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/MorphDefaultDelegationTargetExplicit.class
new file mode 100644
index 0000000..f836db1
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/MorphDefaultDelegationTargetExplicit.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/MorphDefaultDelegationTargetExplicit.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/MorphDefaultDelegationTargetExplicit.java
new file mode 100644
index 0000000..f32a034
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/MorphDefaultDelegationTargetExplicit.java
@@ -0,0 +1,14 @@
+package net.bytebuddy.test.precompiled;
+
+import net.bytebuddy.implementation.MethodDelegationMorphTest;
+import net.bytebuddy.implementation.bind.annotation.Morph;
+
+public class MorphDefaultDelegationTargetExplicit {
+
+ private static final String BAR = "bar";
+
+ public static String intercept(@Morph(defaultTarget = MorphDefaultInterface.class)
+ MethodDelegationMorphTest.Morphing<String> morphing) {
+ return morphing.morph(BAR);
+ }
+}
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/MorphDefaultDelegationTargetImplicit.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/MorphDefaultDelegationTargetImplicit.class
new file mode 100644
index 0000000..0667f32
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/MorphDefaultDelegationTargetImplicit.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/MorphDefaultDelegationTargetImplicit.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/MorphDefaultDelegationTargetImplicit.java
new file mode 100644
index 0000000..85bf7f3
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/MorphDefaultDelegationTargetImplicit.java
@@ -0,0 +1,14 @@
+package net.bytebuddy.test.precompiled;
+
+import net.bytebuddy.implementation.MethodDelegationMorphTest;
+import net.bytebuddy.implementation.bind.annotation.Morph;
+
+public class MorphDefaultDelegationTargetImplicit {
+
+ private static final String BAR = "bar";
+
+ public static String intercept(@Morph(defaultMethod = true)
+ MethodDelegationMorphTest.Morphing<String> morphing) {
+ return morphing.morph(BAR);
+ }
+}
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/MorphDefaultInterface.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/MorphDefaultInterface.class
new file mode 100644
index 0000000..7e141ea
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/MorphDefaultInterface.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/MorphDefaultInterface.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/MorphDefaultInterface.java
new file mode 100644
index 0000000..df9cb75
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/MorphDefaultInterface.java
@@ -0,0 +1,10 @@
+package net.bytebuddy.test.precompiled;
+
+public interface MorphDefaultInterface {
+
+ static final String FOO = "foo";
+
+ default String foo(String value) {
+ return FOO + value;
+ }
+}
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/OriginExecutable.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/OriginExecutable.class
new file mode 100644
index 0000000..f97a009
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/OriginExecutable.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/OriginExecutable.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/OriginExecutable.java
new file mode 100644
index 0000000..20022f9
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/OriginExecutable.java
@@ -0,0 +1,15 @@
+package net.bytebuddy.test.precompiled;
+
+import net.bytebuddy.implementation.bind.annotation.Origin;
+
+import java.lang.reflect.Executable;
+
+public class OriginExecutable {
+
+ public Executable executable;
+
+ public Executable intercept(@Origin(cache = false) Executable executable) {
+ this.executable = executable;
+ return executable;
+ }
+}
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/OriginExecutableWithCache.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/OriginExecutableWithCache.class
new file mode 100644
index 0000000..68af362
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/OriginExecutableWithCache.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/OriginExecutableWithCache.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/OriginExecutableWithCache.java
new file mode 100644
index 0000000..bdcc1f0
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/OriginExecutableWithCache.java
@@ -0,0 +1,15 @@
+package net.bytebuddy.test.precompiled;
+
+import net.bytebuddy.implementation.bind.annotation.Origin;
+
+import java.lang.reflect.Executable;
+
+public class OriginExecutableWithCache {
+
+ public Executable executable;
+
+ public Executable intercept(@Origin(cache = true) Executable executable) {
+ this.executable = executable;
+ return executable;
+ }
+}
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/OriginMethodHandle.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/OriginMethodHandle.class
new file mode 100644
index 0000000..768fdd5
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/OriginMethodHandle.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/OriginMethodHandle.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/OriginMethodHandle.java
new file mode 100644
index 0000000..9d68c30
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/OriginMethodHandle.java
@@ -0,0 +1,14 @@
+package net.bytebuddy.test.precompiled;
+
+import net.bytebuddy.implementation.bind.annotation.Origin;
+
+import java.lang.invoke.MethodHandle;
+
+public class OriginMethodHandle {
+
+ public static Class<?> TYPE = MethodHandle.class;
+
+ public static Object foo(@Origin MethodHandle methodHandle) {
+ return methodHandle;
+ }
+}
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/OriginMethodType.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/OriginMethodType.class
new file mode 100644
index 0000000..7d2d00c
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/OriginMethodType.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/OriginMethodType.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/OriginMethodType.java
new file mode 100644
index 0000000..c85280a
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/OriginMethodType.java
@@ -0,0 +1,14 @@
+package net.bytebuddy.test.precompiled;
+
+import net.bytebuddy.implementation.bind.annotation.Origin;
+
+import java.lang.invoke.MethodType;
+
+public class OriginMethodType {
+
+ public static Class<?> TYPE = MethodType.class;
+
+ public static Object foo(@Origin MethodType methodType) {
+ return methodType;
+ }
+}
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/OtherTypeAnnotation.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/OtherTypeAnnotation.class
new file mode 100644
index 0000000..f62bd3d
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/OtherTypeAnnotation.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/OtherTypeAnnotation.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/OtherTypeAnnotation.java
new file mode 100644
index 0000000..ffa87e6
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/OtherTypeAnnotation.java
@@ -0,0 +1,13 @@
+package net.bytebuddy.test.precompiled;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+public @interface OtherTypeAnnotation {
+
+ int value();
+}
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ParameterBootstrap.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ParameterBootstrap.class
new file mode 100644
index 0000000..9636ca7
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ParameterBootstrap.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ParameterBootstrap.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ParameterBootstrap.java
new file mode 100644
index 0000000..63161c5
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ParameterBootstrap.java
@@ -0,0 +1,39 @@
+package net.bytebuddy.test.precompiled;
+
+import java.lang.invoke.*;
+
+public class ParameterBootstrap {
+
+ private static final String FOO = "foo";
+
+ public static Object[] arguments;
+
+ public static CallSite bootstrapArrayArguments(MethodHandles.Lookup lookup,
+ String methodName,
+ MethodType methodType,
+ Object... arguments)
+ throws NoSuchMethodException, IllegalAccessException {
+ ParameterBootstrap.arguments = arguments;
+ return new ConstantCallSite(lookup.findStatic(ParameterBootstrap.class, methodName, methodType));
+ }
+
+ public static CallSite bootstrapExplicitArguments(MethodHandles.Lookup lookup,
+ String methodName,
+ MethodType methodType,
+ int arg0,
+ long arg1,
+ float arg2,
+ double arg3,
+ String arg4,
+ Class<?> arg5,
+ MethodType arg6,
+ MethodHandle arg7)
+ throws NoSuchMethodException, IllegalAccessException {
+ ParameterBootstrap.arguments = new Object[]{arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7};
+ return new ConstantCallSite(lookup.findStatic(ParameterBootstrap.class, methodName, methodType));
+ }
+
+ public static String foo() {
+ return FOO;
+ }
+}
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ParameterNames.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ParameterNames.class
new file mode 100644
index 0000000..56164b3
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ParameterNames.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ParameterNames.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ParameterNames.java
new file mode 100644
index 0000000..3b58074
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ParameterNames.java
@@ -0,0 +1,16 @@
+package net.bytebuddy.test.precompiled;
+
+/**
+ * This class must be compiled with enabling {@code -parameters} for the related tests to work!
+ */
+public abstract class ParameterNames {
+
+ public ParameterNames(String first, final int second) {
+ }
+
+ public void foo(final String first, long second, int third) {
+
+ }
+
+ public abstract void bar(String first, final long second, int third);
+}
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ReceiverTypeSample$Generic$Inner.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ReceiverTypeSample$Generic$Inner.class
new file mode 100644
index 0000000..649ee53
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ReceiverTypeSample$Generic$Inner.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ReceiverTypeSample$Generic$Nested.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ReceiverTypeSample$Generic$Nested.class
new file mode 100644
index 0000000..16d9d24
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ReceiverTypeSample$Generic$Nested.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ReceiverTypeSample$Generic.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ReceiverTypeSample$Generic.class
new file mode 100644
index 0000000..37209fa
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ReceiverTypeSample$Generic.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ReceiverTypeSample$Inner.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ReceiverTypeSample$Inner.class
new file mode 100644
index 0000000..afdbe80
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ReceiverTypeSample$Inner.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ReceiverTypeSample$Nested.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ReceiverTypeSample$Nested.class
new file mode 100644
index 0000000..8e9e9b8
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ReceiverTypeSample$Nested.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ReceiverTypeSample.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ReceiverTypeSample.class
new file mode 100644
index 0000000..4fd0627
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ReceiverTypeSample.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ReceiverTypeSample.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ReceiverTypeSample.java
new file mode 100644
index 0000000..f0b5ce4
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ReceiverTypeSample.java
@@ -0,0 +1,55 @@
+package net.bytebuddy.test.precompiled;
+
+public abstract class ReceiverTypeSample {
+
+ abstract void foo(@TypeAnnotation(0) ReceiverTypeSample this);
+
+ ReceiverTypeSample() {
+ /* empty */
+ }
+
+ abstract class Inner {
+
+ abstract void foo(@TypeAnnotation(1) Inner this);
+
+ Inner(@TypeAnnotation(2) ReceiverTypeSample ReceiverTypeSample.this) {
+ /* empty */
+ }
+ }
+
+ abstract static class Nested {
+
+ abstract void foo(@TypeAnnotation(3) Nested this);
+
+ Nested() {
+ /* empty */
+ }
+ }
+
+ abstract static class Generic<T> {
+
+ abstract void foo(@TypeAnnotation(4) Generic<@TypeAnnotation(5) T> this);
+
+ Generic() {
+ /* empty */
+ }
+
+ abstract class Inner<S> {
+
+ abstract void foo(@TypeAnnotation(6) Generic<@TypeAnnotation(7) T>. at TypeAnnotation(8) Inner<@TypeAnnotation(9) S> this);
+
+ Inner(@TypeAnnotation(10) Generic<@TypeAnnotation(11) T> Generic.this) {
+ /* empty */
+ }
+ }
+
+ abstract static class Nested<S> {
+
+ abstract void foo(@TypeAnnotation(12) Nested<@TypeAnnotation(13) S> this);
+
+ Nested() {
+ /* empty */
+ }
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ReturnTypeInterfaceBridge.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ReturnTypeInterfaceBridge.class
new file mode 100644
index 0000000..821abff
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ReturnTypeInterfaceBridge.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ReturnTypeInterfaceBridge.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ReturnTypeInterfaceBridge.java
new file mode 100644
index 0000000..4d487eb
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ReturnTypeInterfaceBridge.java
@@ -0,0 +1,11 @@
+package net.bytebuddy.test.precompiled;
+
+public interface ReturnTypeInterfaceBridge extends ReturnTypeInterfaceBridgeBase {
+
+ String BAR = "bar";
+
+ @Override
+ default String foo() {
+ return BAR;
+ }
+}
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ReturnTypeInterfaceBridgeBase.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ReturnTypeInterfaceBridgeBase.class
new file mode 100644
index 0000000..29e87ef
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ReturnTypeInterfaceBridgeBase.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ReturnTypeInterfaceBridgeBase.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ReturnTypeInterfaceBridgeBase.java
new file mode 100644
index 0000000..0e19229
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/ReturnTypeInterfaceBridgeBase.java
@@ -0,0 +1,10 @@
+package net.bytebuddy.test.precompiled;
+
+public interface ReturnTypeInterfaceBridgeBase {
+
+ String FOO = "foo";
+
+ default Object foo() {
+ return FOO;
+ }
+}
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SimpleTypeAnnotatedType.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SimpleTypeAnnotatedType.class
new file mode 100644
index 0000000..c3e8bcc
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SimpleTypeAnnotatedType.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SimpleTypeAnnotatedType.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SimpleTypeAnnotatedType.java
new file mode 100644
index 0000000..ec79ebb
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SimpleTypeAnnotatedType.java
@@ -0,0 +1,5 @@
+package net.bytebuddy.test.precompiled;
+
+public abstract class SimpleTypeAnnotatedType<@TypeAnnotation(42) foo> implements @TypeAnnotation(84) Runnable{
+ /* empty */
+}
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodClass.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodClass.class
new file mode 100644
index 0000000..376f9e6
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodClass.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodClass.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodClass.java
new file mode 100644
index 0000000..9f73e0a
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodClass.java
@@ -0,0 +1,5 @@
+package net.bytebuddy.test.precompiled;
+
+public class SingleDefaultMethodClass implements SingleDefaultMethodInterface {
+ /* empty */
+}
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodConflictingInterface.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodConflictingInterface.class
new file mode 100644
index 0000000..bf85ed0
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodConflictingInterface.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodConflictingInterface.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodConflictingInterface.java
new file mode 100644
index 0000000..97072e1
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodConflictingInterface.java
@@ -0,0 +1,10 @@
+package net.bytebuddy.test.precompiled;
+
+public interface SingleDefaultMethodConflictingInterface {
+
+ static final String QUX = "qux";
+
+ default Object foo() {
+ return QUX;
+ }
+}
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodConflictingPreferringInterceptor.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodConflictingPreferringInterceptor.class
new file mode 100644
index 0000000..71086c3
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodConflictingPreferringInterceptor.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodConflictingPreferringInterceptor.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodConflictingPreferringInterceptor.java
new file mode 100644
index 0000000..b5d2bdc
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodConflictingPreferringInterceptor.java
@@ -0,0 +1,12 @@
+package net.bytebuddy.test.precompiled;
+
+import net.bytebuddy.implementation.bind.annotation.DefaultCall;
+
+import java.util.concurrent.Callable;
+
+public class SingleDefaultMethodConflictingPreferringInterceptor {
+
+ public static Object foo(@DefaultCall(targetType = SingleDefaultMethodConflictingInterface.class) Callable<?> callable) throws Exception {
+ return callable.call();
+ }
+}
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodInterface.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodInterface.class
new file mode 100644
index 0000000..38c56e7
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodInterface.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodInterface.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodInterface.java
new file mode 100644
index 0000000..18e73f6
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodInterface.java
@@ -0,0 +1,10 @@
+package net.bytebuddy.test.precompiled;
+
+public interface SingleDefaultMethodInterface {
+
+ static final String FOO = "foo";
+
+ default Object foo() {
+ return FOO;
+ }
+}
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodNonOverridingInterface.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodNonOverridingInterface.class
new file mode 100644
index 0000000..9180141
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodNonOverridingInterface.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodNonOverridingInterface.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodNonOverridingInterface.java
new file mode 100644
index 0000000..2834f56
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodNonOverridingInterface.java
@@ -0,0 +1,5 @@
+package net.bytebuddy.test.precompiled;
+
+public interface SingleDefaultMethodNonOverridingInterface extends SingleDefaultMethodInterface {
+ /* empty */
+}
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodPreferringInterceptor.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodPreferringInterceptor.class
new file mode 100644
index 0000000..a9f5f90
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodPreferringInterceptor.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodPreferringInterceptor.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodPreferringInterceptor.java
new file mode 100644
index 0000000..e5297fa
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/SingleDefaultMethodPreferringInterceptor.java
@@ -0,0 +1,12 @@
+package net.bytebuddy.test.precompiled;
+
+import net.bytebuddy.implementation.bind.annotation.DefaultCall;
+
+import java.util.concurrent.Callable;
+
+public class SingleDefaultMethodPreferringInterceptor {
+
+ public static Object foo(@DefaultCall(targetType = SingleDefaultMethodInterface.class) Callable<?> callable) throws Exception {
+ return callable.call();
+ }
+}
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/StandardArgumentBootstrap.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/StandardArgumentBootstrap.class
new file mode 100644
index 0000000..f16141b
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/StandardArgumentBootstrap.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/StandardArgumentBootstrap.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/StandardArgumentBootstrap.java
new file mode 100644
index 0000000..601e210
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/StandardArgumentBootstrap.java
@@ -0,0 +1,55 @@
+package net.bytebuddy.test.precompiled;
+
+import java.lang.invoke.CallSite;
+import java.lang.invoke.ConstantCallSite;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+
+public class StandardArgumentBootstrap extends ConstantCallSite {
+
+ private static final String FOO = "foo";
+
+ public StandardArgumentBootstrap(Object... args)
+ throws NoSuchMethodException, IllegalAccessException {
+ super(((MethodHandles.Lookup) args[0]).findStatic(StandardArgumentBootstrap.class, (String) args[1], (MethodType) args[2]));
+ }
+
+ public StandardArgumentBootstrap(MethodHandles.Lookup lookup, Object... args)
+ throws NoSuchMethodException, IllegalAccessException {
+ super(lookup.findStatic(StandardArgumentBootstrap.class, (String) args[0], (MethodType) args[1]));
+ }
+
+ public StandardArgumentBootstrap(MethodHandles.Lookup lookup, String methodName, MethodType methodType)
+ throws NoSuchMethodException, IllegalAccessException {
+ super(lookup.findStatic(StandardArgumentBootstrap.class, methodName, methodType));
+ }
+
+ public StandardArgumentBootstrap(MethodHandles.Lookup lookup, String methodName, Object... args)
+ throws NoSuchMethodException, IllegalAccessException {
+ super(lookup.findStatic(StandardArgumentBootstrap.class, methodName, (MethodType) args[0]));
+ }
+
+ public static CallSite bootstrap(Object... args)
+ throws NoSuchMethodException, IllegalAccessException {
+ return new ConstantCallSite(((MethodHandles.Lookup) args[0]).findStatic(StandardArgumentBootstrap.class, (String) args[1], (MethodType) args[2]));
+ }
+
+ public static CallSite bootstrap(MethodHandles.Lookup lookup, Object... args)
+ throws NoSuchMethodException, IllegalAccessException {
+ return new ConstantCallSite(lookup.findStatic(StandardArgumentBootstrap.class, (String) args[0], (MethodType) args[1]));
+ }
+
+ public static CallSite bootstrap(MethodHandles.Lookup lookup, String methodName, MethodType methodType)
+ throws NoSuchMethodException, IllegalAccessException {
+ return new ConstantCallSite(lookup.findStatic(StandardArgumentBootstrap.class, methodName, methodType));
+ }
+
+ public static CallSite bootstrap(MethodHandles.Lookup lookup, String methodName, Object... args)
+ throws NoSuchMethodException, IllegalAccessException {
+ return new ConstantCallSite(lookup.findStatic(StandardArgumentBootstrap.class, methodName, (MethodType) args[0]));
+ }
+
+ public static String foo() {
+ return FOO;
+ }
+}
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeAnnotation.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeAnnotation.class
new file mode 100644
index 0000000..c188a98
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeAnnotation.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeAnnotation.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeAnnotation.java
new file mode 100644
index 0000000..9a38903
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeAnnotation.java
@@ -0,0 +1,13 @@
+package net.bytebuddy.test.precompiled;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+public @interface TypeAnnotation {
+
+ int value();
+}
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeAnnotationOtherSamples$Bar.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeAnnotationOtherSamples$Bar.class
new file mode 100644
index 0000000..155f2da
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeAnnotationOtherSamples$Bar.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeAnnotationOtherSamples$Qux$Baz.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeAnnotationOtherSamples$Qux$Baz.class
new file mode 100644
index 0000000..9a79504
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeAnnotationOtherSamples$Qux$Baz.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeAnnotationOtherSamples$Qux.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeAnnotationOtherSamples$Qux.class
new file mode 100644
index 0000000..27a9757
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeAnnotationOtherSamples$Qux.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeAnnotationOtherSamples.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeAnnotationOtherSamples.class
new file mode 100644
index 0000000..3434353
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeAnnotationOtherSamples.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeAnnotationOtherSamples.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeAnnotationOtherSamples.java
new file mode 100644
index 0000000..dc8ae44
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeAnnotationOtherSamples.java
@@ -0,0 +1,26 @@
+package net.bytebuddy.test.precompiled;
+
+public abstract class TypeAnnotationOtherSamples<T> {
+
+ @TypeAnnotation(0)
+ Void foo;
+
+ @TypeAnnotation(1) TypeAnnotationOtherSamples<@TypeAnnotation(2) Void>. at TypeAnnotation(3) Bar<@TypeAnnotation(4) Void> bar;
+
+ @TypeAnnotation(5) @OtherTypeAnnotation(6) Void qux;
+
+ @TypeAnnotation(7) Qux. at TypeAnnotation(8) Baz baz;
+
+ @TypeAnnotation(9)
+ abstract Void foo(@TypeAnnotation(10) Void v) throws @TypeAnnotation(11) Exception;
+
+ class Bar<S> {
+ /* empty */
+ }
+
+ static class Qux {
+ class Baz {
+ /* empty */
+ }
+ }
+}
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeAnnotationSamples.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeAnnotationSamples.class
new file mode 100644
index 0000000..a87d17c
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeAnnotationSamples.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeAnnotationSamples.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeAnnotationSamples.java
new file mode 100644
index 0000000..37095f9
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeAnnotationSamples.java
@@ -0,0 +1,25 @@
+package net.bytebuddy.test.precompiled;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+public abstract class TypeAnnotationSamples<@TypeAnnotation(0) T,
+ S,
+ @TypeAnnotation(2) U extends String & @TypeAnnotation(3) Callable<@TypeAnnotation(4) ?> & @TypeAnnotation(5) List<@TypeAnnotation(6) ?>,
+ @TypeAnnotation(7) V extends Map<@TypeAnnotation(8) ? extends @TypeAnnotation(9) String, @TypeAnnotation(10) Callable<@TypeAnnotation(11) ? super @TypeAnnotation(12) U>>,
+ @TypeAnnotation(13) W extends @TypeAnnotation(14) V,
+ @TypeAnnotation(15) X extends @TypeAnnotation(16) ArrayList<@TypeAnnotation(17) ?>>
+ extends @TypeAnnotation(18) Object
+ implements @TypeAnnotation(19) Callable<@TypeAnnotation(20) Object>, Map<@TypeAnnotation(21) String, Object> {
+
+ @TypeAnnotation(22) Callable<@TypeAnnotation(23) ?> @TypeAnnotation(24) [] @TypeAnnotation(25) [] foo;
+
+ abstract <@TypeAnnotation(26) T extends @TypeAnnotation(27) Exception> @TypeAnnotation(28) int foo(@TypeAnnotation(29) T @TypeAnnotation(30) [] @TypeAnnotation(31) [] v)
+ throws @TypeAnnotation(32) T, @TypeAnnotation(33) RuntimeException;
+
+ abstract @TypeAnnotation(34) int @TypeAnnotation(35) [] @TypeAnnotation(36) [] bar(@TypeAnnotation(37) Void @TypeAnnotation(38) [] @TypeAnnotation(39) [] v);
+
+ abstract <T> @TypeAnnotation(40) int @TypeAnnotation(41) [] @TypeAnnotation(42) [] qux(@TypeAnnotation(43) Void @TypeAnnotation(44) [] @TypeAnnotation(45) [] v);
+}
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeConstantSample.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeConstantSample.class
new file mode 100644
index 0000000..443e7da
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeConstantSample.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeConstantSample.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeConstantSample.java
new file mode 100644
index 0000000..3ccd03d
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeConstantSample.java
@@ -0,0 +1,9 @@
+package net.bytebuddy.test.precompiled;
+
+public class TypeConstantSample {
+
+ public static Object bar() {
+ return TypeConstantSample.class;
+ }
+}
+
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeVariableInterfaceBridge.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeVariableInterfaceBridge.class
new file mode 100644
index 0000000..909f1a7
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeVariableInterfaceBridge.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeVariableInterfaceBridge.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeVariableInterfaceBridge.java
new file mode 100644
index 0000000..b4deb51
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeVariableInterfaceBridge.java
@@ -0,0 +1,11 @@
+package net.bytebuddy.test.precompiled;
+
+public interface TypeVariableInterfaceBridge extends TypeVariableInterfaceBridgeBase<String> {
+
+ String FOO = "foo";
+
+ @Override
+ default String foo(String s) {
+ return FOO;
+ }
+}
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeVariableInterfaceBridgeBase.class b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeVariableInterfaceBridgeBase.class
new file mode 100644
index 0000000..a99deb7
Binary files /dev/null and b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeVariableInterfaceBridgeBase.class differ
diff --git a/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeVariableInterfaceBridgeBase.java b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeVariableInterfaceBridgeBase.java
new file mode 100644
index 0000000..e879ef2
--- /dev/null
+++ b/byte-buddy-dep/src/test/resources/net/bytebuddy/test/precompiled/TypeVariableInterfaceBridgeBase.java
@@ -0,0 +1,8 @@
+package net.bytebuddy.test.precompiled;
+
+public interface TypeVariableInterfaceBridgeBase<T> {
+
+ default T foo(T t) {
+ return t;
+ }
+}
diff --git a/byte-buddy-gradle-plugin/build.gradle b/byte-buddy-gradle-plugin/build.gradle
new file mode 100644
index 0000000..f3db724
--- /dev/null
+++ b/byte-buddy-gradle-plugin/build.gradle
@@ -0,0 +1,53 @@
+apply plugin: 'java'
+apply plugin: 'java-gradle-plugin'
+
+def pom = new XmlSlurper().parse(file('./pom.xml'))
+def outerPom = new XmlSlurper().parse(file('../pom.xml'))
+
+group = pom.parent.groupId.text().toString()
+version = pom.parent.version.text().toString()
+
+description = pom.description.text().toString()
+
+sourceCompatibility = JavaVersion.VERSION_1_6
+targetCompatibility = JavaVersion.VERSION_1_6
+
+repositories {
+ mavenLocal()
+ mavenCentral()
+}
+
+dependencies {
+ compile gradleApi()
+ // At this point, it is not given that any artifact from the Maven build can be found in a repository.
+ def location = new File(project.buildscript.sourceFile.getParentFile(), "../byte-buddy/target/byte-buddy-${version}.jar").canonicalFile
+ logger.info("Relying on ${location.absolutePath} as Byte Buddy dependency")
+ if (location.exists()) {
+ compile files(location.absolutePath)
+ } else {
+ logger.warn("${location.absolutePath} does not exist, can clean but not build project")
+ }
+ testCompile gradleTestKit()
+ testCompile group: 'junit', name: 'junit', version: outerPom.properties.'version.junit'
+ testCompile (group: 'org.mockito', name: 'mockito-core', version: outerPom.properties.'version.mockito') {
+ exclude group: 'net.bytebuddy'
+ }
+ compileOnly group: 'org.projectlombok', name: 'lombok', version: outerPom.properties.'version.lombok'
+}
+
+// Without the extras property, creating a javadoc artifact is not necessary.
+if (Boolean.getBoolean('net.bytebuddy.misc.extras')) {
+ task javadocJar(type: Jar, dependsOn: javadoc) {
+ classifier = 'javadoc'
+ from javadoc.destinationDir
+ }
+} else {
+ task javadocJar {
+ logger.info('javadoc is only generated if net.bytebuddy.misc.extras is set to true')
+ }
+}
+
+test {
+ systemProperty('net.bytebuddy.test.version', version)
+ systemProperty('net.bytebuddy.test.integration', Boolean.getBoolean('net.bytebuddy.test.integration'))
+}
diff --git a/byte-buddy-gradle-plugin/classes/production/byte-buddy-gradle-plugin/META-INF/gradle-plugins/net.bytebuddy.byte-buddy.properties b/byte-buddy-gradle-plugin/classes/production/byte-buddy-gradle-plugin/META-INF/gradle-plugins/net.bytebuddy.byte-buddy.properties
new file mode 100644
index 0000000..7db90a7
--- /dev/null
+++ b/byte-buddy-gradle-plugin/classes/production/byte-buddy-gradle-plugin/META-INF/gradle-plugins/net.bytebuddy.byte-buddy.properties
@@ -0,0 +1 @@
+implementation-class=net.bytebuddy.build.gradle.ByteBuddyPlugin
\ No newline at end of file
diff --git a/byte-buddy-gradle-plugin/classes/test/byte-buddy-gradle-plugin/net.bytebuddy.test/Sample.java.raw b/byte-buddy-gradle-plugin/classes/test/byte-buddy-gradle-plugin/net.bytebuddy.test/Sample.java.raw
new file mode 100644
index 0000000..1ae1aa0
--- /dev/null
+++ b/byte-buddy-gradle-plugin/classes/test/byte-buddy-gradle-plugin/net.bytebuddy.test/Sample.java.raw
@@ -0,0 +1,12 @@
+package net.bytebuddy.test;
+
+public class Sample {
+
+ public String foo() {
+ return "bar";
+ }
+
+ public static void main(String[] args) {
+ System.out.println("foo=" + new Sample().foo());
+ }
+}
diff --git a/byte-buddy-gradle-plugin/gradle/wrapper/gradle-wrapper.jar b/byte-buddy-gradle-plugin/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..3baa851
Binary files /dev/null and b/byte-buddy-gradle-plugin/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/byte-buddy-gradle-plugin/gradle/wrapper/gradle-wrapper.properties b/byte-buddy-gradle-plugin/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..268e7d3
--- /dev/null
+++ b/byte-buddy-gradle-plugin/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Aug 31 15:32:56 CEST 2016
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-bin.zip
diff --git a/byte-buddy-gradle-plugin/gradlew b/byte-buddy-gradle-plugin/gradlew
new file mode 100755
index 0000000..27309d9
--- /dev/null
+++ b/byte-buddy-gradle-plugin/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/byte-buddy-gradle-plugin/gradlew.bat b/byte-buddy-gradle-plugin/gradlew.bat
new file mode 100644
index 0000000..f6d5974
--- /dev/null
+++ b/byte-buddy-gradle-plugin/gradlew.bat
@@ -0,0 +1,90 @@
+ at if "%DEBUG%" == "" @echo off
+ at rem ##########################################################################
+ at rem
+ at rem Gradle startup script for Windows
+ at rem
+ at rem ##########################################################################
+
+ at rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+ at rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+ at rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+ at rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+ at rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+ at rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+ at rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+ at rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+ at rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/byte-buddy-gradle-plugin/pom.xml b/byte-buddy-gradle-plugin/pom.xml
new file mode 100644
index 0000000..cb592b1
--- /dev/null
+++ b/byte-buddy-gradle-plugin/pom.xml
@@ -0,0 +1,177 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>byte-buddy-parent</artifactId>
+ <groupId>net.bytebuddy</groupId>
+ <version>1.7.1</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>byte-buddy-gradle-plugin</artifactId>
+
+ <name>Byte Buddy (Gradle plugin)</name>
+ <description>A plugin for post-processing class files via Byte Buddy in a Gradle build.</description>
+
+ <!--
+ This sub project is built by Gradle as the Gradle plugin API requires a Gradle build. This POM
+ triggers the Gradle build via a Gradle execution plugin in order to keep the build seamless. The
+ built artifacts are then attached to this Maven project once the build completed successfully.
+
+ IDEs are typically confused by this setup and this sub project should be directly imported as
+ a Gradle project to avoid errors. Alternatively, this project should be ignored.
+ -->
+
+ <properties>
+ <version.plugin.gradlerun>1.0.8</version.plugin.gradlerun>
+ <version.plugin.antrun>1.8</version.plugin.antrun>
+ <version.plugin.buildhelp>1.12</version.plugin.buildhelp>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <!-- The Gradle plugin must be built by Gradle; therefore the compilation is skipped. -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <skipMain>true</skipMain>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ <!-- Gradle is also responsible for creating javadoc such that this task is skipped here. -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>${version.plugin.javadoc}</version>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ <!-- Tie Maven executions into the Gradle life-cycle. -->
+ <plugin>
+ <groupId>org.fortasoft</groupId>
+ <artifactId>gradle-maven-plugin</artifactId>
+ <version>${version.plugin.gradlerun}</version>
+ <executions>
+ <execution>
+ <id>gradle-clean</id>
+ <phase>clean</phase>
+ <goals>
+ <goal>invoke</goal>
+ </goals>
+ <configuration>
+ <tasks>
+ <task>clean</task>
+ </tasks>
+ <args>
+ <arg>--info</arg>
+ </args>
+ </configuration>
+ </execution>
+ <execution>
+ <id>gradle-build</id>
+ <phase>compile</phase>
+ <goals>
+ <goal>invoke</goal>
+ </goals>
+ <configuration>
+ <tasks>
+ <task>build</task>
+ <task>javadocJar</task>
+ </tasks>
+ <args>
+ <arg>--info</arg>
+ </args>
+ <jvmArgs>
+ <jvmArg>-Dnet.bytebuddy.test.integration=${bytebuddy.integration}</jvmArg>
+ <jvmArg>-Dnet.bytebuddy.misc.extras=${bytebuddy.extras}</jvmArg>
+ </jvmArgs>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <!-- Copies the artifact created by Gradle back to the Maven target folder. -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-antrun-plugin</artifactId>
+ <version>${version.plugin.antrun}</version>
+ <executions>
+ <execution>
+ <id>copy-jar</id>
+ <phase>package</phase>
+ <goals>
+ <goal>run</goal>
+ </goals>
+ <configuration>
+ <target>
+ <copy file="${project.basedir}/build/libs/${project.artifactId}-${project.version}.jar" todir="${project.build.directory}" overwrite="true" />
+ </target>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>extras</id>
+ <build>
+ <plugins>
+ <!-- If the javadoc artifact is built by Gradle, we copy it back to the Maven target. -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-antrun-plugin</artifactId>
+ <version>${version.plugin.antrun}</version>
+ <executions>
+ <execution>
+ <id>copy-javadoc</id>
+ <phase>package</phase>
+ <goals>
+ <goal>run</goal>
+ </goals>
+ <configuration>
+ <target>
+ <copy file="${project.basedir}/build/libs/${project.artifactId}-${project.version}-javadoc.jar" todir="${project.build.directory}" overwrite="true" />
+ </target>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <!-- As the javadoc artifact is built by Gradle, it needs to be attached to Maven manually. -->
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <version>${version.plugin.buildhelp}</version>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>attach-artifact</goal>
+ </goals>
+ <configuration>
+ <artifacts>
+ <artifact>
+ <file>build/libs/${project.artifactId}-${project.version}-javadoc.jar</file>
+ <type>jar</type>
+ <classifier>javadoc</classifier>
+ </artifact>
+ </artifacts>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/byte-buddy-gradle-plugin/src/main/java/net/bytebuddy/build/gradle/AbstractUserConfiguration.java b/byte-buddy-gradle-plugin/src/main/java/net/bytebuddy/build/gradle/AbstractUserConfiguration.java
new file mode 100644
index 0000000..469cbe9
--- /dev/null
+++ b/byte-buddy-gradle-plugin/src/main/java/net/bytebuddy/build/gradle/AbstractUserConfiguration.java
@@ -0,0 +1,140 @@
+package net.bytebuddy.build.gradle;
+
+import lombok.EqualsAndHashCode;
+
+import java.io.File;
+import java.util.Iterator;
+
+/**
+ * An abstract base class for a user configuration implying a class path.
+ */
+public class AbstractUserConfiguration {
+
+ /**
+ * The class to use for this configuration path or {@code null} if no class path is specified.
+ */
+ private Iterable<File> classPath;
+
+ /**
+ * Returns the class path or builds a class path from the supplied arguments if no class path was set.
+ *
+ * @param root The root directory of the project being built.
+ * @param classPath The class path dependencies.
+ * @return An iterable of all elements of the class path to be used.
+ */
+ public Iterable<? extends File> getClassPath(File root, Iterable<? extends File> classPath) {
+ return this.classPath == null
+ ? new PrefixIterable(root, classPath)
+ : this.classPath;
+ }
+
+ /**
+ * Sets the class path to use for this configuration.
+ *
+ * @param classPath The class path to use.
+ */
+ public void setClassPath(Iterable<File> classPath) {
+ this.classPath = classPath;
+ }
+
+ /**
+ * An iterable with a single {@link File} element prepended.
+ */
+ @EqualsAndHashCode
+ protected static class PrefixIterable implements Iterable<File> {
+
+ /**
+ * The prefixed file.
+ */
+ private final File file;
+
+ /**
+ * The iterable containing the reminder files.
+ */
+ private final Iterable<? extends File> files;
+
+ /**
+ * @param file The prefixed file.
+ * @param files The iterable containing the reminder files.
+ */
+ protected PrefixIterable(File file, Iterable<? extends File> files) {
+ this.file = file;
+ this.files = files;
+ }
+
+ @Override
+ public Iterator<File> iterator() {
+ return new PrefixIterator(file, files.iterator());
+ }
+
+ @Override
+ public String toString() {
+ return "AbstractUserConfiguration.PrefixIterable{" +
+ "file=" + file +
+ ", files=" + files +
+ '}';
+ }
+
+ /**
+ * An iterator with a single prefixed file.
+ */
+ protected static class PrefixIterator implements Iterator<File> {
+
+ /**
+ * The file being prefixed.
+ */
+ private final File file;
+
+ /**
+ * An iterator over the reminind files.
+ */
+ private final Iterator<? extends File> files;
+
+ /**
+ * {@code true} if the prefix was not yet returned from the iteration.
+ */
+ private boolean first;
+
+ /**
+ * Creates a prefix iterator.
+ *
+ * @param file The file being prefixed.
+ * @param files An iterator over the reminind files.
+ */
+ protected PrefixIterator(File file, Iterator<? extends File> files) {
+ this.file = file;
+ this.files = files;
+ first = true;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return first || files.hasNext();
+ }
+
+ @Override
+ public File next() {
+ if (first) {
+ first = false;
+ return file;
+ } else {
+ return files.next();
+ }
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("Cannot remove file from iterator");
+ }
+
+ @Override
+ public String toString() {
+ return "AbstractUserConfiguration.PrefixIterable.PrefixIterator{" +
+ "file=" + file +
+ ", files=" + files +
+ ", first=" + first +
+ '}';
+ }
+ }
+ }
+}
diff --git a/byte-buddy-gradle-plugin/src/main/java/net/bytebuddy/build/gradle/ByteBuddyExtension.java b/byte-buddy-gradle-plugin/src/main/java/net/bytebuddy/build/gradle/ByteBuddyExtension.java
new file mode 100644
index 0000000..19e7817
--- /dev/null
+++ b/byte-buddy-gradle-plugin/src/main/java/net/bytebuddy/build/gradle/ByteBuddyExtension.java
@@ -0,0 +1,166 @@
+package net.bytebuddy.build.gradle;
+
+import groovy.lang.Closure;
+import net.bytebuddy.dynamic.scaffold.inline.MethodNameTransformer;
+import org.gradle.api.GradleException;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Representation of a Gradle configuration for Byte Buddy.
+ */
+public class ByteBuddyExtension {
+
+ /**
+ * The current project.
+ */
+ private final Project project;
+
+ /**
+ * A list of registered transformations.
+ */
+ private final List<Transformation> transformations;
+
+ /**
+ * The initialization or {@code null} if no initialization was defined by a user.
+ */
+ private Initialization initialization;
+
+ /**
+ * The suffix to use for rebased methods or {@code null} if a random suffix should be used.
+ */
+ private String suffix;
+
+ /**
+ * {@code true} if the plugin should fail upon discovering a live runtime initializer.
+ */
+ private boolean failOnLiveInitializer;
+
+ /**
+ * A list of task names for which to apply a transformation or {@code null} if the task should apply to all tasks.
+ */
+ private Set<String> tasks;
+
+ /**
+ * Creates a new Byte Buddy extension.
+ *
+ * @param project The current project.
+ */
+ public ByteBuddyExtension(Project project) {
+ this.project = project;
+ transformations = new ArrayList<Transformation>();
+ failOnLiveInitializer = true;
+ }
+
+ /**
+ * Adds a transformation to apply.
+ *
+ * @param closure The closure for configuring the transformation.
+ */
+ public void transformation(Closure<?> closure) {
+ Transformation transformation = (Transformation) project.configure(new Transformation(), closure);
+ transformations.add(transformation);
+ }
+
+ /**
+ * Adds an initialization to apply.
+ *
+ * @param closure The closure for configuring the initialization.
+ */
+ public void initialization(Closure<?> closure) {
+ if (initialization != null) {
+ throw new GradleException("Initialization is already set");
+ }
+ initialization = (Initialization) project.configure(new Initialization(), closure);
+ }
+
+ /**
+ * Returns a list of transformations to apply.
+ *
+ * @return A list of transformations to apply.
+ */
+ public List<Transformation> getTransformations() {
+ return transformations;
+ }
+
+ /**
+ * Returns the initialization to use.
+ *
+ * @return The initialization to use.
+ */
+ public Initialization getInitialization() {
+ return initialization == null
+ ? Initialization.makeDefault()
+ : initialization;
+ }
+
+ /**
+ * Returns the method name transformer to use.
+ *
+ * @return The method name transformer to use.
+ */
+ public MethodNameTransformer getMethodNameTransformer() {
+ return suffix == null || suffix.isEmpty()
+ ? MethodNameTransformer.Suffixing.withRandomSuffix()
+ : new MethodNameTransformer.Suffixing(suffix);
+ }
+
+ /**
+ * Sets the suffix to apply upon rebased methods.
+ *
+ * @param suffix The suffix to apply upon rebased methods.
+ */
+ public void setSuffix(String suffix) {
+ this.suffix = suffix;
+ }
+
+ /**
+ * Returns {@code true} if the build should fail upon discovering a live runtime initializer.
+ *
+ * @return {@code true} if the build should fail upon discovering a live runtime initializer.
+ */
+ public boolean isFailOnLiveInitializer() {
+ return failOnLiveInitializer;
+ }
+
+ /**
+ * Determines if the build should fail upon discovering a live runtime initializer.
+ *
+ * @param failOnLiveInitializer {@code true} if the build should fail upon discovering a live runtime initializer.
+ */
+ public void setFailOnLiveInitializer(boolean failOnLiveInitializer) {
+ this.failOnLiveInitializer = failOnLiveInitializer;
+ }
+
+ /**
+ * Sets the initialization that should be used.
+ *
+ * @param initialization The initialization to be used.
+ */
+ public void setInitialization(Initialization initialization) {
+ this.initialization = initialization;
+ }
+
+ /**
+ * Determines if a task is subject to transformation.
+ *
+ * @param task The task to consider.
+ * @return {@code true} if this task should be followed up by a transformation.
+ */
+ public boolean implies(Task task) {
+ return tasks == null || tasks.contains(task.getName());
+ }
+
+ /**
+ * Sets an explicit list of tasks for which a transformation should be applied.
+ *
+ * @param tasks The tasks to explicitly append a transformation to.
+ */
+ public void setTasks(Set<String> tasks) {
+ this.tasks = tasks;
+ }
+}
diff --git a/byte-buddy-gradle-plugin/src/main/java/net/bytebuddy/build/gradle/ByteBuddyPlugin.java b/byte-buddy-gradle-plugin/src/main/java/net/bytebuddy/build/gradle/ByteBuddyPlugin.java
new file mode 100644
index 0000000..d3d10fa
--- /dev/null
+++ b/byte-buddy-gradle-plugin/src/main/java/net/bytebuddy/build/gradle/ByteBuddyPlugin.java
@@ -0,0 +1,16 @@
+package net.bytebuddy.build.gradle;
+
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.tasks.compile.AbstractCompile;
+
+/**
+ * A Byte Buddy plugin that appends transformations to all compilation tasks.
+ */
+public class ByteBuddyPlugin implements Plugin<Project> {
+
+ @Override
+ public void apply(Project project) {
+ project.getTasks().withType(AbstractCompile.class, PostCompilationAction.of(project));
+ }
+}
diff --git a/byte-buddy-gradle-plugin/src/main/java/net/bytebuddy/build/gradle/ClassLoaderResolver.java b/byte-buddy-gradle-plugin/src/main/java/net/bytebuddy/build/gradle/ClassLoaderResolver.java
new file mode 100644
index 0000000..48d079f
--- /dev/null
+++ b/byte-buddy-gradle-plugin/src/main/java/net/bytebuddy/build/gradle/ClassLoaderResolver.java
@@ -0,0 +1,95 @@
+package net.bytebuddy.build.gradle;
+
+import net.bytebuddy.ByteBuddy;
+import org.gradle.api.GradleException;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.*;
+
+/**
+ * A class loader resolver for creating class loaders for given class paths.
+ */
+public class ClassLoaderResolver implements Closeable {
+
+ /**
+ * A mapping of files collections to previously created class loaders.
+ */
+ private final Map<Set<? extends File>, ClassLoader> classLoaders;
+
+ /**
+ * Creates a new class loader resolver.
+ */
+ public ClassLoaderResolver() {
+ classLoaders = new HashMap<Set<? extends File>, ClassLoader>();
+ }
+
+ /**
+ * Resolves a class path to a class loader. If a class loader for the same file collection was created
+ * previously, the previous class loader is returned.
+ *
+ * @param classPath The class path to consider.
+ * @return A class loader for the supplied class path.
+ */
+ public ClassLoader resolve(Iterable<? extends File> classPath) {
+ Set<File> classPathList = new LinkedHashSet<File>();
+ for (File file : classPath) {
+ classPathList.add(file);
+ }
+ return resolve(classPathList);
+ }
+
+ /**
+ * Resolves a class path to a class loader. If a class loader for the same file collection was created
+ * previously, the previous class loader is returned.
+ *
+ * @param classPath The class path to consider.
+ * @return A class loader for the supplied class path.
+ */
+ private ClassLoader resolve(Set<? extends File> classPath) {
+ ClassLoader classLoader = classLoaders.get(classPath);
+ if (classLoader == null) {
+ classLoader = doResolve(classPath);
+ classLoaders.put(classPath, classLoader);
+ }
+ return classLoader;
+ }
+
+ /**
+ * Resolves a class path to a class loader.
+ *
+ * @param classPath The class path to consider.
+ * @return A class loader for the supplied class path.
+ */
+ private ClassLoader doResolve(Set<? extends File> classPath) {
+ List<URL> urls = new ArrayList<URL>(classPath.size());
+ for (File file : classPath) {
+ try {
+ urls.add(file.toURI().toURL());
+ } catch (MalformedURLException exception) {
+ throw new GradleException("Cannot resolve " + file + " as URL", exception);
+ }
+ }
+ return new URLClassLoader(urls.toArray(new URL[urls.size()]), ByteBuddy.class.getClassLoader());
+ }
+
+ @Override
+ public void close() throws IOException {
+ for (ClassLoader classLoader : classLoaders.values()) {
+ if (classLoader instanceof Closeable) { // URLClassLoaders are only closeable since Java 1.7.
+ ((Closeable) classLoader).close();
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "ClassLoaderResolver{" +
+ "classLoaders=" + classLoaders +
+ '}';
+ }
+}
diff --git a/byte-buddy-gradle-plugin/src/main/java/net/bytebuddy/build/gradle/Initialization.java b/byte-buddy-gradle-plugin/src/main/java/net/bytebuddy/build/gradle/Initialization.java
new file mode 100644
index 0000000..e69c4f6
--- /dev/null
+++ b/byte-buddy-gradle-plugin/src/main/java/net/bytebuddy/build/gradle/Initialization.java
@@ -0,0 +1,63 @@
+package net.bytebuddy.build.gradle;
+
+import net.bytebuddy.build.EntryPoint;
+import org.gradle.api.GradleException;
+
+import java.io.File;
+
+/**
+ * Defines an entry point for a Byte Buddy transformation in a Gradle build.
+ */
+public class Initialization extends AbstractUserConfiguration {
+
+ /**
+ * The fully-qualified name of the entry point or any constant name of {@link EntryPoint.Default}.
+ */
+ private String entryPoint;
+
+ /**
+ * Creates a default initialization instance.
+ *
+ * @return A default initialization instance.
+ */
+ public static Initialization makeDefault() {
+ Initialization initialization = new Initialization();
+ initialization.setEntryPoint(EntryPoint.Default.REBASE.name());
+ return initialization;
+ }
+
+ /**
+ * Sets the default entry point or any constant name of {@link net.bytebuddy.build.EntryPoint.Default}.
+ *
+ * @param entryPoint The default entry point or any constant name of {@link net.bytebuddy.build.EntryPoint.Default}.
+ */
+ public void setEntryPoint(String entryPoint) {
+ this.entryPoint = entryPoint;
+ }
+
+ /**
+ * Resolves this initialization to an entry point instance.
+ *
+ * @param classLoaderResolver The class loader resolver to use if appropriate.
+ * @param root The root file describing the current tasks classes.
+ * @param classPath The class path of the current task.
+ * @return A resolved entry point.
+ */
+ public EntryPoint getEntryPoint(ClassLoaderResolver classLoaderResolver, File root, Iterable<? extends File> classPath) {
+ if (entryPoint == null || entryPoint.isEmpty()) {
+ throw new GradleException("Entry point name is not defined");
+ }
+ for (EntryPoint.Default entryPoint : EntryPoint.Default.values()) {
+ if (this.entryPoint.equals(entryPoint.name())) {
+ return entryPoint;
+ }
+ }
+ try {
+ return (EntryPoint) Class.forName(entryPoint, false, classLoaderResolver.resolve(getClassPath(root, classPath)))
+ .getDeclaredConstructor()
+ .newInstance();
+ } catch (Exception exception) {
+ throw new GradleException("Cannot create entry point: " + entryPoint, exception);
+ }
+ }
+}
diff --git a/byte-buddy-gradle-plugin/src/main/java/net/bytebuddy/build/gradle/PostCompilationAction.java b/byte-buddy-gradle-plugin/src/main/java/net/bytebuddy/build/gradle/PostCompilationAction.java
new file mode 100644
index 0000000..ebe3b41
--- /dev/null
+++ b/byte-buddy-gradle-plugin/src/main/java/net/bytebuddy/build/gradle/PostCompilationAction.java
@@ -0,0 +1,51 @@
+package net.bytebuddy.build.gradle;
+
+import org.gradle.api.Action;
+import org.gradle.api.Project;
+import org.gradle.api.tasks.compile.AbstractCompile;
+
+/**
+ * A compilation action that applies a class file transformation after a compilation task.
+ */
+public class PostCompilationAction implements Action<AbstractCompile> {
+
+ /**
+ * The current project.
+ */
+ private final Project project;
+
+ /**
+ * The Byte Buddy extension of this build.
+ */
+ private final ByteBuddyExtension byteBuddyExtension;
+
+ /**
+ * Createsa a new post compilation action.
+ *
+ * @param project The current project.
+ * @param byteBuddyExtension The Byte Buddy extension of this build.
+ */
+ protected PostCompilationAction(Project project, ByteBuddyExtension byteBuddyExtension) {
+ this.project = project;
+ this.byteBuddyExtension = byteBuddyExtension;
+ }
+
+ /**
+ * Creates a post compilation action.
+ *
+ * @param project The project to apply the action upon.
+ * @return An appropriate action.
+ */
+ public static Action<AbstractCompile> of(Project project) {
+ return new PostCompilationAction(project, project.getExtensions().create("byteBuddy", ByteBuddyExtension.class, project));
+ }
+
+ @Override
+ public void execute(AbstractCompile task) {
+ if (byteBuddyExtension.implies(task)) {
+ task.doLast(new TransformationAction(project, byteBuddyExtension, task));
+ } else {
+ project.getLogger().info("Skipping non-specified task {}", task.getName());
+ }
+ }
+}
diff --git a/byte-buddy-gradle-plugin/src/main/java/net/bytebuddy/build/gradle/Transformation.java b/byte-buddy-gradle-plugin/src/main/java/net/bytebuddy/build/gradle/Transformation.java
new file mode 100644
index 0000000..6cbb779
--- /dev/null
+++ b/byte-buddy-gradle-plugin/src/main/java/net/bytebuddy/build/gradle/Transformation.java
@@ -0,0 +1,44 @@
+package net.bytebuddy.build.gradle;
+
+import org.gradle.api.GradleException;
+
+/**
+ * A transformation specification to apply during the Gradle plugin's execution.
+ */
+public class Transformation extends AbstractUserConfiguration {
+
+ /**
+ * The fully-qualified name of the plugin type.
+ */
+ private String plugin;
+
+ /**
+ * Returns the plugin type name.
+ *
+ * @return The plugin type name.
+ */
+ public String getPlugin() {
+ if (plugin == null || plugin.isEmpty()) {
+ throw new GradleException("Plugin name was not specified or is empty");
+ }
+ return plugin;
+ }
+
+ /**
+ * Returns the plugin name or {@code null} if it is not set.
+ *
+ * @return The configured plugin name.
+ */
+ public String getRawPlugin() {
+ return plugin;
+ }
+
+ /**
+ * Sets the plugin's name.
+ *
+ * @param plugin The fully-qualified name of the plugin type.
+ */
+ public void setPlugin(String plugin) {
+ this.plugin = plugin;
+ }
+}
diff --git a/byte-buddy-gradle-plugin/src/main/java/net/bytebuddy/build/gradle/TransformationAction.java b/byte-buddy-gradle-plugin/src/main/java/net/bytebuddy/build/gradle/TransformationAction.java
new file mode 100644
index 0000000..3dd964f
--- /dev/null
+++ b/byte-buddy-gradle-plugin/src/main/java/net/bytebuddy/build/gradle/TransformationAction.java
@@ -0,0 +1,248 @@
+package net.bytebuddy.build.gradle;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.build.EntryPoint;
+import net.bytebuddy.build.Plugin;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.scaffold.inline.MethodNameTransformer;
+import net.bytebuddy.implementation.LoadedTypeInitializer;
+import net.bytebuddy.pool.TypePool;
+import org.gradle.api.Action;
+import org.gradle.api.GradleException;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.tasks.compile.AbstractCompile;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Applies a transformation to the classes that were generated by a compilation task.
+ */
+public class TransformationAction implements Action<Task> {
+
+ /**
+ * The class file extension for Java files.
+ */
+ private static final String CLASS_FILE_EXTENSION = ".class";
+
+ /**
+ * The current project.
+ */
+ private final Project project;
+
+ /**
+ * The current project's Byte Buddy extension.
+ */
+ private final ByteBuddyExtension byteBuddyExtension;
+
+ /**
+ * The task to which this transformation was appended.
+ */
+ private final AbstractCompile task;
+
+ /**
+ * Creates a new transformation action.
+ *
+ * @param project The current project.
+ * @param extension The current project's Byte Buddy extension.
+ * @param task The task to which this transformation was appended.
+ */
+ public TransformationAction(Project project, ByteBuddyExtension extension, AbstractCompile task) {
+ this.project = project;
+ this.byteBuddyExtension = extension;
+ this.task = task;
+ }
+
+ @Override
+ public void execute(Task task) {
+ try {
+ processOutputDirectory(this.task.getDestinationDir(), this.task.getClasspath());
+ } catch (IOException exception) {
+ throw new GradleException("Error accessing file system", exception);
+ }
+ }
+
+ /**
+ * Processes all class files within the given directory.
+ *
+ * @param root The root directory to process.
+ * @param classPath A list of class path elements expected by the processed classes.
+ * @throws IOException If an I/O exception occurs.
+ */
+ private void processOutputDirectory(File root, Iterable<? extends File> classPath) throws IOException {
+ if (!root.isDirectory()) {
+ throw new GradleException("Target location does not exist or is no directory: " + root);
+ }
+ ClassLoaderResolver classLoaderResolver = new ClassLoaderResolver();
+ try {
+ List<Plugin> plugins = new ArrayList<Plugin>(byteBuddyExtension.getTransformations().size());
+ for (Transformation transformation : byteBuddyExtension.getTransformations()) {
+ String plugin = transformation.getPlugin();
+ try {
+ plugins.add((Plugin) Class.forName(plugin, false, classLoaderResolver.resolve(transformation.getClassPath(root, classPath)))
+ .getDeclaredConstructor()
+ .newInstance());
+ project.getLogger().info("Created plugin: {}", plugin);
+ } catch (Exception exception) {
+ if (exception instanceof GradleException) {
+ throw (GradleException) exception;
+ }
+ throw new GradleException("Cannot create plugin: " + transformation.getRawPlugin(), exception);
+ }
+ }
+ EntryPoint entryPoint = byteBuddyExtension.getInitialization().getEntryPoint(classLoaderResolver, root, classPath);
+ project.getLogger().info("Resolved entry point: {}", entryPoint);
+ transform(root, classPath, entryPoint, plugins);
+ } finally {
+ classLoaderResolver.close();
+ }
+ }
+
+ /**
+ * Applies all registered transformations.
+ *
+ * @param root The root directory to process.
+ * @param entryPoint The transformation's entry point.
+ * @param classPath A list of class path elements expected by the processed classes.
+ * @param plugins The plugins to apply.
+ * @throws IOException If an I/O exception occurs.
+ */
+ private void transform(File root, Iterable<? extends File> classPath, EntryPoint entryPoint, List<Plugin> plugins) throws IOException {
+ List<ClassFileLocator> classFileLocators = new ArrayList<ClassFileLocator>();
+ classFileLocators.add(new ClassFileLocator.ForFolder(root));
+ for (File artifact : classPath) {
+ classFileLocators.add(artifact.isFile()
+ ? ClassFileLocator.ForJarFile.of(artifact)
+ : new ClassFileLocator.ForFolder(artifact));
+ }
+ ClassFileLocator classFileLocator = new ClassFileLocator.Compound(classFileLocators);
+ try {
+ TypePool typePool = new TypePool.Default.WithLazyResolution(new TypePool.CacheProvider.Simple(),
+ classFileLocator,
+ TypePool.Default.ReaderMode.FAST,
+ TypePool.ClassLoading.ofBootPath());
+ project.getLogger().info("Processing class files located in in: {}", root);
+ ByteBuddy byteBuddy;
+ try {
+ byteBuddy = entryPoint.getByteBuddy();
+ } catch (Throwable throwable) {
+ throw new GradleException("Cannot create Byte Buddy instance", throwable);
+ }
+ processDirectory(root,
+ root,
+ byteBuddy,
+ entryPoint,
+ byteBuddyExtension.getMethodNameTransformer(),
+ classFileLocator,
+ typePool,
+ plugins);
+ } finally {
+ classFileLocator.close();
+ }
+ }
+
+ /**
+ * Processes a directory.
+ *
+ * @param root The root directory to process.
+ * @param folder The currently processed folder.
+ * @param byteBuddy The Byte Buddy instance to use.
+ * @param entryPoint The transformation's entry point.
+ * @param methodNameTransformer The method name transformer to use.
+ * @param classFileLocator The class file locator to use.
+ * @param typePool The type pool to query for type descriptions.
+ * @param plugins The plugins to apply.
+ */
+ private void processDirectory(File root,
+ File folder,
+ ByteBuddy byteBuddy,
+ EntryPoint entryPoint,
+ MethodNameTransformer methodNameTransformer,
+ ClassFileLocator classFileLocator,
+ TypePool typePool,
+ List<Plugin> plugins) {
+ File[] file = folder.listFiles();
+ if (file != null) {
+ for (File aFile : file) {
+ if (aFile.isDirectory()) {
+ processDirectory(root, aFile, byteBuddy, entryPoint, methodNameTransformer, classFileLocator, typePool, plugins);
+ } else if (aFile.isFile() && aFile.getName().endsWith(CLASS_FILE_EXTENSION)) {
+ processClassFile(root,
+ root.toURI().relativize(aFile.toURI()).toString(),
+ byteBuddy,
+ entryPoint,
+ methodNameTransformer,
+ classFileLocator,
+ typePool,
+ plugins);
+ } else {
+ project.getLogger().debug("Skipping ignored file: {}", aFile);
+ }
+ }
+ }
+ }
+
+ /**
+ * Processes a class file.
+ *
+ * @param root The root directory to process.
+ * @param file The class file to process.
+ * @param byteBuddy The Byte Buddy instance to use.
+ * @param entryPoint The transformation's entry point.
+ * @param methodNameTransformer The method name transformer to use.
+ * @param classFileLocator The class file locator to use.
+ * @param typePool The type pool to query for type descriptions.
+ * @param plugins The plugins to apply.
+ */
+ private void processClassFile(File root,
+ String file,
+ ByteBuddy byteBuddy,
+ EntryPoint entryPoint,
+ MethodNameTransformer methodNameTransformer,
+ ClassFileLocator classFileLocator,
+ TypePool typePool,
+ List<Plugin> plugins) {
+ String typeName = file.replace('/', '.').substring(0, file.length() - CLASS_FILE_EXTENSION.length());
+ project.getLogger().debug("Processing class file: {}", typeName);
+ TypeDescription typeDescription = typePool.describe(typeName).resolve();
+ DynamicType.Builder<?> builder;
+ try {
+ builder = entryPoint.transform(typeDescription, byteBuddy, classFileLocator, methodNameTransformer);
+ } catch (Throwable throwable) {
+ throw new GradleException("Cannot transform type: " + typeName, throwable);
+ }
+ boolean transformed = false;
+ for (Plugin plugin : plugins) {
+ try {
+ if (plugin.matches(typeDescription)) {
+ builder = plugin.apply(builder, typeDescription);
+ transformed = true;
+ }
+ } catch (Throwable throwable) {
+ throw new GradleException("Cannot apply " + plugin + " on " + typeName, throwable);
+ }
+ }
+ if (transformed) {
+ project.getLogger().info("Transformed type: {}", typeName);
+ DynamicType dynamicType = builder.make();
+ for (Map.Entry<TypeDescription, LoadedTypeInitializer> entry : dynamicType.getLoadedTypeInitializers().entrySet()) {
+ if (byteBuddyExtension.isFailOnLiveInitializer() && entry.getValue().isAlive()) {
+ throw new GradleException("Cannot apply live initializer for " + entry.getKey());
+ }
+ }
+ try {
+ dynamicType.saveIn(root);
+ } catch (IOException exception) {
+ throw new GradleException("Cannot save " + typeName + " in " + root, exception);
+ }
+ } else {
+ project.getLogger().debug("Skipping non-transformed type: {}", typeName);
+ }
+ }
+}
diff --git a/byte-buddy-gradle-plugin/src/main/java/net/bytebuddy/build/gradle/package-info.java b/byte-buddy-gradle-plugin/src/main/java/net/bytebuddy/build/gradle/package-info.java
new file mode 100644
index 0000000..19b2760
--- /dev/null
+++ b/byte-buddy-gradle-plugin/src/main/java/net/bytebuddy/build/gradle/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * A package containing classes for applying Byte Buddy transformers within a Gradle build.
+ */
+package net.bytebuddy.build.gradle;
diff --git a/byte-buddy-gradle-plugin/src/main/resources/META-INF/gradle-plugins/net.bytebuddy.byte-buddy.properties b/byte-buddy-gradle-plugin/src/main/resources/META-INF/gradle-plugins/net.bytebuddy.byte-buddy.properties
new file mode 100644
index 0000000..21760ce
--- /dev/null
+++ b/byte-buddy-gradle-plugin/src/main/resources/META-INF/gradle-plugins/net.bytebuddy.byte-buddy.properties
@@ -0,0 +1 @@
+implementation-class=net.bytebuddy.build.gradle.ByteBuddyPlugin
diff --git a/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/build/gradle/AbstractUserConfigurationPrefixIterableTest.java b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/build/gradle/AbstractUserConfigurationPrefixIterableTest.java
new file mode 100644
index 0000000..a399b77
--- /dev/null
+++ b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/build/gradle/AbstractUserConfigurationPrefixIterableTest.java
@@ -0,0 +1,39 @@
+package net.bytebuddy.build.gradle;
+
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.Iterator;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class AbstractUserConfigurationPrefixIterableTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private File primary, other;
+
+ @Test
+ public void testIteration() throws Exception {
+ Iterator<? extends File> iterator = new AbstractUserConfiguration.PrefixIterable(primary, Collections.singleton(other)).iterator();
+ assertThat(iterator.hasNext(), is(true));
+ assertThat(iterator.next(), is(primary));
+ assertThat(iterator.hasNext(), is(true));
+ assertThat(iterator.next(), is(other));
+ assertThat(iterator.hasNext(), is(false));
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testRemoval() throws Exception {
+ new AbstractUserConfiguration.PrefixIterable(primary, Collections.<File>emptySet()).iterator().remove();
+ }
+}
diff --git a/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/build/gradle/ByteBuddyExtensionTest.java b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/build/gradle/ByteBuddyExtensionTest.java
new file mode 100644
index 0000000..928cef0
--- /dev/null
+++ b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/build/gradle/ByteBuddyExtensionTest.java
@@ -0,0 +1,147 @@
+package net.bytebuddy.build.gradle;
+
+import groovy.lang.Closure;
+import net.bytebuddy.build.EntryPoint;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.gradle.api.GradleException;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.File;
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.endsWith;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.CoreMatchers.is;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.*;
+
+public class ByteBuddyExtensionTest {
+
+ private static final String FOO = "foo", BAR = "bar";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private Project project;
+
+ @Mock
+ private MethodDescription methodDescription;
+
+ @Mock
+ private Closure<?> closure;
+
+ @Mock
+ private Task task;
+
+ @Test
+ public void testLiveInitializer() throws Exception {
+ ByteBuddyExtension byteBuddyExtension = new ByteBuddyExtension(project);
+ byteBuddyExtension.setFailOnLiveInitializer(false);
+ assertThat(byteBuddyExtension.isFailOnLiveInitializer(), is(false));
+ }
+
+ @Test
+ public void testLiveInitializerDefault() throws Exception {
+ assertThat(new ByteBuddyExtension(project).isFailOnLiveInitializer(), is(true));
+ }
+
+ @Test
+ public void testSuffix() throws Exception {
+ when(methodDescription.getName()).thenReturn(BAR);
+ ByteBuddyExtension byteBuddyExtension = new ByteBuddyExtension(project);
+ byteBuddyExtension.setSuffix(FOO);
+ assertThat(byteBuddyExtension.getMethodNameTransformer().transform(methodDescription), endsWith(FOO));
+ }
+
+ @Test
+ public void testSuffixEmpty() throws Exception {
+ when(methodDescription.getName()).thenReturn(BAR);
+ ByteBuddyExtension byteBuddyExtension = new ByteBuddyExtension(project);
+ byteBuddyExtension.setSuffix("");
+ assertThat(byteBuddyExtension.getMethodNameTransformer().transform(methodDescription), not(BAR));
+ }
+
+ @Test
+ public void testSuffixDefault() throws Exception {
+ when(methodDescription.getName()).thenReturn(BAR);
+ assertThat(new ByteBuddyExtension(project).getMethodNameTransformer().transform(methodDescription), not(BAR));
+ }
+
+ @Test
+ public void testTransformation() throws Exception {
+ when(project.configure(any(Transformation.class), eq(closure))).then(new Answer<Transformation>() {
+ @Override
+ public Transformation answer(InvocationOnMock invocationOnMock) throws Throwable {
+ return invocationOnMock.getArgument(0);
+ }
+ });
+ ByteBuddyExtension byteBuddyExtension = new ByteBuddyExtension(project);
+ byteBuddyExtension.transformation(closure);
+ assertThat(byteBuddyExtension.getTransformations().size(), is(1));
+ }
+
+ @Test
+ public void testInitialization() throws Exception {
+ when(project.configure(any(Initialization.class), eq(closure))).then(new Answer<Initialization>() {
+ @Override
+ public Initialization answer(InvocationOnMock invocationOnMock) throws Throwable {
+ return invocationOnMock.getArgument(0);
+ }
+ });
+ ByteBuddyExtension byteBuddyExtension = new ByteBuddyExtension(project);
+ byteBuddyExtension.initialization(closure);
+ assertThat(byteBuddyExtension.getInitialization(), notNullValue(Initialization.class));
+ }
+
+ @Test
+ public void testInitializationDefault() throws Exception {
+ assertThat(new ByteBuddyExtension(project).getInitialization().getEntryPoint(mock(ClassLoaderResolver.class), mock(File.class), Collections.<File>emptySet()),
+ is((EntryPoint) EntryPoint.Default.REBASE));
+ }
+
+ @Test(expected = GradleException.class)
+ public void testInitializationDuplicate() throws Exception {
+ when(project.configure(any(Initialization.class), eq(closure))).then(new Answer<Initialization>() {
+ @Override
+ public Initialization answer(InvocationOnMock invocationOnMock) throws Throwable {
+ return invocationOnMock.getArgument(0);
+ }
+ });
+ ByteBuddyExtension byteBuddyExtension = new ByteBuddyExtension(project);
+ byteBuddyExtension.initialization(closure);
+ byteBuddyExtension.initialization(closure);
+ }
+
+ @Test
+ public void testTasks() throws Exception {
+ assertThat(new ByteBuddyExtension(project).implies(task), is(true));
+ verifyZeroInteractions(task);
+ }
+
+ @Test
+ public void testTaskExplicitIncluded() throws Exception {
+ ByteBuddyExtension byteBuddyExtension = new ByteBuddyExtension(project);
+ byteBuddyExtension.setTasks(Collections.singleton(FOO));
+ when(task.getName()).thenReturn(FOO);
+ assertThat(byteBuddyExtension.implies(task), is(true));
+ }
+
+ @Test
+ public void testTaskExplicitExcluded() throws Exception {
+ ByteBuddyExtension byteBuddyExtension = new ByteBuddyExtension(project);
+ byteBuddyExtension.setTasks(Collections.singleton(FOO));
+ when(task.getName()).thenReturn(BAR);
+ assertThat(byteBuddyExtension.implies(task), is(false));
+ }
+}
diff --git a/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/build/gradle/ByteBuddyPluginTest.java b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/build/gradle/ByteBuddyPluginTest.java
new file mode 100644
index 0000000..cb85d5f
--- /dev/null
+++ b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/build/gradle/ByteBuddyPluginTest.java
@@ -0,0 +1,102 @@
+package net.bytebuddy.build.gradle;
+
+import net.bytebuddy.test.utility.IntegrationRule;
+import org.gradle.testkit.runner.BuildResult;
+import org.gradle.testkit.runner.GradleRunner;
+import org.gradle.testkit.runner.TaskOutcome;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.rules.MethodRule;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.*;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.CoreMatchers.is;
+
+public class ByteBuddyPluginTest {
+
+ private static final String BYTE_BUDDY_VERSION = System.getProperty("net.bytebuddy.test.version", "1.4.22");
+
+ @ClassRule
+ public static final TemporaryFolder TEMPORARY_FOLDER = new TemporaryFolder();
+
+ private static File pluginJar;
+
+ @Rule
+ public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ @Rule
+ public MethodRule integrationRule = new IntegrationRule();
+
+ @BeforeClass
+ public static void createTestPluginJarFile() throws IOException {
+ pluginJar = makePluginJar();
+ }
+
+ private static File makePluginJar() throws IOException {
+ File pluginFolder = TEMPORARY_FOLDER.newFolder("test-byte-buddy-plugin");
+ store("plugins { id 'java' }\n" +
+ "repositories {\n" +
+ " mavenLocal()\n" +
+ " mavenCentral()\n" +
+ "}\n" +
+ "dependencies {\n" +
+ " compile 'net.bytebuddy:byte-buddy:" + BYTE_BUDDY_VERSION + "'\n" +
+ "}\n", new File(pluginFolder, "build.gradle"));
+ File pluginRoot = new File(pluginFolder, "src/main/java/net/bytebuddy/test");
+ assertThat(pluginRoot.mkdirs(), is(true));
+ store("package net.bytebuddy.test;\n" +
+ "\n" +
+ "import net.bytebuddy.build.Plugin;\n" +
+ "import net.bytebuddy.description.type.TypeDescription;\n" +
+ "import net.bytebuddy.dynamic.DynamicType;\n" +
+ "import net.bytebuddy.implementation.FixedValue;\n" +
+ "\n" +
+ "import static net.bytebuddy.matcher.ElementMatchers.named;\n" +
+ "\n" +
+ "public class SimplePlugin implements Plugin {\n" +
+ " @Override\n" +
+ " public boolean matches(TypeDescription target) {\n" +
+ " return target.getName().equals(\"net.bytebuddy.test.Sample\");\n" +
+ " }\n" +
+ " @Override\n" +
+ " public DynamicType.Builder<?> apply(DynamicType.Builder<?> builder, TypeDescription typeDescription) {\n" +
+ " return builder.method(named(\"foo\")).intercept(FixedValue.value(\"qux\"));\n" +
+ " }\n" +
+ "}\n", new File(pluginRoot, "SimplePlugin.java"));
+ BuildResult result = GradleRunner.create()
+ .withProjectDir(pluginFolder)
+ .withArguments("jar")
+ .build();
+ assertThat(result.task(":jar").getOutcome(), is(TaskOutcome.SUCCESS));
+ return new File(pluginFolder, "build/libs/test-byte-buddy-plugin.jar");
+ }
+
+ private static void store(String source, File target) throws IOException {
+ store(source, target, false);
+ }
+
+ private static void append(String source, File target) throws IOException {
+ store(source, target, true);
+ }
+
+ private static void store(String source, File target, boolean append) throws IOException {
+ InputStream inputStream = new ByteArrayInputStream(source.getBytes("UTF-8"));
+ try {
+ OutputStream outputStream = new FileOutputStream(target, append);
+ try {
+ byte[] buffer = new byte[1024];
+ int length;
+ while ((length = inputStream.read(buffer)) != -1) {
+ outputStream.write(buffer, 0, length);
+ }
+ } finally {
+ outputStream.close();
+ }
+ } finally {
+ inputStream.close();
+ }
+ }
+}
diff --git a/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/build/gradle/ClassLoaderResolverTest.java b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/build/gradle/ClassLoaderResolverTest.java
new file mode 100644
index 0000000..1762603
--- /dev/null
+++ b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/build/gradle/ClassLoaderResolverTest.java
@@ -0,0 +1,44 @@
+package net.bytebuddy.build.gradle;
+
+import net.bytebuddy.test.utility.MockitoRule;
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+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.net.URI;
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.when;
+
+public class ClassLoaderResolverTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private File file;
+
+ @Before
+ public void setUp() throws Exception {
+ when(file.toURI()).thenReturn(new URI("file://foo"));
+ }
+
+ @Test
+ public void testResolution() throws Exception {
+ ClassLoaderResolver classLoaderResolver = new ClassLoaderResolver();
+ assertThat(classLoaderResolver.resolve(Collections.singleton(file)), sameInstance(classLoaderResolver.resolve(Collections.singleton(file))));
+ }
+
+ @Test
+ public void testClose() throws Exception {
+ ClassLoaderResolver classLoaderResolver = new ClassLoaderResolver();
+ classLoaderResolver.resolve(Collections.singleton(file));
+ classLoaderResolver.close();
+ }
+}
diff --git a/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/build/gradle/InitializationTest.java b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/build/gradle/InitializationTest.java
new file mode 100644
index 0000000..70f4f7d
--- /dev/null
+++ b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/build/gradle/InitializationTest.java
@@ -0,0 +1,129 @@
+package net.bytebuddy.build.gradle;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.build.EntryPoint;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.scaffold.inline.MethodNameTransformer;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.gradle.api.GradleException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.Iterator;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class InitializationTest {
+
+ private static final String FOO = "foo";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private ClassLoaderResolver classLoaderResolver;
+
+ @Mock
+ private File file, explicit, other;
+
+ @Test
+ public void testRebase() throws Exception {
+ Initialization initalization = new Initialization();
+ initalization.setEntryPoint(EntryPoint.Default.REBASE.name());
+ assertThat(initalization.getEntryPoint(classLoaderResolver, explicit, Collections.singleton(other)), is((EntryPoint) EntryPoint.Default.REBASE));
+ verifyZeroInteractions(classLoaderResolver);
+ }
+
+ @Test
+ public void testRedefine() throws Exception {
+ Initialization initalization = new Initialization();
+ initalization.setEntryPoint(EntryPoint.Default.REDEFINE.name());
+ assertThat(initalization.getEntryPoint(classLoaderResolver, explicit, Collections.singleton(other)), is((EntryPoint) EntryPoint.Default.REDEFINE));
+ verifyZeroInteractions(classLoaderResolver);
+ }
+
+ @Test
+ public void testRedefineLocal() throws Exception {
+ Initialization initalization = new Initialization();
+ initalization.setEntryPoint(EntryPoint.Default.REDEFINE_LOCAL.name());
+ assertThat(initalization.getEntryPoint(classLoaderResolver, explicit, Collections.singleton(other)), is((EntryPoint) EntryPoint.Default.REDEFINE_LOCAL));
+ verifyZeroInteractions(classLoaderResolver);
+ }
+
+ @Test
+ public void testExplicitClassPath() throws Exception {
+ Initialization initialization = new Initialization();
+ initialization.setClassPath(Collections.singleton(file));
+ Iterator<? extends File> iterator = initialization.getClassPath(explicit, Collections.singleton(other)).iterator();
+ assertThat(iterator.hasNext(), is(true));
+ assertThat(iterator.next(), is(file));
+ assertThat(iterator.hasNext(), is(false));
+ }
+
+ @Test
+ public void testCustom() throws Exception {
+ Initialization initalization = new Initialization();
+ initalization.setEntryPoint(Foo.class.getName());
+ initalization.setClassPath(Collections.singleton(file));
+ when(classLoaderResolver.resolve(Collections.singleton(file))).thenReturn(Foo.class.getClassLoader());
+ assertThat(initalization.getEntryPoint(classLoaderResolver, explicit, Collections.singleton(other)), instanceOf(Foo.class));
+ verify(classLoaderResolver).resolve(Collections.singleton(file));
+ verifyNoMoreInteractions(classLoaderResolver);
+ }
+
+ @Test(expected = GradleException.class)
+ public void testCustomFailed() throws Exception {
+ Initialization initalization = new Initialization();
+ initalization.setClassPath(Collections.singleton(file));
+ initalization.setEntryPoint(FOO);
+ when(classLoaderResolver.resolve(Collections.singleton(file)));
+ initalization.getEntryPoint(classLoaderResolver, explicit, Collections.singleton(other));
+ }
+
+ @Test(expected = GradleException.class)
+ public void testEmpty() throws Exception {
+ Initialization initalization = new Initialization();
+ initalization.setEntryPoint("");
+ initalization.getEntryPoint(classLoaderResolver, explicit, Collections.singleton(other));
+ }
+
+ @Test(expected = GradleException.class)
+ public void testNull() throws Exception {
+ new Initialization().getEntryPoint(classLoaderResolver, explicit, Collections.singleton(other));
+ }
+
+ @Test
+ public void testImplicitClassPath() throws Exception {
+ Initialization initialization = new Initialization();
+ Iterator<? extends File> iterator = initialization.getClassPath(explicit, Collections.singleton(other)).iterator();
+ assertThat(iterator.hasNext(), is(true));
+ assertThat(iterator.next(), is(explicit));
+ assertThat(iterator.hasNext(), is(true));
+ assertThat(iterator.next(), is(other));
+ assertThat(iterator.hasNext(), is(false));
+ }
+
+ public static class Foo implements EntryPoint {
+
+ @Override
+ public ByteBuddy getByteBuddy() {
+ throw new AssertionError();
+ }
+
+ @Override
+ public DynamicType.Builder<?> transform(TypeDescription typeDescription, ByteBuddy byteBuddy,
+ ClassFileLocator classFileLocator,
+ MethodNameTransformer methodNameTransformer) {
+ throw new AssertionError();
+ }
+ }
+}
diff --git a/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/build/gradle/PostCompilationActionTest.java b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/build/gradle/PostCompilationActionTest.java
new file mode 100644
index 0000000..d5582f1
--- /dev/null
+++ b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/build/gradle/PostCompilationActionTest.java
@@ -0,0 +1,53 @@
+package net.bytebuddy.build.gradle;
+
+import net.bytebuddy.test.utility.MockitoRule;
+import org.gradle.api.Action;
+import org.gradle.api.Project;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.tasks.compile.AbstractCompile;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.*;
+
+public class PostCompilationActionTest {
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private Project project;
+
+ @Mock
+ private Logger logger;
+
+ @Mock
+ private ByteBuddyExtension byteBuddyExtension;
+
+ @Mock
+ private AbstractCompile task;
+
+ @Before
+ public void setUp() throws Exception {
+ when(project.getLogger()).thenReturn(logger);
+ }
+
+ @Test
+ public void testApplication() throws Exception {
+ when(byteBuddyExtension.implies(task)).thenReturn(true);
+ new PostCompilationAction(project, byteBuddyExtension).execute(task);
+ verify(task).doLast(any(TransformationAction.class));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testNoApplication() throws Exception {
+ when(byteBuddyExtension.implies(task)).thenReturn(false);
+ new PostCompilationAction(project, byteBuddyExtension).execute(task);
+ verify(task, never()).doLast(any(Action.class));
+ }
+}
diff --git a/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/build/gradle/TransformationActionTest.java b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/build/gradle/TransformationActionTest.java
new file mode 100644
index 0000000..d32e4a3
--- /dev/null
+++ b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/build/gradle/TransformationActionTest.java
@@ -0,0 +1,297 @@
+package net.bytebuddy.build.gradle;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.build.EntryPoint;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.dynamic.scaffold.inline.MethodNameTransformer;
+import net.bytebuddy.implementation.FixedValue;
+import net.bytebuddy.test.*;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.gradle.api.GradleException;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.tasks.compile.AbstractCompile;
+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 org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.*;
+
+import static junit.framework.TestCase.fail;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.CoreMatchers.is;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class TransformationActionTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux", TEMP = ".tmp";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private Project project;
+
+ @Mock
+ private Logger logger;
+
+ @Mock
+ private ByteBuddyExtension byteBuddyExtension;
+
+ @Mock
+ private AbstractCompile parent;
+
+ @Mock
+ private Task task;
+
+ @Mock
+ private Transformation transformation;
+
+ @Mock
+ private Initialization initialization;
+
+ @Mock
+ private FileCollection fileCollection;
+
+ private File target;
+
+ private TransformationAction transformationAction;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() throws Exception {
+ target = File.createTempFile(FOO, TEMP);
+ assertThat(target.delete(), is(true));
+ assertThat(target.mkdir(), is(true));
+ when(project.getLogger()).thenReturn(logger);
+ when(byteBuddyExtension.getTransformations()).thenReturn(Collections.singletonList(transformation));
+ when(byteBuddyExtension.getInitialization()).thenReturn(initialization);
+ when(parent.getDestinationDir()).thenReturn(target);
+ when(transformation.getClassPath(any(File.class), any(Iterable.class))).thenReturn((Iterable) Collections.emptySet());
+ when(parent.getClasspath()).thenReturn(fileCollection);
+ when(fileCollection.iterator()).then(new Answer<Iterator<File>>() {
+ @Override
+ public Iterator<File> answer(InvocationOnMock invocationOnMock) throws Throwable {
+ return Collections.singleton(target).iterator();
+ }
+ });
+ when(byteBuddyExtension.getMethodNameTransformer()).thenReturn(MethodNameTransformer.Suffixing.withRandomSuffix());
+ transformationAction = new TransformationAction(project, byteBuddyExtension, parent);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ assertThat(target.delete(), is(true));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testSimpleTransformation() throws Exception {
+ Set<File> files = new HashSet<File>();
+ files.addAll(addClass("foo.Bar"));
+ files.addAll(addClass("foo.Qux"));
+ try {
+ when(transformation.getPlugin()).thenReturn(SimplePlugin.class.getName());
+ when(initialization.getEntryPoint(any(ClassLoaderResolver.class), any(File.class), any(Iterable.class))).thenReturn(EntryPoint.Default.REBASE);
+ transformationAction.execute(task);
+ ClassLoader classLoader = new URLClassLoader(new URL[]{target.toURI().toURL()});
+ assertMethod(classLoader.loadClass("foo.Bar"), FOO, QUX);
+ assertMethod(classLoader.loadClass("foo.Bar"), BAR, BAR);
+ assertMethod(classLoader.loadClass("foo.Qux"), FOO, FOO);
+ assertMethod(classLoader.loadClass("foo.Qux"), BAR, BAR);
+ } finally {
+ for (File file : files) {
+ assertThat(file.delete(), is(true));
+ }
+ assertThat(new File(target, FOO).delete(), is(true));
+ }
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testLiveInitializer() throws Exception {
+ Set<File> files = new HashSet<File>();
+ files.addAll(addClass("foo.Bar"));
+ try {
+ when(transformation.getPlugin()).thenReturn(LiveInitializerPlugin.class.getName());
+ when(initialization.getEntryPoint(any(ClassLoaderResolver.class), any(File.class), any(Iterable.class))).thenReturn(EntryPoint.Default.REBASE);
+ transformationAction.execute(task);
+ ClassLoader classLoader = new URLClassLoader(new URL[]{target.toURI().toURL()});
+ try {
+ assertMethod(classLoader.loadClass("foo.Bar"), FOO, QUX);
+ fail();
+ } catch (InvocationTargetException exception) {
+ assertThat(exception.getCause(), instanceOf(NullPointerException.class));
+ }
+ } finally {
+ for (File file : files) {
+ assertThat(file.delete(), is(true));
+ }
+ assertThat(new File(target, FOO).delete(), is(true));
+ }
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testLiveInitializerAllowed() throws Exception {
+ Set<File> files = new HashSet<File>();
+ files.addAll(addClass("foo.Bar"));
+ try {
+ when(transformation.getPlugin()).thenReturn(LiveInitializerPlugin.class.getName());
+ when(byteBuddyExtension.isFailOnLiveInitializer()).thenReturn(false);
+ when(initialization.getEntryPoint(any(ClassLoaderResolver.class), any(File.class), any(Iterable.class))).thenReturn(EntryPoint.Default.REBASE);
+ transformationAction.execute(task);
+ ClassLoader classLoader = new URLClassLoader(new URL[]{target.toURI().toURL()});
+ try {
+ assertMethod(classLoader.loadClass("foo.Bar"), FOO, QUX);
+ fail();
+ } catch (InvocationTargetException exception) {
+ assertThat(exception.getCause(), instanceOf(NullPointerException.class));
+ }
+ } finally {
+ for (File file : files) {
+ assertThat(file.delete(), is(true));
+ }
+ assertThat(new File(target, FOO).delete(), is(true));
+ }
+ }
+
+ @Test(expected = GradleException.class)
+ @SuppressWarnings("unchecked")
+ public void testIllegalTransformer() throws Exception {
+ Set<File> files = new HashSet<File>();
+ files.addAll(addClass("foo.Bar"));
+ try {
+ when(transformation.getPlugin()).thenReturn(IllegalTransformPlugin.class.getName());
+ when(initialization.getEntryPoint(any(ClassLoaderResolver.class), any(File.class), any(Iterable.class))).thenReturn(EntryPoint.Default.REBASE);
+ transformationAction.execute(task);
+ } finally {
+ for (File file : files) {
+ assertThat(file.delete(), is(true));
+ }
+ assertThat(new File(target, FOO).delete(), is(true));
+ }
+ }
+
+ @Test(expected = GradleException.class)
+ @SuppressWarnings("unchecked")
+ public void testIllegalTransformation() throws Exception {
+ Set<File> files = new HashSet<File>();
+ files.addAll(addClass("foo.Bar"));
+ try {
+ when(transformation.getPlugin()).thenReturn(IllegalPlugin.class.getName());
+ when(initialization.getEntryPoint(any(ClassLoaderResolver.class), any(File.class), any(Iterable.class))).thenReturn(EntryPoint.Default.REBASE);
+ transformationAction.execute(task);
+ } finally {
+ for (File file : files) {
+ assertThat(file.delete(), is(true));
+ }
+ assertThat(new File(target, FOO).delete(), is(true));
+ }
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testSimpleEntry() throws Exception {
+ Set<File> files = new HashSet<File>();
+ files.addAll(addClass("foo.Bar"));
+ files.addAll(addClass("foo.Qux"));
+ try {
+ when(transformation.getPlugin()).thenReturn(SimplePlugin.class.getName());
+ when(initialization.getEntryPoint(any(ClassLoaderResolver.class), any(File.class), any(Iterable.class))).thenReturn(new SimpleEntryPoint());
+ transformationAction.execute(task);
+ ClassLoader classLoader = new URLClassLoader(new URL[]{target.toURI().toURL()});
+ assertMethod(classLoader.loadClass("foo.Bar"), FOO, QUX);
+ assertMethod(classLoader.loadClass("foo.Bar"), BAR, BAR);
+ assertMethod(classLoader.loadClass("foo.Qux"), FOO, FOO);
+ assertMethod(classLoader.loadClass("foo.Qux"), BAR, BAR);
+ } finally {
+ for (File file : files) {
+ assertThat(file.delete(), is(true));
+ }
+ assertThat(new File(target, FOO).delete(), is(true));
+ }
+ }
+
+ @Test(expected = GradleException.class)
+ @SuppressWarnings("unchecked")
+ public void testIllegalByteBuddy() throws Exception {
+ Set<File> files = new HashSet<File>();
+ files.addAll(addClass("foo.Bar"));
+ files.addAll(addClass("foo.Qux"));
+ try {
+ when(transformation.getPlugin()).thenReturn(SimplePlugin.class.getName());
+ when(initialization.getEntryPoint(any(ClassLoaderResolver.class), any(File.class), any(Iterable.class))).thenReturn(new IllegalEntryPoint());
+ transformationAction.execute(task);
+ ClassLoader classLoader = new URLClassLoader(new URL[]{target.toURI().toURL()});
+ assertMethod(classLoader.loadClass("foo.Bar"), FOO, QUX);
+ assertMethod(classLoader.loadClass("foo.Bar"), BAR, BAR);
+ assertMethod(classLoader.loadClass("foo.Qux"), FOO, FOO);
+ assertMethod(classLoader.loadClass("foo.Qux"), BAR, BAR);
+ } finally {
+ for (File file : files) {
+ assertThat(file.delete(), is(true));
+ }
+ assertThat(new File(target, FOO).delete(), is(true));
+ }
+ }
+
+ @Test(expected = GradleException.class)
+ @SuppressWarnings("unchecked")
+ public void testIllegalTransform() throws Exception {
+ Set<File> files = new HashSet<File>();
+ files.addAll(addClass("foo.Bar"));
+ files.addAll(addClass("foo.Qux"));
+ try {
+ when(transformation.getPlugin()).thenReturn(SimplePlugin.class.getName());
+ when(initialization.getEntryPoint(any(ClassLoaderResolver.class), any(File.class), any(Iterable.class))).thenReturn(new IllegalTransformEntryPoint());
+ transformationAction.execute(task);
+ ClassLoader classLoader = new URLClassLoader(new URL[]{target.toURI().toURL()});
+ assertMethod(classLoader.loadClass("foo.Bar"), FOO, QUX);
+ assertMethod(classLoader.loadClass("foo.Bar"), BAR, BAR);
+ assertMethod(classLoader.loadClass("foo.Qux"), FOO, FOO);
+ assertMethod(classLoader.loadClass("foo.Qux"), BAR, BAR);
+ } finally {
+ for (File file : files) {
+ assertThat(file.delete(), is(true));
+ }
+ assertThat(new File(target, FOO).delete(), is(true));
+ }
+ }
+
+ @Test(expected = GradleException.class)
+ public void testNoDirectory() throws Exception {
+ when(parent.getDestinationDir()).thenReturn(mock(File.class));
+ transformationAction.execute(task);
+ }
+
+ private void assertMethod(Class<?> type, String name, Object expected) throws Exception {
+ assertThat(type.getDeclaredMethod(name).invoke(type.getDeclaredConstructor().newInstance()), is(expected));
+ }
+
+ private Collection<File> addClass(String name) throws IOException {
+ return new ByteBuddy()
+ .subclass(Object.class)
+ .name(name)
+ .defineMethod(FOO, String.class, Visibility.PUBLIC).intercept(FixedValue.value(FOO))
+ .defineMethod(BAR, String.class, Visibility.PUBLIC).intercept(FixedValue.value(BAR))
+ .make()
+ .saveIn(target)
+ .values();
+ }
+}
diff --git a/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/build/gradle/TransformationTest.java b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/build/gradle/TransformationTest.java
new file mode 100644
index 0000000..c6914e0
--- /dev/null
+++ b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/build/gradle/TransformationTest.java
@@ -0,0 +1,72 @@
+package net.bytebuddy.build.gradle;
+
+import net.bytebuddy.test.utility.MockitoRule;
+import org.gradle.api.GradleException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.Iterator;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class TransformationTest {
+
+ private static final String FOO = "foo";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private File file, explicit, other;
+
+ @Test
+ public void testPlugin() throws Exception {
+ Transformation transformation = new Transformation();
+ transformation.setPlugin(FOO);
+ assertThat(transformation.getPlugin(), is(FOO));
+ }
+
+ @Test(expected = GradleException.class)
+ public void testEmptyPlugin() throws Exception {
+ new Transformation().getPlugin();
+ }
+
+ @Test(expected = GradleException.class)
+ public void testUnnamedPlugin() throws Exception {
+ Transformation transformation = new Transformation();
+ transformation.setPlugin("");
+ transformation.getPlugin();
+ }
+
+ @Test
+ public void testRawPlugin() throws Exception {
+ assertThat(new Transformation().getRawPlugin(), nullValue(String.class));
+ }
+
+ @Test
+ public void testExplicitClassPath() throws Exception {
+ Transformation transformation = new Transformation();
+ transformation.setClassPath(Collections.singleton(file));
+ Iterator<? extends File> iterator = transformation.getClassPath(explicit, Collections.singleton(other)).iterator();
+ assertThat(iterator.hasNext(), is(true));
+ assertThat(iterator.next(), is(file));
+ assertThat(iterator.hasNext(), is(false));
+ }
+
+ @Test
+ public void testImplicitClassPath() throws Exception {
+ Transformation transformation = new Transformation();
+ Iterator<? extends File> iterator = transformation.getClassPath(explicit, Collections.singleton(other)).iterator();
+ assertThat(iterator.hasNext(), is(true));
+ assertThat(iterator.next(), is(explicit));
+ assertThat(iterator.hasNext(), is(true));
+ assertThat(iterator.next(), is(other));
+ assertThat(iterator.hasNext(), is(false));
+ }
+}
diff --git a/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/test/IllegalEntryPoint.java b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/test/IllegalEntryPoint.java
new file mode 100644
index 0000000..b99d1f9
--- /dev/null
+++ b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/test/IllegalEntryPoint.java
@@ -0,0 +1,24 @@
+package net.bytebuddy.test;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.build.EntryPoint;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.scaffold.inline.MethodNameTransformer;
+
+public class IllegalEntryPoint implements EntryPoint {
+
+ @Override
+ public ByteBuddy getByteBuddy() {
+ throw new RuntimeException();
+ }
+
+ @Override
+ public DynamicType.Builder<?> transform(TypeDescription typeDescription,
+ ByteBuddy byteBuddy,
+ ClassFileLocator classFileLocator,
+ MethodNameTransformer methodNameTransformer) {
+ throw new RuntimeException();
+ }
+}
diff --git a/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/test/IllegalPlugin.java b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/test/IllegalPlugin.java
new file mode 100644
index 0000000..49938f7
--- /dev/null
+++ b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/test/IllegalPlugin.java
@@ -0,0 +1,18 @@
+package net.bytebuddy.test;
+
+import net.bytebuddy.build.Plugin;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+
+public class IllegalPlugin implements Plugin {
+
+ @Override
+ public boolean matches(TypeDescription target) {
+ throw new RuntimeException();
+ }
+
+ @Override
+ public DynamicType.Builder<?> apply(DynamicType.Builder<?> builder, TypeDescription typeDescription) {
+ throw new RuntimeException();
+ }
+}
diff --git a/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/test/IllegalTransformEntryPoint.java b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/test/IllegalTransformEntryPoint.java
new file mode 100644
index 0000000..0306273
--- /dev/null
+++ b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/test/IllegalTransformEntryPoint.java
@@ -0,0 +1,24 @@
+package net.bytebuddy.test;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.build.EntryPoint;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.scaffold.inline.MethodNameTransformer;
+
+public class IllegalTransformEntryPoint implements EntryPoint {
+
+ @Override
+ public ByteBuddy getByteBuddy() {
+ return new ByteBuddy();
+ }
+
+ @Override
+ public DynamicType.Builder<?> transform(TypeDescription typeDescription,
+ ByteBuddy byteBuddy,
+ ClassFileLocator classFileLocator,
+ MethodNameTransformer methodNameTransformer) {
+ throw new RuntimeException();
+ }
+}
diff --git a/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/test/IllegalTransformPlugin.java b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/test/IllegalTransformPlugin.java
new file mode 100644
index 0000000..1f25911
--- /dev/null
+++ b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/test/IllegalTransformPlugin.java
@@ -0,0 +1,22 @@
+package net.bytebuddy.test;
+
+import net.bytebuddy.build.Plugin;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+
+public class IllegalTransformPlugin implements Plugin {
+
+ public IllegalTransformPlugin() {
+ throw new RuntimeException();
+ }
+
+ @Override
+ public boolean matches(TypeDescription target) {
+ throw new AssertionError();
+ }
+
+ @Override
+ public DynamicType.Builder<?> apply(DynamicType.Builder<?> builder, TypeDescription typeDescription) {
+ throw new AssertionError();
+ }
+}
diff --git a/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/test/LiveInitializerPlugin.java b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/test/LiveInitializerPlugin.java
new file mode 100644
index 0000000..ce0a3eb
--- /dev/null
+++ b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/test/LiveInitializerPlugin.java
@@ -0,0 +1,25 @@
+package net.bytebuddy.test;
+
+import net.bytebuddy.build.Plugin;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.implementation.MethodDelegation;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+
+public class LiveInitializerPlugin implements Plugin {
+
+ @Override
+ public boolean matches(TypeDescription target) {
+ return target.getName().equals("foo.Bar");
+ }
+
+ @Override
+ public DynamicType.Builder<?> apply(DynamicType.Builder<?> builder, TypeDescription typeDescription) {
+ return builder.method(named("foo")).intercept(MethodDelegation.to(new LiveInitializerPlugin()));
+ }
+
+ public String intercept() {
+ return "qux";
+ }
+}
diff --git a/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/test/SimpleEntryPoint.java b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/test/SimpleEntryPoint.java
new file mode 100644
index 0000000..b4af6d4
--- /dev/null
+++ b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/test/SimpleEntryPoint.java
@@ -0,0 +1,24 @@
+package net.bytebuddy.test;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.build.EntryPoint;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.scaffold.inline.MethodNameTransformer;
+
+public class SimpleEntryPoint implements EntryPoint {
+
+ @Override
+ public ByteBuddy getByteBuddy() {
+ return new ByteBuddy();
+ }
+
+ @Override
+ public DynamicType.Builder<?> transform(TypeDescription typeDescription,
+ ByteBuddy byteBuddy,
+ ClassFileLocator classFileLocator,
+ MethodNameTransformer methodNameTransformer) {
+ return byteBuddy.rebase(typeDescription, classFileLocator, methodNameTransformer);
+ }
+}
diff --git a/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/test/SimplePlugin.java b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/test/SimplePlugin.java
new file mode 100644
index 0000000..edb3f60
--- /dev/null
+++ b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/test/SimplePlugin.java
@@ -0,0 +1,21 @@
+package net.bytebuddy.test;
+
+import net.bytebuddy.build.Plugin;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.implementation.FixedValue;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+
+public class SimplePlugin implements Plugin {
+
+ @Override
+ public boolean matches(TypeDescription target) {
+ return target.getName().equals("foo.Bar");
+ }
+
+ @Override
+ public DynamicType.Builder<?> apply(DynamicType.Builder<?> builder, TypeDescription typeDescription) {
+ return builder.method(named("foo")).intercept(FixedValue.value("qux"));
+ }
+}
diff --git a/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/test/utility/IntegrationRule.java b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/test/utility/IntegrationRule.java
new file mode 100644
index 0000000..aa0dbff
--- /dev/null
+++ b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/test/utility/IntegrationRule.java
@@ -0,0 +1,40 @@
+package net.bytebuddy.test.utility;
+
+import org.junit.rules.MethodRule;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.logging.Logger;
+
+public class IntegrationRule implements MethodRule {
+
+ private static final String PROPERTY_KEY = "net.bytebuddy.test.integration";
+
+ private final boolean integration;
+
+ public IntegrationRule() {
+ integration = Boolean.getBoolean(PROPERTY_KEY);
+ }
+
+ @Override
+ public Statement apply(Statement base, FrameworkMethod method, Object target) {
+ return !integration && method.getAnnotation(Enforce.class) != null
+ ? new NoOpStatement()
+ : base;
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface Enforce {
+ /* empty */
+ }
+
+ private static class NoOpStatement extends Statement {
+
+ @Override
+ public void evaluate() throws Throwable {
+ Logger.getLogger("net.bytebuddy").warning("Ignored test case that is only to be run on the CI server due to long runtime");
+ }
+ }
+}
diff --git a/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/test/utility/MockitoRule.java b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/test/utility/MockitoRule.java
new file mode 100644
index 0000000..10df0c0
--- /dev/null
+++ b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/test/utility/MockitoRule.java
@@ -0,0 +1,30 @@
+package net.bytebuddy.test.utility;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * A rule that applies Mockito's annotations to any test. This is preferred over the Mockito runner since it allows
+ * to use tests with parameters that require a specific runner.
+ */
+public class MockitoRule implements TestRule {
+
+ private final Object target;
+
+ public MockitoRule(Object target) {
+ this.target = target;
+ }
+
+ @Override
+ public Statement apply(final Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ MockitoAnnotations.initMocks(target);
+ base.evaluate();
+ }
+ };
+ }
+}
diff --git a/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/test/utility/ObjectPropertyAssertion.java b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/test/utility/ObjectPropertyAssertion.java
new file mode 100644
index 0000000..9ce154d
--- /dev/null
+++ b/byte-buddy-gradle-plugin/src/test/java/net/bytebuddy/test/utility/ObjectPropertyAssertion.java
@@ -0,0 +1,372 @@
+package net.bytebuddy.test.utility;
+
+import net.bytebuddy.utility.CompoundList;
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class ObjectPropertyAssertion<T> {
+
+ private static final boolean DEFAULT_BOOLEAN = false, OTHER_BOOLEAN = true;
+
+ private static final byte DEFAULT_BYTE = 1, OTHER_BYTE = 42;
+
+ private static final char DEFAULT_CHAR = 1, OTHER_CHAR = 42;
+
+ private static final short DEFAULT_SHORT = 1, OTHER_SHORT = 42;
+
+ private static final int DEFAULT_INT = 1, OTHER_INT = 42;
+
+ private static final long DEFAULT_LONG = 1, OTHER_LONG = 42;
+
+ private static final float DEFAULT_FLOAT = 1, OTHER_FLOAT = 42;
+
+ private static final double DEFAULT_DOUBLE = 1, OTHER_DOUBLE = 42;
+
+ private static final String DEFAULT_STRING = "foo", OTHER_STRING = "bar";
+
+ private final Class<T> type;
+
+ private final ApplicableRefinement refinement;
+
+ private final ApplicableGenerator generator;
+
+ private final ApplicableCreator creator;
+
+ private final boolean skipSynthetic;
+
+ private final String optionalToStringRegex;
+
+ private final boolean skipToString;
+
+ private final Set<String> ignoredFields;
+
+ private ObjectPropertyAssertion(Class<T> type,
+ ApplicableGenerator generator,
+ ApplicableRefinement refinement,
+ ApplicableCreator creator,
+ boolean skipSynthetic,
+ boolean skipToString,
+ String optionalToStringRegex,
+ Set<String> ignoredFields) {
+ this.type = type;
+ this.generator = generator;
+ this.refinement = refinement;
+ this.creator = creator;
+ this.skipSynthetic = skipSynthetic;
+ this.skipToString = skipToString;
+ this.optionalToStringRegex = optionalToStringRegex;
+ this.ignoredFields = ignoredFields;
+ }
+
+ public static <S> ObjectPropertyAssertion<S> of(Class<S> type) {
+ return new ObjectPropertyAssertion<S>(type,
+ new ApplicableGenerator(),
+ new ApplicableRefinement(),
+ new ApplicableCreator(),
+ false,
+ false,
+ null,
+ new HashSet<String>());
+ }
+
+ public ObjectPropertyAssertion<T> refine(Refinement<?> refinement) {
+ return new ObjectPropertyAssertion<T>(type,
+ generator,
+ this.refinement.with(refinement),
+ creator,
+ skipSynthetic,
+ skipToString,
+ optionalToStringRegex,
+ ignoredFields);
+ }
+
+ public ObjectPropertyAssertion<T> generate(Generator<?> generator) {
+ return new ObjectPropertyAssertion<T>(type,
+ this.generator.with(generator),
+ refinement,
+ creator,
+ skipSynthetic,
+ skipToString,
+ optionalToStringRegex,
+ ignoredFields);
+ }
+
+ public ObjectPropertyAssertion<T> create(Creator<?> creator) {
+ return new ObjectPropertyAssertion<T>(type,
+ generator,
+ refinement,
+ this.creator.with(creator),
+ skipSynthetic,
+ skipToString,
+ optionalToStringRegex,
+ ignoredFields);
+ }
+
+ public ObjectPropertyAssertion<T> skipSynthetic() {
+ return new ObjectPropertyAssertion<T>(type, generator, refinement, creator, true, skipToString, optionalToStringRegex, ignoredFields);
+ }
+
+ public ObjectPropertyAssertion<T> skipToString() {
+ return new ObjectPropertyAssertion<T>(type, generator, refinement, creator, skipSynthetic, true, optionalToStringRegex, ignoredFields);
+ }
+
+ public ObjectPropertyAssertion<T> specificToString(String stringRegex) {
+ return new ObjectPropertyAssertion<T>(type, generator, refinement, creator, skipSynthetic, skipToString, stringRegex, ignoredFields);
+ }
+
+ public ObjectPropertyAssertion<T> ignoreFields(String... field) {
+ Set<String> ignoredFields = new HashSet<String>(this.ignoredFields);
+ ignoredFields.addAll(Arrays.asList(field));
+ return new ObjectPropertyAssertion<T>(type, generator, refinement, creator, skipSynthetic, skipToString, optionalToStringRegex, ignoredFields);
+ }
+
+ public void apply() throws IllegalAccessException, InvocationTargetException, InstantiationException {
+ if (type.isEnum()) {
+ for (T instance : type.getEnumConstants()) {
+ assertThat(instance.toString(), is(type.getCanonicalName()
+ .substring(type.getPackage().getName().length() + 1) + "." + ((Enum<?>) instance).name()));
+ }
+ return;
+ }
+ for (Constructor<?> constructor : type.getDeclaredConstructors()) {
+ if (constructor.isSynthetic() && skipSynthetic) {
+ continue;
+ }
+ constructor.setAccessible(true);
+ Class<?>[] parameterTypes = constructor.getParameterTypes();
+ Object[] actualArguments = new Object[parameterTypes.length];
+ Object[] otherArguments = new Object[parameterTypes.length];
+ int index = 0;
+ for (Class<?> parameterType : parameterTypes) {
+ putInstance(parameterType, actualArguments, otherArguments, index++);
+ }
+ int testIndex = 0;
+ @SuppressWarnings("unchecked")
+ T instance = (T) constructor.newInstance(actualArguments);
+ assertThat(instance, is(instance));
+ assertThat(instance, not(equalTo(null)));
+ assertThat(instance, not(new Object()));
+ Object similarInstance = constructor.newInstance(actualArguments);
+ assertThat(instance.hashCode(), is(similarInstance.hashCode()));
+ assertThat(instance, is(similarInstance));
+ if (skipToString) {
+ assertThat(instance.toString(), notNullValue());
+ } else if (optionalToStringRegex == null) {
+ checkString(instance);
+ } else {
+ assertThat(instance.toString(), new RegexMatcher(optionalToStringRegex));
+ }
+ for (Object otherArgument : otherArguments) {
+ Object[] compareArguments = new Object[actualArguments.length];
+ int argumentIndex = 0;
+ for (Object actualArgument : actualArguments) {
+ if (argumentIndex == testIndex) {
+ compareArguments[argumentIndex] = otherArgument;
+ } else {
+ compareArguments[argumentIndex] = actualArgument;
+ }
+ argumentIndex++;
+ }
+ Object unlikeInstance = constructor.newInstance(compareArguments);
+ assertThat(instance.hashCode(), not(unlikeInstance));
+ assertThat(instance, not(unlikeInstance));
+ testIndex++;
+ }
+ }
+ }
+
+ private void checkString(T instance) {
+ assertThat(instance.toString(), CoreMatchers.startsWith(type.getCanonicalName()
+ .substring(type.getPackage().getName().length() + 1) + "{"));
+ assertThat(instance.toString(), endsWith("}"));
+ Class<?> currentType = type;
+ do {
+ for (Field field : type.getDeclaredFields()) {
+ if (!field.isSynthetic() && !Modifier.isStatic(field.getModifiers()) && !ignoredFields.contains(field.getName())) {
+ assertThat(instance.toString(), containsString(field.getName()));
+ }
+ }
+ } while ((currentType = currentType.getSuperclass()) != Object.class);
+ }
+
+ private void putInstance(Class<?> parameterType, Object actualArguments, Object otherArguments, int index) {
+ Object actualArgument, otherArgument;
+ if (parameterType == boolean.class) {
+ actualArgument = DEFAULT_BOOLEAN;
+ otherArgument = OTHER_BOOLEAN;
+ } else if (parameterType == byte.class) {
+ actualArgument = DEFAULT_BYTE;
+ otherArgument = OTHER_BYTE;
+ } else if (parameterType == char.class) {
+ actualArgument = DEFAULT_CHAR;
+ otherArgument = OTHER_CHAR;
+ } else if (parameterType == short.class) {
+ actualArgument = DEFAULT_SHORT;
+ otherArgument = OTHER_SHORT;
+ } else if (parameterType == int.class) {
+ actualArgument = DEFAULT_INT;
+ otherArgument = OTHER_INT;
+ } else if (parameterType == long.class) {
+ actualArgument = DEFAULT_LONG;
+ otherArgument = OTHER_LONG;
+ } else if (parameterType == float.class) {
+ actualArgument = DEFAULT_FLOAT;
+ otherArgument = OTHER_FLOAT;
+ } else if (parameterType == double.class) {
+ actualArgument = DEFAULT_DOUBLE;
+ otherArgument = OTHER_DOUBLE;
+ } else if (parameterType.isEnum()) {
+ Object[] enumConstants = parameterType.getEnumConstants();
+ if (enumConstants.length == 1) {
+ throw new IllegalArgumentException("Enum with only one constant: " + parameterType);
+ }
+ actualArgument = enumConstants[0];
+ otherArgument = enumConstants[1];
+ } else if (parameterType.isArray()) {
+ actualArgument = Array.newInstance(parameterType.getComponentType(), 1);
+ otherArgument = Array.newInstance(parameterType.getComponentType(), 1);
+ putInstance(parameterType.getComponentType(), actualArgument, otherArgument, 0);
+ } else {
+ actualArgument = creator.replace(parameterType, generator, false);
+ refinement.apply(actualArgument);
+ otherArgument = creator.replace(parameterType, generator, true);
+ refinement.apply(otherArgument);
+ }
+ Array.set(actualArguments, index, actualArgument);
+ Array.set(otherArguments, index, otherArgument);
+ }
+
+ public interface Refinement<T> {
+
+ void apply(T mock);
+ }
+
+ public interface Generator<T> {
+
+ Class<? extends T> generate();
+ }
+
+ public interface Creator<T> {
+
+ T create();
+ }
+
+ private static class ApplicableRefinement {
+
+ private final List<Refinement<?>> refinements;
+
+ private ApplicableRefinement() {
+ refinements = Collections.emptyList();
+ }
+
+ private ApplicableRefinement(List<Refinement<?>> refinements) {
+ this.refinements = refinements;
+ }
+
+ @SuppressWarnings("unchecked")
+ private void apply(Object mock) {
+ for (Refinement refinement : refinements) {
+ ParameterizedType generic = (ParameterizedType) refinement.getClass().getGenericInterfaces()[0];
+ Class<?> restrained = generic.getActualTypeArguments()[0] instanceof ParameterizedType
+ ? (Class<?>) ((ParameterizedType) generic.getActualTypeArguments()[0]).getRawType()
+ : (Class<?>) generic.getActualTypeArguments()[0];
+ if (restrained.isInstance(mock)) {
+ refinement.apply(mock);
+ }
+ }
+ }
+
+ private ApplicableRefinement with(Refinement<?> refinement) {
+ return new ApplicableRefinement(CompoundList.of(refinements, refinement));
+ }
+ }
+
+ private static class ApplicableGenerator {
+
+ private final List<Generator<?>> generators;
+
+ private ApplicableGenerator() {
+ generators = Collections.emptyList();
+ }
+
+ private ApplicableGenerator(List<Generator<?>> generators) {
+ this.generators = generators;
+ }
+
+ private Object generate(Class<?> type, boolean alternative) {
+ for (Generator<?> generator : generators) {
+ ParameterizedType generic = (ParameterizedType) generator.getClass().getGenericInterfaces()[0];
+ Class<?> restrained = generic.getActualTypeArguments()[0] instanceof ParameterizedType
+ ? (Class<?>) ((ParameterizedType) generic.getActualTypeArguments()[0]).getRawType()
+ : (Class<?>) generic.getActualTypeArguments()[0];
+ if (type.isAssignableFrom(restrained)) {
+ type = generator.generate();
+ }
+ }
+ return type == String.class
+ ? alternative ? OTHER_STRING : DEFAULT_STRING
+ : mock(type);
+ }
+
+ private ApplicableGenerator with(Generator<?> generator) {
+ return new ApplicableGenerator(CompoundList.of(generators, generator));
+ }
+ }
+
+ private static class ApplicableCreator {
+
+ private final List<Creator<?>> creators;
+
+ private ApplicableCreator() {
+ creators = Collections.emptyList();
+ }
+
+ private ApplicableCreator(List<Creator<?>> creators) {
+ this.creators = creators;
+ }
+
+ private Object replace(Class<?> type, ApplicableGenerator generator, boolean alternative) {
+ for (Creator<?> creator : creators) {
+ ParameterizedType generic = (ParameterizedType) creator.getClass().getGenericInterfaces()[0];
+ Class<?> restrained = generic.getActualTypeArguments()[0] instanceof ParameterizedType
+ ? (Class<?>) ((ParameterizedType) generic.getActualTypeArguments()[0]).getRawType()
+ : (Class<?>) generic.getActualTypeArguments()[0];
+ if (type.isAssignableFrom(restrained)) {
+ return creator.create();
+ }
+ }
+ return generator.generate(type, alternative);
+ }
+
+ private ApplicableCreator with(Creator<?> creator) {
+ return new ApplicableCreator(CompoundList.of(creators, creator));
+ }
+ }
+
+ private static class RegexMatcher extends TypeSafeMatcher<String> {
+
+ private final String regex;
+
+ public RegexMatcher(final String regex) {
+ this.regex = regex;
+ }
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText("matches regex='" + regex + "'");
+ }
+
+ @Override
+ public boolean matchesSafely(final String string) {
+ return string.matches(regex);
+ }
+ }
+}
diff --git a/byte-buddy-maven-plugin/pom.xml b/byte-buddy-maven-plugin/pom.xml
new file mode 100644
index 0000000..0c791f5
--- /dev/null
+++ b/byte-buddy-maven-plugin/pom.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>byte-buddy-parent</artifactId>
+ <groupId>net.bytebuddy</groupId>
+ <version>1.7.1</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>byte-buddy-maven-plugin</artifactId>
+ <packaging>maven-plugin</packaging>
+
+ <name>Byte Buddy (Maven plugin)</name>
+ <description>A plugin for post-processing class files via Byte Buddy in a Maven build.</description>
+
+ <properties>
+ <!-- Newer Maven versions require Java 1.7. -->
+ <version.maven>3.2.5</version.maven>
+ <version.maven.annotation>3.4</version.maven.annotation>
+ <version.maven.aether>1.1.0</version.maven.aether>
+ <version.maven.test>3.3.0</version.maven.test>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-plugin-api</artifactId>
+ <version>${version.maven}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven.plugin-tools</groupId>
+ <artifactId>maven-plugin-annotations</artifactId>
+ <version>${version.maven.annotation}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.aether</groupId>
+ <artifactId>aether-api</artifactId>
+ <version>${version.maven.aether}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.aether</groupId>
+ <artifactId>aether-util</artifactId>
+ <version>${version.maven.aether}</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <scope>test</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy-agent</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-compat</artifactId>
+ <version>${version.maven}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven.plugin-testing</groupId>
+ <artifactId>maven-plugin-testing-harness</artifactId>
+ <version>${version.maven.test}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/byte-buddy-maven-plugin/src/main/java/net/bytebuddy/build/maven/AbstractUserConfiguration.java b/byte-buddy-maven-plugin/src/main/java/net/bytebuddy/build/maven/AbstractUserConfiguration.java
new file mode 100644
index 0000000..c59f330
--- /dev/null
+++ b/byte-buddy-maven-plugin/src/main/java/net/bytebuddy/build/maven/AbstractUserConfiguration.java
@@ -0,0 +1,74 @@
+package net.bytebuddy.build.maven;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+/**
+ * An abstract base class for a user configuration implying a Maven coordinate.
+ */
+ at SuppressFBWarnings(value = "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", justification = "Written to by Maven")
+public class AbstractUserConfiguration {
+
+ /**
+ * The group id of the project containing the plugin type or {@code null} if the current project's group id should be used.
+ */
+ protected String groupId;
+
+ /**
+ * The artifact id of the project containing the plugin type or {@code null} if the current project's artifact id should be used.
+ */
+ protected String artifactId;
+
+ /**
+ * The version of the project containing the plugin type or {@code null} if the current project's version should be used.
+ */
+ protected String version;
+
+ /**
+ * Returns the group id to use.
+ *
+ * @param groupId The current project's group id.
+ * @return The group id to use.
+ */
+ protected String getGroupId(String groupId) {
+ return this.groupId == null || this.groupId.isEmpty()
+ ? groupId
+ : this.groupId;
+ }
+
+ /**
+ * Returns the artifact id to use.
+ *
+ * @param artifactId The current project's artifact id.
+ * @return The artifact id to use.
+ */
+ protected String getArtifactId(String artifactId) {
+ return this.artifactId == null || this.artifactId.isEmpty()
+ ? artifactId
+ : this.artifactId;
+ }
+
+
+ /**
+ * Returns the version to use.
+ *
+ * @param version The current project's version.
+ * @return The version to use.
+ */
+ protected String getVersion(String version) {
+ return this.version == null || this.version.isEmpty()
+ ? version
+ : this.version;
+ }
+
+ /**
+ * Resolves this transformation to a Maven coordinate.
+ *
+ * @param groupId The current project's build id.
+ * @param artifactId The current project's artifact id.
+ * @param version The current project's version.
+ * @return The resolved Maven coordinate.
+ */
+ public MavenCoordinate asCoordinate(String groupId, String artifactId, String version) {
+ return new MavenCoordinate(getGroupId(groupId), getArtifactId(artifactId), getVersion(version));
+ }
+}
diff --git a/byte-buddy-maven-plugin/src/main/java/net/bytebuddy/build/maven/ByteBuddyMojo.java b/byte-buddy-maven-plugin/src/main/java/net/bytebuddy/build/maven/ByteBuddyMojo.java
new file mode 100644
index 0000000..1fadd64
--- /dev/null
+++ b/byte-buddy-maven-plugin/src/main/java/net/bytebuddy/build/maven/ByteBuddyMojo.java
@@ -0,0 +1,441 @@
+package net.bytebuddy.build.maven;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.build.EntryPoint;
+import net.bytebuddy.build.Plugin;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.scaffold.inline.MethodNameTransformer;
+import net.bytebuddy.implementation.LoadedTypeInitializer;
+import net.bytebuddy.pool.TypePool;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.*;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.repository.RemoteRepository;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A Maven plugin for applying Byte Buddy transformations during a build.
+ */
+public abstract class ByteBuddyMojo extends AbstractMojo {
+
+ /**
+ * The file extension of a Java class file.
+ */
+ private static final String CLASS_FILE_EXTENSION = ".class";
+
+ /**
+ * The built project's group id.
+ */
+ @Parameter(defaultValue = "${project.groupId}", required = true, readonly = true)
+ protected String groupId;
+
+ /**
+ * The built project's artifact id.
+ */
+ @Parameter(defaultValue = "${project.artifactId}", required = true, readonly = true)
+ protected String artifactId;
+
+ /**
+ * The built project's version.
+ */
+ @Parameter(defaultValue = "${project.version}", required = true, readonly = true)
+ protected String version;
+
+ /**
+ * <p>
+ * The list of transformations. A transformation <b>must</b> specify the {@code plugin} property, containing the name of a class to apply.
+ * Additionally, it is possible to optionally specify Maven coordinates for a project that contains this plugin class as {@code groupId},
+ * {@code artifactId} and {@code version}. If any of the latter properties is not set, this projects coordinate is used.
+ * </p>
+ * <p>
+ * For example, the following configuration applies the {@code foo.Bar} class which must implement {@link Plugin} from artifact
+ * {@code transform-artifact} with this project's group and version:
+ * </p>
+ * <blockquote><pre>{@code
+ * <transformations>
+ * <transformation>
+ * <plugin>foo.Bar< /plugin>
+ * <artifactId>transform-artifact< /artifactId>
+ * < /transformation>
+ * < /transformations>
+ * }</pre></blockquote>
+ * <p>
+ * If the list of {@code transformations} is empty or is not supplied at all, this plugin does not apply but prints a warning.
+ * </p>
+ */
+ @Parameter
+ protected List<Transformation> transformations;
+
+ /**
+ * <p>
+ * The initializer used for creating a {@link ByteBuddy} instance and for applying a transformation. By default, a type is
+ * rebased. The initializer's {@code entryPoint} property can be set to any constant name of {@link EntryPoint.Default} or
+ * to a class name. If the latter applies, it is possible to set Maven coordinates for a Maven plugin which defines this
+ * class where any property defaults to this project's coordinates.
+ * </p>
+ * <p>
+ * For example, the following configuration applies the {@code foo.Qux} class which must implement {@link EntryPoint} from
+ * artifact {@code initialization-artifact} with this project's group and version:
+ * </p>
+ * <blockquote><pre>{@code
+ * <initialization>
+ * <entryPoint>foo.Qux< /entryPoint>
+ * <artifactId>initialization-artifact< /artifactId>
+ * < /initialization>
+ * }</pre></blockquote>
+ */
+ @Parameter
+ protected Initialization initialization;
+
+ /**
+ * Specifies the method name suffix that is used when type's method need to be rebased. If this property is not
+ * set or is empty, a random suffix will be appended to any rebased method. If this property is set, the supplied
+ * value is appended to the original method name.
+ */
+ @Parameter
+ protected String suffix;
+
+ /**
+ * When transforming classes during build time, it is not possible to apply any transformations which require a class
+ * in its loaded state. Such transformations might imply setting a type's static field to a user interceptor or similar
+ * transformations. If this property is set to {@code false}, this plugin does not throw an exception if such a live
+ * initializer is defined during a transformation process.
+ */
+ @Parameter(defaultValue = "true", required = true)
+ protected boolean failOnLiveInitializer;
+
+ /**
+ * When set to {@code true}, this mojo is not applied to the current module.
+ */
+ @Parameter(defaultValue = "false", required = true)
+ protected boolean skip;
+
+ /**
+ * When set to {@code true}, this mojo warns of an non-existent output directory.
+ */
+ @Parameter(defaultValue = "true", required = true)
+ protected boolean warnOnMissingOutputDirectory;
+
+ /**
+ * The currently used repository system.
+ */
+ @Component
+ protected RepositorySystem repositorySystem;
+
+ /**
+ * The currently used system session for the repository system.
+ */
+ @Parameter(defaultValue = "${repositorySystemSession}", required = true, readonly = true)
+ protected RepositorySystemSession repositorySystemSession;
+
+ /**
+ * A list of all remote repositories.
+ */
+ @Parameter(defaultValue = "${project.remoteProjectRepositories}", required = true, readonly = true)
+ protected List<RemoteRepository> remoteRepositories;
+
+ @Override
+ public void execute() throws MojoExecutionException, MojoFailureException {
+ if (skip) {
+ getLog().info("Not applying instrumentation as a result of plugin configuration.");
+ return;
+ } else if (transformations == null || transformations.isEmpty()) {
+ getLog().warn("No transformations are specified. Skipping plugin application.");
+ return;
+ }
+ try {
+ processOutputDirectory(new File(getOutputDirectory()), getClassPathElements());
+ } catch (IOException exception) {
+ throw new MojoFailureException("Error during writing process", exception);
+ }
+ }
+
+ /**
+ * Returns the output directory to search for class files.
+ *
+ * @return The output directory to search for class files.
+ */
+ protected abstract String getOutputDirectory();
+
+ /**
+ * Returns the class path elements of the relevant output directory.
+ *
+ * @return The class path elements of the relevant output directory.
+ */
+ protected abstract List<String> getClassPathElements();
+
+ /**
+ * Processes all class files within the given directory.
+ *
+ * @param root The root directory to process.
+ * @param classPath A list of class path elements expected by the processed classes.
+ * @throws MojoExecutionException If the user configuration results in an error.
+ * @throws MojoFailureException If the plugin application raises an error.
+ * @throws IOException If an I/O exception occurs.
+ */
+ @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Applies Maven exception wrapper")
+ private void processOutputDirectory(File root, List<? extends String> classPath) throws MojoExecutionException, MojoFailureException, IOException {
+ if (!root.exists()) {
+ String message = "Skipping instrumentation due to missing directory: " + root;
+ if (warnOnMissingOutputDirectory) {
+ getLog().warn(message);
+ } else {
+ getLog().info(message);
+ }
+ return;
+ } else if (!root.isDirectory()) {
+ throw new MojoExecutionException("Not a directory: " + root);
+ }
+ ClassLoaderResolver classLoaderResolver = new ClassLoaderResolver(getLog(), repositorySystem, repositorySystemSession, remoteRepositories);
+ try {
+ List<Plugin> plugins = new ArrayList<Plugin>(transformations.size());
+ for (Transformation transformation : transformations) {
+ String plugin = transformation.getPlugin();
+ try {
+ plugins.add((Plugin) Class.forName(plugin, false, classLoaderResolver.resolve(transformation.asCoordinate(groupId, artifactId, version)))
+ .getDeclaredConstructor()
+ .newInstance());
+ getLog().info("Created plugin: " + plugin);
+ } catch (Exception exception) {
+ throw new MojoExecutionException("Cannot create plugin: " + transformation.getRawPlugin(), exception);
+ }
+ }
+ EntryPoint entryPoint = (initialization == null
+ ? Initialization.makeDefault()
+ : initialization).getEntryPoint(classLoaderResolver, groupId, artifactId, version);
+ getLog().info("Resolved entry point: " + entryPoint);
+ transform(root, entryPoint, classPath, plugins);
+ } finally {
+ classLoaderResolver.close();
+ }
+ }
+
+ /**
+ * Applies all registered transformations.
+ *
+ * @param root The root directory to process.
+ * @param entryPoint The transformation's entry point.
+ * @param classPath A list of class path elements expected by the processed classes.
+ * @param plugins The plugins to apply.
+ * @throws MojoExecutionException If the user configuration results in an error.
+ * @throws MojoFailureException If the plugin application raises an error.
+ * @throws IOException If an I/O exception occurs.
+ */
+ private void transform(File root,
+ EntryPoint entryPoint,
+ List<? extends String> classPath,
+ List<Plugin> plugins) throws MojoExecutionException, MojoFailureException, IOException {
+ List<ClassFileLocator> classFileLocators = new ArrayList<ClassFileLocator>(classPath.size() + 1);
+ classFileLocators.add(new ClassFileLocator.ForFolder(root));
+ for (String target : classPath) {
+ File artifact = new File(target);
+ classFileLocators.add(artifact.isFile()
+ ? ClassFileLocator.ForJarFile.of(artifact)
+ : new ClassFileLocator.ForFolder(artifact));
+ }
+ ClassFileLocator classFileLocator = new ClassFileLocator.Compound(classFileLocators);
+ try {
+ TypePool typePool = new TypePool.Default.WithLazyResolution(new TypePool.CacheProvider.Simple(),
+ classFileLocator,
+ TypePool.Default.ReaderMode.FAST,
+ TypePool.ClassLoading.ofBootPath());
+ getLog().info("Processing class files located in in: " + root);
+ ByteBuddy byteBuddy;
+ try {
+ byteBuddy = entryPoint.getByteBuddy();
+ } catch (Throwable throwable) {
+ throw new MojoExecutionException("Cannot create Byte Buddy instance", throwable);
+ }
+ processDirectory(root,
+ root,
+ byteBuddy,
+ entryPoint,
+ suffix == null || suffix.isEmpty()
+ ? MethodNameTransformer.Suffixing.withRandomSuffix()
+ : new MethodNameTransformer.Suffixing(suffix),
+ classFileLocator,
+ typePool,
+ plugins);
+ } finally {
+ classFileLocator.close();
+ }
+ }
+
+ /**
+ * Processes a directory.
+ *
+ * @param root The root directory to process.
+ * @param folder The currently processed folder.
+ * @param byteBuddy The Byte Buddy instance to use.
+ * @param entryPoint The transformation's entry point.
+ * @param methodNameTransformer The method name transformer to use.
+ * @param classFileLocator The class file locator to use.
+ * @param typePool The type pool to query for type descriptions.
+ * @param plugins The plugins to apply.
+ * @throws MojoExecutionException If the user configuration results in an error.
+ * @throws MojoFailureException If the plugin application raises an error.
+ */
+ private void processDirectory(File root,
+ File folder,
+ ByteBuddy byteBuddy,
+ EntryPoint entryPoint,
+ MethodNameTransformer methodNameTransformer,
+ ClassFileLocator classFileLocator,
+ TypePool typePool,
+ List<Plugin> plugins) throws MojoExecutionException, MojoFailureException {
+ File[] file = folder.listFiles();
+ if (file != null) {
+ for (File aFile : file) {
+ if (aFile.isDirectory()) {
+ processDirectory(root, aFile, byteBuddy, entryPoint, methodNameTransformer, classFileLocator, typePool, plugins);
+ } else if (aFile.isFile() && aFile.getName().endsWith(CLASS_FILE_EXTENSION)) {
+ processClassFile(root,
+ root.toURI().relativize(aFile.toURI()).toString(),
+ byteBuddy,
+ entryPoint,
+ methodNameTransformer,
+ classFileLocator,
+ typePool,
+ plugins);
+ } else {
+ getLog().debug("Skipping ignored file: " + aFile);
+ }
+ }
+ }
+ }
+
+ /**
+ * Processes a class file.
+ *
+ * @param root The root directory to process.
+ * @param file The class file to process.
+ * @param byteBuddy The Byte Buddy instance to use.
+ * @param entryPoint The transformation's entry point.
+ * @param methodNameTransformer The method name transformer to use.
+ * @param classFileLocator The class file locator to use.
+ * @param typePool The type pool to query for type descriptions.
+ * @param plugins The plugins to apply.
+ * @throws MojoExecutionException If the user configuration results in an error.
+ * @throws MojoFailureException If the plugin application raises an error.
+ */
+ private void processClassFile(File root,
+ String file,
+ ByteBuddy byteBuddy,
+ EntryPoint entryPoint,
+ MethodNameTransformer methodNameTransformer,
+ ClassFileLocator classFileLocator,
+ TypePool typePool,
+ List<Plugin> plugins) throws MojoExecutionException, MojoFailureException {
+ String typeName = file.replace('/', '.').substring(0, file.length() - CLASS_FILE_EXTENSION.length());
+ getLog().debug("Processing class file: " + typeName);
+ TypeDescription typeDescription = typePool.describe(typeName).resolve();
+ DynamicType.Builder<?> builder;
+ try {
+ builder = entryPoint.transform(typeDescription, byteBuddy, classFileLocator, methodNameTransformer);
+ } catch (Throwable throwable) {
+ throw new MojoExecutionException("Cannot transform type: " + typeName, throwable);
+ }
+ boolean transformed = false;
+ for (Plugin plugin : plugins) {
+ try {
+ if (plugin.matches(typeDescription)) {
+ builder = plugin.apply(builder, typeDescription);
+ transformed = true;
+ }
+ } catch (Throwable throwable) {
+ throw new MojoExecutionException("Cannot apply " + plugin + " on " + typeName, throwable);
+ }
+ }
+ if (transformed) {
+ getLog().info("Transformed type: " + typeName);
+ DynamicType dynamicType = builder.make();
+ for (Map.Entry<TypeDescription, LoadedTypeInitializer> entry : dynamicType.getLoadedTypeInitializers().entrySet()) {
+ if (failOnLiveInitializer && entry.getValue().isAlive()) {
+ throw new MojoExecutionException("Cannot apply live initializer for " + entry.getKey());
+ }
+ }
+ try {
+ dynamicType.saveIn(root);
+ } catch (IOException exception) {
+ throw new MojoFailureException("Cannot save " + typeName + " in " + root, exception);
+ }
+ } else {
+ getLog().debug("Skipping non-transformed type: " + typeName);
+ }
+ }
+
+ /**
+ * A Byte Buddy plugin that transforms a project's production class files.
+ */
+ @Mojo(name = "transform",
+ defaultPhase = LifecyclePhase.PROCESS_CLASSES,
+ threadSafe = true,
+ requiresDependencyResolution = ResolutionScope.COMPILE)
+ public static class ForProductionTypes extends ByteBuddyMojo {
+
+ /**
+ * The current build's production output directory.
+ */
+ @Parameter(defaultValue = "${project.build.outputDirectory}", required = true, readonly = true)
+ protected String outputDirectory;
+
+ /**
+ * The production class path.
+ */
+ @Parameter(defaultValue = "${project.compileClasspathElements}", required = true, readonly = true)
+ protected List<String> compileClasspathElements;
+
+ @Override
+ protected String getOutputDirectory() {
+ return outputDirectory;
+ }
+
+ @Override
+ protected List<String> getClassPathElements() {
+ return compileClasspathElements;
+ }
+ }
+
+ /**
+ * A Byte Buddy plugin that transforms a project's test class files.
+ */
+ @Mojo(name = "transform-test",
+ defaultPhase = LifecyclePhase.PROCESS_TEST_CLASSES,
+ threadSafe = true,
+ requiresDependencyResolution = ResolutionScope.TEST)
+ public static class ForTestTypes extends ByteBuddyMojo {
+
+ /**
+ * The current build's test output directory.
+ */
+ @Parameter(defaultValue = "${project.build.testOutputDirectory}", required = true, readonly = true)
+ protected String testOutputDirectory;
+
+ /**
+ * The test class path.
+ */
+ @Parameter(defaultValue = "${project.testClasspathElements}", required = true, readonly = true)
+ protected List<String> testClasspathElements;
+
+ @Override
+ protected String getOutputDirectory() {
+ return testOutputDirectory;
+ }
+
+ @Override
+ protected List<String> getClassPathElements() {
+ return testClasspathElements;
+ }
+ }
+}
diff --git a/byte-buddy-maven-plugin/src/main/java/net/bytebuddy/build/maven/ClassLoaderResolver.java b/byte-buddy-maven-plugin/src/main/java/net/bytebuddy/build/maven/ClassLoaderResolver.java
new file mode 100644
index 0000000..d3d75ec
--- /dev/null
+++ b/byte-buddy-maven-plugin/src/main/java/net/bytebuddy/build/maven/ClassLoaderResolver.java
@@ -0,0 +1,131 @@
+package net.bytebuddy.build.maven;
+
+import net.bytebuddy.ByteBuddy;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugin.logging.Log;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.collection.CollectRequest;
+import org.eclipse.aether.collection.DependencyCollectionException;
+import org.eclipse.aether.graph.Dependency;
+import org.eclipse.aether.graph.DependencyNode;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.resolution.DependencyRequest;
+import org.eclipse.aether.resolution.DependencyResolutionException;
+import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A resolver that transforms a Maven coordinate into a class loader that can view the dependencies implied by this coordinate.
+ */
+public class ClassLoaderResolver implements Closeable {
+
+ /**
+ * The Maven log dispatcher.
+ */
+ private final Log log;
+
+ /**
+ * The repository system to use.
+ */
+ private final RepositorySystem repositorySystem;
+
+ /**
+ * The repository system session to use.
+ */
+ private final RepositorySystemSession repositorySystemSession;
+
+ /**
+ * A list of remote repositories available.
+ */
+ private final List<RemoteRepository> remoteRepositories;
+
+ /**
+ * A mapping of Maven coordinates to already existing class loaders.
+ */
+ private final Map<MavenCoordinate, ClassLoader> classLoaders;
+
+ /**
+ * Creates a new class loader resolver.
+ *
+ * @param log The Maven log dispatcher.
+ * @param repositorySystem The repository system to use.
+ * @param repositorySystemSession The repository system session to use.
+ * @param remoteRepositories A list of remote repositories available.
+ */
+ public ClassLoaderResolver(Log log, RepositorySystem repositorySystem, RepositorySystemSession repositorySystemSession, List<RemoteRepository> remoteRepositories) {
+ this.log = log;
+ this.repositorySystem = repositorySystem;
+ this.repositorySystemSession = repositorySystemSession;
+ this.remoteRepositories = remoteRepositories;
+ classLoaders = new HashMap<MavenCoordinate, ClassLoader>();
+ }
+
+
+ /**
+ * Resolves a Maven coordinate to a class loader that can load all of the coordinates classes. If a Maven coordinate was resolved previously,
+ * the previously created class loader is returned.
+ *
+ * @param mavenCoordinate The Maven coordinate to resolve.
+ * @return A class loader that references all of the class loader's dependencies and which is a child of this class's class loader.
+ * @throws MojoExecutionException If the user configuration results in an error.
+ * @throws MojoFailureException If the plugin application raises an error.
+ */
+ public ClassLoader resolve(MavenCoordinate mavenCoordinate) throws MojoFailureException, MojoExecutionException {
+ ClassLoader classLoader = classLoaders.get(mavenCoordinate);
+ if (classLoader == null) {
+ classLoader = doResolve(mavenCoordinate);
+ classLoaders.put(mavenCoordinate, classLoader);
+ }
+ return classLoader;
+ }
+
+ /**
+ * Resolves a Maven coordinate to a class loader that can load all of the coordinates classes.
+ *
+ * @param mavenCoordinate The Maven coordinate to resolve.
+ * @return A class loader that references all of the class loader's dependencies and which is a child of this class's class loader.
+ * @throws MojoExecutionException If the user configuration results in an error.
+ * @throws MojoFailureException If the plugin application raises an error.
+ */
+ private ClassLoader doResolve(MavenCoordinate mavenCoordinate) throws MojoExecutionException, MojoFailureException {
+ List<URL> urls = new ArrayList<URL>();
+ log.info("Resolving transformer dependency: " + mavenCoordinate);
+ try {
+ DependencyNode root = repositorySystem.collectDependencies(repositorySystemSession, new CollectRequest(new Dependency(mavenCoordinate.asArtifact(), "runtime"), remoteRepositories)).getRoot();
+ repositorySystem.resolveDependencies(repositorySystemSession, new DependencyRequest().setRoot(root));
+ PreorderNodeListGenerator preorderNodeListGenerator = new PreorderNodeListGenerator();
+ root.accept(preorderNodeListGenerator);
+ for (Artifact artifact : preorderNodeListGenerator.getArtifacts(false)) {
+ urls.add(artifact.getFile().toURI().toURL());
+ }
+ } catch (DependencyCollectionException exception) {
+ throw new MojoExecutionException("Could not collect dependencies for " + mavenCoordinate, exception);
+ } catch (DependencyResolutionException exception) {
+ throw new MojoExecutionException("Could not resolve dependencies for " + mavenCoordinate, exception);
+ } catch (MalformedURLException exception) {
+ throw new MojoFailureException("Could not resolve file as URL for " + mavenCoordinate, exception);
+ }
+ return new URLClassLoader(urls.toArray(new URL[urls.size()]), ByteBuddy.class.getClassLoader());
+ }
+
+ @Override
+ public void close() throws IOException {
+ for (ClassLoader classLoader : classLoaders.values()) {
+ if (classLoader instanceof Closeable) { // URLClassLoaders are only closeable since Java 1.7.
+ ((Closeable) classLoader).close();
+ }
+ }
+ }
+}
diff --git a/byte-buddy-maven-plugin/src/main/java/net/bytebuddy/build/maven/Initialization.java b/byte-buddy-maven-plugin/src/main/java/net/bytebuddy/build/maven/Initialization.java
new file mode 100644
index 0000000..156be9a
--- /dev/null
+++ b/byte-buddy-maven-plugin/src/main/java/net/bytebuddy/build/maven/Initialization.java
@@ -0,0 +1,57 @@
+package net.bytebuddy.build.maven;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import net.bytebuddy.build.EntryPoint;
+import org.apache.maven.plugin.MojoExecutionException;
+
+/**
+ * Defines a configuration for a Maven build's type transformation.
+ */
+ at SuppressFBWarnings(value = "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", justification = "Written to by Maven")
+public class Initialization extends AbstractUserConfiguration {
+
+ /**
+ * The fully-qualified name of the entry point or any constant name of {@link EntryPoint.Default}.
+ */
+ protected String entryPoint;
+
+ /**
+ * Creates a default initialization instance.
+ *
+ * @return A default initialization instance.
+ */
+ public static Initialization makeDefault() {
+ Initialization initialization = new Initialization();
+ initialization.entryPoint = EntryPoint.Default.REBASE.name();
+ return initialization;
+ }
+
+ /**
+ * Resolves the described entry point.
+ *
+ * @param classLoaderResolver The class loader resolved to use.
+ * @param groupId This project's group id.
+ * @param artifactId This project's artifact id.
+ * @param version This project's version id.
+ * @return The resolved entry point.
+ * @throws MojoExecutionException If the entry point cannot be created.
+ */
+ @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Applies Maven exception wrapper")
+ public EntryPoint getEntryPoint(ClassLoaderResolver classLoaderResolver, String groupId, String artifactId, String version) throws MojoExecutionException {
+ if (entryPoint == null || entryPoint.isEmpty()) {
+ throw new MojoExecutionException("Entry point name is not defined");
+ }
+ for (EntryPoint.Default entryPoint : EntryPoint.Default.values()) {
+ if (this.entryPoint.equals(entryPoint.name())) {
+ return entryPoint;
+ }
+ }
+ try {
+ return (EntryPoint) Class.forName(entryPoint, false, classLoaderResolver.resolve(asCoordinate(groupId, artifactId, version)))
+ .getDeclaredConstructor()
+ .newInstance();
+ } catch (Exception exception) {
+ throw new MojoExecutionException("Cannot create entry point: " + entryPoint, exception);
+ }
+ }
+}
diff --git a/byte-buddy-maven-plugin/src/main/java/net/bytebuddy/build/maven/MavenCoordinate.java b/byte-buddy-maven-plugin/src/main/java/net/bytebuddy/build/maven/MavenCoordinate.java
new file mode 100644
index 0000000..06c5f69
--- /dev/null
+++ b/byte-buddy-maven-plugin/src/main/java/net/bytebuddy/build/maven/MavenCoordinate.java
@@ -0,0 +1,74 @@
+package net.bytebuddy.build.maven;
+
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.DefaultArtifact;
+
+/**
+ * A Maven coordinate.
+ */
+public class MavenCoordinate {
+
+ /**
+ * The project's group id.
+ */
+ private final String groupId;
+
+ /**
+ * The project's artifact id.
+ */
+ private final String artifactId;
+
+ /**
+ * The project's version.
+ */
+ private final String version;
+
+ /**
+ * Creates a new Maven coordinate.
+ *
+ * @param groupId The project's group id.
+ * @param artifactId The project's artifact id.
+ * @param version The project's version.
+ */
+ protected MavenCoordinate(String groupId, String artifactId, String version) {
+ this.groupId = groupId;
+ this.artifactId = artifactId;
+ this.version = version;
+ }
+
+ /**
+ * Returns this coordinate as a jar-file {@link Artifact}.
+ *
+ * @return An artifact representation of this coordinate.
+ */
+ public Artifact asArtifact() {
+ return new DefaultArtifact(groupId, artifactId, "jar", version);
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (this == object) return true;
+ if (!(object instanceof MavenCoordinate)) return false;
+ MavenCoordinate that = (MavenCoordinate) object;
+ return groupId.equals(that.groupId)
+ && artifactId.equals(that.artifactId)
+ && version.equals(that.version);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = groupId.hashCode();
+ result = 31 * result + artifactId.hashCode();
+ result = 31 * result + version.hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "MavenCoordinate{" +
+ "groupId='" + groupId + '\'' +
+ ", artifactId='" + artifactId + '\'' +
+ ", version='" + version + '\'' +
+ '}';
+ }
+}
diff --git a/byte-buddy-maven-plugin/src/main/java/net/bytebuddy/build/maven/Transformation.java b/byte-buddy-maven-plugin/src/main/java/net/bytebuddy/build/maven/Transformation.java
new file mode 100644
index 0000000..6ef0527
--- /dev/null
+++ b/byte-buddy-maven-plugin/src/main/java/net/bytebuddy/build/maven/Transformation.java
@@ -0,0 +1,38 @@
+package net.bytebuddy.build.maven;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import org.apache.maven.plugin.MojoExecutionException;
+
+/**
+ * A transformation specification to apply during the plugin's execution.
+ */
+ at SuppressFBWarnings(value = "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", justification = "Written to by Maven")
+public class Transformation extends AbstractUserConfiguration {
+
+ /**
+ * The fully-qualified name of the plugin type.
+ */
+ protected String plugin;
+
+ /**
+ * Returns the plugin type name.
+ *
+ * @return The plugin type name.
+ * @throws MojoExecutionException If the plugin name was not specified or is empty.
+ */
+ public String getPlugin() throws MojoExecutionException {
+ if (plugin == null || plugin.isEmpty()) {
+ throw new MojoExecutionException("Plugin name was not specified");
+ }
+ return plugin;
+ }
+
+ /**
+ * Returns the plugin name or {@code null} if it is not set.
+ *
+ * @return The configured plugin name.
+ */
+ public String getRawPlugin() {
+ return plugin;
+ }
+}
diff --git a/byte-buddy-maven-plugin/src/main/java/net/bytebuddy/build/maven/package-info.java b/byte-buddy-maven-plugin/src/main/java/net/bytebuddy/build/maven/package-info.java
new file mode 100644
index 0000000..b3f6158
--- /dev/null
+++ b/byte-buddy-maven-plugin/src/main/java/net/bytebuddy/build/maven/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * A package containing classes for applying Byte Buddy transformers within a Maven build.
+ */
+package net.bytebuddy.build.maven;
diff --git a/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/build/maven/ByteBuddyMojoTest.java b/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/build/maven/ByteBuddyMojoTest.java
new file mode 100644
index 0000000..bf48382
--- /dev/null
+++ b/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/build/maven/ByteBuddyMojoTest.java
@@ -0,0 +1,297 @@
+package net.bytebuddy.build.maven;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.implementation.FixedValue;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.apache.maven.plugin.Mojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.testing.MojoRule;
+import org.apache.maven.plugin.testing.SilentLog;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.collection.CollectRequest;
+import org.eclipse.aether.collection.CollectResult;
+import org.eclipse.aether.graph.DependencyNode;
+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 org.mockito.Mockito;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import static junit.framework.TestCase.fail;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+public class ByteBuddyMojoTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux", TEMP = "tmp";
+
+ @Rule
+ public MojoRule mojoRule = new MojoRule();
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private RepositorySystem repositorySystem;
+
+ @Mock
+ private DependencyNode root;
+
+ private File project;
+
+ @Before
+ public void setUp() throws Exception {
+ when(repositorySystem.collectDependencies(Mockito.<RepositorySystemSession>any(), Mockito.<CollectRequest>any()))
+ .thenReturn(new CollectResult(new CollectRequest()).setRoot(root));
+ project = File.createTempFile(FOO, TEMP);
+ assertThat(project.delete(), is(true));
+ assertThat(project.mkdir(), is(true));
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ assertThat(project.delete(), is(true));
+ }
+
+ @Test
+ public void testEmptyTransformation() throws Exception {
+ execute("transform", "empty");
+ }
+
+ @Test
+ public void testSimpleTransformation() throws Exception {
+ Set<File> files = new HashSet<File>();
+ files.addAll(addClass("foo.Bar"));
+ files.addAll(addClass("foo.Qux"));
+ try {
+ execute("transform", "simple");
+ ClassLoader classLoader = new URLClassLoader(new URL[]{project.toURI().toURL()});
+ assertMethod(classLoader.loadClass("foo.Bar"), FOO, QUX);
+ assertMethod(classLoader.loadClass("foo.Bar"), BAR, BAR);
+ assertMethod(classLoader.loadClass("foo.Qux"), FOO, FOO);
+ assertMethod(classLoader.loadClass("foo.Qux"), BAR, BAR);
+ } finally {
+ for (File file : files) {
+ assertThat(file.delete(), is(true));
+ }
+ assertThat(new File(project, FOO).delete(), is(true));
+ }
+ }
+
+ @Test
+ public void testSimpleTransformationWithSuffix() throws Exception {
+ Set<File> files = new HashSet<File>();
+ files.addAll(addClass("foo.Bar"));
+ files.addAll(addClass("foo.Qux"));
+ try {
+ execute("transform", "suffix");
+ ClassLoader classLoader = new URLClassLoader(new URL[]{project.toURI().toURL()});
+ assertMethod(classLoader.loadClass("foo.Bar"), FOO, QUX);
+ assertMethod(classLoader.loadClass("foo.Bar"), BAR, BAR);
+ assertThat(classLoader.loadClass("foo.Bar").getDeclaredMethod(FOO + "$" + QUX), notNullValue(Method.class));
+ assertMethod(classLoader.loadClass("foo.Qux"), FOO, FOO);
+ assertMethod(classLoader.loadClass("foo.Qux"), BAR, BAR);
+ } finally {
+ for (File file : files) {
+ assertThat(file.delete(), is(true));
+ }
+ assertThat(new File(project, FOO).delete(), is(true));
+ }
+ }
+
+ @Test(expected = MojoExecutionException.class)
+ public void testLiveInitializer() throws Exception {
+ Set<File> files = new HashSet<File>();
+ files.addAll(addClass("foo.Bar"));
+ try {
+ execute("transform", "live");
+ ClassLoader classLoader = new URLClassLoader(new URL[]{project.toURI().toURL()});
+ assertMethod(classLoader.loadClass("foo.Bar"), FOO, QUX);
+ } finally {
+ for (File file : files) {
+ assertThat(file.delete(), is(true));
+ }
+ assertThat(new File(project, FOO).delete(), is(true));
+ }
+ }
+
+ @Test
+ public void testLiveInitializerAllowed() throws Exception {
+ Set<File> files = new HashSet<File>();
+ files.addAll(addClass("foo.Bar"));
+ try {
+ execute("transform", "live.allowed");
+ ClassLoader classLoader = new URLClassLoader(new URL[]{project.toURI().toURL()});
+ try {
+ assertMethod(classLoader.loadClass("foo.Bar"), FOO, QUX);
+ fail();
+ } catch (InvocationTargetException exception) {
+ assertThat(exception.getCause(), instanceOf(NullPointerException.class));
+ }
+ } finally {
+ for (File file : files) {
+ assertThat(file.delete(), is(true));
+ }
+ assertThat(new File(project, FOO).delete(), is(true));
+ }
+ }
+
+ @Test(expected = MojoExecutionException.class)
+ public void testIllegalTransformer() throws Exception {
+ Set<File> files = new HashSet<File>();
+ files.addAll(addClass("foo.Bar"));
+ try {
+ execute("transform", "illegal");
+ } finally {
+ for (File file : files) {
+ assertThat(file.delete(), is(true));
+ }
+ assertThat(new File(project, FOO).delete(), is(true));
+ }
+ }
+
+ @Test(expected = MojoExecutionException.class)
+ public void testIllegalTransformation() throws Exception {
+ Set<File> files = new HashSet<File>();
+ files.addAll(addClass("foo.Bar"));
+ try {
+ execute("transform", "illegal.apply");
+ } finally {
+ for (File file : files) {
+ assertThat(file.delete(), is(true));
+ }
+ assertThat(new File(project, FOO).delete(), is(true));
+ }
+ }
+
+ @Test
+ public void testTestTransformation() throws Exception {
+ Set<File> files = new HashSet<File>();
+ files.addAll(addClass("foo.Bar"));
+ files.addAll(addClass("foo.Qux"));
+ try {
+ execute("transform-test", "simple");
+ ClassLoader classLoader = new URLClassLoader(new URL[]{project.toURI().toURL()});
+ assertMethod(classLoader.loadClass("foo.Bar"), FOO, QUX);
+ assertMethod(classLoader.loadClass("foo.Bar"), BAR, BAR);
+ assertMethod(classLoader.loadClass("foo.Qux"), FOO, FOO);
+ assertMethod(classLoader.loadClass("foo.Qux"), BAR, BAR);
+ } finally {
+ for (File file : files) {
+ assertThat(file.delete(), is(true));
+ }
+ assertThat(new File(project, FOO).delete(), is(true));
+ }
+ }
+
+ @Test
+ public void testSimpleEntry() throws Exception {
+ Set<File> files = new HashSet<File>();
+ files.addAll(addClass("foo.Bar"));
+ files.addAll(addClass("foo.Qux"));
+ try {
+ execute("transform", "entry");
+ ClassLoader classLoader = new URLClassLoader(new URL[]{project.toURI().toURL()});
+ assertMethod(classLoader.loadClass("foo.Bar"), FOO, QUX);
+ assertMethod(classLoader.loadClass("foo.Bar"), BAR, BAR);
+ assertMethod(classLoader.loadClass("foo.Qux"), FOO, FOO);
+ assertMethod(classLoader.loadClass("foo.Qux"), BAR, BAR);
+ } finally {
+ for (File file : files) {
+ assertThat(file.delete(), is(true));
+ }
+ assertThat(new File(project, FOO).delete(), is(true));
+ }
+ }
+
+ @Test(expected = MojoExecutionException.class)
+ public void testIllegalByteBuddy() throws Exception {
+ Set<File> files = new HashSet<File>();
+ files.addAll(addClass("foo.Bar"));
+ files.addAll(addClass("foo.Qux"));
+ try {
+ execute("transform", "entry.illegal");
+ ClassLoader classLoader = new URLClassLoader(new URL[]{project.toURI().toURL()});
+ assertMethod(classLoader.loadClass("foo.Bar"), FOO, QUX);
+ assertMethod(classLoader.loadClass("foo.Bar"), BAR, BAR);
+ assertMethod(classLoader.loadClass("foo.Qux"), FOO, FOO);
+ assertMethod(classLoader.loadClass("foo.Qux"), BAR, BAR);
+ } finally {
+ for (File file : files) {
+ assertThat(file.delete(), is(true));
+ }
+ assertThat(new File(project, FOO).delete(), is(true));
+ }
+ }
+
+ @Test(expected = MojoExecutionException.class)
+ public void testIllegalTransform() throws Exception {
+ Set<File> files = new HashSet<File>();
+ files.addAll(addClass("foo.Bar"));
+ files.addAll(addClass("foo.Qux"));
+ try {
+ execute("transform", "entry.illegal.transform");
+ ClassLoader classLoader = new URLClassLoader(new URL[]{project.toURI().toURL()});
+ assertMethod(classLoader.loadClass("foo.Bar"), FOO, QUX);
+ assertMethod(classLoader.loadClass("foo.Bar"), BAR, BAR);
+ assertMethod(classLoader.loadClass("foo.Qux"), FOO, FOO);
+ assertMethod(classLoader.loadClass("foo.Qux"), BAR, BAR);
+ } finally {
+ for (File file : files) {
+ assertThat(file.delete(), is(true));
+ }
+ assertThat(new File(project, FOO).delete(), is(true));
+ }
+ }
+
+ private void execute(String goal, String target) throws Exception {
+ Mojo mojo = mojoRule.lookupMojo(goal, new File("src/test/resources/net/bytebuddy/test/" + target + ".pom.xml"));
+ if (goal.equals("transform")) {
+ mojoRule.setVariableValueToObject(mojo, "outputDirectory", project.getAbsolutePath());
+ mojoRule.setVariableValueToObject(mojo, "compileClasspathElements", Collections.emptyList());
+ } else if (goal.equals("transform-test")) {
+ mojoRule.setVariableValueToObject(mojo, "testOutputDirectory", project.getAbsolutePath());
+ mojoRule.setVariableValueToObject(mojo, "testClasspathElements", Collections.emptyList());
+ } else {
+ throw new AssertionError("Unknown goal: " + goal);
+ }
+ mojoRule.setVariableValueToObject(mojo, "repositorySystem", repositorySystem);
+ mojoRule.setVariableValueToObject(mojo, "groupId", FOO);
+ mojoRule.setVariableValueToObject(mojo, "artifactId", BAR);
+ mojoRule.setVariableValueToObject(mojo, "version", QUX);
+ mojo.setLog(new SilentLog());
+ mojo.execute();
+ }
+
+ private Collection<File> addClass(String name) throws IOException {
+ return new ByteBuddy()
+ .subclass(Object.class)
+ .name(name)
+ .defineMethod(FOO, String.class, Visibility.PUBLIC).intercept(FixedValue.value(FOO))
+ .defineMethod(BAR, String.class, Visibility.PUBLIC).intercept(FixedValue.value(BAR))
+ .make()
+ .saveIn(project)
+ .values();
+ }
+
+ private void assertMethod(Class<?> type, String name, Object expected) throws Exception {
+ assertThat(type.getDeclaredMethod(name).invoke(type.getDeclaredConstructor().newInstance()), is(expected));
+ }
+}
diff --git a/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/build/maven/ClassLoaderResolverTest.java b/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/build/maven/ClassLoaderResolverTest.java
new file mode 100644
index 0000000..f986099
--- /dev/null
+++ b/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/build/maven/ClassLoaderResolverTest.java
@@ -0,0 +1,103 @@
+package net.bytebuddy.build.maven;
+
+import net.bytebuddy.test.utility.MockitoRule;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.logging.Log;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.collection.CollectRequest;
+import org.eclipse.aether.collection.CollectResult;
+import org.eclipse.aether.collection.DependencyCollectionException;
+import org.eclipse.aether.graph.Dependency;
+import org.eclipse.aether.graph.DependencyFilter;
+import org.eclipse.aether.graph.DependencyNode;
+import org.eclipse.aether.graph.DependencyVisitor;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.resolution.DependencyRequest;
+import org.eclipse.aether.resolution.DependencyResolutionException;
+import org.eclipse.aether.resolution.DependencyResult;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.File;
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class ClassLoaderResolverTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux", BAZ = "baz";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private Log log;
+
+ @Mock
+ private RepositorySystem repositorySystem;
+
+ @Mock
+ private RepositorySystemSession repositorySystemSession;
+
+ @Mock
+ private DependencyNode root, child;
+
+ private ClassLoaderResolver classLoaderResolver;
+
+ @Before
+ public void setUp() throws Exception {
+ classLoaderResolver = new ClassLoaderResolver(log, repositorySystem, repositorySystemSession, Collections.<RemoteRepository>emptyList());
+ when(repositorySystem.collectDependencies(eq(repositorySystemSession), any(CollectRequest.class)))
+ .thenReturn(new CollectResult(new CollectRequest()).setRoot(root));
+ when(child.getDependency()).thenReturn(new Dependency(new DefaultArtifact(FOO,
+ BAR,
+ QUX,
+ BAZ,
+ FOO + BAR,
+ Collections.<String, String>emptyMap(),
+ new File(FOO + "/" + BAR)), QUX + BAZ));
+ when(root.accept(any(DependencyVisitor.class))).then(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocationOnMock) throws Throwable {
+ DependencyVisitor dependencyVisitor = invocationOnMock.getArgument(0);
+ dependencyVisitor.visitEnter(child);
+ dependencyVisitor.visitLeave(child);
+ return null;
+ }
+ });
+ }
+
+ @Test
+ public void testResolution() throws Exception {
+ assertThat(classLoaderResolver.resolve(new MavenCoordinate(FOO, BAR, QUX)), sameInstance(classLoaderResolver.resolve(new MavenCoordinate(FOO, BAR, QUX))));
+ }
+
+ @Test(expected = MojoExecutionException.class)
+ public void testCollectionFailure() throws Exception {
+ when(repositorySystem.collectDependencies(eq(repositorySystemSession), any(CollectRequest.class)))
+ .thenThrow(new DependencyCollectionException(new CollectResult(new CollectRequest())));
+ classLoaderResolver.resolve(new MavenCoordinate(FOO, BAR, QUX));
+ }
+
+ @Test(expected = MojoExecutionException.class)
+ public void testResolutionFailure() throws Exception {
+ when(repositorySystem.resolveDependencies(eq(repositorySystemSession), any(DependencyRequest.class)))
+ .thenThrow(new DependencyResolutionException(new DependencyResult(new DependencyRequest(root, mock(DependencyFilter.class))), new Throwable()));
+ classLoaderResolver.resolve(new MavenCoordinate(FOO, BAR, QUX));
+ }
+
+ @Test
+ public void testClose() throws Exception {
+ classLoaderResolver.resolve(new MavenCoordinate(FOO, BAR, QUX));
+ classLoaderResolver.close();
+ }
+}
diff --git a/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/build/maven/InitializationTest.java b/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/build/maven/InitializationTest.java
new file mode 100644
index 0000000..88b990a
--- /dev/null
+++ b/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/build/maven/InitializationTest.java
@@ -0,0 +1,133 @@
+package net.bytebuddy.build.maven;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.build.EntryPoint;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.scaffold.inline.MethodNameTransformer;
+import net.bytebuddy.test.utility.MockitoRule;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.Mock;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class InitializationTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux", BAZ = "baz";
+
+ @Rule
+ public TestRule mockitoRule = new MockitoRule(this);
+
+ @Mock
+ private ClassLoaderResolver classLoaderResolver;
+
+ @Test
+ public void testResolved() throws Exception {
+ Initialization initalization = new Initialization();
+ initalization.groupId = BAR;
+ initalization.artifactId = QUX;
+ initalization.version = BAZ;
+ assertThat(initalization.getGroupId(FOO), is(BAR));
+ assertThat(initalization.getArtifactId(FOO), is(QUX));
+ assertThat(initalization.getVersion(FOO), is(BAZ));
+ }
+
+ @Test
+ public void testRebase() throws Exception {
+ Initialization initalization = new Initialization();
+ initalization.entryPoint = EntryPoint.Default.REBASE.name();
+ assertThat(initalization.getEntryPoint(classLoaderResolver, BAR, QUX, BAZ), is((EntryPoint) EntryPoint.Default.REBASE));
+ verifyZeroInteractions(classLoaderResolver);
+ }
+
+ @Test
+ public void testRedefine() throws Exception {
+ Initialization initalization = new Initialization();
+ initalization.entryPoint = EntryPoint.Default.REDEFINE.name();
+ assertThat(initalization.getEntryPoint(classLoaderResolver, BAR, QUX, BAZ), is((EntryPoint) EntryPoint.Default.REDEFINE));
+ verifyZeroInteractions(classLoaderResolver);
+ }
+
+ @Test
+ public void testRedefineLocal() throws Exception {
+ Initialization initalization = new Initialization();
+ initalization.entryPoint = EntryPoint.Default.REDEFINE_LOCAL.name();
+ assertThat(initalization.getEntryPoint(classLoaderResolver, BAR, QUX, BAZ), is((EntryPoint) EntryPoint.Default.REDEFINE_LOCAL));
+ verifyZeroInteractions(classLoaderResolver);
+ }
+
+ @Test
+ public void testCustom() throws Exception {
+ Initialization initalization = new Initialization();
+ initalization.entryPoint = Foo.class.getName();
+ when(classLoaderResolver.resolve(new MavenCoordinate(BAR, QUX, BAZ))).thenReturn(Foo.class.getClassLoader());
+ assertThat(initalization.getEntryPoint(classLoaderResolver, BAR, QUX, BAZ), instanceOf(Foo.class));
+ verify(classLoaderResolver).resolve(new MavenCoordinate(BAR, QUX, BAZ));
+ verifyNoMoreInteractions(classLoaderResolver);
+ }
+
+ @Test(expected = MojoExecutionException.class)
+ public void testCustomFailed() throws Exception {
+ Initialization initalization = new Initialization();
+ initalization.entryPoint = FOO;
+ when(classLoaderResolver.resolve(new MavenCoordinate(BAR, QUX, BAZ))).thenReturn(Foo.class.getClassLoader());
+ initalization.getEntryPoint(classLoaderResolver, BAR, QUX, BAZ);
+ }
+
+ @Test(expected = MojoExecutionException.class)
+ public void testEmpty() throws Exception {
+ Initialization initalization = new Initialization();
+ initalization.entryPoint = "";
+ initalization.getEntryPoint(classLoaderResolver, BAR, QUX, BAZ);
+ }
+
+ @Test(expected = MojoExecutionException.class)
+ public void testNull() throws Exception {
+ new Initialization().getEntryPoint(classLoaderResolver, BAR, QUX, BAZ);
+ }
+
+ @Test
+ public void testDefault() throws Exception {
+ Initialization initialization = Initialization.makeDefault();
+ assertThat(initialization.entryPoint, is(EntryPoint.Default.REBASE.name()));
+ assertThat(initialization.groupId, nullValue(String.class));
+ assertThat(initialization.artifactId, nullValue(String.class));
+ assertThat(initialization.version, nullValue(String.class));
+ }
+
+ @Test
+ public void testAsCoordinateResolved() throws Exception {
+ Initialization initialization = new Initialization();
+ initialization.groupId = BAR;
+ initialization.artifactId = QUX;
+ initialization.version = BAZ;
+ assertThat(initialization.asCoordinate(FOO, FOO, FOO), is(new MavenCoordinate(BAR, QUX, BAZ)));
+ }
+
+ @Test
+ public void testAsCoordinateUnresolved() throws Exception {
+ Initialization initialization = new Initialization();
+ assertThat(initialization.asCoordinate(BAR, QUX, BAZ), is(new MavenCoordinate(BAR, QUX, BAZ)));
+ }
+
+ public static class Foo implements EntryPoint {
+
+ @Override
+ public ByteBuddy getByteBuddy() {
+ throw new AssertionError();
+ }
+
+ @Override
+ public DynamicType.Builder<?> transform(TypeDescription typeDescription, ByteBuddy byteBuddy,
+ ClassFileLocator classFileLocator,
+ MethodNameTransformer methodNameTransformer) {
+ throw new AssertionError();
+ }
+ }
+}
diff --git a/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/build/maven/MavenCoordinateTest.java b/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/build/maven/MavenCoordinateTest.java
new file mode 100644
index 0000000..c217ba2
--- /dev/null
+++ b/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/build/maven/MavenCoordinateTest.java
@@ -0,0 +1,27 @@
+package net.bytebuddy.build.maven;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.eclipse.aether.artifact.Artifact;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class MavenCoordinateTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux";
+
+ @Test
+ public void testAsArtifact() throws Exception {
+ Artifact artifact = new MavenCoordinate(FOO, BAR, QUX).asArtifact();
+ assertThat(artifact.getGroupId(), is(FOO));
+ assertThat(artifact.getArtifactId(), is(BAR));
+ assertThat(artifact.getVersion(), is(QUX));
+ assertThat(artifact.getExtension(), is("jar"));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(MavenCoordinate.class).apply();
+ }
+}
diff --git a/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/build/maven/TransformationTest.java b/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/build/maven/TransformationTest.java
new file mode 100644
index 0000000..e522121
--- /dev/null
+++ b/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/build/maven/TransformationTest.java
@@ -0,0 +1,65 @@
+package net.bytebuddy.build.maven;
+
+import org.apache.maven.plugin.MojoExecutionException;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class TransformationTest {
+
+ private static final String FOO = "foo", BAR = "bar", QUX = "qux", BAZ = "baz";
+
+ @Test
+ public void testResolved() throws Exception {
+ Transformation transformation = new Transformation();
+ transformation.plugin = FOO;
+ transformation.groupId = BAR;
+ transformation.artifactId = QUX;
+ transformation.version = BAZ;
+ assertThat(transformation.getPlugin(), is(FOO));
+ assertThat(transformation.getRawPlugin(), is(FOO));
+ assertThat(transformation.getGroupId(FOO), is(BAR));
+ assertThat(transformation.getArtifactId(FOO), is(QUX));
+ assertThat(transformation.getVersion(FOO), is(BAZ));
+ }
+
+ @Test
+ public void testUndefined() throws Exception {
+ Transformation transformation = new Transformation();
+ assertThat(transformation.getGroupId(BAR), is(BAR));
+ assertThat(transformation.getArtifactId(QUX), is(QUX));
+ assertThat(transformation.getVersion(BAZ), is(BAZ));
+ }
+
+ @Test
+ public void testEmpty() throws Exception {
+ Transformation transformation = new Transformation();
+ transformation.groupId = "";
+ transformation.artifactId = "";
+ transformation.version = "";
+ assertThat(transformation.getGroupId(BAR), is(BAR));
+ assertThat(transformation.getArtifactId(QUX), is(QUX));
+ assertThat(transformation.getVersion(BAZ), is(BAZ));
+ }
+
+ @Test(expected = MojoExecutionException.class)
+ public void testUndefinedName() throws Exception {
+ new Transformation().getPlugin();
+ }
+
+ @Test
+ public void testAsCoordinateResolved() throws Exception {
+ Transformation transformation = new Transformation();
+ transformation.groupId = BAR;
+ transformation.artifactId = QUX;
+ transformation.version = BAZ;
+ assertThat(transformation.asCoordinate(FOO, FOO, FOO), is(new MavenCoordinate(BAR, QUX, BAZ)));
+ }
+
+ @Test
+ public void testAsCoordinateUnresolved() throws Exception {
+ Transformation transformation = new Transformation();
+ assertThat(transformation.asCoordinate(BAR, QUX, BAZ), is(new MavenCoordinate(BAR, QUX, BAZ)));
+ }
+}
diff --git a/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/test/IllegalEntryPoint.java b/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/test/IllegalEntryPoint.java
new file mode 100644
index 0000000..b99d1f9
--- /dev/null
+++ b/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/test/IllegalEntryPoint.java
@@ -0,0 +1,24 @@
+package net.bytebuddy.test;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.build.EntryPoint;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.scaffold.inline.MethodNameTransformer;
+
+public class IllegalEntryPoint implements EntryPoint {
+
+ @Override
+ public ByteBuddy getByteBuddy() {
+ throw new RuntimeException();
+ }
+
+ @Override
+ public DynamicType.Builder<?> transform(TypeDescription typeDescription,
+ ByteBuddy byteBuddy,
+ ClassFileLocator classFileLocator,
+ MethodNameTransformer methodNameTransformer) {
+ throw new RuntimeException();
+ }
+}
diff --git a/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/test/IllegalPlugin.java b/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/test/IllegalPlugin.java
new file mode 100644
index 0000000..49938f7
--- /dev/null
+++ b/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/test/IllegalPlugin.java
@@ -0,0 +1,18 @@
+package net.bytebuddy.test;
+
+import net.bytebuddy.build.Plugin;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+
+public class IllegalPlugin implements Plugin {
+
+ @Override
+ public boolean matches(TypeDescription target) {
+ throw new RuntimeException();
+ }
+
+ @Override
+ public DynamicType.Builder<?> apply(DynamicType.Builder<?> builder, TypeDescription typeDescription) {
+ throw new RuntimeException();
+ }
+}
diff --git a/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/test/IllegalTransformEntryPoint.java b/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/test/IllegalTransformEntryPoint.java
new file mode 100644
index 0000000..0306273
--- /dev/null
+++ b/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/test/IllegalTransformEntryPoint.java
@@ -0,0 +1,24 @@
+package net.bytebuddy.test;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.build.EntryPoint;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.scaffold.inline.MethodNameTransformer;
+
+public class IllegalTransformEntryPoint implements EntryPoint {
+
+ @Override
+ public ByteBuddy getByteBuddy() {
+ return new ByteBuddy();
+ }
+
+ @Override
+ public DynamicType.Builder<?> transform(TypeDescription typeDescription,
+ ByteBuddy byteBuddy,
+ ClassFileLocator classFileLocator,
+ MethodNameTransformer methodNameTransformer) {
+ throw new RuntimeException();
+ }
+}
diff --git a/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/test/IllegalTransformPlugin.java b/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/test/IllegalTransformPlugin.java
new file mode 100644
index 0000000..1f25911
--- /dev/null
+++ b/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/test/IllegalTransformPlugin.java
@@ -0,0 +1,22 @@
+package net.bytebuddy.test;
+
+import net.bytebuddy.build.Plugin;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+
+public class IllegalTransformPlugin implements Plugin {
+
+ public IllegalTransformPlugin() {
+ throw new RuntimeException();
+ }
+
+ @Override
+ public boolean matches(TypeDescription target) {
+ throw new AssertionError();
+ }
+
+ @Override
+ public DynamicType.Builder<?> apply(DynamicType.Builder<?> builder, TypeDescription typeDescription) {
+ throw new AssertionError();
+ }
+}
diff --git a/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/test/LiveInitializerPlugin.java b/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/test/LiveInitializerPlugin.java
new file mode 100644
index 0000000..ce0a3eb
--- /dev/null
+++ b/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/test/LiveInitializerPlugin.java
@@ -0,0 +1,25 @@
+package net.bytebuddy.test;
+
+import net.bytebuddy.build.Plugin;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.implementation.MethodDelegation;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+
+public class LiveInitializerPlugin implements Plugin {
+
+ @Override
+ public boolean matches(TypeDescription target) {
+ return target.getName().equals("foo.Bar");
+ }
+
+ @Override
+ public DynamicType.Builder<?> apply(DynamicType.Builder<?> builder, TypeDescription typeDescription) {
+ return builder.method(named("foo")).intercept(MethodDelegation.to(new LiveInitializerPlugin()));
+ }
+
+ public String intercept() {
+ return "qux";
+ }
+}
diff --git a/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/test/SimpleEntryPoint.java b/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/test/SimpleEntryPoint.java
new file mode 100644
index 0000000..b4af6d4
--- /dev/null
+++ b/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/test/SimpleEntryPoint.java
@@ -0,0 +1,24 @@
+package net.bytebuddy.test;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.build.EntryPoint;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.scaffold.inline.MethodNameTransformer;
+
+public class SimpleEntryPoint implements EntryPoint {
+
+ @Override
+ public ByteBuddy getByteBuddy() {
+ return new ByteBuddy();
+ }
+
+ @Override
+ public DynamicType.Builder<?> transform(TypeDescription typeDescription,
+ ByteBuddy byteBuddy,
+ ClassFileLocator classFileLocator,
+ MethodNameTransformer methodNameTransformer) {
+ return byteBuddy.rebase(typeDescription, classFileLocator, methodNameTransformer);
+ }
+}
diff --git a/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/test/SimplePlugin.java b/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/test/SimplePlugin.java
new file mode 100644
index 0000000..edb3f60
--- /dev/null
+++ b/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/test/SimplePlugin.java
@@ -0,0 +1,21 @@
+package net.bytebuddy.test;
+
+import net.bytebuddy.build.Plugin;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.implementation.FixedValue;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+
+public class SimplePlugin implements Plugin {
+
+ @Override
+ public boolean matches(TypeDescription target) {
+ return target.getName().equals("foo.Bar");
+ }
+
+ @Override
+ public DynamicType.Builder<?> apply(DynamicType.Builder<?> builder, TypeDescription typeDescription) {
+ return builder.method(named("foo")).intercept(FixedValue.value("qux"));
+ }
+}
diff --git a/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/test/utility/MockitoRule.java b/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/test/utility/MockitoRule.java
new file mode 100644
index 0000000..10df0c0
--- /dev/null
+++ b/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/test/utility/MockitoRule.java
@@ -0,0 +1,30 @@
+package net.bytebuddy.test.utility;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * A rule that applies Mockito's annotations to any test. This is preferred over the Mockito runner since it allows
+ * to use tests with parameters that require a specific runner.
+ */
+public class MockitoRule implements TestRule {
+
+ private final Object target;
+
+ public MockitoRule(Object target) {
+ this.target = target;
+ }
+
+ @Override
+ public Statement apply(final Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ MockitoAnnotations.initMocks(target);
+ base.evaluate();
+ }
+ };
+ }
+}
diff --git a/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/test/utility/ObjectPropertyAssertion.java b/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/test/utility/ObjectPropertyAssertion.java
new file mode 100644
index 0000000..88adf50
--- /dev/null
+++ b/byte-buddy-maven-plugin/src/test/java/net/bytebuddy/test/utility/ObjectPropertyAssertion.java
@@ -0,0 +1,331 @@
+package net.bytebuddy.test.utility;
+
+import net.bytebuddy.utility.CompoundList;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class ObjectPropertyAssertion<T> {
+
+ private static final boolean DEFAULT_BOOLEAN = false, OTHER_BOOLEAN = true;
+
+ private static final byte DEFAULT_BYTE = 1, OTHER_BYTE = 42;
+
+ private static final char DEFAULT_CHAR = 1, OTHER_CHAR = 42;
+
+ private static final short DEFAULT_SHORT = 1, OTHER_SHORT = 42;
+
+ private static final int DEFAULT_INT = 1, OTHER_INT = 42;
+
+ private static final long DEFAULT_LONG = 1, OTHER_LONG = 42;
+
+ private static final float DEFAULT_FLOAT = 1, OTHER_FLOAT = 42;
+
+ private static final double DEFAULT_DOUBLE = 1, OTHER_DOUBLE = 42;
+
+ private static final String DEFAULT_STRING = "foo", OTHER_STRING = "bar";
+
+ private final Class<T> type;
+
+ private final ApplicableRefinement refinement;
+
+ private final ApplicableGenerator generator;
+
+ private final ApplicableCreator creator;
+
+ private final boolean skipSynthetic;
+
+ private final String optionalToStringRegex;
+
+ private ObjectPropertyAssertion(Class<T> type,
+ ApplicableGenerator generator,
+ ApplicableRefinement refinement,
+ ApplicableCreator creator,
+ boolean skipSynthetic,
+ String optionalToStringRegex) {
+ this.type = type;
+ this.generator = generator;
+ this.refinement = refinement;
+ this.creator = creator;
+ this.skipSynthetic = skipSynthetic;
+ this.optionalToStringRegex = optionalToStringRegex;
+ }
+
+ public static <S> ObjectPropertyAssertion<S> of(Class<S> type) {
+ return new ObjectPropertyAssertion<S>(type,
+ new ApplicableGenerator(),
+ new ApplicableRefinement(),
+ new ApplicableCreator(),
+ false,
+ null
+ );
+ }
+
+ public ObjectPropertyAssertion<T> refine(Refinement<?> refinement) {
+ return new ObjectPropertyAssertion<T>(type,
+ generator,
+ this.refinement.with(refinement),
+ creator,
+ skipSynthetic,
+ optionalToStringRegex
+ );
+ }
+
+ public ObjectPropertyAssertion<T> generate(Generator<?> generator) {
+ return new ObjectPropertyAssertion<T>(type,
+ this.generator.with(generator),
+ refinement,
+ creator,
+ skipSynthetic,
+ optionalToStringRegex
+ );
+ }
+
+ public ObjectPropertyAssertion<T> create(Creator<?> creator) {
+ return new ObjectPropertyAssertion<T>(type,
+ generator,
+ refinement,
+ this.creator.with(creator),
+ skipSynthetic,
+ optionalToStringRegex
+ );
+ }
+
+ public ObjectPropertyAssertion<T> skipSynthetic() {
+ return new ObjectPropertyAssertion<T>(type, generator, refinement, creator, true, optionalToStringRegex);
+ }
+
+ public ObjectPropertyAssertion<T> specificToString(String stringRegex) {
+ return new ObjectPropertyAssertion<T>(type, generator, refinement, creator, skipSynthetic, stringRegex);
+ }
+
+ public void apply() throws IllegalAccessException, InvocationTargetException, InstantiationException {
+ if (type.isEnum()) {
+ for (T instance : type.getEnumConstants()) {
+ assertThat(instance.toString(), is(type.getCanonicalName()
+ .substring(type.getPackage().getName().length() + 1) + "." + ((Enum<?>) instance).name()));
+ }
+ return;
+ }
+ for (Constructor<?> constructor : type.getDeclaredConstructors()) {
+ if (constructor.isSynthetic() && skipSynthetic) {
+ continue;
+ }
+ constructor.setAccessible(true);
+ Class<?>[] parameterTypes = constructor.getParameterTypes();
+ Object[] actualArguments = new Object[parameterTypes.length];
+ Object[] otherArguments = new Object[parameterTypes.length];
+ int index = 0;
+ for (Class<?> parameterType : parameterTypes) {
+ putInstance(parameterType, actualArguments, otherArguments, index++);
+ }
+ int testIndex = 0;
+ @SuppressWarnings("unchecked")
+ T instance = (T) constructor.newInstance(actualArguments);
+ assertThat(instance, is(instance));
+ assertThat(instance, not(equalTo(null)));
+ assertThat(instance, not(new Object()));
+ Object similarInstance = constructor.newInstance(actualArguments);
+ assertThat(instance.hashCode(), is(similarInstance.hashCode()));
+ assertThat(instance, is(similarInstance));
+ if (optionalToStringRegex != null) {
+ assertThat(instance.toString(), new RegexMatcher(optionalToStringRegex));
+ }
+ for (Object otherArgument : otherArguments) {
+ Object[] compareArguments = new Object[actualArguments.length];
+ int argumentIndex = 0;
+ for (Object actualArgument : actualArguments) {
+ if (argumentIndex == testIndex) {
+ compareArguments[argumentIndex] = otherArgument;
+ } else {
+ compareArguments[argumentIndex] = actualArgument;
+ }
+ argumentIndex++;
+ }
+ Object unlikeInstance = constructor.newInstance(compareArguments);
+ assertThat(instance.hashCode(), not(unlikeInstance));
+ assertThat(instance, not(unlikeInstance));
+ testIndex++;
+ }
+ }
+ }
+
+ private void putInstance(Class<?> parameterType, Object actualArguments, Object otherArguments, int index) {
+ Object actualArgument, otherArgument;
+ if (parameterType == boolean.class) {
+ actualArgument = DEFAULT_BOOLEAN;
+ otherArgument = OTHER_BOOLEAN;
+ } else if (parameterType == byte.class) {
+ actualArgument = DEFAULT_BYTE;
+ otherArgument = OTHER_BYTE;
+ } else if (parameterType == char.class) {
+ actualArgument = DEFAULT_CHAR;
+ otherArgument = OTHER_CHAR;
+ } else if (parameterType == short.class) {
+ actualArgument = DEFAULT_SHORT;
+ otherArgument = OTHER_SHORT;
+ } else if (parameterType == int.class) {
+ actualArgument = DEFAULT_INT;
+ otherArgument = OTHER_INT;
+ } else if (parameterType == long.class) {
+ actualArgument = DEFAULT_LONG;
+ otherArgument = OTHER_LONG;
+ } else if (parameterType == float.class) {
+ actualArgument = DEFAULT_FLOAT;
+ otherArgument = OTHER_FLOAT;
+ } else if (parameterType == double.class) {
+ actualArgument = DEFAULT_DOUBLE;
+ otherArgument = OTHER_DOUBLE;
+ } else if (parameterType.isEnum()) {
+ Object[] enumConstants = parameterType.getEnumConstants();
+ if (enumConstants.length == 1) {
+ throw new IllegalArgumentException("Enum with only one constant: " + parameterType);
+ }
+ actualArgument = enumConstants[0];
+ otherArgument = enumConstants[1];
+ } else if (parameterType.isArray()) {
+ actualArgument = Array.newInstance(parameterType.getComponentType(), 1);
+ otherArgument = Array.newInstance(parameterType.getComponentType(), 1);
+ putInstance(parameterType.getComponentType(), actualArgument, otherArgument, 0);
+ } else {
+ actualArgument = creator.replace(parameterType, generator, false);
+ refinement.apply(actualArgument);
+ otherArgument = creator.replace(parameterType, generator, true);
+ refinement.apply(otherArgument);
+ }
+ Array.set(actualArguments, index, actualArgument);
+ Array.set(otherArguments, index, otherArgument);
+ }
+
+ public interface Refinement<T> {
+
+ void apply(T mock);
+ }
+
+ public interface Generator<T> {
+
+ Class<? extends T> generate();
+ }
+
+ public interface Creator<T> {
+
+ T create();
+ }
+
+ private static class ApplicableRefinement {
+
+ private final List<Refinement<?>> refinements;
+
+ private ApplicableRefinement() {
+ refinements = Collections.emptyList();
+ }
+
+ private ApplicableRefinement(List<Refinement<?>> refinements) {
+ this.refinements = refinements;
+ }
+
+ @SuppressWarnings("unchecked")
+ private void apply(Object mock) {
+ for (Refinement refinement : refinements) {
+ ParameterizedType generic = (ParameterizedType) refinement.getClass().getGenericInterfaces()[0];
+ Class<?> restrained = generic.getActualTypeArguments()[0] instanceof ParameterizedType
+ ? (Class<?>) ((ParameterizedType) generic.getActualTypeArguments()[0]).getRawType()
+ : (Class<?>) generic.getActualTypeArguments()[0];
+ if (restrained.isInstance(mock)) {
+ refinement.apply(mock);
+ }
+ }
+ }
+
+ private ApplicableRefinement with(Refinement<?> refinement) {
+ return new ApplicableRefinement(CompoundList.of(refinements, refinement));
+ }
+ }
+
+ private static class ApplicableGenerator {
+
+ private final List<Generator<?>> generators;
+
+ private ApplicableGenerator() {
+ generators = Collections.emptyList();
+ }
+
+ private ApplicableGenerator(List<Generator<?>> generators) {
+ this.generators = generators;
+ }
+
+ private Object generate(Class<?> type, boolean alternative) {
+ for (Generator<?> generator : generators) {
+ ParameterizedType generic = (ParameterizedType) generator.getClass().getGenericInterfaces()[0];
+ Class<?> restrained = generic.getActualTypeArguments()[0] instanceof ParameterizedType
+ ? (Class<?>) ((ParameterizedType) generic.getActualTypeArguments()[0]).getRawType()
+ : (Class<?>) generic.getActualTypeArguments()[0];
+ if (type.isAssignableFrom(restrained)) {
+ type = generator.generate();
+ }
+ }
+ return type == String.class
+ ? alternative ? OTHER_STRING : DEFAULT_STRING
+ : mock(type);
+ }
+
+ private ApplicableGenerator with(Generator<?> generator) {
+ return new ApplicableGenerator(CompoundList.of(generators, generator));
+ }
+ }
+
+ private static class ApplicableCreator {
+
+ private final List<Creator<?>> creators;
+
+ private ApplicableCreator() {
+ creators = Collections.emptyList();
+ }
+
+ private ApplicableCreator(List<Creator<?>> creators) {
+ this.creators = creators;
+ }
+
+ private Object replace(Class<?> type, ApplicableGenerator generator, boolean alternative) {
+ for (Creator<?> creator : creators) {
+ ParameterizedType generic = (ParameterizedType) creator.getClass().getGenericInterfaces()[0];
+ Class<?> restrained = generic.getActualTypeArguments()[0] instanceof ParameterizedType
+ ? (Class<?>) ((ParameterizedType) generic.getActualTypeArguments()[0]).getRawType()
+ : (Class<?>) generic.getActualTypeArguments()[0];
+ if (type.isAssignableFrom(restrained)) {
+ return creator.create();
+ }
+ }
+ return generator.generate(type, alternative);
+ }
+
+ private ApplicableCreator with(Creator<?> creator) {
+ return new ApplicableCreator(CompoundList.of(creators, creator));
+ }
+ }
+
+ private static class RegexMatcher extends TypeSafeMatcher<String> {
+
+ private final String regex;
+
+ public RegexMatcher(final String regex) {
+ this.regex = regex;
+ }
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText("matches regex='" + regex + "'");
+ }
+
+ @Override
+ public boolean matchesSafely(final String string) {
+ return string.matches(regex);
+ }
+ }
+}
diff --git a/byte-buddy-maven-plugin/src/test/resources/net/bytebuddy/test/empty.pom.xml b/byte-buddy-maven-plugin/src/test/resources/net/bytebuddy/test/empty.pom.xml
new file mode 100644
index 0000000..25bd469
--- /dev/null
+++ b/byte-buddy-maven-plugin/src/test/resources/net/bytebuddy/test/empty.pom.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy-maven-plugin</artifactId>
+ <configuration/>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/byte-buddy-maven-plugin/src/test/resources/net/bytebuddy/test/entry.illegal.pom.xml b/byte-buddy-maven-plugin/src/test/resources/net/bytebuddy/test/entry.illegal.pom.xml
new file mode 100644
index 0000000..17602e5
--- /dev/null
+++ b/byte-buddy-maven-plugin/src/test/resources/net/bytebuddy/test/entry.illegal.pom.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy-maven-plugin</artifactId>
+ <configuration>
+ <initialization>
+ <entryPoint>net.bytebuddy.test.IllegalEntryPoint</entryPoint>
+ </initialization>
+ <transformations>
+ <transformation>
+ <plugin>net.bytebuddy.test.SimplePlugin</plugin>
+ </transformation>
+ </transformations>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/byte-buddy-maven-plugin/src/test/resources/net/bytebuddy/test/entry.illegal.transform.pom.xml b/byte-buddy-maven-plugin/src/test/resources/net/bytebuddy/test/entry.illegal.transform.pom.xml
new file mode 100644
index 0000000..74e5c0c
--- /dev/null
+++ b/byte-buddy-maven-plugin/src/test/resources/net/bytebuddy/test/entry.illegal.transform.pom.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy-maven-plugin</artifactId>
+ <configuration>
+ <initialization>
+ <entryPoint>net.bytebuddy.test.IllegalTransformEntryPoint</entryPoint>
+ </initialization>
+ <transformations>
+ <transformation>
+ <plugin>net.bytebuddy.test.SimplePlugin</plugin>
+ </transformation>
+ </transformations>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/byte-buddy-maven-plugin/src/test/resources/net/bytebuddy/test/entry.pom.xml b/byte-buddy-maven-plugin/src/test/resources/net/bytebuddy/test/entry.pom.xml
new file mode 100644
index 0000000..94a8190
--- /dev/null
+++ b/byte-buddy-maven-plugin/src/test/resources/net/bytebuddy/test/entry.pom.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy-maven-plugin</artifactId>
+ <configuration>
+ <initialization>
+ <entryPoint>net.bytebuddy.test.SimpleEntryPoint</entryPoint>
+ </initialization>
+ <transformations>
+ <transformation>
+ <plugin>net.bytebuddy.test.SimplePlugin</plugin>
+ </transformation>
+ </transformations>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/byte-buddy-maven-plugin/src/test/resources/net/bytebuddy/test/illegal.apply.pom.xml b/byte-buddy-maven-plugin/src/test/resources/net/bytebuddy/test/illegal.apply.pom.xml
new file mode 100644
index 0000000..a6d4455
--- /dev/null
+++ b/byte-buddy-maven-plugin/src/test/resources/net/bytebuddy/test/illegal.apply.pom.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy-maven-plugin</artifactId>
+ <configuration>
+ <transformations>
+ <transformation>
+ <plugin>net.bytebuddy.test.IllegalPlugin</plugin>
+ </transformation>
+ </transformations>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/byte-buddy-maven-plugin/src/test/resources/net/bytebuddy/test/illegal.pom.xml b/byte-buddy-maven-plugin/src/test/resources/net/bytebuddy/test/illegal.pom.xml
new file mode 100644
index 0000000..c9ad800
--- /dev/null
+++ b/byte-buddy-maven-plugin/src/test/resources/net/bytebuddy/test/illegal.pom.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy-maven-plugin</artifactId>
+ <configuration>
+ <transformations>
+ <transformation>
+ <plugin>net.bytebuddy.test.IllegalTransformPlugin</plugin>
+ </transformation>
+ </transformations>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/byte-buddy-maven-plugin/src/test/resources/net/bytebuddy/test/live.allowed.pom.xml b/byte-buddy-maven-plugin/src/test/resources/net/bytebuddy/test/live.allowed.pom.xml
new file mode 100644
index 0000000..05143f7
--- /dev/null
+++ b/byte-buddy-maven-plugin/src/test/resources/net/bytebuddy/test/live.allowed.pom.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy-maven-plugin</artifactId>
+ <configuration>
+ <failOnLiveInitializer>false</failOnLiveInitializer>
+ <transformations>
+ <transformation>
+ <plugin>net.bytebuddy.test.LiveInitializerPlugin</plugin>
+ </transformation>
+ </transformations>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/byte-buddy-maven-plugin/src/test/resources/net/bytebuddy/test/live.pom.xml b/byte-buddy-maven-plugin/src/test/resources/net/bytebuddy/test/live.pom.xml
new file mode 100644
index 0000000..12f6f6b
--- /dev/null
+++ b/byte-buddy-maven-plugin/src/test/resources/net/bytebuddy/test/live.pom.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy-maven-plugin</artifactId>
+ <configuration>
+ <failOnLiveInitializer>true</failOnLiveInitializer>
+ <transformations>
+ <transformation>
+ <plugin>net.bytebuddy.test.LiveInitializerPlugin</plugin>
+ </transformation>
+ </transformations>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/byte-buddy-maven-plugin/src/test/resources/net/bytebuddy/test/simple.pom.xml b/byte-buddy-maven-plugin/src/test/resources/net/bytebuddy/test/simple.pom.xml
new file mode 100644
index 0000000..10e3c75
--- /dev/null
+++ b/byte-buddy-maven-plugin/src/test/resources/net/bytebuddy/test/simple.pom.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy-maven-plugin</artifactId>
+ <configuration>
+ <transformations>
+ <transformation>
+ <plugin>net.bytebuddy.test.SimplePlugin</plugin>
+ </transformation>
+ </transformations>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/byte-buddy-maven-plugin/src/test/resources/net/bytebuddy/test/suffix.pom.xml b/byte-buddy-maven-plugin/src/test/resources/net/bytebuddy/test/suffix.pom.xml
new file mode 100644
index 0000000..87c96c9
--- /dev/null
+++ b/byte-buddy-maven-plugin/src/test/resources/net/bytebuddy/test/suffix.pom.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy-maven-plugin</artifactId>
+ <configuration>
+ <suffix>qux</suffix>
+ <transformations>
+ <transformation>
+ <plugin>net.bytebuddy.test.SimplePlugin</plugin>
+ </transformation>
+ </transformations>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/byte-buddy-maven-plugin/src/test/resources/net/bytebuddy/test/test.pom.xml b/byte-buddy-maven-plugin/src/test/resources/net/bytebuddy/test/test.pom.xml
new file mode 100644
index 0000000..10e3c75
--- /dev/null
+++ b/byte-buddy-maven-plugin/src/test/resources/net/bytebuddy/test/test.pom.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy-maven-plugin</artifactId>
+ <configuration>
+ <transformations>
+ <transformation>
+ <plugin>net.bytebuddy.test.SimplePlugin</plugin>
+ </transformation>
+ </transformations>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/byte-buddy/pom.xml b/byte-buddy/pom.xml
new file mode 100644
index 0000000..9b95c4e
--- /dev/null
+++ b/byte-buddy/pom.xml
@@ -0,0 +1,178 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy-parent</artifactId>
+ <version>1.7.1</version>
+ </parent>
+
+ <artifactId>byte-buddy</artifactId>
+ <packaging>jar</packaging>
+
+ <name>Byte Buddy (without dependencies)</name>
+ <description>
+ Byte Buddy is a Java library for creating Java classes at run time.
+ This artifact is a build of Byte Buddy with all ASM dependencies repackaged into its own name space.
+ </description>
+
+ <!--
+ Because of the Shade plugin, the variables of this POM are resolved with the current work station's values the
+ dependency reduced POM. The POM that is presented to the Shade plugin is already resolved which is why it is
+ not possible to prevent this behavior as the profiles are not removed form the POM. This does not effect the
+ usability of the POM for a dependency but might confuse users that read the POM. The Shade plugin also removes
+ this comment from deployment such that there is no easy way to explain this in the deployed POM.
+ -->
+
+ <properties>
+ <shade.source>org.objectweb.asm</shade.source>
+ <shade.target>net.bytebuddy.jar.asm</shade.target>
+ </properties>
+
+ <!--
+ IDEs might complain about the following dependency not being OSGI compatible. While this is true, it does not
+ matter as the dependency is repackaged by the Shade plugin and becomes part of this module's jar artifact.
+ -->
+
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>byte-buddy-dep</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <!-- Shade the ASM dependency. -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <version>${version.plugin.shade}</version>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ <configuration>
+ <shadedArtifactAttached>false</shadedArtifactAttached>
+ <createDependencyReducedPom>true</createDependencyReducedPom>
+ <createSourcesJar>true</createSourcesJar>
+ <shadeSourcesContent>true</shadeSourcesContent>
+ <relocations>
+ <relocation>
+ <pattern>${shade.source}</pattern>
+ <shadedPattern>${shade.target}</shadedPattern>
+ </relocation>
+ </relocations>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <!-- Disable pitest as it fails for empty modules -->
+ <plugin>
+ <groupId>org.pitest</groupId>
+ <artifactId>pitest-maven</artifactId>
+ <version>${version.plugin.pitest}</version>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>extras</id>
+ <activation>
+ <activeByDefault>false</activeByDefault>
+ </activation>
+ <build>
+ <plugins>
+ <!-- Create manifest file which is required for creating an OSGi bundle. -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>${version.plugin.jar}</version>
+ <configuration>
+ <archive>
+ <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+ </archive>
+ </configuration>
+ </plugin>
+ <!-- Configure OSGi bundle. Due to interaction with the shade plugin, IDEs might identify the following configuration as illegal. -->
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <version>${version.plugin.bundle}</version>
+ <executions>
+ <execution>
+ <phase>process-classes</phase>
+ <goals>
+ <goal>manifest</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <instructions>
+ <!-- Cannot use wildcards because of use of the shade plugin which imports from 'byte-buddy-dep'. -->
+ <Export-Package>
+ net.bytebuddy,
+ net.bytebuddy.agent.builder,
+ net.bytebuddy.asm,
+ net.bytebuddy.build,
+ net.bytebuddy.description,
+ net.bytebuddy.description.annotation,
+ net.bytebuddy.description.enumeration,
+ net.bytebuddy.description.field,
+ net.bytebuddy.description.method,
+ net.bytebuddy.description.modifier,
+ net.bytebuddy.description.type,
+ net.bytebuddy.dynamic,
+ net.bytebuddy.dynamic.loading,
+ net.bytebuddy.dynamic.scaffold,
+ net.bytebuddy.dynamic.scaffold.inline,
+ net.bytebuddy.dynamic.scaffold.subclass,
+ net.bytebuddy.implementation,
+ net.bytebuddy.implementation.attribute,
+ net.bytebuddy.implementation.auxiliary,
+ net.bytebuddy.implementation.bind,
+ net.bytebuddy.implementation.bind.annotation,
+ net.bytebuddy.implementation.bytecode,
+ net.bytebuddy.implementation.bytecode.assign,
+ net.bytebuddy.implementation.bytecode.assign.primitive,
+ net.bytebuddy.implementation.bytecode.assign.reference,
+ net.bytebuddy.implementation.bytecode.collection,
+ net.bytebuddy.implementation.bytecode.constant,
+ net.bytebuddy.implementation.bytecode.member,
+ net.bytebuddy.matcher,
+ net.bytebuddy.pool,
+ net.bytebuddy.utility,
+ net.bytebuddy.utility.privilege,
+ net.bytebuddy.utility.visitor,
+ ${shade.target}
+ </Export-Package>
+ <Automatic-Module-Name>${project.groupId}</Automatic-Module-Name>
+ </instructions>
+ </configuration>
+ </plugin>
+ <!-- The shadowed source files of this module need to be included explicitly to create a javadoc artifact.-->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>${version.plugin.javadoc}</version>
+ <configuration>
+ <includeDependencySources>true</includeDependencySources>
+ <dependencySourceIncludes>
+ <dependencySourceInclude>${project.groupId}:byte-buddy-dep</dependencySourceInclude>
+ </dependencySourceIncludes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/checkstyle.xml b/checkstyle.xml
new file mode 100644
index 0000000..fda5ce8
--- /dev/null
+++ b/checkstyle.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0"?>
+<!DOCTYPE module PUBLIC
+ "-//Puppy Crawl//DTD Check Configuration 1.2//EN"
+ "http://www.puppycrawl.com/dtds/configuration_1_2.dtd">
+<module name="Checker">
+
+ <module name="JavadocPackage"/>
+
+ <module name="NewlineAtEndOfFile">
+ <property name="lineSeparator" value="lf"/>
+ </module>
+
+ <module name="FileTabCharacter"/>
+
+ <module name="RegexpSingleline">
+ <property name="format" value="\s+$"/>
+ <property name="minimum" value="0"/>
+ <property name="maximum" value="0"/>
+ <property name="message" value="Line has trailing spaces."/>
+ </module>
+
+ <module name="TreeWalker">
+
+ <module name="JavadocMethod"/>
+ <module name="JavadocType"/>
+ <module name="JavadocVariable"/>
+ <module name="JavadocStyle"/>
+
+ <module name="TodoComment"/>
+
+ <module name="ConstantName"/>
+ <module name="LocalFinalVariableName"/>
+ <module name="LocalVariableName"/>
+ <module name="MemberName"/>
+ <module name="MethodName"/>
+ <module name="PackageName"/>
+ <module name="ParameterName"/>
+ <module name="StaticVariableName"/>
+ <module name="TypeName"/>
+
+ <module name="IllegalImport"/>
+ <module name="RedundantImport"/>
+ <module name="UnusedImports"/>
+
+ <module name="EmptyForIteratorPad"/>
+ <module name="GenericWhitespace"/>
+ <module name="MethodParamPad"/>
+ <module name="ParenPad"/>
+ <module name="TypecastParenPad"/>
+ <module name="WhitespaceAfter"/>
+ <module name="WhitespaceAround">
+ <!-- The RCURLY token has strange semantics where annotation arrays should have a spacing while normal
+ arrays should not have such a spacing - therefore, we rather exclude it from automatic checks -->
+ <property name="tokens"
+ value="ASSIGN,BAND,BAND_ASSIGN,BOR,BOR_ASSIGN,BSR,BSR_ASSIGN,BXOR,BXOR_ASSIGN,COLON,DIV,DIV_ASSIGN,EQUAL,GE,GT,LAND,LCURLY,LE,LITERAL_ASSERT,LITERAL_CATCH,LITERAL_DO,LITERAL_ELSE,LITERAL_FINALLY,LITERAL_FOR,LITERAL_IF,LITERAL_RETURN,LITERAL_SYNCHRONIZED,LITERAL_TRY,LITERAL_WHILE,LOR,LT,MINUS,MINUS_ASSIGN,MOD,MOD_ASSIGN,NOT_EQUAL,PLUS,PLUS_ASSIGN,QUESTION,SL,SLIST,SL_ASSIGN,SR,SR_ASSIGN,STAR,STAR_ASSIGN,TYPE_EXTENSION_AND"/>
+ </module>
+ <module name="NoWhitespaceAfter"/>
+ <module name="NoWhitespaceBefore"/>
+
+ <module name="ModifierOrder"/>
+
+ <module name="LeftCurly"/>
+ <module name="RightCurly"/>
+
+ <module name="EmptyStatement"/>
+ <module name="EqualsHashCode"/>
+ <module name="IllegalInstantiation"/>
+ <module name="SimplifyBooleanExpression"/>
+ <module name="SimplifyBooleanReturn"/>
+
+ <module name="HideUtilityClassConstructor"/>
+
+ <module name="ArrayTypeStyle"/>
+ <module name="UpperEll"/>
+ </module>
+</module>
diff --git a/mvnw b/mvnw
new file mode 100755
index 0000000..c04daa6
--- /dev/null
+++ b/mvnw
@@ -0,0 +1,214 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven2 Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Migwn, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+ #
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`which java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+ local basedir=$(pwd)
+ local wdir=$(pwd)
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ wdir=$(cd "$wdir/.."; pwd)
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)}
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+# avoid using MAVEN_CMD_LINE_ARGS below since that would loose parameter escaping in $@
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/mvnw.cmd b/mvnw.cmd
new file mode 100644
index 0000000..d435a6e
--- /dev/null
+++ b/mvnw.cmd
@@ -0,0 +1,146 @@
+ at REM ----------------------------------------------------------------------------
+ at REM Licensed to the Apache Software Foundation (ASF) under one
+ at REM or more contributor license agreements. See the NOTICE file
+ at REM distributed with this work for additional information
+ at REM regarding copyright ownership. The ASF licenses this file
+ at REM to you under the Apache License, Version 2.0 (the
+ at REM "License"); you may not use this file except in compliance
+ at REM with the License. You may obtain a copy of the License at
+ at REM
+ at REM http://www.apache.org/licenses/LICENSE-2.0
+ at REM
+ at REM Unless required by applicable law or agreed to in writing,
+ at REM software distributed under the License is distributed on an
+ at REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ at REM KIND, either express or implied. See the License for the
+ at REM specific language governing permissions and limitations
+ at REM under the License.
+ at REM ----------------------------------------------------------------------------
+
+ at REM ----------------------------------------------------------------------------
+ at REM Maven2 Start Up Batch script
+ at REM
+ at REM Required ENV vars:
+ at REM JAVA_HOME - location of a JDK home dir
+ at REM
+ at REM Optional ENV vars
+ at REM M2_HOME - location of maven2's installed home dir
+ at REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+ at REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
+ at REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+ at REM e.g. to debug Maven itself, use
+ at REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+ at REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+ at REM ----------------------------------------------------------------------------
+
+ at REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+ at echo off
+ at REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
+ at if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+ at REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+ at REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+ at REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+ at setlocal
+
+set ERROR_CODE=0
+
+ at REM To isolate internal variables from possible post scripts, we use another setlocal
+ at setlocal
+
+ at REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+ at REM ==== END VALIDATION ====
+
+:init
+
+set MAVEN_CMD_LINE_ARGS=%MAVEN_CONFIG% %*
+
+ at REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+ at REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+ at setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+ at endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+
+set WRAPPER_JAR=""%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar""
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+ at REM avoid using MAVEN_CMD_LINE_ARGS below since that would loose parameter escaping in %*
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+ at endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+ at REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+ at REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..c93449b
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,579 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy-parent</artifactId>
+ <version>1.7.1</version>
+ <packaging>pom</packaging>
+
+ <inceptionYear>2014</inceptionYear>
+
+ <name>Byte Buddy (parent)</name>
+ <description>
+ Byte Buddy is a Java library for creating Java classes at run time.
+ The parent artifact contains configuration information that concern all modules.
+ </description>
+ <url>http://bytebuddy.net</url>
+
+ <!--
+ There are several build profiles available:
+ - extras: Creates additional artifacts containing source files and javadoc. (activated on release)
+ - gpg: Sign all artifacts using gpg. (activated on release)
+ - checks: Applies style checks to the source files. (activated by default, activated on release)
+ - integration: Runs additional unit tests and executes static code analysis (activated on Travis CI)
+ - android: Builds an Android test application. An Android SDK is required for doing so. (excluded from release)
+
+ It is also possible to build Byte Buddy against a specific byte code level. By default, Byte Buddy is Java 6 compatible:
+ - java7: Sets the target level to Java 7.
+ - java8: Sets the target level to Java 8.
+
+ Additionally, the following reports are available via Maven:
+ - jacoco:prepare-agent verify jacoco:report - Computes coverage for test suite (all modules)
+ - org.pitest:pitest-maven:mutationCoverage - Runs mutation tests (all modules)
+ - findbugs:findbugs findbugs:gui - Runs findbugs and shows a report in a graphical interface (module specific)
+ - com.github.ferstl:jitwatch-jarscan-maven-plugin:scan - Finds all methods above HotSpot's inlining threshold
+ - clirr:check - Checks for binary changes in the API
+ -->
+
+ <modules>
+ <module>byte-buddy</module>
+ <module>byte-buddy-dep</module>
+ <module>byte-buddy-benchmark</module>
+ <module>byte-buddy-agent</module>
+ <module>byte-buddy-android</module>
+ <module>byte-buddy-maven-plugin</module>
+ <module>byte-buddy-gradle-plugin</module>
+ </modules>
+
+ <properties>
+ <bytebuddy.extras>false</bytebuddy.extras>
+ <bytebuddy.integration>false</bytebuddy.integration>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <code.level>1.6</code.level>
+ <pitest.target>net.bytebuddy</pitest.target>
+ <asm.javadoc>http://asm.ow2.org/asm50/javadoc/user/</asm.javadoc>
+ <version.asm>5.2</version.asm>
+ <version.junit>4.12</version.junit>
+ <version.mockito>2.7.22</version.mockito>
+ <version.plugin.clean>3.0.0</version.plugin.clean>
+ <version.plugin.bundle>3.3.0</version.plugin.bundle>
+ <version.plugin.compiler>3.6.1</version.plugin.compiler>
+ <version.plugin.install>2.5.2</version.plugin.install>
+ <version.plugin.deploy>2.8.2</version.plugin.deploy>
+ <version.plugin.javadoc>2.10.4</version.plugin.javadoc>
+ <version.plugin.source>3.0.1</version.plugin.source>
+ <version.plugin.shade>2.4.3</version.plugin.shade>
+ <version.plugin.gpg>1.6</version.plugin.gpg>
+ <version.plugin.jxr>2.5</version.plugin.jxr>
+ <version.plugin.jar>3.0.2</version.plugin.jar>
+ <version.plugin.release>2.5.3</version.plugin.release>
+ <version.plugin.resources>3.0.2</version.plugin.resources>
+ <version.plugin.surefire>2.20</version.plugin.surefire>
+ <version.plugin.pitest>1.2.0</version.plugin.pitest>
+ <version.plugin.animal-sniffer>1.15</version.plugin.animal-sniffer>
+ <version.plugin.enforcer>1.4.1</version.plugin.enforcer>
+ <version.plugin.jacoco>0.7.9</version.plugin.jacoco>
+ <version.plugin.coveralls>4.1.0</version.plugin.coveralls>
+ <version.plugin.checkstyle>2.17</version.plugin.checkstyle>
+ <version.plugin.findbugs>3.0.4</version.plugin.findbugs>
+ <version.annotations.findbugs>3.0.1u2</version.annotations.findbugs>
+ <version.plugin.jitwatch>1.0.1</version.plugin.jitwatch>
+ <version.plugin.clirr>2.8</version.plugin.clirr>
+ <version.android.sdk>4.1.1.4</version.android.sdk>
+ <version.lombok>1.16.16</version.lombok>
+ </properties>
+
+ <licenses>
+ <license>
+ <name>The Apache Software License, Version 2.0</name>
+ <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+ <distribution>repo</distribution>
+ <comments>A business-friendly OSS license</comments>
+ </license>
+ </licenses>
+
+ <developers>
+ <developer>
+ <id>raphw</id>
+ <name>Rafael Winterhalter</name>
+ <email>rafael.wth at gmail.com</email>
+ <url>http://rafael.codes</url>
+ <roles>
+ <role>developer</role>
+ </roles>
+ <timezone>+1</timezone>
+ </developer>
+ </developers>
+
+ <issueManagement>
+ <system>github.com</system>
+ <url>https://github.com/raphw/byte-buddy/issues</url>
+ </issueManagement>
+
+ <scm>
+ <connection>scm:git:git at github.com:raphw/byte-buddy.git</connection>
+ <developerConnection>scm:git:git at github.com:raphw/byte-buddy.git</developerConnection>
+ <url>git at github.com:raphw/byte-buddy.git</url>
+ <tag>byte-buddy-1.7.1</tag>
+ </scm>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.ow2.asm</groupId>
+ <artifactId>asm</artifactId>
+ <version>${version.asm}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.ow2.asm</groupId>
+ <artifactId>asm-commons</artifactId>
+ <version>${version.asm}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.ow2.asm</groupId>
+ <artifactId>asm-util</artifactId>
+ <version>${version.asm}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.ow2.asm</groupId>
+ <artifactId>asm-analysis</artifactId>
+ <version>${version.asm}</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${version.junit}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${version.mockito}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.android</groupId>
+ <artifactId>android</artifactId>
+ <version>${version.android.sdk}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <!-- Allows the suppression of find bugs false-positives by annotations without adding an actual dependency. -->
+ <dependency>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>annotations</artifactId>
+ <version>${version.annotations.findbugs}</version>
+ <scope>provided</scope>
+ </dependency>
+ <!-- Allow usage of Lombok annotations. -->
+ <dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ <version>${version.lombok}</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <!-- Define release properties. -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-release-plugin</artifactId>
+ <version>${version.plugin.release}</version>
+ <configuration>
+ <useReleaseProfile>false</useReleaseProfile>
+ <releaseProfiles>extras,gpg</releaseProfiles>
+ <autoVersionSubmodules>true</autoVersionSubmodules>
+ <tagNameFormat>byte-buddy-@{project.version}</tagNameFormat>
+ </configuration>
+ </plugin>
+ <!-- Enable mutation testing. -->
+ <plugin>
+ <groupId>org.pitest</groupId>
+ <artifactId>pitest-maven</artifactId>
+ <version>${version.plugin.pitest}</version>
+ <configuration>
+ <targetClasses>
+ <param>${pitest.target}.*</param>
+ </targetClasses>
+ <targetTests>
+ <param>${pitest.target}.*</param>
+ </targetTests>
+ </configuration>
+ </plugin>
+ <!-- Configure Jacoco support for evaluating test case coverage. -->
+ <plugin>
+ <groupId>org.jacoco</groupId>
+ <artifactId>jacoco-maven-plugin</artifactId>
+ <version>${version.plugin.jacoco}</version>
+ <configuration>
+ <includes>
+ <include>net/bytebuddy/**</include>
+ </includes>
+ <excludes>
+ <exclude>net/bytebuddy/benchmark/generated/*</exclude>
+ <!-- Avoid adding synthetic members to test classes as test assert class members. -->
+ <exclude>*Test*</exclude>
+ <exclude>*test*</exclude>
+ </excludes>
+ </configuration>
+ </plugin>
+ <!-- Generate coveralls reports from Travis. -->
+ <plugin>
+ <groupId>org.eluder.coveralls</groupId>
+ <artifactId>coveralls-maven-plugin</artifactId>
+ <version>${version.plugin.coveralls}</version>
+ </plugin>
+ <!-- Also allow for manual findbugs execution. Note that the generated warnings do not always apply for Byte Buddy's use case. -->
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>findbugs-maven-plugin</artifactId>
+ <version>${version.plugin.findbugs}</version>
+ <configuration>
+ <effort>Max</effort>
+ <threshold>Low</threshold>
+ <xmlOutput>true</xmlOutput>
+ <failOnError>false</failOnError>
+ <findbugsXmlOutputDirectory>${project.build.directory}/findbugs</findbugsXmlOutputDirectory>
+ </configuration>
+ </plugin>
+ <!-- Enable scanning for methods above the inlining threshold (JDK 7+) -->
+ <plugin>
+ <groupId>com.github.ferstl</groupId>
+ <artifactId>jitwatch-jarscan-maven-plugin</artifactId>
+ <version>${version.plugin.jitwatch}</version>
+ </plugin>
+ <!-- Enable scanning for binar changes between releases -->
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>clirr-maven-plugin</artifactId>
+ <version>${version.plugin.clirr}</version>
+ </plugin>
+ </plugins>
+
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-clean-plugin</artifactId>
+ <version>${version.plugin.clean}</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>${version.plugin.jar}</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-resources-plugin</artifactId>
+ <version>${version.plugin.resources}</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-install-plugin</artifactId>
+ <version>${version.plugin.install}</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>${version.plugin.surefire}</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <version>${version.plugin.deploy}</version>
+ <configuration>
+ <updateReleaseInfo>true</updateReleaseInfo>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>${version.plugin.compiler}</version>
+ <inherited>true</inherited>
+ <configuration>
+ <source>${code.level}</source>
+ <target>${code.level}</target>
+ <encoding>${project.build.sourceEncoding}</encoding>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+
+ <!-- Define explicit version to overcome problem with generated reports. -->
+ <reporting>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jxr-plugin</artifactId>
+ <version>${version.plugin.jxr}</version>
+ </plugin>
+ </plugins>
+ </reporting>
+
+ <distributionManagement>
+ <repository>
+ <id>bintray</id>
+ <url>https://api.bintray.com/maven/raphw/maven/ByteBuddy</url>
+ </repository>
+ </distributionManagement>
+
+ <profiles>
+ <!-- Fallback for Java 6 build to use older, compatible versions of plugins. -->
+ <profile>
+ <id>java6</id>
+ <activation>
+ <activeByDefault>false</activeByDefault>
+ <jdk>1.6</jdk>
+ </activation>
+ <properties>
+ <version.plugin.findbugs>2.5.5</version.plugin.findbugs>
+ <version.annotations.findbugs>2.0.3</version.annotations.findbugs>
+ <version.plugin.checkstyle>2.15</version.plugin.checkstyle>
+ </properties>
+ </profile>
+ <!-- Runs the build with a target version for Java 7 -->
+ <profile>
+ <id>java7</id>
+ <activation>
+ <activeByDefault>false</activeByDefault>
+ </activation>
+ <properties>
+ <code.level>1.7</code.level>
+ </properties>
+ </profile>
+ <!-- Runs the build with a target version for Java 8 -->
+ <profile>
+ <id>java8</id>
+ <activation>
+ <activeByDefault>false</activeByDefault>
+ </activation>
+ <properties>
+ <code.level>1.8</code.level>
+ </properties>
+ </profile>
+ <!-- Creates additional artifacts that are required for deployment. -->
+ <profile>
+ <id>extras</id>
+ <activation>
+ <activeByDefault>false</activeByDefault>
+ </activation>
+ <properties>
+ <bytebuddy.extras>true</bytebuddy.extras>
+ </properties>
+ <build>
+ <plugins>
+ <!-- Create source code artifact. -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <version>${version.plugin.source}</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <!-- Create javadoc artifact. -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>${version.plugin.javadoc}</version>
+ <executions>
+ <execution>
+ <id>attach-javadoc</id>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ <!-- Additionally to the regular jar file specified above, create an aggregated jar. -->
+ <execution>
+ <id>aggregate-javadoc</id>
+ <goals>
+ <goal>aggregate</goal>
+ </goals>
+ <phase>package</phase>
+ <configuration>
+ <doctitle>Byte Buddy (full API), version ${project.version}</doctitle>
+ <!-- Fails due to repacking of ASM by the Shade plugin. -->
+ <failOnError>false</failOnError>
+ </configuration>
+ </execution>
+ </executions>
+ <configuration>
+ <detectJavaApiLink>true</detectJavaApiLink>
+ <links>
+ <link>${asm.javadoc}</link>
+ </links>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <!-- Sign any created artifact. (Requires configuration of gpg on the executing machine.) -->
+ <profile>
+ <id>gpg</id>
+ <activation>
+ <activeByDefault>false</activeByDefault>
+ </activation>
+ <build>
+ <plugins>
+ <!-- Sign artifacts. -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-gpg-plugin</artifactId>
+ <version>${version.plugin.gpg}</version>
+ <executions>
+ <execution>
+ <phase>verify</phase>
+ <goals>
+ <goal>sign</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <!-- Basic checks that are not requiring too much runtime. -->
+ <profile>
+ <id>checks</id>
+ <activation>
+ <activeByDefault>true</activeByDefault>
+ </activation>
+ <build>
+ <plugins>
+ <!-- Check style on build. -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-checkstyle-plugin</artifactId>
+ <version>${version.plugin.checkstyle}</version>
+ <executions>
+ <execution>
+ <phase>validate</phase>
+ <goals>
+ <goal>check</goal>
+ </goals>
+ <configuration>
+ <configLocation>checkstyle.xml</configLocation>
+ <consoleOutput>true</consoleOutput>
+ <failsOnError>true</failsOnError>
+ <excludes>**/generated/**/*</excludes>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <!-- Check API compatibility. -->
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>animal-sniffer-maven-plugin</artifactId>
+ <version>${version.plugin.animal-sniffer}</version>
+ <executions>
+ <execution>
+ <phase>validate</phase>
+ <goals>
+ <goal>check</goal>
+ </goals>
+ <configuration>
+ <signature>
+ <groupId>org.codehaus.mojo.signature</groupId>
+ <artifactId>java16</artifactId>
+ <version>1.1</version>
+ </signature>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <!-- Make sure that Byte Buddy does never depend on ASM's tree API. -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-enforcer-plugin</artifactId>
+ <version>${version.plugin.enforcer}</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>enforce</goal>
+ </goals>
+ <configuration>
+ <fail>true</fail>
+ <rules>
+ <bannedDependencies>
+ <includes>
+ <include>org.ow2.asm:asm-tree</include>
+ </includes>
+ </bannedDependencies>
+ </rules>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <!-- Integration profile that executes long-running tasks and additional static code analysis. -->
+ <profile>
+ <id>integration</id>
+ <activation>
+ <activeByDefault>false</activeByDefault>
+ </activation>
+ <properties>
+ <bytebuddy.integration>true</bytebuddy.integration>
+ </properties>
+ <build>
+ <plugins>
+ <!-- Enable non-fast-running unit tests by setting the integration profile. -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <systemPropertyVariables>
+ <net.bytebuddy.test.integration>true</net.bytebuddy.test.integration>
+ </systemPropertyVariables>
+ </configuration>
+ </plugin>
+ <!-- Run findbugs if not specified differently in a module.-->
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>findbugs-maven-plugin</artifactId>
+ <version>${version.plugin.findbugs}</version>
+ <executions>
+ <execution>
+ <phase>verify</phase>
+ <goals>
+ <goal>check</goal>
+ </goals>
+ <configuration>
+ <effort>Max</effort>
+ <threshold>Low</threshold>
+ <xmlOutput>true</xmlOutput>
+ <failOnError>true</failOnError>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <!-- Only build the Android test application on demand as it requires Android SDK installation. -->
+ <profile>
+ <id>android</id>
+ <activation>
+ <activeByDefault>false</activeByDefault>
+ </activation>
+ <modules>
+ <module>byte-buddy</module>
+ <module>byte-buddy-dep</module>
+ <module>byte-buddy-benchmark</module>
+ <module>byte-buddy-agent</module>
+ <module>byte-buddy-android</module>
+ <module>byte-buddy-android-test</module>
+ </modules>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/release-notes.md b/release-notes.md
new file mode 100644
index 0000000..14b982e
--- /dev/null
+++ b/release-notes.md
@@ -0,0 +1,860 @@
+Byte Buddy release notes
+------------------------
+
+### 14. Mai 2017: version 1.7.0
+
+- Define names for automatic modules in Java 9.
+- Introduce property `net.bytebuddy.nexus.disabled` to allow disabling `Nexus` mechanism.
+- Do not use context `ProtectionDomain` when using `Nexus` class.
+- Normalize `Advice` class custom bindings via opening internally used `OffsetMapping` API. Remove `CustomValue` binding which is less powerful.
+- Do not group `transient` with `volatile` modifier.
+- Introduce `MemberRemoval` component for removing fields and/or methods.
+- Introduce first version for `MemberSubstituion` class for replacing field/method access.
+
+### 26. April 2017: version 1.6.14
+
+- Extended `AgentBuilder` listener API.
+- Added trivial `RawMatcher`.
+- Check modules for modifiability.
+- Adapt new Java 9 namespaces for modules.
+- Start external process for self-attachment if self-attachment is not allowed.
+
+### 17. April 2017: version 1.6.13
+
+- Explicit consistency check for stack map frame information in `Advice`.
+- Extended `InstallationListener` API.
+- Fixed stack size information on variable storage.
+
+### 18. March 2017: version 1.6.12
+
+- Add `InstallationListener` in favor of `InstallationStrategy` and allow resubmission strategy to hook into it in order to cancel submitted jobs.
+
+### 12. March 2017: version 1.6.11
+
+- Fix modifier adjustment for visibility bridges (did not work last time)
+- Added class injector for Java 9 handle class definition.
+
+### 1. March 2017: version 1.6.10
+
+- Allow installation of `ClassFileTransformer` in byte array class loader.
+- Adjust visibility for bridge methods.
+
+### 20. February 2017: version 1.6.9
+
+- Properly add visibility bridges for default methods.
+- Added matcher for unresolvable types.
+- Improved `ByteBuddyAgent` API.
+- Fixed Gradle and Maven plugin path resolution.
+
+### 12. February 2017: version 1.6.8
+
+- Avoid logging on empty resubmission.
+- Retain actual modifiers on frozen instrumented type.
+
+### 26. January 2017: version 1.6.7
+
+- Refactored `Resubmitter` to a DSL-step within the redefinition configuration.
+- Added additional element matchers.
+
+### 24. January 2017: version 1.6.6
+
+- Fixed computation of modifiers for rebased method in native state.
+
+### 20. January 2017: version 1.6.5
+
+- Improved lazy resolution of super types in matchers.
+- Added frozen instrumented type and factory for such types when no class file format changes are desired.
+- Improved lazy resolution for generic type signatures.
+
+### 19. January 2017: version 1.6.4
+
+- Refactored super type visitors to always be lazy until generic properties are required to be resolved.
+- Apply proper raw type resolution. Made default method graph compiler reify generic types to compute correct bridges.
+
+### 13. January 2017: version 1.6.3
+
+- Improved `Resubmitter` configuration.
+- Added `AgentBuilder.Transformation.ForAdvice` to allow for simple creation of `Advice` classes from Java agents.
+
+### 11. January 2017: version 1.6.2
+
+- Removed obsolete `toString` representations.
+- Start using Lombok for equals/hashCode unless explicit.
+- Add security manager check to Byte Buddy agent.
+- Added asynchronous super type loading strategies.
+- Added resubmitter.
+- Added class injection strategy for Android.
+- Fixed type initializer instrumentation for redefinitions.
+- Added `loaded` property for listener on agent builder.
+
+### 5. January 2017: version 1.6.1
+
+- Added check to `@Pipe` for method invokability.
+- Added unsafe `ClassInjector` and class loading strategy.
+- Improved reflection-based class injector on Java 9.
+- Removed uneccessary class file location using modules on Java 9.
+- Improved fail-safety for type variable resolution to allow processing incorrectly declared type variables.
+
+### 2. January 2017: version 1.6.0
+
+- Added `InjectingClassLoader` with class loading strategy that allows for reflection-free loading.
+- Added proper class loader locking to injection strategy.
+- Fixed method lookup to not use *declared* accessors unless necessary to avoid security manager check.
+- Added `@SuperMethod` and `@DefaultMethod` annotations for `MethodDelegation`.
+- Refactored `AsmVisitorWrapper` to accept a list of fields and methods that are intercepted. This allows to use the wrapper also for methods that are overridden.
+- Added a `MethodGraph.Compiler.ForDeclaredMethods` to avoid processing full type hierarchy if only type enhancement should be done without declaring new methods on a type. This should be used in combination with `Advice` instead of `MethodGraph.Empty` as those methods are supplied to the ASM visitor wrappers.
+- Refactored `MethodDelegation` to precomile records for all candidates to avoid duplicate annotation processing.
+
+### 29. December 2016: version 1.5.13
+
+- Updates to ASM 5.2
+- Updates Android DX-maker.
+- Adds API to `MultipleParentClassLoader` to use other loader than bootstrap loader as a parent which is not always legal, e.g. on Android.
+- Make `ClassInjector` use official class loading lock if one is available.
+- Make `ClassInjector` use `getDefinedPackage` instead of `getPackage` if available.
+- Declare UNIX socket library as provided in Byte Buddy agent to only add the dependency on demand.
+
+### 27. December 2016: version 1.5.12
+
+- Refactored rebasing of type initializers. Do no longer rebase into static method to pass validation for final static field assignment on Java 9.
+- Added fallback to `sun.misc.Unsafe` for class definition if reflective access to the protected `ClassLoader` methods is not available which are required for the injection strategy.
+- Added super-type-loading `DescriptorStrategy` for agent builder.
+- Added assignment checks for `MethodCall` for invocation target.
+
+### 20. December 2016: version 1.5.11
+
+- Resolved compound components to linerarize nested collections for vastly improved performance with large structures.
+- Added `TypeCache`.
+- Added fallback to assign `null` to `SuperCall` and `DefaultCall` if assignment is impossible.
+- Deprecated `Forwarding` in favor of `MethodCall`.
+- Fixed matcher for interfaces in type builder DSL.
+- Fixed resolution of field type in `MethodCall`.
+
+### 16. December 2016: version 1.5.10
+
+- Added possibility for readding types after a failed retransformation batch.
+- Added partitioning batch allocator.
+
+### 13. December 2016: version 1.5.9
+
+- Allow specifying `TargetType` in `Advice.FieldValue`.
+- Allow array value explosion in `MethodCall`.
+- Extended `FieldAccessor` to allow reading `FieldDescription`s directly.
+- Fixed class name resolution in Maven and Gradle plugins.
+
+### 5. December 2016: version 1.5.8
+
+- Added implementation for attachment on Linux and HotSpot using a non-JDK VM.
+- Fixed argument resolution for `ByteBuddyAgent`.
+- Fixed field resolution for `MethodCall` to allow custom definition of fields.
+- Fixed visibility checks.
+- Do not override default method for proxies for `Pipe`.
+
+### 25. November 2016: version 1.5.7
+
+- Fixed type discovery for custom advice annotation bindings.
+
+### 18. November 2016: version 1.5.6
+
+- Added possibility to configure suppression handler in `Advice` classes.
+
+### 17. November 2016: version 1.5.5
+
+- Refactored `Advice` to use stack manipulations and `Assigner`.
+- Refactored `Advice` to use `Return` instead of `BoxedReturn` and added `AllArguments` instead of `BoxedArguments` in conjunction with allowing to use dynamic typing for assignments via the annotation.
+- Added fixed value instrumentation for method parameters.
+- Added weak class loader referencing for `Nexus` and allow registration of a `ReferenceQueue`.
+
+### 11. November 2016: version 1.5.4
+
+- Extended `MethodCall` API.
+- Added additional element matchers.
+- Extended `AsmVisitorWrapper` API.
+
+### 3. November 2016: version 1.5.3
+
+- Refactored `Advice` to allow usage as a wrapper for an `Implementation`. This allows chaining of such advices.
+- Allow to dynamically locate a `FieldDescription` or `ParameterDescription` from a custom `Advice` annotation which binds the field or parameter value.
+- Added `invokeSelf` option to `MethodCall` instrumentation.
+
+### 31. October 2016: version 1.5.2
+
+- Refactored `FieldAccessor` to allow more flexible creation of getters and setters of particular parameters.
+- Create string-based hashes for random fields that depend on a value's hash value.
+
+### 27. October 2016: version 1.5.1
+
+- Fixed stack size computation when using `@Advice.Origin`.
+
+### 25. October 2016: version 1.5.0
+
+- Refactor `Instrumentation`s to only delegate to fields instead of requireing their definition. The `defineField` API should be generally preferred for defining fields as it is much richer and therefore easier to extend.
+- Made type annotation reader more robust towards older versions of Java 8.
+- Refactored lazy type resolution for generic types to no longer eagerly load generic types when navigating through a type hierarchy.
+- Unified several implementation APIs and added better abstractions.
+- Fixed some missing bits of validation of implementations.
+- Do not replicate incompatible bridge methods.
+
+### 17. October 2016: version 1.4.33
+
+- Use `IMITATE_SUPER_CLASS_OPENING` as a default constructor strategy.
+- Extract method visibility during method graph compilation.
+- Apply a type variable's erasure if a type variable is out of scope instead of throwing an exception. This can happen when subclassing an inner type outside of its outer type or when a compiler such as *scalac* adds inconsistent generic type information.
+- Optimize the application of the ignore matcher within an agent builder to only be applied once.
+
+### 11. October 2016: version 1.4.32
+
+- Added `ConstructorStrategy` for inheriting constructors but make them `public`.
+- Do not instrument anonymously loaded types during redefinition unless the lambda strategy is enabled.
+
+### 6. October 2016: version 1.4.31
+
+- Reuse `CircularityLock` on all `AgentBuilder`s by default to avoid that Byte Buddy agents introduce circularities to different agents.
+- Also allow using `Advice` as `Implementation`.
+- Added `FixedValue.self()` and added `FieldPersistence` for describing `volatile` fields.
+
+### 4. October 2016: version 1.4.30
+
+- Also acquire circularity lock during class file retransformation.
+- Added slicing `BatchAllocator`.
+
+### 3. October 2016: version 1.4.29
+
+- Explicitly check for recursive transformation of types used during a transformation causing a `ClassCircularityError` from an `AgentBuilder` by adding thread-local locking.
+
+### 30. September 2016: version 1.4.28
+
+- Additional refactoring of the `AgentBuilder` to fix a regression of 1.4.27.
+- Unified the error listener and the regular listener that were added in the previous version.
+
+### 29. September 2016: version 1.4.27
+
+- Refactored `AgentBuilder` retransformation mechanism to allow for custom recovery and batch strategies.
+- Fixed Gradle plugin build which did not contain files.
+- Supply no argument to agent attachment by default instead of empty string argument.
+
+*Note*: Currently, it seems like the new retransformation mechanism introduces a racing condition in class loading resulting in some classes not being instrumented
+
+### 21. September 2016: version 1.4.26
+
+- Refactored `skipOn` property of `Advice` component.
+- Allow reading a `Method`/`Constructor` property from `Advice`.
+- Fixed bug that duplicated added parameter annotations on a `DynamicType` builder.
+
+### 20. September 2016: version 1.4.25
+
+- Added overloaded versions for `byte-buddy-agent` to allow agent attachment with explicit argument.
+- Made `Advice` more flexible to allow skipping of instrumented method for complex advice method return types.
+
+### 15. September 2016: version 1.4.24
+
+- Make `AgentBuilder` produce `ResettableClassFileTransformer`s which can undo their transformation.
+
+### 14. September 2016: version 1.4.23
+
+- Made `TypeDescription.ForLoadedType` serializable for better alignment with reflection API.
+- Adapted changes in `Instrumentation` API for Java 9.
+- Refactored `AnnotationValue` to apply Java 9 specific string rendering on Java 9 VMs.
+- Adapted new `toString` representation of parameterized types on Java 9 VMs.
+
+### 02. September 2016: version 1.4.22
+
+- Added Byte Buddy plugin for Gradle.
+- Improved Byte Buddy plugin for Maven.
+
+### 28. August 2016: version 1.4.21
+
+- Fixed modifier resolution for anonymous classes to preserve a shadowed `final` modifier.
+- Added Byte Buddy plugin for Maven.
+
+### 21. August 2016: version 1.4.20
+
+- Fixed stack size adjustment for accessing double-sized primitive access array elements.
+- Fixed `Advice` adjustment of local variable index for debugging information (improves Java agent compatibility).
+- Renamed `TypeLocator` to `PoolStrategy` to avoid confusion with the names.
+- Removed `DescriptionStrategy`s that rely on fallback-description as those do not properly fallback for meta data.
+- Added `FallbackStrategy` as a replacement which allows to reattempt a transformation without using loaded type information.
+
+### 14. August 2016: version 1.4.19
+
+- Added `@StubValue` and `@Unused` annotations to `Advice` component.
+- Added possibility to retain line number information for entry`Advice`
+- Removed class loader dependency when querying loaded annotation values.
+- Made annotation values more type-safe.
+
+### 9. August 2016: version 1.4.18
+
+- Added automatic support for Java 9 class file location for boot modules.
+- Improvided `FieldProxy.Binder` to allow for a single accessor interface.
+- Fixed counting problem in `Advice` component.
+
+### 1. August 2016: version 1.4.17
+
+- Fixed annotation resolution for Java 9 to exlude the `jdk.internal` namespace by default.
+- Do not copy annotations for default constructor strategies but allow configuring annotation strategy.
+- Added file-system class file locators for modules in Java 9.
+- Added convenience methods to default location strategies.
+- Exclude `sun.reflect` namespace by default from `AgentBuilder` to avoid error messages.
+- Fixed resolution of type varibales for transformed methods and fields.
+- Fixed stack-aware method visitor when encountering exchanging duplication instructions.
+
+### 28. July 2016: version 1.4.16
+
+- Added `POOL_LAST_DEFERRED` and `POOL_LAST_FALLBACK` description strategy.
+- Fixed resolution of bridge methods for diamond inheritance.
+- Refactored modifier API to only expose named modifier checks for an element that apply to it.
+- Fixed resolution for type variables for transformed methods.
+
+### 25. July 2016: version 1.4.15
+
+- Fixed frame generation for `void` methods without regular return in `Advice`.
+- Fixed `TypeValidation` for Java 8 interfaces not allowing private methods.
+- Simplified and documented `AccessController` usage.
+
+### 21. July 2016: version 1.4.14
+
+- Fixed bug with handling of legacy byte code instructions in `Advice` component.
+- Cleaned up and refactored usage of `AccessController`. Added privileged handling to `AgentBuilder`.
+- Added proper buffering to non-buffered interaction with file streams.
+- Make `ByteBuddy` creation more robust by adding a default fallback for unknown VMs.
+- Improved support for Java 9.
+
+### 19. July 2016: version 1.4.13
+
+- Lazily compute `Implementation.Target` and `Implementation.Context` in case of a type inlining to provide correct feature set. Added validation if this constraint is broken.
+- Make `TypePool` using an eager `TypeDescription` more robust towards errors.
+
+### 15. July 2016: version 1.4.12
+
+- Monitor advice code for inconsistent stack heights at return statements to clean the stack during instrumentation to not trigger verifier errors if such atypical but legal code is encountered.
+- Do not generate handlers for return values if an instrumented method or an advice method only throws exceptions but never returns regularly.
+
+### 15. July 2016: version 1.4.11
+
+- Added tracer for the state of the operand stack for the `Advice` component to clear the stack upon a return. Without this, if code would return without leaving the stack empty, a verifier error would be thrown. This typically is only a problem when processing code that was produced by other code generation libraries.
+
+### 15. July 2016: version 1.4.10
+
+- Fixed resolution of modifiers and local type properties from a default type pool.
+- Improved key for caching `TypeLocator` to share a key for the system and bootstrap class loader.
+
+### 11. July 2016: version 1.4.9
+
+- Added additional implementations of a `DescriptionStrategy` for `POOL_LAST` and `POOL_FIRST` resolution.
+
+### 6. July 2016: version 1.4.8
+
+- Allow to skip execution of instrumented method from `Advice` via entry advice indicated by return value.
+- Added API to transform predefined type variables on a dynamic type.
+- Refactored `Transformer` API to be shared for methods, fields and type variables.
+- Allow to spread `Advice` methods over multiple classes.
+- Added convenience methods to `AsmVisitorWrapper`s for declared fields and methods.
+- Performance improvements in `Advice` class for byte code parsing.
+
+### 6. July 2016: version 1.4.7
+
+- Added default `TypePool` that allows for lazy resolution of referenced types. This can both be a performance improvement and allows working with optional types as long as they are not directly required within a transformation. This type pool is now used by default.
+- Make interfaces public by default when creating them via `ByteBuddy::makeInterface`.
+- Added `TypeResolutionStrategy` to allow for active resolution via the `Nexus` also from outside the `AgentBuilder`.
+- Make best effort from a `ClassLoadingStrategy` to not resolve types during loading.
+- Added convenience method for loading a dynamic type with an implicit `ClassLoadingStrategy`.
+
+### 30. June 2016: version 1.4.6
+
+- Added a `ClassFileLocator` for a class loader that only references it weakly.
+- Allow to supply `TypePool` and `ClassFileLocator` seperatly within an `AgentBuilder`.
+- Made `MethodPool` sensitive to bridge methods which should only be added to classes of a version older than Java 4.
+- Fixed creation of Java 9 aware `ClassFileTransformer` to only apply on Java 9 VMs.
+- Added matcher for the type of a class loader.
+- Fixed name resolution of anonymously-loaded types.
+
+### 24. June 2016: version 1.4.5
+
+- Added `InstallationStrategy` to `AgentBuilder` that allows customization of error handling.
+- Added *chunked* redefinition and retransformation strategies.
+
+### 23. June 2016: version 1.4.4
+
+- Added `net.bytebuddy` qualifier when logging.
+- Added `net.bytebuddy.dump` system property for specifing a location for writing all created class files.
+
+### 17. June 2016: version 1.4.3
+
+- Fixed bug in `MultipleParentClassLoader` where class loaders were no longer filtered properly.
+- Added support for major.minor version 53 (Java 9).
+- Made `DescriptionStrategy` customizable.
+
+### 16. June 2016: version 1.4.2
+
+- Changed storage order of return values in `Advice` methods to avoid polluting the local variable array when dealing with nested exception handlers.
+- Added caching `ElementMatcher` as a wrapping matcher.
+- Exclude Byte Buddy types by default from an `AgentBuilder`.
+- Added `DescriptionStrategy` that allows not using reflection in case that a class references non-available classes.
+
+### 7. June 2016: version 1.4.1
+
+- Fixed validation by `MethodCall` instrumentation for number of arguments provided to a method.
+- Added further support for module system.
+- Allow automatic adding of read-edges to specified classes/modules when instrumenting module classes.
+- Implicitly skip methods without byte code from advice component.
+
+### 2. June 2016: version 1.4.0
+
+- Added initial support for Jigsaw modules.
+- Adjusted agent builder API to expose modules of instrumented classes.
+- Added additional matchers.
+- Simplified `BinaryLocator` and changed its name to `TypeLocator`.
+
+### 30. May 2016: version 1.3.20
+
+- Fixed `MultipleParentClassLoader` to support usage as being a parent itself.
+- Fixed default ignore matcher for `AgentBuilder` to ignore synthetic types.
+
+### 29. April 2016: version 1.3.19
+
+- Added convenience method to `MethodCall` to add all arguments of the instrumented method.
+- Added `optional` attribute to `Advice.This`.
+
+### 25. April 2016: version 1.3.18
+
+- The Owner type of a parameterized type created by a `TypePool` is no longer parameterized for a static inner type.
+- The receiver type of a executingTransformer is no longer considered parameterized for a static inner type.
+
+### 23. April 2016: version 1.3.17
+
+- Removed overvalidation of default values for non-static fields.
+
+### 21. April 2016: version 1.3.16
+
+- Better support for Java 1 to Java 4 by automatically resolving type references from a type pool to `forName` lookups.
+- Better support for dealing with package-private types by doing the same for invisible types.
+- Simplified `MethodHandle` and `MethodType` handling as `JavaInstance`.
+
+### 19. April 2016: version 1.3.15
+
+- Extended the `AgentBuilder` to allow for transformations that apply fall-through semantics, i.e. work as a decorator.
+- Added map-based `BinaryLocator`.
+
+### 19. April 2016: version 1.3.14
+
+- Only add frames in `Advice` components if class file version is above 1.5.
+- Allow to specify exception type for exit advice to be named. **This implies a default change** where exit advice is no longer invoked by default when an exception is thrown from the instrumented method.
+- Added possibility to assign a value to a `@BoxedReturn` value to change the returned value.
+
+### 16. April 2016: version 1.3.13
+
+- Extended the `Advice` component storing serializable values that cannot be represented in the constant pool as encodings in the constant pool.
+- Added support for non-inlined `Advice` method.
+- Mask modifiers of ASM to not longer leak internal flags beyond the second byte.
+- Added support for suppressing an exception of the instrumented method from within exit advice.
+
+### 13. April 2016: version 1.3.12
+
+- Fixed error during computation of frames for the `Advice` computation.
+- Avoid reusing labels during the computations of exception tables of the `Advice` component.
+
+### 12. April 2016: version 1.3.11
+
+- Byte Buddy `Advice` now appends handlers to an existing exception handler instead of prepending them. Before, existing exception handlers were shadowed when applying suppression or exit advice on an exception.
+- Added additional annotations for `Advice` such as `@Advice.BoxedReturn` and `@Advice.BoxedArguments` for more generic advice. Added possibility to write to fields from advice.
+- Added mechanism for adding custom annotations to `Advice` that map compile-time constants.
+- Implemented a canonical binder for adding custom compile-time constants to a `MethodDelegation` mapping.
+
+### 8. April 2016: version 1.3.10
+
+- Fixed another bug during frame translation of the `Advice` component when suppression were not catched for an exit advice.
+- Improved unit tests to automatically build Byte Buddy with Java 7 and Java 8 byte code targets in integration.
+
+### 7. April 2016: version 1.3.9
+
+- Optimized method size for `Advice` when exception is not catched.
+- Improved convenience method `disableClassFormatChanges` for `AgentBuilder`.
+
+### 6. April 2016: version 1.3.8
+
+- Fixed frame computation for the `Advice`.
+- Optimized frame computation to emitt frames of the minimal, possible size when using `Advice`.
+- Only add exit `Advice` once to reduce amound of added bytes to avoid size explosion when a method supplied several exits.
+- Optimized `Advice` injection to only add advice infrastucture if entry/exit advice is supplied.
+- Optimized exception handling infrastructure for exit `Advice` to only be applied when exceptions are catched.
+- Added mapping for the *IINC* instruction which was missing from before.
+- Added possibility to propagate AMS reader and writer flags for `AsmVisitorWrapper`.
+- Made `Advice` method parser respect ASM reader flags for expanding frames.
+
+### 3. April 2016: version 1.3.7
+
+- Fixed bug when returning from an `Advice` exit method without return value and accessing `@Advice.Thrown`.
+- Added additional annotations for advice `@Advice.Ignored` and `@Advice.Origin`.
+- Implemented frame translator for `Advice` method to reuse existing frame information instead of recomputing it.
+
+### 1. April 2016: version 1.3.6
+
+- Implemented universal `FieldLocator`.
+- Extended `AgentBuilder` API to allow for more flexible matching and ignoring types.
+
+### 18. Match 2016: version 1.3.5
+
+- Added `Advice.FieldValue` annotation for reading fields from advice.
+
+### 13. March 2016: version 1.3.4
+
+- Added support for new Java 9 version scheme.
+
+### 10. March 2016: version 1.3.3
+
+- Added hierarchical notation to default `TypePool`.
+
+### 10. March 2016: version 1.3.2
+
+- Added possibility to suppress `Throwable` from advice methods when using the `Advice` instrumentation.
+
+### 9. March 2016: version 1.3.1
+
+- Added possibility to use contravariant parameters within the `Advice` adapter for ASM.
+
+### 8. March 2016: version 1.3.0
+
+- Added `Advice` adapter for ASM.
+- Fixed `AsmVisitorWrapper` registration to be stacked instead of replacing a previous value.
+- Added validation for setting field default values what can only be done for `static` fields. Clarified javadoc.
+- Fixed attach functionality to work properly on IBM's J9.
+
+### 22. February 2016: version 1.2.3
+
+- Fixed return type resolution for overloaded bridge method.
+
+### 16. February 2016: version 1.2.2
+
+- Fixed redefinition strategy for `AgentBuilder` where transformations were applied twice.
+- Added `ClassLoader` as a third argument for the `AgentBuilder.Transformer`.
+
+### 6. February 2016: version 1.2.1
+
+- Added validation for receiver types.
+- Set receiver types to be implicit when extracting constructors of a super type.
+
+### 5. February 2016: version 1.2.0
+
+- Added support for receiver type retention during type redefinition and rebasement.
+- Added support for receiver type definitions.
+
+### 5. February 2016: version 1.1.1
+
+- Fixed interface assertion of the custom binder types to accept default methods.
+- Improved documentation.
+
+### 26. January 2016: version 1.1.0
+
+- Refactored `AgentBuilder` API to be more streamlined with the general API and improved documentation.
+- Added possibility to instrument classes that implement lambda expressions.
+- Added possibility to explicitly ignore types from an `AgentBuilder`. By default, synthetic types are ignored.
+- Proper treatment of deprecation which is now written into the class file as part of the resolved modifier and filtered on reading.
+- Added support for Java 9 APIs for process id retrieval.
+
+### 21. January 2016: version 1.0.3
+
+- Added support for Java 9 owner type annotations.
+- Fixed bug in type builder validation that prohibited annotations on owner types for non-generic types.
+- Added additional element matchers for matching an index parameter type.
+
+### 20. January 2016: version 1.0.2
+
+- Fixed resolution of type paths for inner classes.
+- Added preliminary support for receiver types.
+- Fixed resolution of type variables from a static context.
+
+### 18. January 2016: version 1.0.1
+
+- Refactored type variable bindings for generic super types: Always retain variables that are defined by methods.
+- Retain type annotations that are defined on a `TargetType`.
+
+### 15. January 2016: version 1.0.0
+
+- Added support for type annotations.
+- Refactored public API to support type annotations and parameter meta information.
+- Several renamings in preparation of the 1.0.0 release.
+- Refactored type representation to represent raw types as `TypeDescription`s. This allows for resolution of variables on these types as erasures rather than their unresolved form. Refactored naming of generic types to the common naming scheme with nested classes.
+- Replaced generalized token representation to define tokens, type tokens and signature tokens.
+- General API improvements and minor bug fixes.
+
+### 4. January 2016: version 0.7.8
+
+- Implemented all type lists of class file-rooted files to fallback to type erasures in case that the length of generic types and raw types does not match. This makes Byte Buddy more robust when dealing with illegally defined class files.
+- Fixed rule on a default method's invokeability.
+- Extended `MethodCall` implementation to include shortcuts for executing `Runnable` and `Callable` instances.
+- Added `failSafe` matcher that returns `false` for types that throw exceptions during navigation.
+
+### 14. December 2015: version 0.7.7
+
+- Fixed type resolution for anonymously loaded classes by the `ClassReloadingStrategy`.
+- Added additional `InitiailizationStrategy`s for self-injection where the new default strategy loads types that are independent of the instrumented type before completing the instrumentation. This way, the resolution does not fail for types that are accessed via reflection before initializing the types if a executingTransformer is rebased.
+
+### 11. December 2015: version 0.7.6
+
+- Fixed resolution of `@Origin` for constructors and added possibility to use the `Executable` type.
+- Fixed name resolution of types loaded by anonymous class loading.
+- Allowed alternative lookup for redefinitions to support types loaded by anonymous class loading.
+
+### 7. December 2015: version 0.7.5
+
+- Fixed generic type resolution optimization for proxies for `@Super`.
+
+### 2. December 2015: version 0.7.4
+
+- Added `TypePool` that returns precomputed `TypeDescription`s for given types.
+- Fixed agent and nexus attachment and the corresponding value access.
+
+### 30. November 2015: version 0.7.3
+
+- Added visibility substitution for `@Super` when the instrumented type is instrumented to see changed state on a redefinition.
+- Added patch for modifier information of inner classes on a redefinition.
+- Added fallback for `Nexus` injection to attempt lookup of already loaded class if resource cannot be located.
+
+### 26. November 2015: version 0.7.2
+
+- Added `TypePool` that falls back to class loading if a class cannot be located.
+- Added binary locator for agent builder that uses the above class pool and only parses the class file of the instrumented type.
+- Added methods for reading inner classes of a `TypeDescription`.
+- Fixed random naming based on random numbers to avoid signed numbers.
+- Moved `Nexus` and `Installer` types to a package-level to avoid illegal outer and inner class references which could be resolved eagerly.
+- Added validation for illegal constant pool entries.
+- Added a `Premature` initialization strategy for optimistically loading auxiliary types.
+- Added a `ClassVisitorWrapper` for translating Java class files prior to Java 5 to use explicit class loading rather than class pool constants.
+
+### 16. November 2015: version 0.7.1
+
+- Fixed injection order for types to avoid premature loading by dependent auxiliary types.
+- Added additional `ClassFileLocator`s and refactored class file lookup to always use these locators.
+
+### 11. November 2015: version 0.7
+
+- Refactored injection strategy to always inject and load the instrumented type first to avoid premature loading by reference from auxiliary types.
+- Refactored `AgentBuilder.Default` to delay auxiliary type injection until load time to avoid premature loading by reference from auxiliary types.
+- Added API to add additional code to type initializers while building a type.
+- Refactored agent `Nexus` to allow for multiple registrations of self initializers if multiple agents are registered via Byte Buddy.
+- Fixed resolution of interface methods that were represented in the type hierarchy multiple times.
+- Implemented custom ASM class writer to allow for frame computation via Byte Buddy's type pool when this is required by a user.
+- Fallback to no allowing for instrumenting type initializers for rebased or redefined interfaces before Java 8.
+
+### 28. October 2015: version 0.7 (release candidate 6)
+
+- Refactored `AgentBuilder.Default` to delegate exceptions during redefinitions to listener instead of throwing them.
+- Fixed bug where instrumented type would count to auxiliary types and trigger injection strategy.
+- Fixed bug where interface types would resolve to a non-generic type signature.
+- Added strategy to use redefinition or retransformation of the `Instrumentation` API when building agents.
+- Added lazy facade to be used by agent builder to improve performance for name-based matchers.
+
+### 15. October 2015: version 0.7 (release candidate 5)
+
+- Fixed parser to suppress exceptions from generic signatures which are not supposed to be included in the class file if no array type is generic.
+- Fixed class validator which did not allow `<clinit>` blocks in interface types.
+- Added restriction to retransformation to not attempt a retransformation at all if no class should be retransformed.
+- Added a factory for creating an `Implementation.Context` that is configurable. This way, it is possible to avoid a rebase of a type initializer which is not always possible.
+- Added a possibility to specify for an `AgentBuilder` how it should redefine or rebase a class that is intercepted.
+
+### 13. October 2015: version 0.7 (release candidate 4)
+
+- Fixed naming strategy for fields that cache values which chose duplicate names.
+- Fixed resolution of raw types within the type hierarchy which were represented as non-generic `TypeDescription` instances where type variables of members were not resolved.
+- Added possibility to specify hints for `ClassReader` and `ClassWriter` instances.
+- Fixed resolution for modifiers of members that are defined by Byte Buddy. Previously, Byte Buddy would sometimes attempt to define private synthetic methods on generated interfaces.
+- Fixed assignability resolution for arrays.
+- Fixed class file parser which would not recognize outer classes for version 1.3 byte code.
+
+### 6. October 2015: version 0.7 (release candidate 3)
+
+- Read `Nexus` instances of the Byte Buddy agents from the enclosing class loader rather than from the system class loader. This allows for their usage from OSGi environments and for user with other custom class loaders.
+- Changed modifiers for accessor methods and rebased methods to be public when rebasing or accessing methods of a Java 8 interface. For interfaces, all modifiers must be public, even for such synthetic members.
+- Support absolute path names for accessing class file resources of the `ByteArrayClassLoader`.
+- Added random suffix to the names of rebased methods to avoid naming conflicts.
+
+### 16. September 2015: version 0.7 (release candidate 2)
+
+- Refactored runtime attachment of Java agents to support Java 9 and additional legacy VM (version 8-).
+- Refactored `MethodGraph` to only represent virtual methods.
+- Changed notion of visibility to not longer consider the declaring type as part of the visibility.
+- Increased flexibility of defining proxy types for `@Super` and `@Default` annotations.
+- Added directional `AmbigouityResolver`.
+- Fixed detection of methods that can be rebased to not include methods that did not previously exist.
+
+### 11. August 2015: version 0.7 (release candidate 1)
+
+- Added support for generic types.
+- Replaced `MethodLookupEngine` with `MethodGraph.Compiler` to provide a richer data structure.
+- Added support for bridge methods (type and visibility bridges).
+- Refactored the predefined `ElementMatcher`s to allow for matching generic types.
+- Replaced the `ModifierResolver` with a more general `MethodTransformer`.
+
+### 11. August 2015: version 0.6.15
+
+- Added support for discovery and handling of Java 9 VMs.
+- Fixed class loading for Android 5 (Lollipop) API.
+
+### 20. July 2015: version 0.6.14
+
+- Fixed resolution of ignored methods. Previously, additional ignored methods were not appended but added as an additional criteria for ignoring a method.
+
+### 17. July 2015: version 0.6.13
+
+- Fixed resolution of field accessors to not attempt reading of non-static fields from static methods.
+- Fixed renaming strategy for type redefinitions to work around a constraint of ASM where stack map frames required to be expanded even though this was not strictly necessary.
+
+### 10. July 2015: version 0.6.12
+
+- Added API for altering a method's modifiers when intercepting it.
+- Added API for allowing to filter default values when writing annotations.
+
+### 22. June 2015: version 0.6.11
+
+- Added additional `ClassFileLocator`s for locating jar files in folders and jar files.
+- Added explicit check for invalid access of instance fields from static methods in field accessing interceptors.
+- Added the `@StubValue` and `@FieldValue` annotations.
+
+### 18. June 2015: version 0.6.10 (and 0.6.9)
+
+- Corrected the resolution of a type's visibility to another type to determine if a method can be legally overridden.
+- Previous version 0.6.9 contained another bug when attempting to fix this problem.
+
+Corrected incorrect deployment of version 0.6.7 which does not use a dependency reduced POM for the *byte-buddy* module.
+
+### 1. June 2015: version 0.6.8 (and 0.6.7)
+
+- Upgraded ASM dependency to 5.0.4.
+- Fixed OSGi headers in all relevant artifacts.
+
+*Warning*: The *byte-buddy* artifact of version 0.6.7 is accidentally deployed with a defect POM file which does not exclude the shaded resources.
+
+### 28. May 2015: version 0.6.6
+
+- Fixed error in resolution of the `TargetType` pseudo-variable when used as component type of an array.
+
+### 7. May 2015: version 0.6.5
+
+- Extended public API with convenience methods.
+
+### 6. May 2015: version 0.6.4
+
+- Extended public API to accept more general argument types when appropriate.
+- Extended `@Origin` annotation to allow for accepting modifiers.
+
+### 29. April 2015: version 0.6.3
+
+- Made the `TypeDescription.ForLoadedType` class loader agnostic. Before, a class that was loaded by multiple class
+ loaders would have been considered inequal what is not true for the byte code level.
+
+### 23. April 2015: version 0.6.2
+
+- Added additional class validation such that it becomes impossible to define members on classes that do not fit
+ the class's structure, i.e. default methods on Java interfaces in version seven.
+- Added default `Assigner` singleton.
+
+### 21. April 2015: version 0.6.1
+
+- Added `AnnotationDescription.Builder` to allow easy definition of annotation values without loading any values.
+- Added possibility to define enumerations at runtime.
+- Added possibility to dynamically read enumerations for the `MethodCall` and `InvokeDynamic` implementations.
+- Further API clean-up.
+
+### 15. April 2015: version 0.6
+
+- Renamed the `Instrumentation` interface to `Implementation` to avoid naming conflicts with Java types.
+- Renamed the `Field` annotation to `FieldProxy` to avoid naming conflicts with Java types.
+- Refactored package structure to make the implementation more readable.
+- Added possibility to define annotation default values.
+- Avoid creation of an auxiliary placeholder type for method rebasements if it is not required.
+- Avoid rebasing of methods if they are not instrumented.
+- Reimplemented `TypeWriter`, `MethodRegistry` and other supporting infrastructure to make the code simpler.
+- Refactored testing that is related to the previous infrastructure.
+
+### 21. March 2015: version 0.5.6
+
+- Added possibility to write parameter meta information to created classes if it is fully available for a method.
+
+### 20. March 2015: version 0.5.5
+
+- Retrofitted method parameters to be represented by `ParameterDescription`s and added possibility to extract names
+ and modifiers for these parameters, either by using the Java 8 API (if available) or by reading this information
+ from the underlying class file.
+- Fixed a `NullPointerException` being thrown due to accidental return of a `null` value from a method.
+
+### 15. March 2015: version 0.5.4
+
+- Fixed missing retention of method annotations of instrumented types.
+- Allowed dynamic lookup of methods for the `MethodCall` instrumentation.
+
+### 24. February 2015: version 0.5.3
+
+- Changed the `SuperMethodCall` instrumentation to fall back to a default method call if required. A different
+ behavior was found to surprise users and would introduce subtle bugs in user code as the super method instrumentation
+ would always work with subclassing due to Java super method call semantics.
+- Added a `MethodCall` instrumentation that allows hard-coding a method call.
+- Added an `InvokeDynamic` instrumentation that allows runtime dispatching by bootstrap methods.
+- Fixed the default `TypePool` to retain generic signatures in order to avoid that agents delete such signatures.
+- Fixed a bug in all of the the default `ConstructorStrategy` that effectively prevented intercepting of constructors.
+
+### 18. January 2015: version 0.5.2
+
+- Fixed a bug where interface generation would result in a `NullPointerException`.
+- Added additional `ElementMatcher`s that allow to identify class loaders.
+
+### 5. December 2014: version 0.5.1
+
+Added the `andThen` method to the `SuperMethodCall` instrumentation in order to allow for a more convenient
+executingTransformer interception where a hard-coded super method call is required by the Java verifier.
+
+### 3. December 2014: version 0.5
+
+- Added the `DeclaringTypeResolver` as a component in the default chain which selects the most specific method out
+ of two. This is mainly meant to avoid the accidental matching of the methods that are declared by the `Object` type.
+- Added `TypeInitializer`s in order to allow `Instrumentation`s to define type initializer blocks.
+- Replaced the `MethodMatcher` API with the `ElementMatcher` API which allows for a more sophisticated matching DSL.
+- Added a `ClassLoadingStrategy` for Android in its own module.
+- Introduced an `AgentBuilder` API and implementation.
+
+### 26. November 2014: version 0.4.1
+
+- Refactored the implementation of the `VoidAwareAssigner` which would otherwise cause unexpected behavior in its
+ default state.
+- Added a missing boxing instruction to the `InvocationHandlerAdapter`.
+
+### 18. November 2014: version 0.4
+
+- Extended `Instrumentation.Context` to support field accessors.
+- Added the `TypePool` abstraction and added a default implementation.
+- Refactored annotations to have an intermediate form as `AnnotationDescription` which does not need to
+ represent loaded values.
+- Refactored several built-in `Instrumentation`, among others, all implementations now support `TypeDescription`
+ in addition to loaded `Class` as their arguments
+- Added several annotations that apply to the `MethodDelegation`.
+
+### 19. September 2014: version 0.3.1
+
+- Added support for optionally specifying a `ProtectionDomain` for the built-in `ClassLoadingStrategy` implementations.
+- Fixed a bug in the resolution of resources of the `ByteArrayClassLoader` and its child-first implementation.
+
+### 15. September 2014: version 0.3
+
+- Added basic support for Java 7 types `MethodHandle` and `MethodType` which are available from Java 7 for injection.
+- Added support for type redefinition and type rebasing.
+- Added support for accessing a JVM's HotSwap features and a Java agent.
+- Added latent a child-first `ClassLoadingStrategy` and manifest versions of the `WRAPPER` and `CHILD_FIRST` default
+ class loading strategies.
+
+### 20. June 2014: version 0.2.1
+
+- Added proper support for defining class initializers. Added support for field caching from method instrumentations,
+mainly for allowing the reuse of `Method` instances for the `@Origin` annotation and the `InvocationHandlerAdapter`.
+
+### 16. June 2014: version 0.2
+
+ - Changed the semantics of the `@SuperCall` to be only bindable, if a super method can be invoked. Before, an
+ exception was thrown if only a non-existent or abstract super method was found.
+ - Added features for the interaction with Java 8 default methods. Refactored method lookup to extract invokable
+ default methods.
+ - Refactored the invocation of super methods to be created by an `Instrumentation.Target`. For a future release,
+ this hopefully allows for class redefinitions using today's API for creating subclasses.
+ - Upgraded to ASM 5.0.3.
+
+### 02. May 2014: version 0.1
+
+- First general release.
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/libbyte-buddy-java.git
More information about the pkg-java-commits
mailing list